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.
🎯 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)
📚 Before you start
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.
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.
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).
# 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).
⚠️ Common Pitfalls
- A
LoadBalancerService per app instead of one ingress/gateway. - Default-deny without DNS egress.
- NetworkPolicies on a CNI that doesn't enforce them — silently ignored.
- Annotation lock-in to one ingress controller.
- Mismatched timeouts across LB, controller and app (random 502/504s).
- Manual certificates that expire — use cert-manager.
📋 Quick Reference
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
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.
- 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.
- 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).
- 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.
- 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.
- 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.