EduFlow

Section 5 · Running in Production

Traffic In, Trouble Out: Gateway API and Debugging

22 min3 min readChoose wisely

Lesson at a glance

Route public HTTPS traffic to notes-api with Gateway API, harden its Pods with a securityContext, and diagnose broken Pods quickly with get, describe, logs, events and kubectl debug.

  1. 1Getting HTTP traffic in
  2. 2The debugging playbook
  3. 3The last piece: securityContext

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

Two things stand between notes-api and real users. It isn’t reachable from the internet yet, and when something breaks at 3 a.m., you need to find out what within minutes. This lesson covers both, and finishes with the security settings that complete the production picture.

Getting HTTP traffic in

A LoadBalancer Service per app works for one service. With ten, you’re paying for ten load balancers, managing ten certificates, and routing by hostname or path is impossible. You want one shared entry point that routes HTTP requests to the right Service.

For years the answer was Ingress. It still works, but the Ingress API is frozen and gets no new features, and the widely used community ingress-nginx controller was retired in March 2026: no more releases or security fixes. The Kubernetes project points new work at Gateway API, which is generally available as gateway.networking.k8s.io/v1. It’s installed as an add-on (its CRDs plus an implementation, such as your cloud’s load balancer controller, Envoy Gateway, Cilium or Istio), and many managed clusters offer it out of the box.

Gateway API splits the job by role:

  • GatewayClass: which implementation handles traffic. Set up once by whoever runs the cluster.
  • Gateway: an entry point with listeners, ports, hostnames and TLS certificates. Owned by the platform team.
  • HTTPRoute: one app’s routing rules, attached to a Gateway. Owned by the app team.
GatewayClass is chosen by the cluster operator. The Gateway public in namespace infra terminates TLS on port 443. The HTTPRoute notes-api in namespace notes attaches to it and sends requests for notes.skylane.example to the notes-api Service, which forwards to Pods. GatewayClass cluster ops Gateway :443 platform HTTPRoute notes team Service Pods notes.skylane.example → notes-api:80
Each team owns its layer, and routes attach to a shared Gateway

The platform team’s Gateway, once per cluster:

infra/gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public
  namespace: infra
spec:
  gatewayClassName: skylane-lb        # provided by your implementation
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "*.skylane.example"
      tls:
        mode: Terminate
        certificateRefs:
          - name: skylane-wildcard-tls
      allowedRoutes:
        namespaces:
          from: All

Your route, in the notes namespace:

k8s/httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: notes-api
  namespace: notes
spec:
  parentRefs:
    - name: public
      namespace: infra
  hostnames:
    - notes.skylane.example
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: notes-api
          port: 80

kubectl describe httproute notes-api shows whether the Gateway accepted the route, under Status. If you’re migrating existing Ingress objects, the ingress2gateway tool converts them, including many ingress-nginx annotations.

The debugging playbook

When notes-api misbehaves, resist guessing. Follow the same order every time, and let each step choose the next:

Terminal
kubectl get pods -l app.kubernetes.io/name=notes-api
kubectl describe pod <pod>
kubectl logs <pod> --previous
kubectl events --for pod/<pod> --types=Warning

The STATUS column tells you where to look:

Status What it means Where to look
Pending No node can take the Pod describe: events like Insufficient cpu or unmatched node selectors
ImagePullBackOff The kubelet can’t pull the image describe: wrong tag, missing pull credentials, wrong architecture
CreateContainerConfigError A referenced ConfigMap or Secret key is missing describe names the key
CrashLoopBackOff The app starts and exits, repeatedly logs --previous, plus the exit code in describe (137 = OOM or killed)
Running, 0/1 ready Readiness probe is failing describe shows probe failures; check what /readyz depends on

If Pods are healthy but requests fail, work outwards: kubectl get endpointslices -l kubernetes.io/service-name=notes-api (empty means selector or readiness), then the route’s status, then the Gateway.

Distroless images have no shell, so kubectl exec can’t help. Attach an ephemeral debug container instead. It joins the Pod’s network namespace, and with --target it can see the app’s processes too:

Terminal
kubectl debug -it <pod> --image=busybox:1.37 --target=api
# inside: nslookup notes-db, wget -qO- localhost:3000/readyz, ps

The last piece: securityContext

In lesson 2.2 you ran the image as non-root and promised to enforce it in the cluster. Here it is, on the notes-api container:

k8s/deployment.yaml
          securityContext:
            runAsNonRoot: true
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]

runAsNonRoot makes the kubelet refuse to start the container if the image would run as UID 0. It checks a numeric UID, so an image whose USER is a name such as node fails with CreateContainerConfigError; write USER 1000 in the Dockerfile or add runAsUser: 1000 here. Give the app an emptyDir volume at /tmp if it needs scratch space under a read-only root.

That completes notes-api’s path: a cached multi-stage build, a small non-root image pinned by digest and scanned, a Compose stack for development, and a Deployment with probes, resources, autoscaling, safe rollouts, a Gateway route and a hardened security context. Each of those settings is in the file because an incident taught someone it was needed. Keep that habit: when something breaks, ask which setting would have made it impossible, and add it.

Key takeaways

  • Gateway API splits external traffic into GatewayClass (the implementation), Gateway (the entry point and TLS) and HTTPRoute (an app’s routing rules), so platform and app teams each own their part.
  • Ingress still works but is frozen, and the community ingress-nginx controller was retired in March 2026; new setups should use Gateway API.
  • Read Pod status first, then kubectl describe for events, then kubectl logs --previous for crashes; the status tells you which tool to reach for.
  • kubectl debug --target attaches a temporary container with tools to a running Pod, which is how you inspect distroless images.
  • A production Pod runs as non-root with no privilege escalation, a read-only root filesystem and all capabilities dropped.

Further reading

Check your understanding

4 questions

Question 1 of 4

Your platform team runs a shared Gateway in the infra namespace. What do you, the notes-api team, create to expose notes.skylane.example?

Question 2 of 4

A Pod shows ImagePullBackOff. Which is the most useful next step?

Question 3 of 4

notes-api is in CrashLoopBackOff. kubectl logs <pod> shows only a line or two of startup output. What should you run?

Question 4 of 4

notes-api’s image is distroless, so kubectl exec -it <pod> -- sh fails. How do you check DNS resolution from inside the Pod?

Practice

Choose wisely

First move for each symptom

You’re on call for notes-api. For each symptom below, a teammate proposes a first move. Select every pairing where the first move is the right one.

First move for each symptom