Introduction to Services in Kubernetes

In previous articles, we discussed about Pods and Deployments in Kubernetes. We used them to run our applications that did not need to communicate with any other application within the cluster or outside. Now imagine an application with a back end and a front end which are running as different Deployments in the cluster. In such a setup, obviously the front end needs to communicate with the back end. So how do we connect the front end and the back end? We use another resource type called ‘Service’ for that and we will be focusing on Services in Kubernetes in this article.

What are Services in Kubernetes?

Services in Kubernetes
K8s notation for Service

In simple words, a Service in Kubernetes is a way to expose an application running in a set of Pods or a Deployment as a network service. Services help applications connect with other applications within the cluster or outside of the cluster.

Why do we need Services?

One of the important aspects about Pods is that they are ephemeral, which means, they have a short lifespan. This means that even though a Deployment could guarantee a certain number of Pod replicas would be running at any given time, it cannot guarantee that each individual Pod will be alive at all times. When a Pod dies, a new one will replace it and this new Pod will get a new IP address. Because of this, Pods cannot rely on each other’s IP addresses for communication. So how do Pods keep track of the IP addresses that they need to connect to other Pods? This is where Services in Kubernetes come in.

A ‘Service’ is a virtual component that lives in the memory of Kubernetes. Unlike Pods, Services will live in the cluster until they are explicitly destroyed, so they get permanent IP addresses. Any request that comes to a Pod goes to a Service and the Service will forward it to a Pod as appropriate.

In a Kubernetes application setup, we usually create a Service for each distinct pod within a Deployment to handle communications between different applications. Instead of communicating directly via pod IP addresses, communications go through Services. A Service will span all the nodes of the cluster, so it does not matter in which node the Pod runs.

Service Types

There are different types of Services in Kubernetes that can be used for different purposes.

  • ClusterIP – ClusterIP is the default Service type in Kubernetes. If you do not explicitly define a type in the configuration for the Service, it will be of ClusterIP type. Exposes the Service through an internal IP. This type of services are accessible only from within the cluster.
  • NodePort – Exposes the service on each node that belongs to the cluster at a given port(the NodePort). This means that NodePort services are accessible from outside of the cluster. We can contact the service from outside using the <NodeIP>:<NodePort>.
  • LoadBalancer – Exposes the service via a cloud providers load balancer in cloud providers that support external load balancers. Traffic from the external load balancer is sent to the backend Pods. Kubernetes will provision the load balancer. The cloud provider will decide how it is load balanced.
  • ExternalName – Maps the Service to the contents of the externalName field by returning a CNAME record with its value. Can be used to connect applications located in different namespaces.

Now that we are familiar with what Services are, let’s see how we can define a YAML configuration for a Service in Kubernetes.

Writing a Manifest for a Service

You can create Services in Kubernetes using imperative kubectl commands as well. But first, it is important to understand what the manifest of a Service looks like. In this article, we will mainly look into how we can create a Service to connect to an application within the cluster in the same namespace.

In the previous article, we created a MySQL Deployment using an image in DockerHub. Now let’s assume there is another application in our setup that needs to connect to this database. In this case, what we need to do is create a Service for our MySQL Deployment.

This manifest creates a ClusterIP type Service named mysql-service, which will target the TCP port 3306 on any Pod that has the label app=mysql. Kubernetes will assign this Service an IP address, which is known as the Cluster IP, that will be used by the service proxies. Manifest of this Service would look like below.

apiVersion: v1
kind: Service

metadata:
  name: mysql-service

spec:
  type: ClusterIP
  selector:
    app: mysql
  ports:
  - protocol: TCP
    port: 3306
    targetPort: 3306

Let us have a closer look at this manifest file.

Version and Kind
  • Line 1 – apiVersion defines the Kubernetes API version. For a Service, it is v1 at the time of writing this article.
  • Line 2 – kind defines the type of resource we are going to create. In this case the kind is Service.
Metadata
  • Line 4 – the start of the metadata section.
  • Line 5 – here I have defined a name for my Service as mysql-service. You can give any name using alpha numeric characters and hyphens. Spaces are not allowed.
Specification
  • Line 8 – type defines the type of the Service. Here I have given the type as ClusterIP. This means that this Service is only accessible from within the Kubernetes cluster. Therefore, any other application within the cluster can access this service, but not from outside the cluster.
  • Line 9&10 – selector is one of the most important parts in our Service definition. Here I have specified app: mysql which is a name value pair of a label. What this means is that any Pod that has the label app=mysql will belong to this Service. This is how you make the connection between your application Pods and the Service, so this is very important. You can specify as many labels here as necessary to uniquely identify the relevant Pods.
  • Line 11-EOF – the ports section specifies which ports of our Pods we are going to expose through this Service.
    • protocol here defines the network protocol. I have given it as TCP here, which will be the value for most cases.
    • port here defines the port on the Service. This is arbitrary and you can give any port. But as a best practice and for convenience, we set this the same as the targetPort.
    • targetPort here is the port on the Pod we need to expose. This should match the container port of our application, which is defined in the Pod specification. This is how the Service knows which port to forward the requests it gets.

Depending on the Service type, there would be more attributes in the definition. For instance, if the type was NodePort, then there would be a nodePort attribute in the port object or if this were a Multi-Port Service, there would be more than one set of ports.

Once we have our manifest ready, we can use it to create the Service.

Creating the Service

Same as with other Kubernetes YAML configuration files, we can use the kubectl apply command to create the service.

$ kubectl apply -f mysql-service.yaml
service/mysql-service created

Now that the Service is created, let’s run kubectl get service command to see the details.

$ kubectl get services -o wide
NAME                    TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE   SELECTOR
kubernetes              ClusterIP      10.96.0.1        <none>        443/TCP        1d    <none>
mysql-service           ClusterIP      10.103.156.238   <none>        3306/TCP       14m   app=mysql

Here in addition to the default Service named ‘kubernetes’, we can see the Service we created. We can see the type is ClusterIP and the Service has been given an IP address as 10.103.156.238. This means that this Service is accessed using this IP from within the cluster. Even though the Service is a virtual component, it gets an IP address like this. There is no external IP as the type of the Service is ClusterIP, so the Service cannot be accessed from outside of the cluster.

We can also see that it is exposing the TCP port 3306 and the selector is app=mysql, which is the selector we defined in the manifest.

How Does the Service Keep Track of the Pods?

When we create a Service with a selector, Kubernetes automatically creates another object called ‘Endpoints’. This Endpoints object will have the same name as the Service name. The controller for the Service selector continuously scans for Pods that match its selector, and then updates the Endpoints.

If we run kubectl get endpoints command, we would see the output as below.

$ kubectl get endpoints
NAME                    ENDPOINTS                         AGE
kubernetes              192.168.59.110:8443                1d
mysql-service           172.17.0.2:3306,172.17.0.4:3306    14m

In addition to the default K8s Endpoints object, we can see the Endpoints for the Service we created as well. Notice that it has the same name as our Service ‘mysql-service’. And we can see that there are 2 Endpoints with 2 IP addresses in ENDPOINTS column, which means the Service we created is connected to 2 Pods.

We can also see that the port attached to these IPs is 3306, which is the target port we defined in the Service. If we specify 2 sets of ports in the Service for a Pod that exposes 2 ports, then we would see 2 endpoints with the same IP, but different port numbers.

So in this article we mainly discussed about internal Services with a Selector, which is sufficient to get started. There are other types of Services that come in handy for different use cases and we will look into them in later articles of this Kubernetes Series.

Share this article if it was helpful!

Leave a Reply

Your email address will not be published. Required fields are marked *