CKAD — Kubernetes Application Developer
Service, Ingress and NetworkPolicy
Goal
You specify the three kinds of Service ports distinctly, and write named ports, headless Services, StatefulSet DNS, Ingress routing, and a NetworkPolicy allowlist as manifests.
Why it matters
Nearly all the mistakes that come up with Services are one of two: a selector that differs from the Pod labels, and mixing up the three ports. When the selector is off, the endpoints are empty without any error and the client sees only connection refused. The single habit of checking whether kubectl get endpoints is empty saves most of your time.
If you remember ports by direction, you will not mix them up. port is the incoming side (the port a client connects to on the Service), targetPort is the outgoing side (the port the Service connects to on the Pod), and nodePort is the side that comes in from outside (the port opened on the node).
A headless Service is a choice to give up load balancing. It creates no virtual IP and has DNS return the list of Pod IPs as is, and paired with a StatefulSet, each Pod gets a unique name. Use it for workloads where a client has to name a specific instance (a DB primary, a shard).
A NetworkPolicy is summarized in two sentences: allow by default, and an allowlist once a policy applies. That is why in practice you first apply a default deny to the whole namespace and then open only the communication you need. Also remember that policies do not override one another; they add up as a union.
Steps
- Create the namespace
ckad-net. Create a Deploymentfrontend(2 replicas, labelapp=frontend, imagenginx:1.27, container port80namedhttp) and a Servicefrontend(ClusterIP,port: 80,targetPort: 80, selectorapp=frontend). - Create a Deployment
backend(2 replicas, labelapp=backend, imagenginx:1.27, container port8080namedapi) and a Servicebackend(ClusterIP,port: 80,targetPort: api— by name, selectorapp=backend). - Create a Service
frontend-np. TypeNodePort,port: 80,targetPort: 80,nodePort: 30080, selectorapp=frontend. - Create a headless Service
cache-hs(clusterIP: None,port: 6379, selectorapp=cache) and a StatefulSetcache(3 replicas,serviceName: cache-hs, labelapp=cache, imagenginx:1.27). Then write the fully qualified DNS name of Pod 0 on one line in/root/ckad-net/dns.txt. - Create an Ingress
shop.ingressClassName: nginx, hostshop.ckad.local, path/(pathTypePrefix) → Servicefrontendport 80, path/api(pathTypePrefix) → Servicebackendport 80. - Create a NetworkPolicy
backend-allow-frontend. The target is theapp=backendPods,policyTypes: [Ingress], the allowed source is theapp=frontendPods, and the port is TCP8080. - Create a NetworkPolicy
default-deny-ingress.podSelector: {}(the whole namespace),policyTypes: [Ingress], and leave the ingress rules empty. At this point, check withkubectl get pods -n ckad-net -l app=backendthat the Servicebackend's selector actually picks at least 2 Pods.
Notes
- Extract a skeleton with
kubectl create deployment frontend --image=nginx:1.27 --replicas=2 -n ckad-net --dry-run=client -o yamland name the entry underports. kubectl expose deployment frontend --port=80 --target-port=80 -n ckad-netfills in the selector automatically.- DNS name format for step 4:
<파드이름>.<서비스이름>.<네임스페이스>.svc.cluster.local(the placeholders are the Pod name, the Service name, and the namespace) - Common mistake 1: swapping
podSelector(the target the policy applies to) andfrom[].podSelector(the allowed source) in a NetworkPolicy. - Common mistake 2: specifying a
nodePortbelow 30000. The default allowed range is 30000–32767. - Common mistake 3: when you write a name in
targetPort, it must be the port name of the container, not of the Service. - This environment has no Ingress controller and no real CNI data plane, so you cannot verify that routing and policies actually work. The goal is to write the objects correctly.
Deployment and ClusterIP Service
Create the namespace ckad-net. Create a Deployment frontend (2 replicas, label app=frontend, image nginx:1.27, container port 80 named http) and a Service frontend (ClusterIP, port: 80, targetPort: 80, selector app=frontend).
Use kubectl expose deployment or write the Service directly. The endpoints attach only if the Service's selector exactly matches the Pod labels. If you name the container port now, the next step is easier.
Connecting targetPort with a named port
Create a Deployment backend (2 replicas, label app=backend, image nginx:1.27, container port 8080 named api) and a Service backend (ClusterIP, port: 80, targetPort: api — by name, selector app=backend).
Write the name you gave in the container's ports[].name as a string in the Service's targetPort. The name must be at most 15 characters of lowercase letters, digits, and hyphens.
Opening to the outside with NodePort
Create a Service frontend-np. Type NodePort, port: 80, targetPort: 80, nodePort: 30080, selector app=frontend.
Set the type to NodePort and specify nodePort yourself. The allowed range is 30000–32767, and if you do not specify it, one is assigned automatically. port and targetPort stay as they are.
Headless Service and StatefulSet
Create a headless Service cache-hs (clusterIP: None, port: 6379, selector app=cache) and a StatefulSet cache (3 replicas, serviceName: cache-hs, label app=cache, image nginx:1.27). Then write the fully qualified DNS name of Pod 0 on one line in /root/ckad-net/dns.txt.
A headless Service is clusterIP: None. You have to write that Service's name in the StatefulSet's serviceName for per-Pod DNS names to exist. The naming rule puts dots in the order Pod name, Service name, namespace.
Host and path routing with an Ingress
Create an Ingress shop. ingressClassName: nginx, host shop.ckad.local, path / (pathType Prefix) → Service frontend port 80, path /api (pathType Prefix) → Service backend port 80.
It is networking.k8s.io/v1, and the backend is specified with service.name and service.port.number. pathType is required on each path. The rules have the structure rules[].host and rules[].http.paths[].
Allowing only the frontend with a NetworkPolicy
Create a NetworkPolicy backend-allow-frontend. The target is the app=backend Pods, policyTypes: [Ingress], the allowed source is the app=frontend Pods, and the port is TCP 8080.
podSelector points to the target the policy applies to, and ingress[].from[].podSelector points to the allowed source. It is easy to swap the two, so be careful. Narrow the port as well.
Default deny policy and selector check (comprehensive)
Create a NetworkPolicy default-deny-ingress. podSelector: {} (the whole namespace), policyTypes: [Ingress], and leave the ingress rules empty. At this point, check with kubectl get pods -n ckad-net -l app=backend that the Service backend's selector actually picks at least 2 Pods.
An empty podSelector: {} means all Pods in the namespace. If you put no ingress rules at all on it, it becomes a default deny. And check directly with kubectl get pods -l that the Service selector actually picks real Pods.