Engineering Notes
Home/DevOps & Cloud/Kubernetes/Ingress, Gateway API and Network Policies
DevOps & Cloud · Kubernetes

Ingress, Gateway API and Network Policies

Get traffic from the internet to the right pods, and stop pods from talking to things they shouldn't. Follow a request through the load balancer and ingress controller, compare Ingress with the Gateway API, and lock a namespace down with NetworkPolicies.

☁️ DevOps & Cloud● Advanced⏱ 4 min read▶ 2 interactive🛠️ 2 exercises🎤 5 interview Q&As

🎯 What you'll learn

  • The Kubernetes network model
  • Ingress, controllers, TLS with cert-manager
  • The Gateway API and weighted routing
  • NetworkPolicies: default deny plus explicit allows (don't forget DNS)

1The Kubernetes Network Model

Every Pod gets its own IP; all Pods can reach all other Pods without NAT (the CNI plugin makes this true across nodes); Services provide stable virtual IPs (02-pods_deployments_and_services/). By default, everything can talk to everything — network policies restrict that.

2Ingress

An Ingress routes external HTTP(S) traffic to Services by host and path, through one load balancer. It's only a declaration: an ingress controller (ingress-nginx, Traefik, HAProxy, AWS Load Balancer Controller → ALB, GKE → Google LB) implements it.

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: petpal
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts: [petpal.io, api.petpal.io]
      secretName: petpal-tls          # cert-manager creates/renews it
  rules:
    - host: api.petpal.io
      http:
        paths:
          - path: /v1
            pathType: Prefix
            backend: { service: { name: api, port: { number: 80 } } }
    - host: petpal.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend: { service: { name: web, port: { number: 80 } } }

cert-manager automates TLS certificates (ACME/Let's Encrypt, private CAs) into Secrets. Controller-specific behaviour (timeouts, body size, rate limits, rewrites) lives in annotations — not portable between controllers.

3Gateway API

The Gateway API is Ingress's successor: role-oriented, more expressive and portable:

  • GatewayClass (infra provider) → Gateway (platform team: listeners, ports, TLS) → HTTPRoute / GRPCRoute / TCPRoute (app teams: routing rules attached to a Gateway, possibly across namespaces).
  • Native header matching, traffic splitting by weight (canaries), request mirroring, redirects and rewrites without annotations.
yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: { name: api, namespace: petpal }
spec:
  parentRefs: [{ name: public, namespace: gateway-system }]
  hostnames: [api.petpal.io]
  rules:
    - matches: [{ path: { type: PathPrefix, value: /v1 } }]
      backendRefs:
        - { name: api-stable, port: 80, weight: 90 }
        - { name: api-canary, port: 80, weight: 10 }

4NetworkPolicy

A NetworkPolicy selects Pods and declares which ingress/egress traffic is allowed; once any policy selects a Pod for a direction, everything not explicitly allowed in that direction is denied. Enforcement requires a CNI that supports it (Calico, Cilium; AWS VPC CNI supports it when enabled).

yaml
# 1. default deny all ingress and egress in the namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny, namespace: petpal }
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]
---
# 2. allow api ← ingress controller, api → postgres, and DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: api, namespace: petpal }
spec:
  podSelector: { matchLabels: { app: api } }
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: ingress-nginx } }
      ports: [{ port: 8080 }]
  egress:
    - to: [{ podSelector: { matchLabels: { app: postgres } } }]
      ports: [{ port: 5432 }]
    - to: [{ namespaceSelector: {}, podSelector: { matchLabels: { k8s-app: kube-dns } } }]
      ports: [{ port: 53, protocol: UDP }, { port: 53, protocol: TCP }]

Remember DNS egress — the classic mistake after "default deny". Cilium/Calico add L7 and FQDN-based policies (e.g. allow egress only to api.stripe.com).

5Service Mesh (Briefly)

A service mesh (Istio, Linkerd, Cilium mesh) adds mTLS between services, retries/timeouts, traffic shifting and per-request telemetry via sidecar or node proxies. Valuable at scale for zero-trust and observability; overkill for a handful of services.

Under the HoodA Request From the Internet to a Pod▾

