The Exam Blueprint · CKA · D3 · Services & Networking · 20%

Services & Networking

Domain 3 of the CKA blueprint is where Kubernetes stops being a scheduler for isolated containers and becomes a network of things that can actually find and reach each other. Weighted at 20% — the second-largest domain after Troubleshooting — it asks you to demonstrate Pod-to-Pod connectivity across nodes with no NAT in the way, write and reason about NetworkPolicy rules that flip a Pod from open to deny-by-default, choose correctly among ClusterIP, NodePort, and LoadBalancer Service types, route external traffic in with both the older Ingress resource and the newer Gateway API, and read how CoreDNS turns a Service name into a routable IP. None of it is abstract on exam day: you get a live cluster with something broken in exactly one of these places, and there's no partial credit for guessing which. This page goes section by section, with real manifests and the traps that catch people who understand the theory but haven't typed the YAML enough times.

☺ Explain it like I'm 10

Picture a big hotel. Every room has its own phone that can dial any other room directly — no operator, no code to punch in first — that's Pod-to-Pod connectivity. The front desk keeps a board listing "Room Service" and "Housekeeping" by job instead of room number, and always forwards your call to whoever's actually on duty — that's a Service and its endpoints. A guest who calls a friend by name instead of a room number is using CoreDNS, the hotel's phone book. The revolving front door, and the concierge deciding which outside visitors get sent to which floor, is your Ingress or Gateway API. And the little card slipped under one room's door that lists exactly which other rooms are allowed to call in? That's a NetworkPolicy — the moment one shows up on a door, every other room goes silent by default.

🐦Your host for this topic: Pip the Hummingbird — the messenger whose entire job is carrying a request from a Service to the right Pod before you've finished typing the URL. Nobody in the Squad is better placed to walk this domain.

Domain 3 of 5: where this page sits in the blueprint

☺ Like you're 10: Five topics make up the whole exam, and they're not equal — this one is a fifth of the total score, and it's the one about things actually talking to each other.

A quick honesty note before anything else: the "D1–D5" numbering used across this blueprint section is this course's own organizing label for its five pages, in the order the official curriculum happens to list them — it is not a number the CNCF itself prints anywhere. What is official is the weight next to each one, taken from the CNCF's Certified Kubernetes Administrator (CKA) Exam Curriculum, and confirmed against the current version on this course's own certifications hub before you rely on it.

One naming quirk worth flagging up front: the v1.35 curriculum PDF prints this domain's official name as "Servicing and Networking" — a lot of older study material, and this page's own title, write it as "Services and Networking." Same domain, same six competencies below; don't let the word order trip you up if you see it phrased either way.

Published competencies — Servicing and Networking (20%)
Understand connectivity between Pods
Define and enforce Network Policies
Use ClusterIP, NodePort, LoadBalancer service types and endpoints
Use the Gateway API to manage Ingress traffic
Know how to use Ingress controllers and Ingress resources
Understand and use CoreDNS

Six bullets here, out of 27 published competencies across all five domains (8 + 5 + 6 + 5 + 3). The exam itself is performance-based — two hours on live clusters in a browser terminal, graded entirely on the end state you leave behind, not on the commands you typed to get there.

⚠ Verify this before you book

This page describes exam content, not exam logistics — price, pass mark, duration, and the permitted-documentation allowlist all change over time, and this site is an independent, unofficial study resource with no affiliation to the CNCF or the Linux Foundation. For the full, current logistics table and the official links, see this course's certifications hub and the CKA study plan before you pay for anything.

Pod-to-Pod connectivity: the flat network model

☺ Like you're 10: Every Pod gets its own phone number, and any Pod can dial any other Pod's number directly — no front desk has to patch the call through.

Kubernetes networking rests on a small set of guarantees every conformant cluster must provide, regardless of which plugin implements them:

That's the whole model — usually called the "IP-per-Pod, flat network" requirement. Kubernetes itself doesn't implement it; a CNI (Container Network Interface) plugin does, using one of three broad strategies: an overlay network that encapsulates Pod traffic inside VXLAN or Geneve packets between nodes (classic Flannel), native routing that programs each node's kernel routing table or speaks BGP so Pod-CIDR ranges are directly routable (Calico's default mode), or an eBPF datapath that skips iptables entirely and processes packets in the kernel (Cilium). The exam curriculum only asks you to understand that this interface exists — the CNI itself gets its own competency bullet under Domain 1 — but every symptom you'll troubleshoot in this domain assumes you know what "no NAT" is supposed to look like when it's working.

