Skip to main content

Networking & Services

Exam relevance: CKA ✅ (Services & Networking — 20%) | CKAD ✅ (Services & Networking — 20%)


Kubernetes Networking Model — The Rules​

Every Kubernetes cluster follows these rules (regardless of which CNI plugin is used):

  1. Every Pod gets its own IP address — no NAT between pods
  2. Every Pod can reach every other Pod using its Pod IP — across nodes, without NAT
  3. Every Node can reach every Pod — and vice versa, without NAT
  4. The IP a Pod sees for itself is the same IP others see for it — no hidden NAT

This is achieved by the CNI (Container Network Interface) plugin: Calico, Flannel, Cilium, Weave, etc.


Pod Networking — How It Works​

Node 1 (10.0.1.1)                    Node 2 (10.0.2.1)
┌─────────────────────┐ ┌─────────────────────┐
│ Pod A: 10.244.1.5 │ │ Pod C: 10.244.2.3 │
│ Pod B: 10.244.1.6 │──────────────│ Pod D: 10.244.2.4 │
│ │ Overlay │ │
│ veth pairs → cbr0 │ or BGP │ veth pairs → cbr0 │
└─────────────────────┘ └─────────────────────┘
  • Each pod gets a veth (virtual ethernet) pair — one end in the pod, one on the node's bridge
  • The CNI plugin handles cross-node routing (overlay network, BGP, etc.)
  • Pod CIDR is typically 10.244.0.0/16 (Flannel default) or 192.168.0.0/16 (Calico default)

Pod-to-Pod communication within the same node uses the local bridge. Across nodes, the CNI handles routing.


Services — Stable Endpoints for Pods​

Pods are ephemeral — they get new IPs when recreated. Services provide a stable virtual IP (ClusterIP) and DNS name that routes to the correct pods.

How Services Find Pods​

Services use label selectors to find pods. The Endpoints (or EndpointSlices) object tracks which pod IPs match.

Service (selector: app=webapp)
│
└── Endpoints: [10.244.1.5, 10.244.2.3, 10.244.2.4]
↑ Pod IPs that match the selector
# View endpoints for a service
kubectl get endpoints myservice
kubectl get endpointslices -l kubernetes.io/service-name=myservice

ClusterIP — Internal Access Only​

Default service type. Gets a virtual IP reachable only from within the cluster.

apiVersion: v1
kind: Service
metadata:
name: webapp-service
spec:
type: ClusterIP # Default — can omit
selector:
app: webapp # Matches pods with this label
ports:
- port: 80 # Service listens on this port
targetPort: 8080 # Forwards to this port on the pod
protocol: TCP
# Quick create
kubectl expose deployment webapp --port=80 --target-port=8080 --type=ClusterIP

NodePort — External Access via Node IP​

Opens a port on EVERY node (30000–32767). Traffic to <NodeIP>:<NodePort> is forwarded to the service.

apiVersion: v1
kind: Service
metadata:
name: webapp-nodeport
spec:
type: NodePort
selector:
app: webapp
ports:
- port: 80 # ClusterIP port (still accessible internally)
targetPort: 8080 # Pod port
nodePort: 30080 # Port on every node (optional — auto-assigned if omitted)
External → NodeIP:30080 → Service:80 → Pod:8080

LoadBalancer — Cloud Load Balancer​

Creates an external load balancer (in AWS, Azure, GCP). Superset of NodePort.

apiVersion: v1
kind: Service
metadata:
name: webapp-lb
spec:
type: LoadBalancer
selector:
app: webapp
ports:
- port: 80
targetPort: 8080

In cloud environments, this creates:

  • A ClusterIP (for internal access)
  • A NodePort (for node-level access)
  • An external load balancer (cloud-specific, e.g., Azure Load Balancer)

ExternalName — DNS Alias​

Maps a service to an external DNS name. No proxying — just a CNAME record.

apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: db.example.com # DNS CNAME

Headless Service — Direct Pod DNS​

Set clusterIP: None. No virtual IP is assigned. DNS queries return individual pod IPs.

apiVersion: v1
kind: Service
metadata:
name: db-headless
spec:
clusterIP: None # Headless
selector:
app: mysql
ports:
- port: 3306

