Networking, DNS and NetworkPolicy

The flat cluster network, how Services get addresses, how CoreDNS resolves names, and how to cut traffic down from allow-everything.

The cluster network model

Kubernetes assumes a flat network with three simple rules: every pod gets its own IP, every pod can reach every other pod without NAT, and agents on a node can reach all pods on that node. Everything else is the CNI plugin's business.

RangeWhat it addressesSet at
Node networkThe machines themselvesYour infrastructure
Pod CIDREvery pod IP, routed by the CNICluster creation; must not overlap the node network
Service CIDRVirtual IPs that no interface ownsCluster creation; must not overlap pod CIDR or node network
Cluster DNSThe Service IP of CoreDNSUsually the tenth address of the Service CIDR
kubectl get pods -o wide                 # pod IPs and the node each runs on
kubectl get svc -A                       # ClusterIP, and ExternalIP if any
kubectl get svc kubernetes -o jsonpath='{.spec.clusterIP}'
kubectl get nodes -o custom-columns=NAME:.metadata.name,PODCIDR:.spec.podCIDR
kubectl get endpointslices -l kubernetes.io/service-name=web
  • A Service IP is virtual: nothing answers pings to it. Traffic to it is rewritten by kube-proxy or an eBPF dataplane into a real pod IP.
  • kubectl get endpoints empty means the selector matches nothing or no pod is Ready. That is a label problem far more often than a network problem.
  • Pod IPs change on every restart. Address pods through a Service, or through a headless Service when you need a specific one.
  • Overlapping CIDRs between pod, service and node ranges are the classic cause of a cluster that works internally and cannot reach anything external.

CoreDNS and how names resolve

CoreDNS runs as a Deployment in kube-system behind a Service named kube-dns. The kubelet writes its address into every pod's /etc/resolv.conf, along with a list of search domains, which is why short names work at all.

# what a pod's resolver actually contains
kubectl exec -it web-abc -- cat /etc/resolv.conf
# nameserver 10.96.0.10
# search storefront.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5
You ask forResolves to
apiA Service in the same namespace
api.storefrontA Service in another namespace of the same cluster
api.storefront.svc.cluster.localThe fully qualified name — always unambiguous
pool-0.db.storefront.svc.cluster.localOne specific pod behind a headless Service
10-244-1-7.storefront.pod.cluster.localA pod addressed by its IP, dashes for dots
api.storefront.svc.cluster.local.The same name, with no search-list expansion
  • ndots:5 means any name with fewer than five dots is tried against every search domain first. That is convenient interactively and wasteful in a service that makes thousands of calls.
  • Use fully qualified names with a trailing dot in application config to skip the search list entirely.
  • Do not hard-code the CoreDNS ClusterIP in an application. Use the name, and let DNS move if you rebuild the cluster.
  • dnsConfig on the pod can lower ndots and trim the search list when lookup latency shows up in profiles.
spec:
  dnsPolicy: ClusterFirst
  dnsConfig:
    options:
      - name: ndots
        value: "2"
      - name: timeout
        value: "2"

NetworkPolicy: from allow-everything to explicit

By default every pod can talk to every other pod. A NetworkPolicy is an allow-list that restricts this, and it only works if the CNI plugin enforces it — Calico, Cilium and Antrea do; a plain flannel install does not.

# 1. deny everything in the namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
  namespace: storefront
spec:
  podSelector: {}                # every pod in this namespace
  policyTypes: ["Ingress", "Egress"]
---
# 2. allow the API to receive traffic from the ingress controller only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-ingress
  namespace: storefront
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
          podSelector:
            matchLabels:
              app.kubernetes.io/name: ingress-nginx
      ports:
        - protocol: TCP
          port: 8080
---
# 3. allow the API out to Postgres and to DNS, nothing else
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-egress
  namespace: storefront
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes: ["Egress"]
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432
    - to:                          # DNS must be allowed explicitly
        - namespaceSelector: {}
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
  • Policies are additive. Traffic is allowed if any policy permits it, so a default-deny plus a specific allow is the normal shape.
  • Once a pod is selected by any policy for a direction, that direction becomes default-deny for that pod — the allow rules are the only openings.
  • In a from or to array, the entries are OR-ed, but namespaceSelector and podSelector in the same entry are AND-ed.
  • ipBlock handles external addresses, and remember that pod IPs are rewritten by NAT in some paths, which can surprise you at the boundary.
  • Start in audit or dry-run mode if your CNI supports it, or you will lock yourself out of a working system with the first default-deny.
⚠️
A default-deny egress policy that forgets DNS breaks name resolution for every pod it selects, and the symptom looks like a total network outage rather than a firewall rule. Always add the UDP and TCP port 53 rule to the CoreDNS namespace in the same change.

FAQ

Does a NetworkPolicy apply to a Service?
No. Policies select pods, and they filter traffic before or after the Service translation happens, depending on the plugin. Restricting pods that way also restricts what clients can reach through their Service.
How do I debug a blocked connection?
Confirm the pods have the labels your policy expects, verify the CNI enforces policy at all, then look at the plugin's own tooling (for example calicoctl or the Cilium Hubble UI), which shows exactly which rule dropped the packet.

Ingress, Gateway API and exposing applications RBAC, service accounts and pod security

Last refreshed 2026-09-18.