Skip to main content
mastering ckad certified kubernetes application developer

Service Types and DNS Resolution

9 min read Chapter 32 of 87
Summary

Covers ClusterIP as the default internal Service with...

Covers ClusterIP as the default internal Service with iptables/IPVS routing, NodePort for external access on ports 30000-32767, LoadBalancer for cloud-provider integration, Service DNS resolution patterns, port vs targetPort vs containerPort, Endpoints verification, and imperative and declarative Service creation.

Service Types and DNS Resolution

Service types: ClusterIP provides an internal virtual IP, NodePort extends ClusterIP by opening a port on each node, and LoadBalancer extends NodePort by provisioning an external load balancer

Kubernetes Service types build on each other: ClusterIP allocates an internal virtual IP that routes to Pod IPs via iptables or IPVS rules. NodePort extends ClusterIP by binding a static port (30000–32767) on every cluster node, so external clients can reach the Service through any node’s IP. LoadBalancer extends NodePort by requesting a cloud provider’s external load balancer, which distributes traffic to the NodePort across all nodes. Each Service type is a superset of the one before it — a LoadBalancer Service is also a NodePort and a ClusterIP.

ClusterIP: The Default Service

ClusterIP is the most common Service type and the one you will create most often on the CKAD exam. It assigns a virtual IP address from the cluster’s service CIDR range. This IP is not bound to any physical interface — it exists as routing rules in iptables (or IPVS, depending on the kube-proxy mode) on every node.

Creating a ClusterIP Service

Start with a Deployment to serve as the backend:

kubectl create deployment nginx --image=nginx:1.25 --replicas=3

Create a Service imperatively using kubectl expose:

kubectl expose deployment nginx --port=80 --target-port=80

This creates a ClusterIP Service named nginx that:

  • Listens on port 80 (the --port flag)
  • Forwards traffic to port 80 on the backing Pods (the --target-port flag)
  • Selects Pods with the label app=nginx (inherited from the Deployment)

Verify the Service:

kubectl get svc nginx

Expected output:

NAME    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   10.96.45.123   <none>        80/TCP    5s

The CLUSTER-IP is the virtual IP assigned to this Service. The EXTERNAL-IP is <none> because ClusterIP Services are internal-only.

Declarative Service YAML

The equivalent YAML manifest:

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

The selector field is critical. It must match the labels on the Pods you want the Service to route to. A mismatch between the Service selector and Pod labels is one of the most common debugging scenarios on the CKAD exam — the Service will exist but route to nothing.

Understanding port, targetPort, and containerPort

These three values confuse nearly every Kubernetes beginner because they all say “port” but refer to different things:

  • containerPort (in the Pod spec): Declares which port the container process listens on. This is purely informational — Kubernetes does not enforce it. If your app listens on 8080, set containerPort: 8080.

  • port (in the Service spec): The port the Service exposes. Clients connect to <ClusterIP>:<port>. This is the Service’s front door.

  • targetPort (in the Service spec): The port on the backing Pod where traffic is forwarded. This must match the port the container actually listens on (i.e., the containerPort).

A concrete example: your application container listens on port 8080. You want the Service to expose it on port 80:

# Pod spec
containers:
  - name: app
    image: my-app:v1
    ports:
      - containerPort: 8080

---
# Service spec
spec:
  ports:
    - port: 80          # Clients connect here
      targetPort: 8080  # Forwarded to the container

Clients call <ClusterIP>:80. The Service forwards to Pod IP on port 8080. The container receives the connection on 8080. If targetPort is omitted, it defaults to the value of port.

DNS Resolution Inside the Cluster

Every Service gets a DNS entry in CoreDNS following a predictable pattern:

<service-name>.<namespace>.svc.cluster.local

For a Service named nginx in the default namespace:

nginx.default.svc.cluster.local

Short DNS Forms

You do not always need the full name. Kubernetes configures Pod DNS search domains so that shorter forms resolve:

  • Same namespace: use the service name alone — nginx
  • Cross-namespace: use <service>.<namespace>nginx.production
  • Fully qualified: nginx.production.svc.cluster.local

On the exam, you’ll most often use the short form within the same namespace. Cross-namespace resolution comes up when a Pod in namespace frontend needs to reach a Service in namespace backend.

Testing DNS Resolution

Launch a temporary Pod with DNS tools:

kubectl run dnstest --image=busybox:1.36 --rm -it --restart=Never -- nslookup nginx

Expected output:

Server:    10.96.0.10
Address:   10.96.0.10:53

Name:      nginx.default.svc.cluster.local
Address:   10.96.45.123

The resolved address matches the Service’s ClusterIP. To test cross-namespace resolution:

kubectl run dnstest --image=busybox:1.36 --rm -it --restart=Never -- nslookup nginx.default

You can also test from a long-running Pod:

kubectl exec -it busybox -- nslookup nginx.default.svc.cluster.local

Verifying Endpoints

Endpoints are the glue between a Service and its Pods. When the Service selector matches Pod labels, the Endpoints controller automatically populates the Endpoints object with the IPs of those Pods.

kubectl get endpoints nginx

Expected output:

NAME    ENDPOINTS                                      AGE
nginx   10.244.1.3:80,10.244.1.4:80,10.244.2.5:80     2m

Three IPs — one for each replica in the Deployment. If this list is empty, the Service selector does not match any Pod labels. This is the first diagnostic step when a Service is unreachable.

