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:
| Object | Common Owner | Responsibility |
|---|---|---|
GatewayClass | Infrastructure Provider | Selects the controller implementation |
Gateway | Platform Team | Manages listeners, addresses, certificates, and access boundaries |
HTTPRoute | Application Team | Controls 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:
apiVersion: v1
kind: Namespace
metadata:
name: atlas-prod
labels:
gateway-access: publicapiVersion: 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: publicapiVersion: 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: 8080Here, 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:
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.
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: 8080Within 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:
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: failureThe 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
Kubernetes, Ingress
Kubernetes, Ingress NGINX Retirement
Gateway API, Introduction
Gateway API, HTTPRoute
Kubernetes, Network Policies
Gateway API, Cross-namespace routing
Gateway API, ReferenceGrant