Used with StatefulSets to give each pod a DNS name:

mysql-0.db-headless.default.svc.cluster.local
mysql-1.db-headless.default.svc.cluster.local

Service Port Types Summary​

┌──────────────────────────────────────────────────┐
│ port: 80 ← Service's ClusterIP port │
│ targetPort: 8080 ← Container's listening port │
│ nodePort: 30080 ← Port on every node │
│ containerPort: 8080 ← (Pod spec, informational) │
└──────────────────────────────────────────────────┘

Named Ports​

You can reference ports by name instead of number:

# In the Pod/Deployment
containers:
- name: app
ports:
- name: http
containerPort: 8080

# In the Service
ports:
- port: 80
targetPort: http # References the named port

DNS in Kubernetes — CoreDNS​

CoreDNS runs as a Deployment in kube-system namespace. It provides DNS for all services and pods.

Service DNS Records​

Every service gets a DNS record:

<service-name>.<namespace>.svc.cluster.local

Examples:

# From same namespace
curl webapp-service # Short name works

# From different namespace
curl webapp-service.production.svc.cluster.local

# You can also use shorter forms
curl webapp-service.production.svc
curl webapp-service.production

Pod DNS Records​

Pods get DNS records based on their IP (with dots replaced by dashes):

10-244-1-5.default.pod.cluster.local

CoreDNS Configuration​

# Check CoreDNS pods
kubectl get pods -n kube-system -l k8s-app=kube-dns

# Check CoreDNS ConfigMap
kubectl get configmap coredns -n kube-system -o yaml

The CoreDNS ConfigMap (Corefile):

.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}

Troubleshooting DNS​

# Run a debug pod
kubectl run dnstest --image=busybox --restart=Never -- sleep 3600

# Test DNS resolution from inside
kubectl exec dnstest -- nslookup webapp-service
kubectl exec dnstest -- nslookup webapp-service.default.svc.cluster.local
kubectl exec dnstest -- nslookup kubernetes.default

# Check resolv.conf inside a pod
kubectl exec dnstest -- cat /etc/resolv.conf
# Should show: nameserver 10.96.0.10 (CoreDNS ClusterIP)
# search default.svc.cluster.local svc.cluster.local cluster.local

Ingress — HTTP/HTTPS Routing​

Ingress provides HTTP/HTTPS routing to services based on hostname or path. It requires an Ingress Controller (nginx, traefik, HAProxy, etc.) to be installed.

Ingress Controller vs Ingress Resource​

  • Ingress Controller: The actual reverse proxy (e.g., nginx pod). Must be installed separately.
  • Ingress Resource: The routing rules YOU define (YAML).
  • IngressClass: Links Ingress resources to a specific controller.

Simple Ingress — Single Service​

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: simple-ingress
spec:
ingressClassName: nginx # Which Ingress Controller to use
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp-service
port:
number: 80

Host-Based Routing​

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-host-ingress
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp-service
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80

Path-Based Routing​

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-based-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # Rewrite URL path
spec:
ingressClassName: nginx
rules:
- host: example.com
http:
paths:
- path: /app
pathType: Prefix
backend:
service:
name: webapp-service
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80

Path Types​

TypeBehavior
PrefixMatches URL path prefix. /api matches /api, /api/users, /api/v2
ExactExact match only. /api matches only /api, not /api/users
ImplementationSpecificUp to the IngressClass to decide

TLS / HTTPS​

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-tls-secret # Secret containing tls.crt and tls.key
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: webapp-service
port:
number: 80

Create the TLS secret:

kubectl create secret tls app-tls-secret --cert=cert.pem --key=key.pem

Default Backend​

Handle requests that don't match any rule:

spec:
defaultBackend:
service:
name: default-service
port:
number: 80

NetworkPolicy — Firewall Rules for Pods​

By default, ALL pods can communicate with ALL other pods. NetworkPolicy restricts this.

Prerequisites: Your CNI plugin must support NetworkPolicy (Calico, Cilium, Weave = yes; Flannel = no by default).

How NetworkPolicy Works​

  • Policies are additive — if no policy selects a pod, all traffic is allowed
  • Once ANY policy selects a pod, only traffic matching a rule is allowed (default deny for that pod)
  • Policies are namespace-scoped