kubectl get pods -n shop -o wide
# NAME           READY   STATUS    IP            NODE
# web-7f9d8      1/1     Running   10.244.1.5    node-a
# web-8c2e1      1/1     Running   10.244.1.9    node-a
# api-5f7a2      1/1     Running   10.244.2.14   node-b

kubectl exec -n shop web-7f9d8 -- ping -c 2 10.244.2.14
kubectl exec -n shop web-7f9d8 -- curl -s -o /dev/null -w '%{http_code}\n' http://10.244.2.14:8080/healthz
Node A web-7f9d8 10.244.1.5 web-8c2e1 10.244.1.9 CNI agent (per-node) Node B api-5f7a2 10.244.2.14 CNI agent (per-node) 10.244.1.5 → 10.244.2.14 · no NAT Every Pod IP is routable from every other Pod, on any node — no NAT, no port mapping, same IP on both ends.
◆ Key idea

"No NAT" is the guarantee the entire rest of this domain is built on top of. NetworkPolicy filters this flat network without changing its addressing; Services give it stable names for a set of Pod IPs that keeps churning; CoreDNS turns those names back into IPs. Lose sight of the flat network underneath and all three start to feel like unrelated magic instead of layers stacked on one simple rule.

NetworkPolicy: allow-all until one policy says otherwise

☺ Like you're 10: With no rules on the door, every room can call in. The moment even one card gets slipped under a specific door, that door only answers the calls the card allows — every other door stays exactly as open as before.

Out of the box, with zero NetworkPolicy objects in a namespace, the flat network from the section above means every Pod can reach every other Pod — allow-all is the default. A NetworkPolicy doesn't get layered on top of that as an extra filter you have to author from scratch for every Pod; instead, it flips a switch, one Pod (or group of Pods) and one direction at a time. The rule that trips people up every single sitting: the moment any NetworkPolicy's podSelector matches a Pod for a given policyTypes entry, that Pod becomes deny-by-default in that direction — only the traffic explicitly allowed by policies selecting it gets through. Pods that no policy selects are completely unaffected and stay wide open. And when two or more policies do select the same Pod, their rules are additive — the union of everything any of them allows — never a stricter intersection.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: shop
spec:
  podSelector: {}            # empty selector = every Pod in this namespace
  policyTypes:
    - Ingress
    - Egress
                              # no ingress/egress blocks at all = nothing is allowed

That five-line manifest is the standard default-deny baseline — apply it first in a namespace, confirm everything breaks, then add back exactly the paths you mean to allow. Here's the follow-up policy that reopens traffic for one real service, including the DNS egress most people forget:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-allow-scoped
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels: { app: web }   # only web Pods may call api
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:                               # api may call the payments Pods on 443
        - podSelector:
            matchLabels: { app: payments }
      ports:
        - protocol: TCP
          port: 443
    - to:                               # and must be able to reach CoreDNS, or
        - namespaceSelector:
            matchLabels: { kubernetes.io/metadata.name: kube-system }
          podSelector:
            matchLabels: { k8s-app: kube-dns }
      ports:
        - { protocol: UDP, port: 53 }
        - { protocol: TCP, port: 53 }   # TCP fallback for answers too large for UDP
Before any NetworkPolicy After: policy selects app=api (Ingress) api Pod not yet selected web batch-job other-ns Pod all three reach api — allow-all is the default api Pod now selected · deny-by-default web batch-job other-ns Pod allowed blocked blocked only the source the policy names still gets in
⚠ Watch out

Two separate traps live in the manifest above. First: a NetworkPolicy object is accepted by the API server whether or not anything enforces it. Some CNI plugins (Calico, Cilium, Weave Net, Antrea) implement the NetworkPolicy API; a bare-bones plugin like plain Flannel by default does not — the object saves cleanly, kubectl get shows it, and it silently does nothing. Second: an Egress policy with no explicit rule for port 53 to CoreDNS blocks DNS resolution for every selected Pod, and the failure looks exactly like an application bug — timeouts and "could not resolve host," not an obvious permissions error.

✎ Try it

In a scratch namespace, apply the default-deny-all policy above, then confirm two Pods that could ping each other a moment ago now time out. Add back api-allow-scoped, scoped to a fake webapi pair, and confirm that path works while every other Pod stays blocked. Then run kubectl get networkpolicy -n shop -o yaml and read your own rules back — on exam day you won't have time to re-derive default-deny from first principles.

