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.
| Range | What it addresses | Set at |
|---|---|---|
| Node network | The machines themselves | Your infrastructure |
| Pod CIDR | Every pod IP, routed by the CNI | Cluster creation; must not overlap the node network |
| Service CIDR | Virtual IPs that no interface owns | Cluster creation; must not overlap pod CIDR or node network |
| Cluster DNS | The Service IP of CoreDNS | Usually 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 endpointsempty 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 for | Resolves to |
|---|---|
api | A Service in the same namespace |
api.storefront | A Service in another namespace of the same cluster |
api.storefront.svc.cluster.local | The fully qualified name — always unambiguous |
pool-0.db.storefront.svc.cluster.local | One specific pod behind a headless Service |
10-244-1-7.storefront.pod.cluster.local | A pod addressed by its IP, dashes for dots |
api.storefront.svc.cluster.local. | The same name, with no search-list expansion |
ndots:5means 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.
dnsConfigon the pod can lowerndotsand 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
fromortoarray, the entries are OR-ed, butnamespaceSelectorandpodSelectorin the same entry are AND-ed. ipBlockhandles 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.
FAQ
Does a NetworkPolicy apply to a Service?
How do I debug a blocked connection?
calicoctl or the Cilium Hubble UI), which shows exactly which rule dropped the packet.Related
Ingress, Gateway API and exposing applications RBAC, service accounts and pod security
Last refreshed 2026-09-18.