Steven's Knowledge

Networking

Services, Ingress, DNS, and NetworkPolicies - how pods talk to each other and the outside world

Networking

Pods come and go. Their IPs change. Networking in Kubernetes is the layer that gives you stable endpoints despite that churn.

The Model

Three rules to internalize:

  1. Every pod gets its own cluster-routable IP. Containers in the same pod share localhost.
  2. Pods can reach any other pod by IP — no NAT. (The network plugin / CNI makes this true.)
  3. Services are stable virtual IPs in front of pods. Clients talk to the Service, not the pods.

Services

A Service is a stable name + IP that load-balances across a set of pods selected by labels.

ClusterIP — internal (default)

apiVersion: v1
kind: Service
metadata:
  name: api-server
spec:
  type: ClusterIP                          # the default
  selector:
    app: api-server                        # picks pods with this label
  ports:
    - port: 80                             # the Service's port
      targetPort: 3000                     # the pod's port
      protocol: TCP

Other pods reach it as api-server (same namespace) or api-server.production.svc.cluster.local (anywhere).

NodePort — exposes on every node's port

spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 3000
      nodePort: 30080                      # 30000-32767 range

Reachable at http://<any-node-ip>:30080. Fine for dev; ugly for production.

LoadBalancer — provisions a real LB

spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 3000

On a managed cloud, this provisions a cloud load balancer (AWS ALB/NLB, GCP LB, etc.) and gives you a public IP/hostname. One LB per Service — expensive at scale. Use Ingress instead for HTTP.

Service Types Compared

TypeReachable fromUse case
ClusterIPOther podsService-to-service
NodePortAnyone who can reach a nodeDev clusters, niche routing
LoadBalancerThe internetOne-off external services
ExternalNameOther pods (CNAME)Aliases for external DNS

DNS

The cluster runs CoreDNS automatically. Names resolve as:

NameResolves to
api-serverThe Service in the same namespace
api-server.productionThe Service in namespace production
api-server.production.svc.cluster.localFully qualified — works anywhere
postgres-0.postgres.production.svc.cluster.localSpecific pod in a StatefulSet (headless Service)

/etc/resolv.conf inside a pod is auto-configured so short names work.

Ingress

Putting one cloud LB in front of every service is silly. Ingress is a single entry point that does HTTP host/path routing to many Services.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/rate-limit: "100"
    nginx.ingress.kubernetes.io/rate-limit-window: "1m"
spec:
  ingressClassName: nginx
  tls:
    - hosts: [api.example.com]
      secretName: api-tls
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-server
                port: { number: 80 }
          - path: /admin
            pathType: Prefix
            backend:
              service:
                name: admin-portal
                port: { number: 80 }

Ingress doesn't run itself — you install an Ingress controller (the actual reverse proxy) in the cluster:

ControllerNotes
ingress-nginxMost popular; nginx under the hood
TraefikAuto-discovery, dynamic config
AWS Load Balancer ControllerProvisions AWS ALBs from Ingress
Gateway APIThe successor to Ingress; richer model

Gateway API is the modern replacement for Ingress (GA in 2023). It separates the "infra owner" view (GatewayClass, Gateway) from the "app owner" view (HTTPRoute) and handles TCP/UDP, not just HTTP. New projects should consider Gateway API; existing Ingress works fine and isn't going away.

NetworkPolicies

By default, every pod can talk to every other pod. NetworkPolicies add a firewall on top:

# Default-deny ingress for the production namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}                          # all pods in the namespace
  policyTypes: [Ingress]

Then allow what you need:

# Only the API can talk to the database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-allow-api
  namespace: production
spec:
  podSelector:
    matchLabels: { app: postgres }
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector:
            matchLabels: { app: api-server }
      ports:
        - { protocol: TCP, port: 5432 }
# API can call external HTTPS but nothing else
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-egress-https
  namespace: production
spec:
  podSelector:
    matchLabels: { app: api-server }
  policyTypes: [Egress]
  egress:
    - to:
        - namespaceSelector:
            matchLabels: { kubernetes.io/metadata.name: kube-system }   # DNS
      ports:
        - { protocol: UDP, port: 53 }
    - to:
        - ipBlock: { cidr: 0.0.0.0/0 }
      ports:
        - { protocol: TCP, port: 443 }

NetworkPolicies only work if your CNI plugin enforces them. Most do (Calico, Cilium, Weave); some (like flat-config Flannel) silently ignore them. Verify with kubectl describe networkpolicy and an actual connectivity test.

What's Next

Pods can find each other and reach the world. Next, give them their config and storage → Config & Storage.

On this page