K8s by Example: Services (ClusterIP)

A Service provides a stable IP and DNS name for accessing Pods. Pod IPs are unreliable - they change on restarts and scaling. Services solve this with a persistent endpoint. ClusterIP is the default type and is internal-only.

service.yaml

Services use the core v1 API. The selector links the Service to Pods. port is what clients connect to.

apiVersion: v1
kind: Service
metadata:
  name: my-app
  labels:
    app.kubernetes.io/name: my-app
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: my-app

targetPort is where traffic is forwarded to on the Pod. You can use port names instead of numbers.

  ports:
    - name: http
      port: 80
      targetPort: 8080
      protocol: TCP
service-multiport.yaml

Services can expose multiple ports. Pods must have ALL labels in the selector to receive traffic.

spec:
  ports:
    - name: http
      port: 80
      targetPort: http
    - name: metrics
      port: 9090
      targetPort: 9090
  selector:
    app.kubernetes.io/name: my-app
    app.kubernetes.io/component: api
terminal

Each Service gets an Endpoint object automatically. The Endpoint maintains a real-time list of Pod IPs matching the selector.

$ kubectl get svc my-app
NAME     TYPE        CLUSTER-IP    PORT(S)   AGE
my-app   ClusterIP   10.96.0.100   80/TCP    5m

$ kubectl get endpoints my-app
NAME     ENDPOINTS                                   AGE
my-app   10.244.1.5:8080,10.244.2.3:8080,10.244.3.7:8080   5m
terminal

Watch endpoints update in real-time as Pods scale up and down.

$ kubectl scale deployment my-app --replicas=5
deployment.apps/my-app scaled

$ kubectl get endpoints my-app -w
NAME     ENDPOINTS
my-app   10.244.1.5:8080,10.244.2.3:8080,10.244.3.7:8080
my-app   10.244.1.5:8080,10.244.2.3:8080,10.244.3.7:8080,10.244.1.8:8080
my-app   10.244.1.5:8080,10.244.2.3:8080,10.244.3.7:8080,10.244.1.8:8080,10.244.2.9:8080
pod-env.yaml

Service discovery via DNS is preferred. Every Service gets a DNS name: service.namespace.svc.cluster.local.

env:
  - name: API_URL
    value: "http://my-app:80"

Cross-namespace references use service.namespace.svc format.

  - name: REDIS_URL
    value: "redis://redis.cache.svc:6379"

Full FQDN includes .cluster.local. Usually only needed for edge cases.

  - name: DB_HOST
    value: "postgres.database.svc.cluster.local"
terminal

Kubernetes DNS resolves service names dynamically. More reliable than environment variables which are set only at Pod start.

$ kubectl exec my-pod -- nslookup my-app
Server:    10.96.0.10
Address:   10.96.0.10#53

Name:      my-app.default.svc.cluster.local
Address:   10.96.0.100
service-session-affinity.yaml

Session affinity routes a client to the same Pod for a period. Useful for stateful apps. Default is None (round-robin).

spec:
  type: ClusterIP
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 3600
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app: my-app
terminal

Service types build on each other: ClusterIP (internal) → NodePort (adds external port) → LoadBalancer (adds cloud LB).

$ kubectl get svc
NAME              TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)
my-app-internal   ClusterIP      10.96.0.100   <none>          80/TCP
my-app-nodeport   NodePort       10.96.0.101   <none>          80:30080/TCP
my-app-lb         LoadBalancer   10.96.0.102   34.123.45.67    80:31234/TCP
terminal

Debug Service routing with kubectl. Check Endpoints first - if empty, your selector doesn’t match any Pods.

$ kubectl describe svc my-app
Name:              my-app
Selector:          app.kubernetes.io/name=my-app
Type:              ClusterIP
IP:                10.96.0.100
Port:              http  80/TCP
TargetPort:        8080/TCP
Endpoints:         10.244.1.5:8080,10.244.2.3:8080

$ kubectl get pods --show-labels | grep my-app
terminal

Test Service connectivity from inside the cluster with a temporary Pod.

$ kubectl run curl --rm -it --image=curlimages/curl -- curl http://my-app
<!DOCTYPE html>
<html>
<body>Hello from my-app!</body>
</html>
pod "curl" deleted

Index | GitHub | Use arrow keys to navigate |