Scale the Deployment and watch Endpoints update:

kubectl scale deployment nginx --replicas=5
kubectl get endpoints nginx

The Endpoints list now shows five IPs. Scale down:

kubectl scale deployment nginx --replicas=2
kubectl get endpoints nginx

Down to two. Endpoints track the live Pod set in real time.

NodePort: External Access via Node IPs

NodePort extends ClusterIP by opening a specific port on every node in the cluster. External clients can reach the Service by connecting to any node’s IP on that port.

Creating a NodePort Service

kubectl expose deployment nginx --type=NodePort --port=80 --target-port=80

Or declaratively:

apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
      nodePort: 30080

If you omit nodePort, Kubernetes assigns one from the 30000–32767 range automatically.

kubectl get svc nginx-nodeport

Expected output:

NAME             TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nginx-nodeport   NodePort   10.96.78.210   <none>        80:30080/TCP   3s

The 80:30080 notation means port 80 on the ClusterIP maps to port 30080 on every node.

How NodePort Routing Works

When traffic arrives at <NodeIP>:30080, kube-proxy forwards it to the Service’s ClusterIP on port 80, which then routes to a backing Pod on port 80. The path is:

Client → NodeIP:30080 → ClusterIP:80 → PodIP:80

Every node in the cluster listens on port 30080, even nodes that are not running the target Pods. Kube-proxy handles cross-node routing.

Testing in Kind

In a Kind cluster, nodes are Docker containers. Get the node’s internal IP:

kubectl get nodes -o wide

Use the node’s INTERNAL-IP to test:

curl http://<NODE-INTERNAL-IP>:30080

If you configured port mappings in your Kind cluster configuration (as described in Chapter 2), you can also reach the NodePort via localhost.

LoadBalancer: Cloud Provider Integration

LoadBalancer extends NodePort by provisioning an external load balancer through the cloud provider’s API. In AWS, this creates an ELB/NLB. In GCP, a Google Cloud Load Balancer. The external load balancer distributes traffic across all nodes on the NodePort.

apiVersion: v1
kind: Service
metadata:
  name: nginx-lb
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
kubectl get svc nginx-lb

In a cloud environment:

NAME       TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE
nginx-lb   LoadBalancer   10.96.90.100   34.123.45.67     80:31234/TCP   30s

The EXTERNAL-IP is the load balancer’s public IP. Clients connect there, and the LB routes to NodePort 31234 on any cluster node.

In a Kind cluster (no cloud provider), EXTERNAL-IP stays <pending> indefinitely. This is expected — there is no cloud API to provision a load balancer. Tools like MetalLB can simulate LoadBalancer behavior in bare-metal or local environments, but they are outside the scope of CKAD preparation.

CKAD Exam Note

The exam environment runs on a real cluster with cloud capabilities. You may be asked to create a LoadBalancer Service and verify the external IP is assigned. Know the YAML structure and understand that EXTERNAL-IP in <pending> state means the cloud controller has not yet provisioned the load balancer.

Port-Forwarding for Quick Testing

During the exam, you may not have external access to Services. kubectl port-forward creates a tunnel from your local machine to a Service or Pod:

kubectl port-forward svc/nginx 8080:80

This maps local port 8080 to Service port 80. Open another terminal and test:

curl http://localhost:8080

Port-forwarding is a debugging tool, not a production pattern. It is useful on the exam when you need to verify a Service works without setting up NodePort or Ingress.

Multi-Port Services

A single Service can expose multiple ports. This is common when an application serves HTTP on one port and metrics on another:

apiVersion: v1
kind: Service
metadata:
  name: multi-port
spec:
  selector:
    app: my-app
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8080
    - name: metrics
      protocol: TCP
      port: 9090
      targetPort: 9090

When a Service defines multiple ports, each port must have a name field. Without names, the API server rejects the manifest. Clients specify which port they want by connecting to the Service IP on the corresponding port number.

Session Affinity

By default, kube-proxy distributes requests across backing Pods using a round-robin algorithm (iptables mode) or weighted least-connections (IPVS mode). If your application requires that a client consistently reaches the same Pod, configure session affinity:

spec:
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800

With sessionAffinity: ClientIP, kube-proxy routes all requests from a given client IP to the same Pod for the duration of the timeout (default: 10800 seconds / 3 hours). This is sticky sessions at the transport layer — not cookie-based HTTP stickiness, which requires an Ingress Controller.

Session affinity is useful for stateful applications that store session data in memory. On the CKAD exam, you are unlikely to be asked to configure it, but recognizing the field helps when debugging unexpected routing behavior.

Exam Strategies for Services

Services appear in nearly every CKAD exam task, whether directly or as supporting infrastructure:

  • Use imperative creation when possible: kubectl expose deployment <name> --port=80 --target-port=8080 is faster than writing YAML.
  • Verify Endpoints immediately: After creating a Service, run kubectl get ep <name> to confirm Pods are connected.
  • Remember DNS shorthand: Within the same namespace, the service name alone resolves. Cross-namespace, use <service>.<namespace>.
  • Default type is ClusterIP: If a task says “create a Service” without specifying the type, ClusterIP is correct.
  • Port-forward for quick tests: kubectl port-forward svc/<name> 8080:80 lets you test without creating additional Pods.

Cleanup

kubectl delete deployment nginx
kubectl delete svc nginx nginx-nodeport nginx-lb 2>/dev/null