Expose a Deployment with a Service

KubernetesBeginner
Practice Now

Introduction

Pods are temporary. A Deployment can replace Pods during rollout, restart, or scaling, so clients should not connect to individual Pod IPs. A Service gives clients a stable name and virtual IP, then uses labels to route traffic to the current matching Pods.

In this lab, you will create a small web Deployment, expose it with a ClusterIP Service, inspect how the Service selects backend Pods, test the Service from a client Pod, and scale the Deployment to see the Service endpoints update.

Create a Web Deployment

In this step, you will prepare a namespace and create a Deployment that can later be exposed with a Service.

Start in /home/labex/project:

mkdir -p /home/labex/project
cd /home/labex/project

Check that the Kubernetes API server is reachable. If the VM has just started and the node is not ready yet, wait a few seconds and run the command again.

kubectl get nodes --request-timeout=5s

Expected output includes:

NAME         STATUS   ROLES           AGE   VERSION
labex-v135   Ready    control-plane   ...   v1.35...

Wait until the cluster networking Pod is Ready. This lab sends traffic between Pods through a Service, so cluster networking needs to be healthy before you test access.

kubectl wait --namespace kube-system --for=condition=Ready pod -l k8s-app=calico-node --timeout=120s

Expected output:

pod/calico-node-... condition met

Create a namespace named ckad-prep-service and make it the current namespace:

kubectl create namespace ckad-prep-service
kubectl config set-context --current --namespace=ckad-prep-service

Create a Deployment named web-front with one NGINX Pod. The --port=80 flag records the container port in the Pod template. It does not create a Service by itself.

kubectl create deployment web-front --image=nginx:1.25 --port=80

Wait for the Deployment to become available, then inspect the Pod label. The label app=web-front is important because the Service will use it to find backend Pods.

kubectl rollout status deployment/web-front --timeout=120s
kubectl get pods -l app=web-front --show-labels

Expected output includes:

deployment "web-front" successfully rolled out
NAME                         READY   STATUS    RESTARTS   AGE   LABELS
web-front-...                1/1     Running   0          ...   app=web-front,...

Expose the Deployment with a ClusterIP Service

In this step, you will create a Service for the Deployment and inspect the fields that connect the Service to the Pods.

Use kubectl expose deployment to create a ClusterIP Service. The Service port is 80, and --target-port=80 tells the Service to forward traffic to port 80 on matching Pods.

kubectl expose deployment web-front --name=web-front --port=80 --target-port=80

Expected output:

service/web-front exposed

Inspect the Service:

kubectl get service web-front

Expected output includes:

NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
web-front   ClusterIP   ...          <none>        80/TCP    ...

Now inspect the Service selector and port mapping. The selector is how the Service finds Pods; the port mapping is how client traffic reaches the container.

kubectl get service web-front -o jsonpath='{.spec.selector.app}{" "}{.spec.ports[0].port}{":"}{.spec.ports[0].targetPort}{"\n"}'

Expected output:

web-front 80:80

Kubernetes stores the selected backend addresses in EndpointSlices. List the EndpointSlice created for this Service:

kubectl get endpointslice -l kubernetes.io/service-name=web-front

Expected output includes:

NAME              ADDRESSTYPE   PORTS   ENDPOINTS   AGE
web-front-...     IPv4          80      ...         ...

Test the Service from Inside the Cluster

In this step, you will create a temporary client Pod and send an HTTP request to the Service. A ClusterIP Service is meant for in-cluster access, so testing from another Pod is the most direct way to verify it.

Create a client Pod that stays running:

kubectl run service-client --image=busybox:1.36 --restart=Never --command -- sleep 3600

Wait for the client Pod to be Ready:

kubectl wait --for=condition=Ready pod/service-client --timeout=120s

Expected output:

pod/service-client condition met

Use wget inside the client Pod to request http://web-front. The name web-front resolves to the Service inside the namespace.

kubectl exec service-client -- wget -qO- http://web-front | head -n 5

Expected output includes the start of the default NGINX page:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

This proves the client reached the stable Service name, and the Service forwarded traffic to the current backend Pod.

Scale the Backend and Recheck Endpoints

In this step, you will scale the Deployment and confirm that the Service keeps the same stable front door while its backend endpoints change.

Scale the Deployment to two replicas:

kubectl scale deployment web-front --replicas=2
kubectl rollout status deployment/web-front --timeout=120s

Expected output includes:

deployment.apps/web-front scaled
deployment "web-front" successfully rolled out

List the Pods. There should now be two Running backend Pods with the same app=web-front label.

kubectl get pods -l app=web-front

Expected output includes two Pods:

NAME                         READY   STATUS    RESTARTS   AGE
web-front-...                1/1     Running   0          ...
web-front-...                1/1     Running   0          ...

Count the endpoints behind the Service. The command prints one line for each endpoint address and then counts the lines.

kubectl get endpointslice -l kubernetes.io/service-name=web-front -o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{"\n"}{end}' | wc -l

Expected output:

2

Request the Service again from the same client Pod:

kubectl exec service-client -- wget -qO- http://web-front | grep '<title>Welcome to nginx'

Expected output:

<title>Welcome to nginx!</title>

Summary

You exposed a Deployment with a ClusterIP Service and verified the full in-cluster access path. You created a backend Deployment, created a Service with a selector and port mapping, inspected the EndpointSlice generated for the Service, tested access from a client Pod, then scaled the Deployment and confirmed the Service continued routing to the updated backend set.