CKA — Kubernetes Administrator
A Service Is a Promise, Not a Proxy
Summary
A Service object does not carry traffic. It is only a declaration that builds a backend list (EndpointSlice) from a selector, and what actually moves packets is the node's data plane (kube-proxy's iptables/nftables or the CNI's eBPF). If you know this separation, you can split "the Service exists but I can't connect" into three layers.
Why this matters
Pods die and come back up with different IPs. So a name and a stable virtual IP were needed. A Service gives you both.
The problem is who maintains the backend list. When you write a selector in a Service, the endpoints controller finds the Pods that match those labels and are Ready, and fills in the EndpointSlice. If the selector is off by even one character, the EndpointSlice is created normally, but empty. No error appears. The Service, the Deployment, and the Pods are all Ready, yet you just cannot connect.
That is why the first question in diagnosing a service failure is always the same: "Does the endpoint have an IP?" If it does, it is a data plane problem; if not, it is a selector or Pod Ready problem.
How it works
| Type | What it does | Where it is used |
|---|---|---|
| ClusterIP | One virtual IP inside the cluster | Default |
| NodePort | Opens ports 30000–32767 on every node | Temporary access from outside |
| LoadBalancer | NodePort + provisioning of an external LB | Cloud / MetalLB |
| ExternalName | Returns only a CNAME, with no IP | Pointing at a system outside the cluster |
| headless (clusterIP None) | Returns Pod IPs directly, with no VIP | StatefulSet, client-side LB |
If there is more than one port, each port must have a name. This is so that an Ingress or other objects can refer to a port by name.
The place people get wrong most often in a NetworkPolicy is the structure of the from array.
- Two
fromitems, a namespaceSelector and a podSelector respectively → OR. Allowed if either one matches. - One
fromitem containing both → AND. Only Pods with that label in that namespace are allowed.
It is a difference of a single dash (-) in YAML, but the meaning is the opposite. This is where it is decided, both in the exam and in practice.
What it looks like in the field
Case 1 — A cluster with no kube-proxy at all. When I rebuilt a homelab with kubeadm 1.34, I did not install kube-proxy from the start, using --skip-phases=addon/kube-proxy, and brought up Cilium 1.20.1 with kubeProxyReplacement=true. A claim alone was not enough, so I actually counted.
kube-proxy 파드 수: 0
노드의 iptables KUBE- 체인 개수: 0
In a cluster that uses kube-proxy, the KUBE-SERVICES, KUBE-SVC-*, and KUBE-SEP-* chains exist in the dozens to hundreds, in proportion to the number of Services. There were 0. So how do Services work? If you dump the eBPF maps, this is what you get.
SERVICE ADDRESS BACKEND ADDRESS (REVNAT_ID) (SLOT)
10.96.0.10:53/TCP (1) 10.244.0.150:53/TCP (4) (1)
10.96.0.1:443/TCP (1) 10.0.0.120:6443/TCP (1) (1)
The frontends and backends are placed directly in a hash map inside the kernel. With iptables the rule chains grow linearly as Services increase, but eBPF is a hash map lookup, so the cost is constant regardless of the number of Services. The Service objects stay the same and only the data plane was swapped out — this is the moment that separation is confirmed in practice.
A bootstrap problem comes with this. When you turn on kubeProxyReplacement, you must also give k8sServiceHost=10.0.0.120 and k8sServicePort=6443. Without kube-proxy there is nothing to translate the ClusterIP (10.96.0.1) of the kubernetes Service into the real apiserver address, and Cilium itself, which would play that role, has not come up yet. It is a chicken-and-egg problem, so you have to tell it the real address directly.
Case 2 — L7 cannot be done with iptables. On the same cluster I tried a policy that separates GET with 200 and POST with 403. Same IP, same port, but split by HTTP method. iptables works at L3/L4 and cannot see the method, so it is impossible in principle. The standard NetworkPolicy likewise goes only up to L3/L4. It is a good example for drawing the line on how far a NetworkPolicy within the CKA scope can go.
Case 3 — Endpoints is now a thing of the past. The Endpoints API was deprecated in v1.33. If your code or scripts read Endpoints directly, change them to query EndpointSlice with the kubernetes.io/service-name label. It is a staple of pre-upgrade checklists.
What to do in the next lab
In the first lab you create ClusterIP, NodePort, headless, multi-port, ExternalName, and Ingress, and you yourself create and fix a situation where a selector typo leaves the endpoints empty. In the second lab you start a NetworkPolicy from default deny and narrow it down through six forms. This lab environment has no CNI to enforce policies, so you cannot verify actual traffic blocking, and only the object spec is graded. Instead it concentrates on spec-level traps such as the difference between AND and OR.