https://api.petpal.io/v1/pets → DNS (Route 53/ExternalDNS) points to the cloud load balancer created for the ingress controller's Service type: LoadBalancer → the LB forwards to the ingress controller Pods (via NodePorts, or directly to Pod IPs in "IP target" mode) → the controller (an nginx/Envoy process configured by watching Ingress/HTTPRoute objects and their EndpointSlices) terminates TLS with the certificate from the Secret, matches host and path, and proxies to a Pod IP of the api Service directly (bypassing the ClusterIP) → the packet crosses the node network via the CNI → NetworkPolicy rules (iptables/eBPF on the destination node) check that traffic from the ingress-nginx namespace to port 8080 is allowed → the Pod's container receives it. Every hop adds timeouts to configure consistently (LB idle timeout > controller timeout > app timeout).

Traffic from the internet passes through DNS and a cloud load balancer to the ingress controller, which terminates TLS and routes by host and path to Pods of the api and web Services; NetworkPolicies allow only ingress-controller-to-api and api-to-postgres traffic plus DNS
Traffic from the internet passes through DNS and a cloud load balancer to the ingress controller, which terminates TLS and routes by host and path to Pods of the api and web Services; NetworkPolicies allow only ingress-controller-to-api and api-to-postgres traffic plus DNS

⚠️ Common Pitfalls

  1. A LoadBalancer Service per app instead of one ingress/gateway.
  2. Default-deny without DNS egress.
  3. NetworkPolicies on a CNI that doesn't enforce them — silently ignored.
  4. Annotation lock-in to one ingress controller.
  5. Mismatched timeouts across LB, controller and app (random 502/504s).
  6. Manual certificates that expire — use cert-manager.

📋 Quick Reference

text
Ingress (+ controller + ingressClassName) · TLS via cert-manager · annotations = controller-specific
Gateway API: GatewayClass → Gateway → HTTPRoute (weights, headers, cross-namespace)
NetworkPolicy: select Pods → allow from/to (pod/namespace/ipBlock) · default-deny + allow DNS · CNI must enforce
mesh: mTLS, retries, traffic shifting, telemetry (Istio, Linkerd)

🧠 Key takeaways

  • Every pod gets an IP and can reach every other pod unless NetworkPolicies say otherwise (with a CNI that enforces them).
  • Ingress routes HTTP by host/path via a controller; cert-manager automates TLS; annotations are controller-specific.
  • The Gateway API splits roles (GatewayClass → Gateway → HTTPRoute) and supports weights and headers natively.
  • Start with default deny, add explicit allows, and always allow DNS egress.

🎤 Interview Q&A

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

Q1How does pod networking work in Kubernetes?core▾

Every pod gets its own IP; all pods can reach each other without NAT (flat network), implemented by a CNI plugin. Services provide stable virtual IPs; kube-proxy (iptables/IPVS) or eBPF routes traffic to pod endpoints.

Follow-ups interviewers ask
  • What does CoreDNS do? Resolves service names like api.default.svc.cluster.local.
Q2What is an Ingress and an Ingress controller?core▾

An Ingress resource defines HTTP routing rules (hosts, paths, TLS) for external traffic to services; an Ingress controller (NGINX, Traefik, AWS Load Balancer Controller) implements them.

Follow-ups interviewers ask
  • What's the Gateway API? The newer, more expressive successor to Ingress with role-oriented resources.
Q3How do NetworkPolicies work?senior▾

They restrict pod-to-pod and egress traffic by labels, namespaces and ports; once a pod is selected by a policy, unspecified traffic is denied. They require a CNI that enforces them (Calico, Cilium).

Follow-ups interviewers ask
  • Good default? Default-deny per namespace, then allow needed flows.
Q4How is TLS handled for ingress?core▾

TLS certificates in Secrets referenced by the Ingress, automated with cert-manager (Let's Encrypt or private CAs), terminating at the controller or cloud load balancer.

Follow-ups interviewers ask
  • End-to-end encryption inside? A service mesh with mTLS.
Q5How do you debug a service that isn't reachable?core▾

Check pods are ready, the Service selector matches pod labels (kubectl get endpoints), ports/targetPorts match, DNS resolves, NetworkPolicies allow traffic, and the Ingress rules/controller logs.

Follow-ups interviewers ask
  • Quick in-cluster test? kubectl run tmp --rm -it --image=busybox -- wget -O- http://svc:port.

🛠️ Practice exercises

Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.

✅ Check yourself

Answer the questions — feedback is instant.

1. After applying default-deny egress, every app fails to resolve names. Why?
Allow DNS explicitly.
2. What actually implements an Ingress object?
The object is only a declaration.
3. Which supports weighted canary routing without annotations?
backendRefs with weights.
4. NetworkPolicies are silently ignored when…
Use Calico, Cilium or an enforcing CNI.