Skip to content
EgyKode
Incidentkubernetes

Incident: Service-to-Service Calls Fail

Every Pod is Running and healthy, but one service cannot reach another by name. Work down from DNS.

Time
40 min
Level
Advanced
Objectives
3 objectives
Cost
Free

Before you start

You will need

  • kind or minikube
  • kubectl 1.28+

You will be able to

  • Test cluster DNS from inside a Pod
  • Distinguish a name-resolution failure from a connectivity failure
  • Recognise an egress policy that forgot DNS

CostFree

— runs on kind or minikube.

Success criteria

0 of 3

The incident#

The API Pods are Running and READY. The database Pods are Running and READY. The API logs show connection failures to the database, and nobody has changed either application.

Find out why they cannot talk.

Reproduce it#

Terminal
kubectl create namespace incident-03
kubectl apply -n incident-03 -f https://raw.githubusercontent.com/Waleeddarwesh/EgyKode/master/content/labs/fixtures/incident-03.yaml

By hand: deploy two services in one namespace, then apply a default-deny egress NetworkPolicy that does not allow UDP 53 to kube-system.

Your CNI must enforce NetworkPolicy or this will not reproduce.

kind's default CNI (kindnet) accepts policies and silently ignores them, so the calls will simply succeed and you will conclude the lab is broken. Use a cluster with Calico or Cilium:

Terminal
minikube start --cni=calico
# or kind with disableDefaultCNI: true, then apply Calico

Confirm enforcement is real before trusting any result:

Terminal
kubectl get pods -n kube-system -l k8s-app=calico-node

This is worth knowing beyond the lab: a NetworkPolicy that is accepted but not enforced is a security control that exists only on paper, and nothing warns you.

Test DNS before anything else#

More than half of "the network is broken" reports in Kubernetes are name resolution.

Terminal
kubectl -n incident-03 exec deploy/api -- nslookup db
kubectl -n incident-03 exec deploy/api -- nslookup db.incident-03.svc.cluster.local
kubectl -n incident-03 exec deploy/api -- cat /etc/resolv.conf

Three distinct outcomes, three different faults:

ResultMeans
Resolves to an IPDNS is fine — the problem is connectivity or the application
NXDOMAINThe Service does not exist, or the name is wrong for this namespace
Times outCoreDNS is unreachable — often an egress policy blocking UDP 53

That third case is the one that surprises people, because everything looks healthy.

Is CoreDNS itself alright?#

Terminal
kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=20

If the name resolves, test the connection#

Terminal
kubectl -n incident-03 exec deploy/api -- wget -qO- --timeout=5 http://db:5432 || echo "failed"
kubectl -n incident-03 get endpoints db

Separate the two failures deliberately:

  • Refused — something answered and said no. Routing and policy are fine; look at the service.
  • Timed out — nothing answered. Suspect a NetworkPolicy, or a Service with no endpoints.

NetworkPolicies#

Terminal
kubectl -n incident-03 get networkpolicy
kubectl -n incident-03 describe networkpolicy

Two properties cause most of these incidents:

  • A Pod selected by no policy is unrestricted. Security starts only when something selects it — which is why a default-deny is usually the first policy written, and why adding one breaks things that used to work.
  • Policies are additive with no deny rule. Traffic is allowed if any policy allows it, so you cannot fix this by adding a deny — you widen an allow.

The classic mistake: a default-deny egress policy that permits traffic to the database but forgets UDP 53 to CoreDNS. Every hostname lookup in the namespace then times out, while the policy looks correct because the database rule is right there.

yaml
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53

Before you change anything#

"The call fails because ___, which I proved by ___."

When it goes wrong#

nslookup returns NXDOMAIN, not a timeout

That is a different fault — the Service does not exist under that name. Check the namespace and kubectl get svc.

DNS works but the connection is refused

Resolution and policy are fine. The target is not listening on that port — check the Service's targetPort and the container.

Removing the policy fixes it, so you delete the policy

That is not a fix, it is removing the security control. Add the missing egress rule instead.


Check your reasoning#

Read this after you have fixed it, or after a genuine attempt. Being handed the answer costs you the only thing this tier teaches.

Root cause: a default-deny egress NetworkPolicy allows traffic to the database but not to CoreDNS. The API cannot resolve db, so it never opens a connection at all — the database rule is correct and never gets used.

The command that found it: nslookup db from inside the calling Pod timed out rather than returning NXDOMAIN. A timeout points at reachability of the resolver, not at a missing record.

The fix: add an egress rule permitting UDP 53 to the kube-system namespace.

Why everything looked healthy: the policy does exactly what it says, and both workloads are genuinely fine. Nothing reports an error, because from Kubernetes' point of view nothing is wrong.

Clean up#

DestructiveThis removes real resources. Check which environment you are in first.

Terminal
kubectl delete namespace incident-cl

The concept behind it

Next up

Lab 53 of 58 on the project path

Backup & Disaster Recovery DrillLose the database on purpose, restore it, and write down the RTO and RPO you actually achieved.55 minAdvanced

Previous: Incident: 502 Bad Gateway