In the last post, we talked about deployments, which are great for persistence–sort of. The problem is that, when a pod is restarted, it gets restarted with, most likely, a different IP address inside the cluster. This makes it impossible for other things in the cluster to know how to get to that pod!

Pods come and go, and their IP addresses change…So what good does that do us if we’re expecting a pod to always be available to some other part of the cluster or even to a user outside the cluster? Answer: not much.

But don’t worry, Kubernetes has you covered with services!

A service, like a deployment, isn’t actually a “tangible thing” in the cluster. The only tangible part about it is an IP address and list of endpoints–one endpoint per pod included in the service. In our case, we will only have one endpoint in our service, since our deployment only has one pod.

Create a Service

Let’s create a service…But first, delete anything you already have in your namespace from the last post:

kubectl -n <your-ns-name> delete deployment <tab-complete>

Check to make sure you don’t have any pods lingering. kubectl -n <your-ns-name> get all should be empty.

Now create a file named myfirstservice.yml with the following in it:

apiVersion: v1
kind: Service
metadata:
  name: myfirstservice
spec:
  selector:
    app: myfirstdeployment
  ports:
  - port: 80
    targetPort: 80

Again: bonus points for using the VS Code service snippet. It really makes it easy.

Now let’s get the service into the cluster:

kubectl -n <your-ns-name> apply -f myfirstservice.yml # protip: you can tab complete this!

Now a quick kubectl -n <your-ns-name> get service will show the service running, and you’ll notice that it has its own IP address.

What good does that do us? Nothing, currently. Check what the service looks like right now:

kubectl -n <your-ns-name> describe service <tab-complete>

Interactive Pod for Testing

You’ll see that an IP address is listed (take note of it!), but the important line–endpoints–is empty. That makes sense, since we deleted everything earlier. What happens if we try to use that service? Let’s create an interactive pod and try:

kubectl -n <your-ns-name> run --generator=run-pod/v1 --rm -i -t --image=alpine testpod -- sh

Inside this Alpine container, try reaching the service:

ping <service IP>
wget <service IP>

Both of these will fail. Let’s fix that! Exit from the alpine pod with exit, and the pod will be deleted (since we had --rm in the command above).

Now let’s recreate the deployment from the last post:

kubectl -n <your-ns-name> apply -f myfirstdeployment.yml

Check back on the service, and you’ll see that there is now an endpoint:

kubectl -n <your-ns-name> describe service <tab-complete>

At this point, you may be thinking that something magical just happened. And it did! But the explanation lies in how labels and selectors in Kubernetes work. We wrote the template in the deployment with the label app: myfirstdeployment, and in the service we included a selector with that same app: myfirstdeployment label. That’s how the service knew to point itself to our deployment’s pod.

Don’t worry if that doesn’t make sense. There’s a lot going on, I’ve abbreviated a lot, and it takes time and practice to understand how it all fits together. For now, let’s verify that the service is actually working by recreating the interactive Alpine pod again, as above. Then run the same ping and wget commands.

Gotcha! ping won’t work here–good to remember when troubleshooting. But wget will save an index.html file. Run cat index.html, and you’ll see the basic Nginx welcome page.

Fun fact: you can also contact the Nginx pod directly from your Alpine pod if you know its IP address, but remember that pods are ephemeral and their IPs can always change.

DNS inside the cluster

“Jon, aren’t services also ephemeral? I mean, if I create a service, delete it, and create it again, it has a different IP address. How is that different from just using raw pod IPs? Did I just find the 2-meter exhaust port of Kubernetes? Am I genius?”

Glad you asked! We really shouldn’t be using IP addresses for anything. A system was created many years ago to handle this: DNS!

Start your interactive Alpine container back up again if you exited and type the following:

nslookup myfirstservice

Kubernetes has a built-in DNS server that automagically handles lots of things for you. In this case, your service was added as a DNS entry within the cluster. The convention is: <service-name>.<namespace-name>.svc.cluster.local. That’s a lot, but you can shorten it (as we did above) if you’re looking up the service from the same namespace that the service is in.

From the Alpine container, try this:

rm index.html
wget myfirstservice

You just accessed the service by its simple DNS name. No IP needed!

Conclusion

So, while the service resource type may seem boring and unintuitive at first, in reality it gives you the power to make your service easily accessible to other components in the cluster. Once your service is created and linked to a deployment, any other thing in the cluster can access that service–and therefore the deployment and ultimately the pod–by a DNS name that doesn’t change.

That’s well and good, but how do we make our fancy welcome-page-nginx service accessible to someone outside the cluster? Once again, Kubernetes has us covered with ingresses, which will be covered in the next post!