Search…

Kubernetes networking

In this series (14 parts)
  1. Why Kubernetes exists
  2. Kubernetes architecture
  3. Core Kubernetes objects
  4. Kubernetes networking
  5. Storage in Kubernetes
  6. Kubernetes configuration and secrets
  7. Resource management and autoscaling
  8. Kubernetes workload types
  9. Kubernetes observability
  10. Kubernetes security
  11. Helm and package management
  12. GitOps with ArgoCD
  13. Kubernetes cluster operations
  14. Service mesh concepts

Kubernetes networking has three rules. Every pod gets its own IP. Pods can communicate with any other pod without NAT. Agents on a node can communicate with all pods on that node. Everything else builds on these guarantees.

The pod network model

Each pod receives a unique IP from the cluster’s pod CIDR range. Containers within a pod share that IP and communicate over localhost. This is fundamentally different from Docker’s default bridge networking, where containers get isolated IPs behind NAT.

The flat network means:

  • Pod A on Node 1 can reach Pod B on Node 2 directly by IP.
  • No port mapping is needed between pods.
  • Network policies can filter traffic at the pod level.

A CNI (Container Network Interface) plugin implements this model. The plugin runs on each node and configures the network so pod-to-pod traffic flows correctly.

Popular CNI plugins:

PluginApproachKey feature
CalicoBGP or VXLANNetworkPolicy support, high performance
CiliumeBPFKernel-level filtering, observability
FlannelVXLAN overlaySimple setup, no NetworkPolicy
Weave NetMesh overlayAutomatic encryption

Services as stable endpoints

Pod IPs are ephemeral. When a pod dies and is replaced, it gets a new IP. Services solve this by providing a stable virtual IP (ClusterIP) that load-balances across matching pods.

When you create a Service, Kubernetes assigns it a ClusterIP from the service CIDR range. DNS resolves the service name to this IP. kube-proxy programs iptables or IPVS rules on every node so that traffic hitting the ClusterIP is forwarded to one of the backend pods.

apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: production
spec:
  type: ClusterIP
  selector:
    app: order-service
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: grpc
      port: 9090
      targetPort: 9090

DNS records created:

  • order-service.production.svc.cluster.local resolves to the ClusterIP.
  • Short form order-service works within the same namespace.

kube-proxy modes

kube-proxy translates Service definitions into network rules. It supports three modes.

iptables mode (default): Creates one iptables rule per service endpoint. For a service with 100 pods, that is 100 rules. Updates are O(n) where n is the total number of rules.

IPVS mode: Uses the Linux kernel’s IPVS (IP Virtual Server) for load balancing. IPVS is designed for this workload. It handles thousands of services efficiently with O(1) connection routing.

nftables mode: Available in Kubernetes 1.29+. Uses nftables instead of iptables for a cleaner rule set and better performance.

Switch to IPVS mode in kube-proxy configuration:

apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
  scheduler: "rr"

Ingress and IngressController

Services handle internal routing. Ingress handles external HTTP/HTTPS traffic. An Ingress resource defines routing rules. An IngressController implements them.

graph LR
  Client["External Client"] --> LB["Cloud Load Balancer"]
  LB --> IC["Ingress Controller Pod"]
  IC --> |"Host: api.example.com"| SVC1["api-service"]
  IC --> |"Host: web.example.com"| SVC2["web-service"]
  SVC1 --> P1["Pod"]
  SVC1 --> P2["Pod"]
  SVC2 --> P3["Pod"]
  SVC2 --> P4["Pod"]

External traffic flows through the load balancer to the IngressController, which routes by hostname and path to backend Services.

A working Ingress manifest:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.example.com
        - web.example.com
      secretName: app-tls
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80
    - host: web.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-service
                port:
                  number: 80

Common IngressControllers:

  • NGINX Ingress Controller: Most widely used. Rich annotation set.
  • Traefik: Auto-discovery, built-in Let’s Encrypt.
  • HAProxy Ingress: High performance, enterprise features.
  • AWS ALB Ingress Controller: Native AWS ALB integration.

The newer Gateway API is replacing Ingress with a more expressive model. It separates infrastructure concerns (GatewayClass, Gateway) from application routing (HTTPRoute).

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-route
  namespace: production
spec:
  parentRefs:
    - name: main-gateway
  hostnames:
    - "api.example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1
      backendRefs:
        - name: api-service
          port: 80

NetworkPolicy

By default, all pods can communicate with all other pods. NetworkPolicy restricts this. Think of it as a firewall at the pod level.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-network-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api-service
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              environment: production
          podSelector:
            matchLabels:
              app: web-service
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53

This policy allows the api-service pod to receive traffic only from web-service pods in production namespaces, and to send traffic only to the postgres pod and DNS. Everything else is blocked.

Important: NetworkPolicy requires a CNI plugin that supports it. Flannel does not. Calico and Cilium do.

A default-deny baseline

Start with denying all traffic, then open specific paths:

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

This blocks all ingress and egress for every pod in the namespace. You then add targeted policies for each service.

Debugging networking issues

Common commands for troubleshooting:

# Check service endpoints
kubectl get endpoints order-service -n production

# Test DNS resolution from inside a pod
kubectl exec -it debug-pod -- nslookup order-service.production.svc.cluster.local

# Check if kube-proxy rules exist
kubectl exec -it debug-pod -- sh -c "iptables-save | grep order-service"

# Verify pod-to-pod connectivity
kubectl exec -it pod-a -- curl -s http://10.244.1.5:8080/healthz

# Check NetworkPolicy effects
kubectl describe networkpolicy api-network-policy -n production

When a service is unreachable, check this order: DNS resolution, endpoint list (are pods selected?), kube-proxy rules, NetworkPolicy, and finally pod health.

What comes next

Networking gets traffic to your pods. But stateful applications need persistent data. The next article covers storage in Kubernetes: PersistentVolumes, StorageClasses, and StatefulSets.

Start typing to search across all content
navigate Enter open Esc close