Service types: ClusterIP, NodePort, LoadBalancer

☺ Like you're 10: Pods get replaced constantly and their IPs change every time — a Service is the one phone number that never changes, no matter which Pod actually answers.

A Service is a stable virtual IP and DNS name in front of a changing set of Pods, selected by label. Membership is tracked in EndpointSlice objects (the scalable successor to the older singular Endpoints object), which kube-proxy on every node watches and turns into local packet-forwarding rules — iptables, IPVS, or an eBPF datapath if the CNI supplies one — that DNAT traffic bound for the Service's virtual IP out to one of the live Pod IPs in its EndpointSlices. There are three Service types the curriculum names explicitly, and each is a strict superset of the one before it:

TypeReachable fromWhat it adds
ClusterIP (default)Inside the cluster onlyA stable virtual IP, routed by kube-proxy to whichever Pods match the selector right now.
NodePortAnywhere that can reach a Node's IPEverything ClusterIP does, plus the same port opened on every Node's IP (default range 30000–32767), forwarded straight into the ClusterIP.
LoadBalancerWherever the external load balancer is exposedEverything NodePort does, plus a cloud-controller-manager request to provision a real external L4 load balancer pointed at the NodePort.
apiVersion: v1
kind: Service
metadata:
  name: checkout
  namespace: shop
spec:
  type: ClusterIP          # or omit `type` entirely — this is the default
  selector:
    app: checkout
  ports:
    - port: 80
      targetPort: 8080
kubectl get svc checkout -n shop
kubectl get endpointslices -n shop -l kubernetes.io/service-name=checkout -o wide
apiVersion: v1
kind: Service
metadata:
  name: checkout-external
  namespace: shop
spec:
  type: LoadBalancer        # superset of NodePort, which is a superset of ClusterIP
  selector:
    app: checkout
  ports:
    - port: 443
      targetPort: 8080
      nodePort: 31743        # optional — auto-assigned from 30000-32767 if omitted
kubectl get svc checkout-external -n shop -w
NAME                TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)
checkout-external   LoadBalancer   10.96.14.201   <pending>     443:31743/TCP
checkout-external   LoadBalancer   10.96.14.201   203.0.113.44  443:31743/TCP
⚠ Watch out

EXTERNAL-IP only moves past <pending> if something is actually watching for LoadBalancer Services and provisioning infrastructure for them. On a managed cloud cluster that's the cloud-controller-manager talking to the provider's API; on bare metal, a kind cluster, or minikube, nothing does that job unless you've separately installed something like MetalLB — the Service will sit at <pending> indefinitely, which is a completely normal, expected state on those clusters and not a bug to chase.

One naming detail worth locking in for exam speed: a headless Service (spec.clusterIP: None) skips virtual-IP load balancing entirely and instead makes CoreDNS return the individual Pod IPs directly — the mechanism StatefulSets rely on for stable per-Pod DNS names, covered in the CoreDNS section below.

Ingress vs. the Gateway API

☺ Like you're 10: Both are ways to let the outside world in through one front door and get routed to the right room by web address — one is the older concierge who only speaks in sticky-notes, the other is a newer one with an actual rulebook.

Services (even LoadBalancer ones) operate at L4 — IPs and ports, no awareness of HTTP hostnames or paths. Routing shop.example.com/checkout and shop.example.com/catalog to two different backend Services through one shared IP needs an L7-aware object, and the curriculum names two of them.

Ingress: the established resource

