# Ingress, Gateway API and Network Policies

## 1. The 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.

## 2. Ingress

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.

## 3. Gateway 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 }
```

## 4. NetworkPolicy

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`).

## 5. Service 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 Hood: A 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](diagrams/01-ingress-traffic.svg)

## 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

```
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)
