CertGrid CertGrid

Kubernetes networking cheat sheet

Services, DNS, Ingress, NetworkPolicy and the CNI, with the command that answers each question and the failure each one distinguishes. Ordered by where in the path a request breaks.

DNS names and resolution

  • <service>.<namespace>.svc.cluster.local

    The fully qualified name of a Service. The short form works only within the same namespace.

    Cross-namespace calls need at least <service>.<namespace>. A name that resolves from one Pod and not another is usually this.

    Full guide
  • <pod>.<service>.<namespace>.svc.cluster.local

    A per-Pod name, which only exists for a headless Service.

    This is what a StatefulSet's stable identity actually is, and it needs the governing Service to have clusterIP: None.

    Full guide
  • kubectl exec <pod> -- cat /etc/resolv.conf

    The nameserver and search domains the Pod was given.

    `ndots: 5` is why a short name generates several failed lookups before the right one, and why an external name resolves slowly.

    Full guide
  • kubectl exec <pod> -- nslookup <name>

    Resolve from inside the cluster, through CoreDNS.

    Resolving from your laptop proves nothing: cluster names only exist to Pods.

    Full guide
  • kubectl -n kube-system get pods -l k8s-app=kube-dns

    Whether CoreDNS is running. A CoreDNS Pod not Ready explains every resolution failure at once.

    Full guide
  • kubectl -n kube-system get cm coredns -o yaml

    The Corefile: the cluster domain, forwarding and any stub domains.

    Full guide
  • kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50

    CoreDNS logs, where an upstream loop or a broken forward shows up.

    Full guide

Services and endpoints

  • kubectl get svc <name> -o wide

    Type, cluster IP, ports and the selector.

    Full guide
  • kubectl get endpointslice -l kubernetes.io/service-name=<svc>

    The addresses behind a Service. The single most useful Service command.

    Empty means the selector matches nothing, the Pods are not Ready, or they are Pending. That splits the problem in one command.

    Full guide
  • kubectl get pods --show-labels -n <ns>

    Compare Pod labels against the Service selector.

    The selector matches labels and nothing else. Two objects can each look correct and still not match.

    Full guide
  • kubectl describe svc <name>

    Includes the resolved endpoints inline, which is quicker than fetching the EndpointSlice.

    Full guide
  • kubectl get svc <name> -o jsonpath='{.spec.ports[0].targetPort}'

    Which container port traffic is sent to.

    Wrong targetPort with healthy endpoints is a 502 through an Ingress and connection refused directly. It does not look like a Service fault.

    Full guide
  • kubectl get svc <name> -o jsonpath='{.status.loadBalancer.ingress}'

    The external address, if anything assigned one.

    Empty with no events at all means nothing is watching LoadBalancer Services. On bare metal that is the normal state without MetalLB.

    Full guide
  • kubectl get ipaddresspool -A

    MetalLB's address pools. None means MetalLB is installed and idle.

    A pool with no L2Advertisement or BGPAdvertisement allocates addresses and never announces them, which looks healthy and is unreachable.

    Full guide

kube-proxy and the data path

  • kubectl -n kube-system get ds kube-proxy

    One Pod per node. A node missing it has broken Service routing and working Pod networking.

    Full guide
  • sudo iptables-save | grep -c KUBE-SERVICES

    Confirm kube-proxy has written its iptables rules on this node.

    Full guide
  • sudo ipvsadm -Ln

    The IPVS virtual servers, when kube-proxy runs in IPVS mode.

    In iptables mode this is empty. Check the mode before concluding anything from an empty list.

    Full guide
  • sudo nft list tables

    Whether the node's rules live in nftables, which modern iptables writes to underneath.

    iptables-save and nft list ruleset are two views of the same rules. Rules under both legacy and nft backends is the real problem.

    Full guide
  • ip route get <pod-ip>

    How this node reaches a Pod address: via another node's IP, or through a tunnel interface.

    With Calico's CrossSubnet, same-subnet nodes route natively and the VXLAN device carries nothing.

    Full guide
  • ip -o route | grep -E 'blackhole|dev cali'

    The per-Pod /32 host routes and the blackhole for the node's own IPAM block.

    The blackhole is deliberate: it drops traffic to unassigned addresses in the local block instead of leaking it upstream.

    Full guide
  • kubectl get blockaffinities.crd.projectcalico.org

    Which IPAM block Calico gave each node. This is where Pod IPs come from.

    Calico ignores the Node object's spec.podCIDR entirely, so reading it while tracing an address is misleading.

    Full guide
  • kubectl get installation default -o jsonpath='{.status.mtu}'

    The MTU Calico calculated.

    MTU mismatch is the classic overlay failure: small requests work, large ones hang, TLS handshakes complete and then stall.

    Full guide