Default Deny All Ingress (Exam Common)​

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: production
spec:
podSelector: {} # Select ALL pods in this namespace
policyTypes:
- Ingress # Only restrict ingress
# No ingress rules = deny all incoming traffic

Default Deny All Egress​

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
# No egress rules = deny all outgoing traffic

Default Deny All (Both Directions)​

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress

Allow Specific Ingress​

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend # This policy applies to backend pods
policyTypes:
- Ingress
ingress:
- from:
- podSelector: # Allow from pods with label app=frontend
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080 # Only on port 8080

Allow from Specific Namespace​

spec:
podSelector:
matchLabels:
app: backend
ingress:
- from:
- namespaceSelector: # Allow from any pod in monitoring namespace
matchLabels:
name: monitoring
- podSelector: # AND/OR allow from frontend pods in same namespace
matchLabels:
app: frontend

Important: AND vs OR logic in NetworkPolicy

# OR (two separate list items) — allow from namespace OR from pods
ingress:
- from:
- namespaceSelector:
matchLabels:
name: monitoring # FROM monitoring namespace
- podSelector:
matchLabels:
app: frontend # OR FROM frontend pods (same namespace)

# AND (combined in one list item) — allow from specific pods in specific namespace
ingress:
- from:
- namespaceSelector:
matchLabels:
name: production
podSelector: # Note: same level, not separate list item
matchLabels:
app: frontend # FROM frontend pods IN production namespace

This AND vs OR distinction is a very common exam trap.

Allow DNS Egress (Almost Always Needed)​

When you deny all egress, pods can't resolve DNS. Always allow DNS:

spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {} # Any namespace (kube-system for CoreDNS)
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to: # Allow backend to talk to database
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432

Allow by IP Block (CIDR)​

ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.0.1.0/24 # Exclude this subnet

Complete Example: Typical 3-Tier App​

# Frontend: allow ingress from external, allow egress to backend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
spec:
podSelector:
matchLabels:
tier: frontend
policyTypes:
- Ingress
- Egress
ingress:
- from: [] # Allow from anywhere (including external)
ports:
- port: 80
egress:
- to:
- podSelector:
matchLabels:
tier: backend
ports:
- port: 8080
- to: # DNS
ports:
- port: 53
protocol: UDP
---
# Backend: only from frontend, only to database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
spec:
podSelector:
matchLabels:
tier: backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
tier: frontend
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
tier: database
ports:
- port: 5432
- to:
ports:
- port: 53
protocol: UDP
---
# Database: only from backend, no egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-policy
spec:
podSelector:
matchLabels:
tier: database
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
tier: backend
ports:
- port: 5432
egress:
- to:
ports:
- port: 53
protocol: UDP

CNI Plugins​

The Container Network Interface (CNI) plugin is responsible for assigning IPs to pods and enabling pod-to-pod communication.

PluginKey Features
CalicoMost popular. BGP routing, NetworkPolicy support, high performance
FlannelSimple overlay (VXLAN). No native NetworkPolicy (use Calico as policy-only)
CiliumeBPF-based, excellent performance, advanced NetworkPolicy, observability
WeaveSimple overlay, NetworkPolicy support, easy setup

In the exam, the CNI is pre-installed. You just need to know:

# Check which CNI is installed
ls /etc/cni/net.d/
cat /etc/cni/net.d/10-calico.conflist

# CNI binaries location
ls /opt/cni/bin/

Key Takeaways​

  1. Every pod gets its own IP — this is the fundamental Kubernetes networking rule
  2. Services provide stable IPs/DNS for ephemeral pods — know all 4 types
  3. ClusterIP = internal, NodePort = external via node:port, LoadBalancer = cloud LB
  4. DNS format: <service>.<namespace>.svc.cluster.local — memorize this
  5. Ingress = HTTP routing — requires an Ingress Controller to be installed
  6. NetworkPolicy = pod-level firewall — default is allow-all; any policy makes it deny-default
  7. AND vs OR in NetworkPolicy from/to is the #1 exam trap — practice it
  8. Always allow DNS (port 53 UDP) when using egress policies
  9. Use kubectl exec + nslookup/curl to debug networking in the exam