Published on

Services: Communicate with Applicaions in K8s

Authors

Using services to expose your application.

We've understood how the application deployment lifecycle happens in kubernetes. Given, we had done the deployment of our application so far but how are we gonna access it? What are kubernetes services? How it works? Let's learn all that in this lesson!

Objectives

  • Learn about a Service in Kubernetes
  • Understand how labels and LabelSelector objects relate to a Service
  • Expose an application outside a Kubernetes cluster using a Service

Kubernetes Pods are mortal. Pods in fact have a lifecycle. When a worker node dies, the Pods running on the Node are also lost. A ReplicaSet might then dynamically drive the cluster back to desired state via creation of new Pods to keep your application running. As another example, consider an image-processing backend with 3 replicas. Those replicas are exchangeable; the front-end system should not care about backend replicas or even if a Pod is lost and recreated. That said, each Pod in a Kubernetes cluster has a unique IP address, even Pods on the same Node, so there needs to be a way of automatically reconciling changes among Pods so that your applications continue to function.

A Service in Kubernetes is an abstraction which defines a logical set of Pods and a policy by which to access them. Services enable a loose coupling between dependent Pods. A Service is defined using YAML (preferred) or JSON, like all Kubernetes objects. The set of Pods targeted by a Service is usually determined by a LabelSelector.

Although each Pod has a unique IP address, those IPs are not exposed outside the cluster without a Service. Services allow your applications to receive traffic. Services can be exposed in different ways by specifying a type in the ServiceSpec:

  • ClusterIP (default) - Exposes the Service on an internal IP in the cluster. This type makes the Service only reachable from within the cluster.
  • NodePort - Exposes the Service on the same port of each selected Node in the cluster using NAT. Makes a Service accessible from outside the cluster using "NodeIP:NodePort". Superset of ClusterIP.
  • LoadBalancer - Creates an external load balancer in the current cloud (if supported) and assigns a fixed, external IP to the Service. Superset of NodePort.

Services and Labels

A Service routes traffic across a set of Pods. Services are the abstraction that allow pods to die and replicate in Kubernetes without impacting your application. Discovery and routing among dependent Pods (such as the frontend and backend components in an application) is handled by Kubernetes Services.

Services match a set of Pods using labels and selectors, a grouping primitive that allows logical operation on objects in Kubernetes.

Let's get our nginx deployment from previous lesson and expose it using a Service.

Firstly, We need to look at the deploymeny yaml file.

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-deploy
  name: nginx-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-deploy
  template:
    metadata:
      labels:
        app: nginx-deploy
    spec:
      containers:
      - image: nginx:latest
        name: nginx
        ports:
        - containerPort: 80

The deployment does have labels associated with it under metadata, app: nginx-deploy. The labels are crucial for a service to identtify which deployment needs to use which service and how it's gonna be exposed.

We can now create a service and using the labels we'll let the service know where to send the traffic.

apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  selector:
    app: nginx-deploy
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

Save the above file as nginx-svc.yaml

We can apply this onto our cluster

kubectl apply -f nginx-svc.yaml
service/nginx-svc created

List the services

kubectl get svc
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   89d
service/nginx-svc    ClusterIP   10.101.109.45   <none>        80/TCP    10s

We can see our service has started and also we've correctly defined the labels selector for our nginx deployment but how can we confirm or validate that our service is correctly configured?

Let's start by describing the service. Here, as well we follow the same syntax.

kubectl describe svc nginx-svc
Name:              nginx-svc
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=nginx-deploy
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.101.109.45
IPs:               10.101.109.45
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.1.1.12:80
Session Affinity:  None
Events:            <none>

We can see that it's a type ClusterIP, The important parameter that we need is Endpoints

Endpoints:         10.1.1.12:80

What is this IP Address? This is not the service IP address? So, where it's getting this IP? To drill down further, let list our pods.

kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
nginx-deploy-fc6884c76-nvk8d   1/1     Running   0          43m

There must be an IP assigned to the Pod, right? How can we see it? Let's do a wide output by passing -o followed by wide

kubectl get pods -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP          NODE             NOMINATED NODE   READINESS GATES
nginx-deploy-fc6884c76-nvk8d   1/1     Running   0          38m   10.1.1.12   docker-desktop   <none>           <none>

That's better! We can see that Pod's IP is the endpoint at our exposed service. Which means that any traffic that hits the service will be sent to the endpoints, which in turn is Pod's IP where our application is hosted.

NodePort Service

Now, Let's have a look at other service type NodePort

NodePort - Exposes the Service on the same port of each selected Node in the cluster using NAT. Makes a Service accessible from outside the cluster using NodeIP:NodePort. Superset of ClusterIP.

Let's edit our nginx-svc to change the type from ClusterIP to NodePort

kubectl edit svc nginx-svc

Change the type to NodePort and list the services.

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        89d
service/nginx-svc    NodePort    10.101.109.45   <none>        80:32282/TCP   12m

We can see the change getting reflected and also have a look at the Ports. It's mapping the 80 port to a port 32282 which is at the node where the pod is placed at the moment.

LoadBalancer Service

We use LoadBalancer service type when we want our application to be exposed to the internet. Let's expose our nginx pod as LoadBalancer and access it through the browser.

Edit the service nginx-svc

kubectl edit svc nginx-svc

Change the service type to LoadBalancer and list the services

kubectl get svc
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1       <none>        443/TCP        89d
nginx-svc    LoadBalancer   10.101.109.45   localhost     80:32282/TCP   22m

Now the service has been assigned an External-IP, In our case, since, we're using docker-desktop which is a local cluster, it's assigned to a localhost at Port 80

If we do a curl to localhost, we can see nginx welcome page.

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h2>Welcome to nginx!</h2>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html> 

This was a fun lesson, we've learned about services and specially how labels are crucial in connecting the service to a Pod and how applications can be exposed using the different services types.