Lesson  in  Kubernetes 101

Exposing the application with a Service

Pods come and go with different IPs; the Service gives them a stable door. Create a ClusterIP, discover it over internal DNS from a client Pod and open it to the outside with a NodePort.

You already know that the Pods of a Deployment appear and disappear all the time: every rollout, every failure, every scaling replaces them with others that have new IPs. Nobody can build anything serious on addresses that change. Kubernetes's solution is the Service: a stable virtual IP and a DNS name that spread the traffic among the Pods matching a label selector.

The Networking chapter of the book walks through Service, Ingress, Gateway API, NetworkPolicy and CoreDNS. This module builds them in that same order, starting with the piece all the others rest on.

Work from the dev-machine tab. The Explorer tab will show you visually how the Service connects to its Pods.

Step 1: The backend

First you need something to expose. By now you can do it on your own: create a Deployment called web with 2 replicas of the image ghcr.io/iximiuz/labs/nginx:alpine, whose Pods carry the label app: web and expose port 80. You can start from the deployment.yaml of the previous lesson.

Note down the Pods' IPs with kubectl get pods -o wide -l app=web. You will soon see why you are never going to need them.

Step 2: The ClusterIP Service

Create the file service.yaml:

cat << 'EOF' > service.yaml
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 80
EOF

The YAML, explained in questions and answers

What exactly does selector do here?

It picks the destination Pods dynamically: all the ones carrying the label app: web, now and in the future. This is where the label you have been setting since the first lesson comes full circle: the Service knows nothing about Deployments or ReplicaSets, only Pod labels.

What is the difference between port and targetPort?

port is the port the Service listens on (its virtual IP), and targetPort the container port it forwards to. Here they match, but it is common to expose port 80 on the Service toward port 8080 on the container.

What does type: ClusterIP mean?

It is the default type: the Service gets a virtual IP reachable only from inside the cluster. It is the right choice for communication between internal services.

Where are the Pods in this YAML?

Nowhere, and that is the beauty of it. The list of real IPs lives in separate objects called EndpointSlice, which Kubernetes maintains automatically from the selector. Look at them with kubectl get endpointslice -l kubernetes.io/service-name=web: you will recognize the IPs of your 2 Pods, each with its ready, serving and terminating conditions. (Until recently this lived in a single Endpoints object, deprecated today; you will run into it in old clusters and in half of Stack Overflow.)

Apply it and check the machinery:

kubectl apply -f service.yaml
kubectl get service web
kubectl get endpointslice -l kubernetes.io/service-name=web

Step 3: Internal DNS and the client test

The ClusterIP's IP is stable, but what gets used in practice is its DNS name. Every Service automatically gets the name <service>.<namespace>.svc.cluster.local, and inside the same namespace the short name is enough.

Check it from where it matters: inside another Pod. Create a long-running client (cliente means "client"):

kubectl run cliente --image=ghcr.io/iximiuz/labs/nginx:alpine --command -- sleep 100000

And from it, first resolve the name and then make the HTTP request:

kubectl exec cliente -- nslookup web.tienda.svc.cluster.local
kubectl exec cliente -- wget -qO- http://web

The answer is the nginx welcome page, served by one of the 2 Pods. Repeat the wget several times: the Service spreads the requests between both.

And now, the experiment that gives the whole lesson its meaning: delete one of the backend Pods (kubectl delete pod <name>) and repeat the wget from the client. It keeps working. The ReplicaSet created a new Pod with another IP, the EndpointSlice updated itself, and neither the client nor you had to notice.

Step 4: Opening up to the outside with NodePort

The ClusterIP does not exist outside the cluster. The NodePort type also opens a port (between 30000 and 32767) on every node. Create service-np.yaml:

cat << 'EOF' > service-np.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-np
spec:
  type: NodePort
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
EOF

The YAML, in one question

What does nodePort add to what was already there?

A third port in the chain: nodePort (30080, open on each node's IP) forwards to port (80, the Service), which forwards to targetPort (80, the container). If you do not declare it, Kubernetes picks one from the range at random; pinning it makes the lab predictable.

Apply it and test it from outside the cluster, that is, from the dev-machine itself:

kubectl apply -f service-np.yaml
curl http://cplane-01:30080

And now the check that really closes the lesson: open the Aplicacion tab. It is that same port 30080 of cplane-01, only seen from a real browser. Until a moment ago that tab gave an error, because nothing was listening there; the web-np you just applied is what turned it on.

Summary

  • A Service gives a stable virtual IP and DNS name to a changing set of Pods, chosen by label selector.
  • EndpointSlices are the live list of real IPs, with the state of each one; Kubernetes maintains it on its own.
  • ClusterIP is for internal traffic; NodePort opens the same Service on a port of every node.
  • The internal DNS (<service>.<namespace>.svc.cluster.local) is the canonical way for a Pod to find another service.

The NodePort opened the door for you, but exposing an odd port per application does not scale. The next lesson presents the real HTTP solution: the Ingress.

Previous lesson
Vertical Pod Autoscaler
Next lesson
Ingress