kubernetes

Kubernetes Part 3 of n, Services

So this will be a shorter post than most of the others in this series, this time we will be covering services. The rough road map is as follows:

 

  1. What is Kubernetes / Installing Minikube
  2. What are pods/labels, declaring your first pod 
  3. Services (this post)
  4. Singletons (such as a DB)
  5. ConfigMaps/Secrets
  6. LivenessProbe/ReadinessProbe/Scaling Deployments

 

Although this post is not going to be a very long one do not underestimate the importance of “Services” in kubernetes.

 

 

So Just What Is A Service?

So before we go into what a service is lets just discuss PODs a bit. A POD is the lowest unit of deployment in Kubenetes. PODs are ephemeral, that is they are intended to die and may be restarted if they are deemed unhealthy. So with all this happening how could we really expose an IP Address/DNS name of something to another POD if it is such a possible state of flux and may be recreated at any time?

 

Services are the answer to this question. Services provide an abstraction over a single POD or group of pods that match a label selector. Services unlike PODs are supposed to live a VERY long time. Their IP address/DNS name and associated environment variables do not disappear or change until the service itself is deleted.

 

Here is an example we have a requirement to do some image processing which could all be done in parallel, we don’t care what POD picks this work up, providing we can reach a POD. This is something that a service will give you, where you would specify a label selector such that it can match the PODs for that label. These PODs will then have their endpoints associated with the service.

 

 

Lets try and visualize this using a diagram or 2

 

image

 

image

 

From this diagram we can see that we had 2 deployments labeller app=A/app=B and we exposed the PODs that run in these deployments using 2 services where we use the app=A/app=B label selectors. So you can see above that we have ended up with 3 PODS that matched app=B in one service and just one POD that matched app=A in the other service.

 

The cool thing about services is that they are always watching for new PODs, so if you did one of the following the service would end up knowing about it

  • Scale the number of PODS either using a ReplicationController or Deployment (the Service would see the new nodes or know which ones to remove)
  • Change the labels associated with a POD in some way which means it should NOW be included as a POD by the service, or should be removed from the Service as the labels no longer match the services POD selection criteria. This one is particularly powerful, as we can have some PODs that are exposed to a service that are running fine, then we can create a new deployment and add a new label say version=2, and can alter the Service selector to only pick up PODs that are NOW version=2 labelled. Then when we are happy we can remove the old PODs. This is pretty awesome and we will be discussing this more in a future post

 

 

What Did The Example Service Look Like Again?

Lets just remind ourselves of what the service looked like for the last post

 

And here is the code : https://github.com/sachabarber/KubernetesExamples

 

And here is the Docker Cloud repo we used last time that we can still use for this post : https://hub.docker.com/r/sachabarber/sswebapp/

 

We used this to create the deployment and expose the service

kubectl run simple-sswebapi-pod-v1 --replicas=1 --labels="run=sswebapi-pod-v1" --image=sachabarber/sswebapp:v1  --port=5000
kubectl expose deployment simple-sswebapi-pod-v1 --type=NodePort --name=simple-sswebapi-service

 

or via YAML

 

Deployment

kubectl.exe apply -f C:\Users\sacha\Desktop\KubernetesExamples\Post2_SimpleServiceStackPod\sswebapp\deployment.yaml

 

Where the YAML looks like this, note those labels

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: simple-sswebapi-pod-v1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: run=sswebapi-pod-v1
    spec:
      containers:
      - name: sswebapi-pod-v1
        image: sachabarber/sswebapp:v1
        ports:
        - containerPort: 5000

 

 

Service

kubectl.exe apply -f C:\Users\sacha\Desktop\KubernetesExamples\Post2_SimpleServiceStackPod\sswebapp\service.yaml

 

Where the YAML looks like this, note the selector, see how it matches the POD labels section

 

apiVersion: v1
kind: Service
metadata:
  name: simple-sswebapi-service
spec:
  selector:
    app: run=sswebapi-pod-v1
  ports:
  - protocol: TCP
    port: 5000
    targetPort: 5000
  type: NodePort

 

In the above example I am using type:NodePort. This means that this service will be exposed on each node in the Kubernetes cluster. We can get the bits of information we want using these command lines

 

Grab the single node IP address for Minikube cluster

.\minikube.exe ip

 

Which gives us this

image

 

Grab the single node IP address for Minikube cluster

.\kubectl describe service simple-sswebapi-service

 

Which gives us this

 

image

 

So we can now try and hit the full service exposes port like so

 

image

 

That is one type of Service type, we will talk about them all below in more detail, but this proves its working

Types Of Service

For some parts of your application (e.g. frontends) you may want to expose a Service onto an external (outside of your cluster) IP address.

Kubernetes ServiceTypes allow you to specify what kind of service you want. The default is ClusterIP.

Type values and their behaviors are:

 

  • ClusterIP: Exposes the service on a cluster-internal IP. Choosing this value makes the service only reachable from within the cluster. This is the default ServiceType.
  • NodePort: Exposes the service on each Node’s IP at a static port (the NodePort). A ClusterIP service, to which the NodePort service will route, is automatically created. You’ll be able to contact the NodePort service, from outside the cluster, by requesting <NodeIP>:<NodePort>.
  • LoadBalancer: Exposes the service externally using a cloud provider’s load balancer. NodePort and ClusterIP services, to which the external load balancer will route, are automatically created.
  • ExternalName: Maps the service to the contents of the externalName field (e.g. foo.bar.example.com), by returning a CNAME record with its value. No proxying of any kind is set up. This requires version 1.7 or higher of kube-dns.

 

Taken from https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services—service-types up on date 05/03/18

 

