Skip to content

VOL 07 / CH 11 / LESSON 01

11.1 Kubernetes Traffic Boundaries: Gateway API and NetworkPolicy ​

In production clusters, traffic boundaries must address two key questions: how external requests enter services, and what communication is permitted between workloads. The first falls under L4/L7 ingress governance, while the second is typically enforced at the L3/L4 layer using NetworkPolicy to establish a minimal communication surface.

Migrating from Ingress to Gateway API ​

The Ingress API remains stable, but its specification is frozen, and Kubernetes recommends Gateway API for new designs. More critically, the community retired ingress-nginx on March 24, 2026, so it no longer receives releases, bug fixes, or security updates. Existing Ingress resources do not automatically become invalid, but continuing to use a retired controller is different from continuing to use the Ingress API.

The Gateway API divides responsibilities among distinct roles:

ObjectCommon OwnerResponsibility
GatewayClassInfrastructure ProviderSelects the controller implementation
GatewayPlatform TeamManages listeners, addresses, certificates, and access boundaries
HTTPRouteApplication TeamControls hostnames, paths, filters, and backend services

This lets application teams manage routes without full control of shared load balancers.

The examples assume installed Gateway API CRDs and an implementation, an existing managed-l7 GatewayClass, and an edge namespace containing the TLS Secret wildcard-example-com. The atlas-prod namespace also needs an atlas-api Service exposing Service port 8080 with ready backends. Cross-namespace attachment requires the route namespace to match the listener selector:

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: atlas-prod
  labels:
    gateway-access: public
yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public-web
  namespace: edge
spec:
  gatewayClassName: managed-l7
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "*.example.com"
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            name: wildcard-example-com
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway-access: public
yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: atlas-api
  namespace: atlas-prod
spec:
  parentRefs:
    - name: public-web
      namespace: edge
  hostnames: ["api.example.com"]
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1
      backendRefs:
        - name: atlas-api
          port: 8080

Here, allowedRoutes authorizes cross-namespace Route attachment. If a Route also references a backend in another namespace, that target namespace needs an appropriate ReferenceGrant. This backend shares the Route’s atlas-prod namespace and does not need that grant.

Creating objects does not establish a working traffic path. Check Gateway Accepted and Programmed, and the relevant Route parent’s Accepted and ResolvedRefs, with observedGeneration matching the current configuration. Then test DNS, TLS, Service endpoints, and the request path. True conditions do not replace request tests; filters and extensions must also be supported by the implementation.

Derive Ingress Policy from the Threat Model ​

Production entry must clearly define:

  • Where TLS terminates, who issues and rotates the certificates;
  • Who trusts and rewrites client real IP addresses and forwarded headers;
  • Where authentication, rate limiting, request body size limits, and timeouts are enforced;
  • Who approves canary weight changes and how failures are rolled back to zero;
  • How the status of controllers, cloud load balancers, and Route objects is monitored.

Do not treat a private annotation on a specific controller as a portable Kubernetes capability. Before adopting any extension, document the implementation dependencies and migration paths.

NetworkPolicy Is Not a Firewall by Default ​

Isolation applies separately to each direction when a policy selects a Pod and includes that direction in policyTypes. Allowed traffic is the union of applicable policies; there is no ordered explicit-deny rule. A broad allow can therefore reopen traffic restricted by a default-deny policy. The network plugin must actually enforce NetworkPolicy. Accepting a YAML configuration via the API does not guarantee that the data plane will intercept and enforce the rules.

A common baseline approach is to first deny all traffic, then explicitly allow specific flows:

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
  namespace: atlas-prod
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]

Next allow Gateway data-plane traffic to the API. This assumes the actual proxy Pods run in edge with label app.kubernetes.io/name: gateway-proxy, and the API Pod target port is also 8080. Adapt these to the implementation: the controller managing configuration may not carry user traffic.

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-gateway-to-api
  namespace: atlas-prod
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: atlas-api
  policyTypes: [Ingress]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: edge
          podSelector:
            matchLabels:
              app.kubernetes.io/name: gateway-proxy
      ports:
        - protocol: TCP
          port: 8080

Within the same from element, namespaceSelector and podSelector are ANDed: they specify Pods that match within the namespace. If written as separate list items, they become ORed, expanding access. Both source egress and destination ingress must allow the connection. Reply traffic for an allowed connection is implicitly permitted; each response does not need another reverse-direction rule.

Default Deny Egress Then Restore Base Dependencies ​

A full egress denial typically cuts off access to DNS, identity services, telemetry exports, and cloud metadata. When permitting DNS access, verify namespace, Pod label, protocol, and port configurations based on the actual CNI and DNS deployment. Do not simply copy online kube-dns labels. With NodeLocal DNSCache or hostNetwork proxies, the visible source or destination may be a node address that Pod selectors do not cover; verify the implementation’s node-traffic exceptions.

For external services accessed only via ipBlock, also consider NAT, load balancer configurations, and changes in vendor IP addresses. NetworkPolicy is not a domain-based firewall and does not understand HTTP paths or service identities. When L7 policies are required, use a data plane solution with the appropriate capabilities and proven reliability.

Test Policy at Both Ends of the Connection ​

Before release, at least verify the following:

text
Allow source → target port: success
Allow source → non-target port: failure
Non-allowed source → target port: failure
Application → DNS/identity/telemetry: success
Application → undeclared external network: failure

The behavior of existing long-lived connections after policy changes may depend on implementation specifics and cannot be validated with a single test of a new connection. Policy testing must be rerun after cluster upgrades or after replacing the CNI plugin.

Workload Security Still Matters ​

Network isolation cannot replace service authentication or container security. Production workloads must still be managed by risk: running without root privileges, using read-only root filesystems, removing unnecessary capabilities, enforcing seccomp, limiting ServiceAccount permissions, enabling Pod Security Admission, and adopting short-lived workload identities. When network boundaries are breached, identity and application authorization remain the next critical line of defense.

The next lesson addresses the most challenging aspects to migrate after traffic arrives: the identity, storage, and recovery of stateful workloads.

References ​

Built with VitePress | Software Systems Atlas