recipes / networking
Pod, Service, Ingress, Gateway API and NetworkPolicy
How a request reaches your container, and why you should say who may talk to it.
internet -> Ingress controller | Gateway -> Service (ClusterIP) -> Pod (Deployment) -> container:3000
| |
Ingress / HTTPRoute rules selector + named port
Pods get an IP each and die with it. A Service gives the group a stable name and spreads connections over the ready Pods. An Ingress maps a hostname and path to that Service at the cluster edge.
Service
apiVersion: v1
kind: Service
metadata:
name: app
spec:
selector:
app.kubernetes.io/name: app
ports:
- name: http
port: 80
targetPort: http # the container port name, not a number
The Service only routes to Pods whose readiness probe passes.
Inside the cluster the app is now http://app.<namespace>.svc.
Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
ingressClassName: nginx
tls:
- hosts: [app.example.com]
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app
port:
name: http
The Ingress resource is only data. An Ingress controller (ingress-nginx, Traefik,
HAProxy, a cloud load balancer) reads it and does the work. TLS ends at the
controller; the app speaks plain HTTP and trusts X-Forwarded-* from it.
Gateway API
Gateway API is the successor to Ingress and splits the job in two objects with two owners.
- The Gateway is infrastructure: which load balancer, which listeners, which TLS certificates. The platform team creates it once per cluster or per environment, in its own namespace, and decides which namespaces may attach.
- The HTTPRoute is application config: which hostname and path go to which Service. It lives next to your Deployment, and you own it.
# Owned by the application team, in the app namespace.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app
spec:
parentRefs:
- name: public # the shared Gateway
namespace: gateway-system
hostnames: [app.example.com]
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: app
port: 80
# Owned by the platform team. Shown for reference; do not ship this with the app.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
namespace: gateway-system
spec:
gatewayClassName: cilium # or istio, envoy-gateway, nginx, a cloud class
listeners:
- name: https
protocol: HTTPS
port: 443
hostname: "*.example.com"
tls:
certificateRefs: [{ name: wildcard-example-com }]
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: "true"
An HTTPRoute can also split traffic by weight between two Services, match on headers, and rewrite paths, all without controller-specific annotations. New clusters should start with Gateway API; Ingress keeps working but gets no new features.
NetworkPolicy
By default every Pod can open a connection to every other Pod in the cluster,
across namespaces. A compromised frontend can reach the database directly.
A NetworkPolicy is a firewall for Pods: once one selects a Pod, only the
listed traffic is allowed.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: app
policyTypes: [Ingress, Egress]
ingress:
# Only the ingress controller may reach the app.
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- port: http
egress:
# DNS
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
# The database, and nothing else.
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: postgres
ports:
- port: 5432
Egress rules matter as much as ingress: they stop a compromised Pod from scanning the cluster or calling home. Remember DNS, the telemetry collector, and any external API you need.
Notes
- Policies need a CNI that enforces them (Cilium, Calico, most managed clusters). On a CNI that does not, they are silently ignored. Test with a
kubectl execfrom another Pod. - Kubelet probes come from the node, not from a Pod, and are not blocked by policies on most CNIs.
- Start with a namespace-wide default deny, then allow per app. Adding policies later to a running namespace is harder than starting with them.
- Same-namespace ingress from the ingress controller depends on how it is deployed. Match its namespace and Pod labels, not an IP.
- With Gateway API the traffic comes from the Gateway’s data plane Pods. Allow ingress from that namespace instead of
ingress-nginx.
See it applied
- examples/full/k8s/base in the repository
- examples/full - every recipe applied to one app
Updated 2026-09-10 · tags: service, ingress, networkpolicy, networking, kubernetes · edit on GitHub