Environment Variables

 

When a Pod is run on a Node, the kubelet adds a set of environment variables for each active Service. It supports both Docker links compatible variables (see makeLinkVariables) and simpler {SVCNAME}_SERVICE_HOST and {SVCNAME}_SERVICE_PORT variables, where the Service name is upper-cased and dashes are converted to underscores.

For example, the Service “redis-master” which exposes TCP port 6379 and has been allocated cluster IP address 10.0.0.11 produces the following environment variables:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

 

This does imply an ordering requirement – any Service that a Pod wants to access must be created before the Pod itself, or else the environment variables will not be populated. DNS does not have this restriction

 

Taken from https://kubernetes.io/docs/concepts/services-networking/service/#environment-variables up on date 02/03/18

 

Lets try this for the code that goes with this post, I am assuming that you had recreated the PODs/Deployment after the service was created (as the environment variables can only be injected into PODs AFTER the service is created as just stated above). This may mean for this post that we are delete the deployment we initially created and create it again after the service is up and running

 

We can use this command line to get ALL the pods

 

.\kubectl.exe get pods

 

Which will then give us something like this for this POD we have used so far

 

image

 

So now lets see what happens when we examine this POD for its environment variables (remember this is after the service has been created for the deployment and I have recreated the deployment once or twice to ensure that the environment variables ARE NOW available)

 

.\kubectl.exe exec simple-sswebapi-pod-v1-f7f8764b9-xs822 env

Which for this POD (currently in this post we have only ever asked for 1 replica set, so we have only ever got this single POD too) gives us the following environment variables, and of which we could use in our code.

 

image

You can see that we ran this command in the context of the demo POD that we are working with, yet that POD can see environment variables from all the different services that are currently available (or where running when the demo POD was created at any rate)

 

Obviously we would have to deal with the fact that these are NOT available to PODs UNTIL the service is created. These env variables may serve you well for say, having a service over one set of PODS and then having another service/PODs that might way to use the 1st service, that would work, as the 1st service would hopefully be running before we start the 2nd service/PODs.

 

None the less DNS is a better option. We will look at that next

 

 

DNS

An optional (though strongly recommended) cluster add-on is a DNS server. The DNS server watches the Kubernetes API for new Services and creates a set of DNS records for each. If DNS has been enabled throughout the cluster then all Pods should be able to do name resolution of Services automatically.

For example, if you have a Service called “my-service” in Kubernetes Namespace “my-ns” a DNS record for “my-service.my-ns” is created. Pods which exist in the “my-ns” Namespace should be able to find it by simply doing a name lookup for “my-service“. Pods which exist in other Namespaces must qualify the name as “my-service.my-ns“. The result of these name lookups is the cluster IP.

Kubernetes also supports DNS SRV (service) records for named ports. If the “my-service.my-nsService has a port named “http” with protocol TCP, you can do a DNS SRV query for “_http._tcp.my-service.my-ns” to discover the port number for “http“.

 

The Kubernetes DNS server is the only way to access services of type ExternalName.

 

Taken from https://kubernetes.io/docs/concepts/services-networking/service/#environment-variables up on date 02/03/18

 

So above where we were talking about environment variables, we talked about the fact that when you create a new service and have some PODs created afterwards the service will inject environment variables into PODs. However we also talked about the fact that the PODs need to started after the service in order to receive the correct environment variables. This is a bit of a limitation, which is solved by DNS.

 

Kubenetes runs a special DNS POD called kube-dns, this is made available thanks to a Kubernetes addon called DNS

 

You can check that the addon is installed like this

.\minikube addons list

Which should show something like this

enter image description here

 

So now that we know we have the DNS addon running how do we see if we have the kube-dns pod running? Well we can simply do this

.\kubectl get pod -n kube-system

Which should show something like this

enter image description here

So now that we have the DNS stuff and we know its running just how do we use it. Essentially what we would like to do is confirm that the DNS lookups within the cluster.. Ideally we would like to get a DNS nslookup command to run directly in the demo container. The demo container in this case is a simple .NET Core REST API, so it won’t have anything more than that. At first I could not see how to do this, but I asked a few dumb questions on stack overflow, and then it came to me, all the demos of using DNS nslookup in Kubernetes that I had seen seemed to use busybox

 

What is BusyBox you ask?

Coming in somewhere between 1 and 5 Mb in on-disk size (depending on the variant), BusyBox is a very good ingredient to craft space-efficient distributions.

BusyBox combines tiny versions of many common UNIX utilities into a single small executable. It provides replacements for most of the utilities you usually find in GNU fileutils, shellutils, etc. The utilities in BusyBox generally have fewer options than their full-featured GNU cousins; however, the options that are included provide the expected functionality and behave very much like their GNU counterparts. BusyBox provides a fairly complete environment for any small or embedded system.

 

Taken from https://hub.docker.com/_/busybox/ up on date 02/03/18

 

Ok great how does that help us? Well what we can now do is just run the BusyBox docker hub image as a POD and use that POD to run our nslookup inside of to see if DNS lookups within the cluster are working.

 

Here is how to run the BusyBox docker hub image as  a POD

.\kubectl run -i --tty busybox --image=busybox --restart=Never

This will run and give us a command prompt, then we can do this to test the DNS for our demo service that goes with this post, like so

nslookup simple-sswebapi-service

Which gives us this

enter image description here

 

Cool, so we should be able to use these DNS names or IP address from within the cluster. Nice

 

Conclusion

So there you go, this was a shorter post than some of the other will be, but I hope you can see just why you need the service abstraction and why it it such as useful construct. Services have one more trick up their sleeve which we will be looking at in the next post

Leave a comment