NetworkPolicy

  • kubectl get netpol -A

    Every policy in the cluster. The first command when connectivity is unexplained.

    Policies are namespaced, so a count above zero says nothing about whether your namespace is affected. Check both ends of the traffic.

    Full guide
  • kubectl describe netpol -n <ns>

    Every policy in a namespace explained in sentences, including what is isolated.

    Unusually good output for this resource: it spells out that an empty podSelector means all Pods in the namespace.

    Full guide
  • kubectl get networkpolicies.crd.projectcalico.org -A

    Calico's own policies, which kubectl get netpol never shows.

    Also check globalnetworkpolicies, which are cluster-wide and absent from any namespaced listing. Native policies can carry explicit deny actions.

    Full guide
  • kubectl run t -n <ns> --restart=Never --image=busybox:1.36 --command -- wget -qO- --timeout=5 http://<svc>/

    Probe from inside the namespace, then read it with kubectl logs.

    Timed out means packets are dropped, which is a policy or a firewall. Connection refused means routing worked and nothing is listening.

    Full guide
  • wget: bad address <name>

    The DNS-blocked signature: an egress policy is denying port 53 to kube-system.

    Distinct from a timeout. Allow both UDP and TCP 53 with a namespaceSelector on kubernetes.io/metadata.name.

    Full guide

Ingress and Gateway

  • kubectl get ingressclass

    Which controllers are installed and what their controller names are.

    An Ingress naming an absent class is created with no error, has an empty ADDRESS, and 404s everything.

    Full guide
  • kubectl describe ingress <name>

    The rules as the controller resolved them, with the Pod IPs behind each backend.

    Empty parentheses after a backend mean the Service selects nothing, which is the difference between a routing problem and a backend problem.

    Full guide
  • curl -s -o /dev/null -w '%{http_code}' -H 'Host: <host>' http://<addr>/<path>

    Test a rule without DNS. 404 is no match, 503 is no endpoints, 502 is a bad backend response.

    Full guide
  • curl -s -H 'Host: <host>' http://<addr>/<path> | grep -iE 'title|center'

    Read the error body. A server version footer means the backend wrote the 404, not the controller.

    The most time-saving check in Ingress debugging, and the one people skip.

    Full guide
  • kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=50

    The access log: method, path and status for every request the controller handled.

    A failing request absent from this log never reached the controller, which moves the search to DNS, the load balancer or NetworkPolicy.

    Full guide
  • curl -sk --resolve <host>:<port>:<ip> https://<host>:<port>/

    Test HTTPS with the real hostname for SNI while connecting to a chosen address.

    A Host header is not enough for TLS: the controller picks a certificate from SNI before decrypting anything.

    Full guide
  • echo | openssl s_client -connect <ip>:443 -servername <host> | openssl x509 -noout -subject -issuer -dates

    The certificate actually being served. Subject equal to issuer means self-signed.

    A subject naming a Fake Certificate means the controller could not load your Secret and fell back to its own.

    Full guide
  • kubectl get gateway <name> -o jsonpath='{.status.conditions}'

    Accepted means a controller claimed it; Programmed means the data plane is configured.

    Both True with an empty status.addresses means the Gateway's Service has no external address. Programmed is not reachable.

    Full guide
  • kubectl get httproute <name> -o jsonpath='{.status.parents}'

    Per-parent status. NotAllowedByListeners is a refused attachment; BackendNotFound is a missing Service.

    Two independent failures with two separate conditions, which is the diagnostic quality Ingress lacks.

    Full guide

Reading the failure

  • 404 from an Ingress or Gateway

    No rule matched the host and path, or the backend itself returned it.

    Establish who wrote it first: read the body, then ask the backend directly through its Service from a Pod.

    Full guide
  • 503 from an Ingress

    A rule matched and the Service has no ready endpoints.

    Go straight to the EndpointSlice. Populated endpoints with a 503 means the Ingress names a port the Service does not define.

    Full guide
  • 502 from an Ingress

    The controller connected to a Pod and could not use the answer.

    Opposite investigation from a 503: the Pod's real port, the protocol, or a crash mid-request. An HTTPS backend needs backend-protocol: HTTPS.

    Full guide
  • Connection timed out

    Packets dropped in silence. A NetworkPolicy, a firewall or a broken route.

    The backend's logs will be empty, because the request never arrived. That emptiness is the clue.

    Full guide
  • Connection refused

    Something answered with a reset: routing works and nothing is listening on that port.

    Points at a wrong port or targetPort, or an application that has not started.

    Full guide
  • curl reports 000

    No HTTP exchange happened at all: the connection or the TLS handshake failed.

    Not a server error, because there was never a response to have a status. Certificate verification failure produces this too.

    Full guide