Kubernetes Administration — Level by Level›04 · Services, DNS & basic networking

Lesson 04 of 32 · Level 1 — Foundations

Services, DNS & basic networking

Pods come and go and their IPs change. Services give them a stable name and address, and CoreDNS makes that name findable. Learn the Service types, how traffic really flows, and how to debug 'can't connect'.

Beginner → Practitioner
Key wordsServiceClusterIPNodePortLoadBalancerEndpointSliceCoreDNSkube-proxy
client pod curl http://web CoreDNS web → 10.96.12.7 Service web ClusterIP 10.96.12.7:80 selector app=web kube-proxy / eBPF rules on every node EndpointSlice ready pod IPs pod web-1 10.244.1.5:8080 pod web-2 10.244.2.5:8080 pod web-3 10.244.3.5:8080 NodePort node:3xxxx LoadBalancer external IP Ingress HTTP host/path ways in from outside
A Service gives pods one stable name and IP; kube-proxy spreads traffic to ready pods.

The problem Services solve

Every pod gets its own IP address, and any pod can reach any other pod across nodes (that's the CNI plugin's job). So why not just use pod IPs?

Because pods are temporary. A rollout, a crash, or a node drain replaces pods, and every new pod gets a new IP. Hard-coding pod IPs is like writing a friend's hotel room number in your address book.

A pizza shop has lots of staff, and they change shifts all day. You don't call Sam's personal phone. You call the shop's number, and whoever is working picks up. A Service is the shop's phone number: it never changes, and it rings whichever pods are working right now. DNS is the phone book that turns the name "pizza-shop" into that number.

Create a Service

Use the web Deployment from the previous lesson (label app: web):

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web          # send traffic to Ready pods with this label
  ports:
    - port: 80        # the Service's port
      targetPort: 80  # the container's port
$ kubectl apply -f web-svc.yaml
service/web created
$ kubectl get svc web
NAME   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   10.96.142.17   <none>        80/TCP    5s
$ kubectl get endpointslices -l kubernetes.io/service-name=web
NAME        ADDRESSTYPE   PORTS   ENDPOINTS                          AGE
web-x7k2p   IPv4          80      10.244.1.7,10.244.2.4,10.244.2.5   5s

The EndpointSlice is the live list of pod IPs behind the Service. Scale the Deployment and watch the list change: the Service IP stays the same.

Talk to it from inside the cluster

Start a throwaway debug pod and call the Service by name:

$ kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- sh
/ # nslookup web
Name:   web.default.svc.cluster.local
Address: 10.96.142.17
/ # wget -qO- http://web | head -4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
/ # exit

And from your laptop, without exposing anything publicly:

$ kubectl port-forward svc/web 8080:80
Forwarding from 127.0.0.1:8080 -> 80

Open http://localhost:8080 in a browser.

How DNS names work

CoreDNS (running in kube-system) gives every Service a name:

<service>.<namespace>.svc.cluster.local
   web   .  default  .svc.cluster.local
  • Same namespace: just web
  • Another namespace: web.shop
  • Always works: web.shop.svc.cluster.local

The Service types

Type Reachable from Typical use
ClusterIP (default) Inside the cluster only Service-to-service traffic (most Services)
NodePort <any node IP>:30000–32767 Simple external access, labs, behind your own LB
LoadBalancer A cloud/external load balancer IP Exposing apps on EKS/GKE/AKS; MetalLB on bare metal
ExternalName DNS alias to an outside name Pointing db at mydb.example.com
Headless (clusterIP: None) DNS returns pod IPs directly StatefulSets: db-0.db, db-1.db

HTTP from the outside world

For websites and APIs you'll usually put an Ingress or Gateway API route in front of ClusterIP Services, so one load balancer serves many apps by hostname and path, with TLS. The next lesson sets one up step by step; TLS is covered in lesson 19.

How traffic actually flows

Nothing actually "listens" on a ClusterIP. kube-proxy on every node watches Services and EndpointSlices, and programs iptables/IPVS rules (or an eBPF dataplane such as Cilium does it instead). A packet to 10.96.142.17:80 is rewritten on the spot to one of the pod IPs.

client pod ──► 10.96.142.17:80 (virtual) ──[kube-proxy rules: pick one]──► 10.244.2.4:80 (real pod)

Try it: break a Service and fix it

  1. Edit the Service and change the selector to app: webb (a typo): kubectl patch svc web -p '{"spec":{"selector":{"app":"webb"}}}'
  2. From the debug pod, wget -qO- http://web now hangs or fails, yet DNS still resolves. Why?
  3. Check kubectl describe svc web: Endpoints: <none>. That's your clue.
  4. Fix it: kubectl patch svc web -p '{"spec":{"selector":{"app":"web"}}}'

Rule of thumb: DNS works but the connection fails → check endpoints. No endpoints → selector/labels or readiness.

Going deeper: the debugging ladder and DNS gotchas

Work up the layers, and stop at the first one that fails:

  1. Are the pods Ready? (kubectl get pods -l app=web)
  2. Does the Service have endpoints? (kubectl get endpointslices)
  3. Does calling a pod IP directly work? (Tests the CNI and the app itself.)
  4. Does the ClusterIP work? (Tests kube-proxy / dataplane rules.)
  5. Does the name resolve? (Tests CoreDNS and /etc/resolv.conf.)

DNS gotcha: pods default to ndots:5, so a name like api.example.com is first tried with each search domain appended, causing extra lookups and latency on busy clusters. Use a trailing dot (api.example.com.) or tune dnsConfig for external-heavy workloads. Also, a ClusterIP not answering ping is normal, not a fault.

Recap

  • Pods get new IPs all the time. A Service gives a stable IP and name in front of matching Ready pods.
  • EndpointSlices hold the live pod list. Empty endpoints is the most common "can't connect" cause.
  • DNS name: service.namespace.svc.cluster.local. Short names work inside the same namespace.
  • ClusterIP inside, NodePort / LoadBalancer outside, Ingress / Gateway for HTTP.

This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.