An Ingress object (networking.k8s.io/v1) declares host- and path-based routing rules, but it does nothing on its own — it needs an Ingress controller (ingress-nginx, Traefik, HAProxy, a cloud provider's own) actually running in the cluster to watch Ingress objects and program a real reverse proxy from them. The object's core fields are portable across controllers; anything beyond basic host/path routing — rewrite rules, rate limiting, canary weighting — gets bolted on through annotations, and annotation keys are controller-specific and non-portable. Move from ingress-nginx to a different controller and every annotation-driven behavior has to be re-translated by hand.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop-ingress
  namespace: shop
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /checkout
            pathType: Prefix
            backend:
              service: { name: checkout, port: { number: 80 } }
          - path: /catalog
            pathType: Prefix
            backend:
              service: { name: catalog, port: { number: 80 } }

Gateway API: the newer, role-oriented family

The Gateway API is a separate resource family, developed as its own subproject with an independent release cadence from Kubernetes core — its v1.0 release, which took GatewayClass, Gateway, and HTTPRoute to General Availability, shipped in October 2023. It splits the single Ingress object into three, deliberately assigning each to a different owner: a GatewayClass (cluster-scoped, provided by whoever installed the implementation — Cilium, Envoy Gateway, and others all ship one) names an implementation; a Gateway (owned by cluster/infra operators) binds to a class and declares listeners — ports, protocols, hostnames, TLS; and one or more Route resources — HTTPRoute, and also TCPRoute, GRPCRoute, TLSRoute for protocols Ingress never addressed at all — attach to a Gateway and are owned by the application teams that actually need the routing rules. That split is the headline improvement over Ingress: infra and app teams stop needing write access to the same object, and traffic splitting, header-based matching, and weighted backends are typed schema fields instead of controller-specific annotation strings.

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: shop-gateway
  namespace: infra
spec:
  gatewayClassName: cilium
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "*.example.com"
      tls:
        certificateRefs:
          - name: shop-tls-cert
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: checkout-route
  namespace: shop
spec:
  parentRefs:
    - { name: shop-gateway, namespace: infra }
  hostnames: [ "shop.example.com" ]
  rules:
    - matches:
        - path: { type: PathPrefix, value: /checkout }
      backendRefs:
        - { name: checkout, port: 80, weight: 90 }
        - { name: checkout-canary, port: 80, weight: 10 }   # native traffic
                                                              # splitting — no
                                                              # annotation needed
IngressGateway API
Owned byOne object, usually one teamSplit across GatewayClass / Gateway (infra) and Route (app team)
Advanced behaviorController-specific annotationsTyped schema fields — traffic weighting, header matching, native
Protocol scopeHTTP/HTTPS onlyHTTP, gRPC, TCP, UDP, TLS passthrough
PortabilityCore rules portable; annotations are notDesigned for portability across implementations
CKA competency"Know how to use Ingress controllers and Ingress resources""Use the Gateway API to manage Ingress traffic"

The curriculum lists both bullets, so treat this as "know both, on a live cluster," not "the new one replaced the old one." For the deeper mechanics of Gateway API implementations and the CNI overlap, see Platform Engineering's Cilium tool page and its Networking & Connectivity deep dive — this course's own ingress-nginx and cert-manager tool guides cover the TLS half of the Ingress example above.

CoreDNS: how a Service gets a name

☺ Like you're 10: Every Service already has a phone number (its ClusterIP) — CoreDNS is just the phone book that turns the name you actually remember into that number.

CoreDNS runs as a Deployment in kube-system, fronted by a Service still historically named kube-dns for backward compatibility — that Service's ClusterIP is what every kubelet writes into a Pod's /etc/resolv.conf as its nameserver, via the default dnsPolicy: ClusterFirst. Every Service automatically gets an A/AAAA record at <service>.<namespace>.svc.cluster.local, resolving to its ClusterIP; a headless Service (clusterIP: None) instead returns the individual Pod IPs directly at that same name, and a StatefulSet's per-Pod hostname resolves at <pod-name>.<service>.<namespace>.svc.cluster.local — the exact mechanism that gives each StatefulSet replica a stable, individually addressable identity.

kubectl exec -n shop web-7f9d8 -- nslookup checkout.shop.svc.cluster.local
kubectl exec -n shop web-7f9d8 -- cat /etc/resolv.conf
Server:    10.96.0.10
Address:   10.96.0.10:53
Name:      checkout.shop.svc.cluster.local
Address:   10.96.22.108

nameserver 10.96.0.10
search shop.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

Read that resolv.conf closely — it's a common exam and production trap. With ndots:5, any name containing fewer than five dots gets every entry in search appended and tried, in order, before the bare name is ever attempted. A short internal name like checkout (0 dots) benefits from this — it resolves in one hop as checkout.shop.svc.cluster.local. But an external name like api.stripe.com (2 dots) also falls under that threshold: the Pod tries api.stripe.com.shop.svc.cluster.local, then api.stripe.com.svc.cluster.local, then api.stripe.com.cluster.local — three lookups that can only fail — before finally trying api.stripe.com itself. That's real added latency on every external call, worse under any packet loss, and it's the classic explanation for "external API calls are randomly slow" tickets that have nothing wrong with the external API at all.

🐦 Pip's-eye view

"I don't carry a single packet myself — I just make sure the name you asked for lands on the Pod that's actually supposed to answer it right now, even though that Pod didn't exist five minutes ago and might not exist five minutes from now. A Service's whole point is that you never have to know which Pod. That's also exactly why a NetworkPolicy that forgets to let me through on port 53 looks like everything is broken — because from where you're standing, it is."

Common exam traps in this domain

☺ Like you're 10: These are the specific ways people who understand every idea above still lose points on the actual task.

🎬 At the Pod Squad
🦊

Foxy: checkout can't reach payments anymore — every call just times out. This started right after Benny's last change.

🦫

Benny: That would be me. I locked checkout down with an Egress NetworkPolicy so it can only talk to the payments Pods — tighter blast radius, felt like a good idea.

🐦

Pip: Pod-to-Pod ping to payments' IP still works fine, so the flat network's not the problem. But nslookup payments.shop.svc.cluster.local from inside checkout just hangs.

🐢

Timmy: Benny — does that policy have an explicit egress rule for port 53 to CoreDNS?

🦫

Benny: ...no. I only wrote the rule for the payments Pods themselves.

👺

Gizmo: Easy — just delete the whole NetworkPolicy. Back to allow-all, problem solved, ship it. 🤑

🐢

Timmy: No — the guardrail stays. Add the missing DNS egress rule, don't tear down the one you just built for a good reason.

🐦

Pip: Added it — UDP and TCP 53 to kube-dns in kube-system. Resolves in one hop now, and payments is reachable again.

🐢 Timmy's checkpoint

1. Name the three network-model guarantees every conformant Kubernetes cluster provides for Pod-to-Pod traffic. 2. The moment a NetworkPolicy's podSelector matches a Pod for Ingress, what happens to that Pod's default ingress behavior — and what happens to every other Pod the policy doesn't select? 3. Put ClusterIP, NodePort, and LoadBalancer in order from most to least restricted, and say what each one adds over the last. 4. Why might a LoadBalancer Service's EXTERNAL-IP stay <pending> forever on some clusters, and is that always a bug? 5. What's the core difference in who owns what between a plain Ingress object and the Gateway API's GatewayClass/Gateway/HTTPRoute split? 6. What DNS name does a headless Service (clusterIP: None) return instead of a single ClusterIP, and which Kubernetes workload type depends on that?

Check your answers
  1. Every Pod gets its own IP; all Pods can reach all other Pods without NAT, on any node; all Nodes can reach all Pods without NAT; and the source IP a Pod sends with is the same IP the receiver sees — no translation happens mid-connection.
  2. That Pod becomes deny-by-default for Ingress traffic — only what's explicitly allowed by policies selecting it gets through. Every Pod the policy doesn't select is completely unaffected and stays exactly as open (or as restricted) as it already was.
  3. ClusterIP (internal only) → NodePort (adds the same port opened on every Node's IP) → LoadBalancer (adds a provisioned external load balancer pointed at the NodePort). Each type is a strict superset of the one before it.
  4. EXTERNAL-IP only resolves if something is watching LoadBalancer Services and provisioning real infrastructure for them — a cloud-controller-manager on managed clusters, or something like MetalLB on bare metal. With neither present, it's the correct, expected permanent state, not a bug.
  5. Ingress bundles routing rules into one object, usually with one owner, extended through controller-specific, non-portable annotations. The Gateway API splits ownership: infra/cluster operators own the GatewayClass and Gateway (listeners, TLS), and application teams own Route resources like HTTPRoute attached to that Gateway — with advanced behavior like traffic weighting as typed, portable schema fields instead of annotations.
  6. It returns the individual Pod IPs directly at the Service's normal DNS name, rather than one ClusterIP. StatefulSets depend on this — it's what gives each replica a stable, individually addressable per-Pod DNS name at <pod-name>.<service>.<namespace>.svc.cluster.local.

Domain 2 covers what runs before traffic ever needs to find it — see Workloads & Scheduling — and Domain 4 covers what those workloads keep on disk, in Storage. For the mechanics behind everything on this page taken further — CNI internals, service mesh sidecars, multi-cluster networking — see this course's own Networking & the CNI deep dive, and for the RBAC and admission-control half of "who's allowed to touch what," see RBAC & Admission Control. NetworkPolicy's security framing is covered from the defense-in-depth angle in this course's own Security: Defense in Depth and, cross-course, in DevSecOps's Kubernetes security deep dive. CKA sits at the base of the CNCF's wider Kubestronaut ladder alongside CKAD and CKS; the other certifications on that ladder — including Cilium's own CCA — are covered on the sibling Golden Astronaut course. When you're ready to drill this domain specifically, continue to the CKA study plan and CKA practice tasks.