Kubernetes Services Tutorials with Examples

Kubernetes Demo: 2 Pods + 1 Service + Label Matching

Goal: Create 2 Pods running different Nginx images and expose both through a single ClusterIP Service using label selectors.

Service: nginx-svc

Selector: app=nginx-demo

nginx-v1

app=nginx-demo

nginx-v2

app=nginx-demo

Step 1: Create 2 Pods and 1 Service

Copy and run:


cat > demo.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: nginx-v1
  labels:
    app: nginx-demo
    version: v1
spec:
  containers:
    - name: nginx
      image: scmgalaxy/nginx-devopsschoolv1
      ports:
        - containerPort: 80
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-v2
  labels:
    app: nginx-demo
    version: v2
spec:
  containers:
    - name: nginx
      image: scmgalaxy/nginx-devopsschoolv2
      ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  type: ClusterIP
  selector:
    app: nginx-demo
  ports:
    - port: 80
      targetPort: 80
EOF

kubectl apply -f demo.yaml

Step 2: Verify Pods, Labels and Service


# View pods with labels
kubectl get pods --show-labels

# View pods matching service selector
kubectl get pods -l app=nginx-demo -o wide

# View service
kubectl get svc nginx-svc

# Check which pods are selected (endpoints)
kubectl get endpointslice -l kubernetes.io/service-name=nginx-svc -o wide

# Check service selector
kubectl describe svc nginx-svc

Step 3: Test Service Load Balancing

Run requests from inside the Kubernetes cluster:


kubectl run test-client --rm -i \
  --restart=Never \
  --image=curlimages/curl \
  -- sh -c 'for i in 1 2 3 4 5 6 7 8 9 10; do curl -s http://nginx-svc; echo; done'

Both Pods are eligible to receive traffic. The exact distribution is not guaranteed.

Step 4: Change Pod Label and Observe Service

Remove nginx-v2 from the Service by changing its matching label:


# Change v2 label
kubectl label pod nginx-v2 app=other --overwrite

# Only nginx-v1 should match
kubectl get pods -l app=nginx-demo

# Verify service endpoints
kubectl get endpointslice -l kubernetes.io/service-name=nginx-svc -o wide

Restore the original label:


kubectl label pod nginx-v2 app=nginx-demo --overwrite

# Both pods match again
kubectl get pods -l app=nginx-demo

kubectl get endpointslice -l kubernetes.io/service-name=nginx-svc -o wide

Step 5: Cleanup

kubectl delete -f demo.yaml

Key concept: Kubernetes Service uses spec.selector to find Pods with matching metadata.labels. The Service does not select Pods by their names or container images.

Kubernetes Deployment + 3 Service Types

Step 1: Create Deployment

kubectl create deployment nginx-demo \
  --image=scmgalaxy/nginx-devopsschoolv1 \
  --replicas=2 \
  --port=80

Step 2: Expose as ClusterIP

kubectl expose deployment nginx-demo \
  --name=nginx-clusterip \
  --type=ClusterIP \
  --port=80 \
  --target-port=80

Step 3: Expose as NodePort

kubectl expose deployment nginx-demo \
  --name=nginx-nodeport \
  --type=NodePort \
  --port=80 \
  --target-port=80

Step 4: Expose as LoadBalancer

kubectl expose deployment nginx-demo \
  --name=nginx-lb \
  --type=LoadBalancer \
  --port=80 \
  --target-port=80

Step 5: Verify Everything

kubectl get deployments
kubectl get pods -o wide
kubectl get svc
kubectl get endpointslice
kubectl get pods --show-labels
kubectl describe svc nginx-nodeport

Step 6: Test Services

ClusterIP (inside cluster):

kubectl run curl-test --rm -it \
  --image=curlimages/curl --restart=Never \
  -- curl http://nginx-clusterip

NodePort (from outside, if node networking permits):

curl http://<NODE-IP>:<NODEPORT>

LoadBalancer (after external IP is assigned):

curl http://<EXTERNAL-IP>

Note: NodePort is normally allocated from 30000–32767. LoadBalancer requires a cloud load balancer integration or a solution such as MetalLB.

Step 7: Cleanup

kubectl delete svc nginx-clusterip nginx-nodeport nginx-lb
kubectl delete deployment nginx-demo

Result: One Deployment → 2 Pods → 3 Services (ClusterIP, NodePort, LoadBalancer), all targeting the same Pods through label matching.