Scaling a Microservice Application in Kubernetes
This is part 2 of the series on Managing Microservices with Kubernetes. You can read part 1 here.
In the part 1, we understood how Kubernetes can be used to deploy a Microservice. In that blog, I mentioned that a couple of Kubernetes objects are used to deploy the voting application- Namespaces, Labels and selectors, Pods, ReplicaSets, Deployment, and Service Objects. In this blog, I will take you through the journey of how these objects are used to deploy the microservice.
The Pod Object (Pod of containers)
To understand a Pod object, let me run this commend:
[email protected]:~$ kubectl get pods -n vote NAME READY STATUS RESTARTS AGE db-6789fcc76c-kkfnk 1/1 Running 0 13h redis-554668f9bf-7qt2x 1/1 Running 0 13h result-79bf6bc748-rhrv8 1/1 Running 46 13h vote-7478984bfb-544p7 1/1 Running 0 13h worker-dd46d7584-4dzzx 1/1 Running 1 13h
The command output indicates that 5 pods are running, and each pod represents each of the microservice that constitutes the voting-app. From this, we can see that, at the basic level, a container in Docker and a Pod in Kubernetes can mean the same thing. However, a Pod can contain more than one container and in that sense, a Pod is an envelope, a wrapper around containers. This is similar to a pod of beans where the case is the pod and the bean seeds are the containers. In the listing of the above command, if you look under READY, you’ll see 1/1 on all the pods. This means that the Pod contains 1 container and the container is running. To see the container, you can describe the Pod object like so:
[email protected]:~$ kubectl describe pod vote-7478984bfb-544p7 -n vote Name: vote-7478984bfb-544p7 Namespace: vote Priority: 0 Node: node02/192.168.0.10 Start Time: Mon, 08 Jun 2020 21:51:35 -0500 Labels: app=vote pod-template-hash=7478984bfb Annotations: <none> Status: Running IP: 10.36.0.2 IPs: IP: 10.36.0.2 Controlled By: ReplicaSet/vote-7478984bfb Containers: vote: Container ID: docker://f66bda85b72e14757f6d95b5a26cf37c5816f2382bb6f34333c7ab3b8b4a7b44 Image: dockersamples/examplevotingapp_vote:before Image ID: docker-pullable://dockersamples/[email protected]:8e64b18b2c87de902f2b72321c89b4af4e2b942d76d0b772532ff27ec4c6ebf6 Port: 80/TCP Host Port: 0/TCP State: Running Started: Mon, 08 Jun 2020 21:51:41 -0500 Ready: True Restart Count: 0 Environment: <none> Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-7rb9h (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-7rb9h: Type: Secret (a volume populated by a Secret) SecretName: default-token-7rb9h Optional: false QoS Class: BestEffort Node-Selectors: <none> Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: <none>
The section in red gives the attributes of the container. It is using the image dockersamples/examplevotingapp_vote:before, which is located at Docker hub, the Container Image Registry. The owner of the image is dockersamples, the name of the image is examplevotingapp_vote and the image version/tag is before.
Most Pods will contain just one container and when a Pod contains more than one container, there is usually the main container and the rest are usually helper containers, helping the main container in one way or the other. This follows the best practice of container designs where a container does just one thing and does it well. In this way, the container/microservice can be scaled independently of the other containers. Resources can also be allocated to the Pod independent of the other Pods/microservices.
Attributes of a Pod Object
When a pod object is created, whether it contains just one container or more than one container:
- Kubernetes allocates IP addresses to the Pod and not the individual containers.
- The containers in the Pod share the same IP address allocated to the Pod.
- To distinguish the containers running in a pod, the containers must have their individual Ports. In that way, containers can address each other by using localhost and the port of the container to access.
- Containers in a Pod share the same volume. To the Pods, a volume is like a shared medium, and information stored in the volume by one pod can be seen by the other pod. This diagram illustrates how a pod looks.
Creating a Pod
Now that we know that the Pod is the most basic object in Kubernetes, let us create a Pod from scratch. We will do this in 2 steps:
- Create a YAML file describing the attributes that you would like the Pod to have.
- Use kubectl create to create the object.
Actually, the above 2 steps describe how to create an object in Kubernetes. Tell Kubernetes your desired profile of the object and let Kubernetes create it.
Here is a YAML file we will use to create the vote-app. Remember that this microservice application has 5 microservices and to create all of them, we will need to repeat the below steps for all the 5 YAML files. An alternative to creating 5 YAML files is to create a single YAML file with 5 sections, each section describing a pod. Each section is separated by 3 dashes like – – -.
apiVersion: v1 kind: Pod metadata: name: voting-app labels: app: voting-app Namespace: vote spec: containers: - name: vote image: dockersamples/examplevotingapp_vote:before ports: - containerPort: 80 name: vote
[email protected]:~/manifests$ kubectl create -f vote-pod.yaml [email protected]:~/manifests$ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES voting-app 1/1 Running 0 18s 10.36.0.3 node02 <none> <none> [email protected]:~/manifests$ curl 10.36.0.3 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Cats vs Dogs!</title> <base href="/index.html"> <meta name = "viewport" content = "width=device-width, initial-scale = 1.0"> <meta name="keywords" content="docker-compose, docker, stack"> <meta name="author" content="Tutum dev team"> <link rel='stylesheet' href="/static/stylesheets/style.css" /> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css"> </head> <body> <div id="content-container"> <div id="content-container-center"> <h3>Cats vs Dogs!</h3> <form id="choice" name='form' method="POST" action="/"> <button id="a" type="submit" name="vote" class="a" value="a">Cats</button> <button id="b" type="submit" name="vote" class="b" value="b">Dogs</button> </form> <div id="tip"> (Tip: you can change your vote) </div> <div id="hostname"> Processed by container ID voting-app </div> </div> </div> <script src="http://code.jquery.com/jquery-latest.min.js" type="text/javascript"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.js"></script> </body> </html>
Above, we performed the following:
- Viewed the YAML file for the Pod. You can read here to understand the syntax of a Pod object.
- Created the Pod object
- Checked the IP address for the Pod
- And then use curl to view the voting app running on port 80.
Note that we can follow the same process to create the other 4 microservices as the YAML files are all provided.
We now have the pod running but there are things that we cannot do at this point, some limitations. For example, we cannot:
- Scale the application (ReplicaSet and Deployment objects will solve that)
- Expose the Pod to the outside world (The service object will solve that)
The ReplicaSet Object
With Replicasets, you can scale the vote-app microservices and ReplicaSet will also ensure that the specified number of Pod replicas are running at any given time. ReplicaSet is a controller object. As a controller object, it will engage in closed-loop monitoring of the Pod to ensure that the Pod and the desired number of replicas running at all times.
When you create the YAML file for ReplicaSet, you describe your ‘desired state’ of the object. The ReplicaSet object will keep monitoring the ‘current state’ of the object for any deviation from the ‘desired state ‘ and bring it back to the desired state if any difference. Note that a ReplicaSet also creates a Pod so instead of just creating a pod directly using the Pod object (as we did earlier), it is better to create a ReplicaSet object. In fact, hardly will you just create a standalone/orphan Pod as this is always done through the ReplicaSet.
Here is the YAML file for the ReplicaSet object.
apiVersion: apps/v1 kind: ReplicaSet metadata: labels: app: vote name: vote namespace: spec: replicas: 1 selector: matchLabels: app: vote template: metadata: labels: app: vote spec: containers: - image: dockersamples/examplevotingapp_vote:before name: vote ports: - containerPort: 80 name: vote
This YAML file has 4 sections:
- Metadata section
- A selector that specifies how to identify Pods
- A number indicating the desired replica for the application
- A Pod template specifying the data of new Pods to create
Scaling Pods Using ReplicaSet Object
Now let’s scale this pod through the ReplicaSet object:
[email protected]:~/manifests$ kubectl get rs NAME DESIRED CURRENT READY AGE vote 1 1 1 11s [email protected]:~/manifests$ kubectl scale rs vote --replicas=4 replicaset.apps/vote scaled [email protected]:~/manifests$ kubectl get rs NAME DESIRED CURRENT READY AGE vote 4 4 4 24m [email protected]:~/manifests$ kubectl get pods NAME READY STATUS RESTARTS AGE vote-7srcc 1/1 Running 0 39s vote-qh484 1/1 Running 0 39s vote-rqbvk 1/1 Running 0 25m vote-w6gn9 1/1 Running 0 39s
Above, we did the following:
- Scaled the ReplicaSet from one Pod to 4 Pods.
- Checked that they are all in Running state.
- Note that:
- Each of the replicas is just a copy of the previous.
- We can access any of them as we did before and they should all have the same content.
ReplicaSet as a Monitor
Here I will delete one of the pods running and we will see it replaced immediately.
[email protected]:~/example-voting-app/k8s-specifications$ kubectl get pods -n vote | grep vote vote-48bjg 1/1 Running 0 98s vote-lzd69 1/1 Running 0 98s vote-qffsr 1/1 Running 0 98s vote-xkrgv 1/1 Running 0 2m31s [email protected]:~/example-voting-app/k8s-specifications$ kubectl delete pod vote-48bjg -n vote pod "vote-48bjg" deleted [email protected]:~/example-voting-app/k8s-specifications$ kubectl get pods -n vote | grep vote vote-bn5tc 1/1 Running 0 13s vote-lzd69 1/1 Running 0 3m20s vote-qffsr 1/1 Running 0 3m20s vote-xkrgv 1/1 Running 0 4m13s
Above you can see that the first pod was deleted but it immediately got replaced so that 4 pods are always running.
Note that each time you create a deployment object, a Replicaset object is also created and if you try to scale the replicaset created by the deployment object, the deployment object will replace any pods created by the replica set object.
Conclusion
With the Pod created through Pod object, we can not scale it while we can scale pods created through the replicaset object. However, there are more things we need to do that Replicaset cannot help us:
- As the voting app evolves, what if we would like to update the images in real life without downtime?
- How about DevOps practices like Blue/Green deployment?
To do the above we would need another Kubernetes abstraction called the Deployment object. With the Deployment object, we no longer need to use the ReplicaSet object directly. Instead, we can use the Deployment object to control the ReplicaSet and the Pod. Deployment object will be addressed in the next blog.Stay tuned.