3. Services & Networking (20%)¶
Level: Intermediate. NetworkPolicy and Gateway API tasks are high-value and easy to get subtly wrong.
3.1 The Kubernetes network model¶
- Every pod gets its own IP. Pods can reach all other pods without NAT, across nodes. The CNI plugin (Calico, Cilium, Flannel, ...) implements this.
- Services give a stable virtual IP and DNS name in front of a changing set of pods. kube-proxy (or Cilium's eBPF replacement) programs the routing.
- NetworkPolicies restrict that default "everything can talk" model. They only work if the CNI enforces them. Flannel alone does not; Calico and Cilium do.
Where cluster CIDRs are configured:
k cluster-info dump | grep -m1 -E 'service-cluster-ip-range|cluster-cidr'
grep -E 'service-cluster-ip-range' /etc/kubernetes/manifests/kube-apiserver.yaml
grep -E 'cluster-cidr' /etc/kubernetes/manifests/kube-controller-manager.yaml
k get nodes -o jsonpath='{.items[*].spec.podCIDR}'
3.2 Services¶
| Type | Reachable from | Notes |
|---|---|---|
| ClusterIP (default) | Inside the cluster | Virtual IP plus DNS name |
| NodePort | <anyNodeIP>:<30000-32767> |
Also gets a ClusterIP |
| LoadBalancer | External LB IP | Needs a cloud or MetalLB. Also gets NodePort and ClusterIP |
| ExternalName | DNS CNAME | No proxying |
Headless (clusterIP: None) |
DNS returns the pod IPs directly | StatefulSets |
k expose deploy web --port=80 --target-port=8080 --name=web-svc
k expose pod db --port=5432 --type=NodePort
k get svc,endpointslices -l app=web # or: k get endpoints web-svc
apiVersion: v1
kind: Service
metadata: {name: web-svc}
spec:
type: NodePort
selector: {app: web} # MUST match pod labels
ports:
- port: 80 # Service port
targetPort: 8080 # container port (number or named port)
nodePort: 30080 # optional, 30000-32767
protocol: TCP
No endpoints? Almost always one of: a selector/label mismatch, pods not Ready (readiness probe failing), or a wrong targetPort. Compare k get pods --show-labels against k describe svc.
3.3 CoreDNS and service discovery¶
| Record | Name |
|---|---|
| Service | <svc>.<ns>.svc.cluster.local (short: <svc> in the same namespace, <svc>.<ns> elsewhere) |
| Pod | <pod-ip-with-dashes>.<ns>.pod.cluster.local (e.g. 10-244-1-5.default.pod.cluster.local) |
| Headless/StatefulSet pod | <pod-name>.<headless-svc>.<ns>.svc.cluster.local |
k run dns --rm -it --image=busybox:1.36 --restart=Never -- nslookup web-svc.default
k -n kube-system get deploy coredns
k -n kube-system get cm coredns -o yaml # the Corefile
cat /var/lib/kubelet/config.yaml | grep -A1 clusterDNS
Corefile highlights: kubernetes cluster.local in-addr.arpa ip6.arpa (cluster zone), forward . /etc/resolv.conf (upstream DNS), cache 30. Add a custom stub domain with another server block, e.g. corp.local:53 { forward . 10.0.0.53 }. After editing, run k -n kube-system rollout restart deploy coredns.
Pod DNS policy: dnsPolicy: ClusterFirst (default), Default (node's resolv.conf), None plus dnsConfig.
3.4 NetworkPolicies¶
Rules of the game:
- Pods are open until any policy selects them. Then only explicitly allowed traffic is permitted, for the listed
policyTypes. - Policies are additive (a union). There are no deny rules beyond "not allowed".
- For egress, remember DNS (UDP and TCP port 53), or name resolution breaks.
Default deny¶
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: default-deny-all, namespace: prod}
spec:
podSelector: {} # all pods in namespace
policyTypes: [Ingress, Egress] # no rules listed = deny everything
Allow specific traffic¶
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: api-allow, namespace: backend}
spec:
podSelector:
matchLabels: {app: api} # policy applies TO these pods
policyTypes: [Ingress, Egress]
ingress:
- from:
- namespaceSelector: # ONE item with both selectors = AND:
matchLabels: # pods labeled app=web IN namespace frontend
kubernetes.io/metadata.name: frontend
podSelector:
matchLabels: {app: web}
ports:
- {protocol: TCP, port: 8080}
egress:
- to:
- podSelector: {matchLabels: {app: db}}
ports:
- {protocol: TCP, port: 5432}
- ports: # DNS to anywhere
- {protocol: UDP, port: 53}
- {protocol: TCP, port: 53}
The AND vs OR trap¶
# AND: one list item -> pod must match BOTH
- from:
- namespaceSelector: {matchLabels: {team: a}}
podSelector: {matchLabels: {app: web}}
# OR: two list items (note the second dash) -> either matches
- from:
- namespaceSelector: {matchLabels: {team: a}}
- podSelector: {matchLabels: {app: web}}
podSelectoralone infrommeans pods in the policy's own namespace.- Every namespace has the label
kubernetes.io/metadata.name=<ns>, so use it to select a namespace by name. ipBlock: {cidr: 10.0.0.0/16, except: [10.0.5.0/24]}handles external IPs.
Verify:
k -n frontend run t --rm -it --image=busybox:1.36 --labels=app=web --restart=Never -- wget -qO- -T2 api.backend:8080
k -n frontend run t --rm -it --image=busybox:1.36 --labels=app=other --restart=Never -- wget -qO- -T2 api.backend:8080 # should time out
3.5 Ingress¶
k get ingressclass # which controller/class exists?
k create ingress web --class=nginx \
--rule="app.example.com/=web-svc:80" \
--rule="app.example.com/api*=api-svc:8080" \
--rule="secure.example.com/*=web-svc:80,tls=web-tls"
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: {name: web}
spec:
ingressClassName: nginx
tls:
- hosts: [secure.example.com]
secretName: web-tls
rules:
- host: app.example.com
http:
paths:
- path: /api
pathType: Prefix # Prefix | Exact | ImplementationSpecific
backend:
service:
name: api-svc
port: {number: 8080}
Test: curl -H "Host: app.example.com" http://<node-ip>:<ingress-nodeport>/api.
3.6 Gateway API (new in the 2025 curriculum)¶
Gateway API is the successor to Ingress. It splits responsibilities across roles:
flowchart LR
GC[GatewayClass<br/>infra provider:<br/>which controller] --> GW[Gateway<br/>cluster operator:<br/>listeners, ports, TLS]
GW --> R1[HTTPRoute<br/>app team:<br/>hosts, paths, backends]
R1 --> S1[Service v1]
R1 --> S2[Service v2]
k get gatewayclass # the exam cluster will have a controller installed
k get gateway,httproute -A
k explain httproute.spec.rules --api-version=gateway.networking.k8s.io/v1
Gateway¶
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata: {name: web-gw, namespace: default}
spec:
gatewayClassName: nginx # from `k get gatewayclass`
listeners:
- name: http
protocol: HTTP
port: 80
- name: https
protocol: HTTPS
port: 443
hostname: secure.example.com
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: web-tls # kubernetes.io/tls secret
allowedRoutes:
namespaces:
from: Same # Same | All | Selector
HTTPRoute: path routing, header matching, traffic split¶
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: {name: web-route, namespace: default}
spec:
parentRefs:
- name: web-gw
sectionName: http # optional: attach to one listener
hostnames: ["app.example.com"]
rules:
- matches:
- path: {type: PathPrefix, value: /api}
backendRefs:
- {name: api-svc, port: 8080}
- matches:
- headers:
- {name: x-canary, value: "true"}
backendRefs:
- {name: web-v2, port: 80}
- backendRefs: # default: 80/20 split
- {name: web-v1, port: 80, weight: 80}
- {name: web-v2, port: 80, weight: 20}
Migrating an Ingress to Gateway API (a likely task shape)¶
| Ingress | Gateway API |
|---|---|
ingressClassName |
Gateway.spec.gatewayClassName |
spec.tls[].secretName |
Gateway listener tls.certificateRefs (HTTPS listener) |
rules[].host |
HTTPRoute.spec.hostnames |
paths[].path + pathType: Prefix |
matches[].path type: PathPrefix |
backend.service.name/port |
backendRefs[].name/port |
Verify:
k describe gateway web-gw # Programmed=True, address assigned
k describe httproute web-route # Parents: Accepted=True, ResolvedRefs=True
curl -H "Host: app.example.com" http://<gateway-address>/api
ResolvedRefs=False usually means a wrong Service name or port, or a cross-namespace backend without a ReferenceGrant.
3.7 Checklist¶
- Expose a Deployment as ClusterIP and NodePort. Fix a Service with no endpoints
- Resolve a Service and a pod via DNS from a busybox pod. Find the CoreDNS config
- Write default-deny plus an allow policy with namespace AND pod selectors. Allow DNS egress
- Create an Ingress with path routing and TLS
- Create a Gateway plus HTTPRoute with path match, header match and weighted backends
- Translate an existing Ingress into Gateway + HTTPRoute
- Find the pod and service CIDRs of the cluster
Next: 4. Storage →