Scaling and Deployment of a Microservice in Kubernetes
Scaling is the fundamental precondition for deploying modern web applications for a global audience. It enables to have fault tolerance, more request handling, and better response time with parallel computation.
To understand scaling and deployment of a service in Kubernetes cluster:
- We will create a sample Spring Boot service
- Create its image and push it to the Docker repository
- Deploy it over Kubernetes cluster to run it in a Pod
- To scale it to multiple instances, we will create ReplicaSet
- To manage rollout to different versions of the same service, we will create a Deployment
- If our latest version of services has critical bugs, we will rollback our deployment
Create an Image
Let’s create a sample Spring Boot application.
@SpringBootApplication
@RestController
public class SpringServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringServiceApplication.class, args);
}
@GetMapping("/greet")
public String sayHi() {
return "Hello from v1";
}
}
Create the Dockerfile for image creation.
FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Create a docker image using:
$ sudo docker build -t omkarshetkar/spring-service:v1 .
Push the image to remote docker repository:$ sudo docker push omkarshetkar/spring-service:v1
Deploy Image in Kubernetes Cluster with Pod
Now, to have this image deployed in the Kubernetes cluster, we need to create a Pod.
A Pod is the smallest deployable resource in the Kubernetes cluster. It represents a collection of containers and storage volumes that share the same execution environment.
There are two ways to create a resource in Kubernetes.
Imperative
It is used for quick tests, debugging, or applying hotfixes in the cluster.
Declarative
The preferred and recommended approach to create resources for production. Here, manifest files are created having configuration details of the specific resource.
To create a Pod with imperative commandkubectl run spring-service --image=omkarshetkar/spring-service:v1
To create with declarative configuration, create the following pod.yaml
:
apiVersion: v1
kind: Pod
metadata:
labels:
app: spring-service-v1
name: spring-service
namespace: default
spec:
containers:
- image: omkarshetkar/spring-service:v1
name: spring-service
ports:
- containerPort: 8080
name: http
protocol: TCP
kind
<-- Specifies the type of resourcemetadata.labels
<-- Label to identify Pod
A label is a key/value pair.
Every resource in Kubernetes can have one or more labels.
Labels help in grouping the resources in various combinations irrespective of their specific kind.
Create the Pod by applying the configuration:$ kubectl apply -f k8s/pod.yaml
pod/spring-service created
List the pods:
$
kubectl get pods
NAME READY STATUS RESTARTS AGE
spring-service 1/1 Running 0 78s
To access the service in Pod, export the port to host:
$
kubectl port-forward pod/spring-service 8080:8080
Access the /greet
API of the deployed service:$ curl http://localhost:8080/greet
Hello from v1
As you can see in the listing of Pods, we have only one Pod for the service. Suddenly, our application has become famous and seen a surge in usage. Hence we want to scale the service. Then, we need to scale up the number of Pods.
Scaling the Service with ReplicaSet
To scale the service, we create a corresponding ReplicaSet object.
ReplicaSet is a cluster-wide Pod manager, to ensure a given number of specific Pods are running all the time.
ReplicaSet specification for our service will look as following replicaset.yaml
:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: spring-service-rs
labels:
app: spring-service-rs
spec:
# modify replicas according to your case
replicas: 3
selector:
matchLabels:
app: spring-service-v1
template:
metadata:
labels:
app: spring-service-v1
spec:
containers:
- name: spring-service-container
image: omkarshetkar/spring-service:v1
ports:
- containerPort: 8080
metadata.name
<-- Name of the ReplicaSetspec.replicas
<-- Desired number of Pods. Here, its 3.spec.template
<-- Corresponding Pod template
How ReplicaSet linked with Pod?
As you can see in replicaset.yaml, we are specifying the Pod template with the label as app: spring-service
. The same label is specified for matchLabels
of ReplicaSet. By this, we are instructing to Kubernetes API server (Engine to execute all kubectl requests) to create ReplicaSet for Pods which match labels under matchLabels.
To create a ReplicaSet:
$
kubectl apply -f k8s/replicaset.yaml
replicaset.apps/spring-service-rs created
Inspect ReplicaSets:$ kubectl get rs
NAME DESIRED CURRENT READY AGE
spring-service-rs 3 3 3 4s
ReplicaSet is showing as 3 Pods are in the ready state.
Let’s list the Pods, and see their status:$ kubectl get pods -l app=spring-service-v1
NAME READY STATUS RESTARTS AGEspring-service 1/1 Running 0 37m
spring-service-rs-ksxzp 1/1 Running 0 8s
spring-service-rs-fwrnd 1/1 Running 0 8s
spring-service-rs-m8z8d 1/1 Running 0 8s
As expected, 3 instances of Pods are running.
How do I scale up/down the ReplicaSet?
To scale up to 4 pods, we can achieve it in either imperative or declarative way.
Imperatively:$ kubectl scale rs spring-service-rs --replicas=4
replicaset.apps/spring-service-rs scaled
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
12m
spring-service-rs 4 4 4
Declaratively, will modify replicaset.yaml
(preferred approach):
spec:
# modify replicas according to your case
replicas: 4
To delete a ReplicaSet:$ kubectl delete rs spring-service-rs
replicaset.apps "spring-service-rs" deleted
It will delete ReplicaSet along with associated Pods.
Rollout/Rollback with Deployment
What if I want to upgrade my service from v1 to v2 and deploy the same in the cluster?
ReplicaSets manage a single instance of service. To manage multiple instances and their rollouts, we need to have Deployment object.
Deployment enables rollout and rollback to different versions of service with less/no downtime.
Deployment configuration deployment.yaml
will be as follows:
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-service-deployment
labels:
app: spring-service-deployment
spec:
replicas: 3
selector:
matchLabels:
app: spring-service
template:
metadata:
labels:
app: spring-service
spec:
containers:
- name: spring-service-container
image: omkarshetkar/spring-service:v1
ports:
- containerPort: 8080
As you might have noticed, the specification for ReplicaSet and Deployment is almost similar except for the kind attribute value.
ReplicaSet is for scaling of a single instance of service whereas Deployment is for managing the scaling of multiple versions of a particular service.
To create Deployment:$ kubectl apply -f k8s/deployment.yaml
deployment.apps/spring-service-deployment created
Let’s see how deployment is listed:$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
spring-service-deployment 3/3 3 3 6m36s
As expected, 3 pods are created and available.
Also, notice every Deployment will have associated ReplicaSet created. It exists as long as the corresponding Deployment exists.$ kubectl get rs
NAME DESIRED CURRENT READY AGE
spring-service-deployment-7b8b6f5dcb 2 2 2 16m
Scaling commands for Deployment are similar to that for ReplicaSet.
Following imperative command will scale down to 2 instances.$ kubectl scale deployments spring-service-deployment --replicas=2
The same can be achieved through a declarative change in the configuration file.
To see it working, let’s export the port for Deployment:$ kubectl port-forward deployment/spring-service-deployment 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080
Handling connection for 8080
$ curl localhost:8080/greet
Hello from v1
Upgrade service from version v1 to v2
Make a change in service:
@GetMapping("/greet")
public String sayHi() {
return "Hello from v2";
}
Create a new image for version v2 and push it to remote Docker repository:$ sudo docker build -t omkarshetkar/spring-service:v2 .
$ sudo docker push omkarshetkar/spring-service:v2
Now, let’s refer new image in our deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-service-deployment
labels:
app: spring-service-deployment
spec:
replicas: 3
selector:
matchLabels:
app: spring-service-v2
template:
metadata:
labels:
app: spring-service-v2
spec:
containers:
- name: spring-service-container
image: omkarshetkar/spring-service:v2
ports:
- containerPort: 8080
Applying the changed configuration:
$ kubectl apply -f k8s/deployment.yamldeployment.apps/spring-service-deployment configured
Let’s export the port for Deployment and see the API response:$ kubectl port-forward deployment/spring-service-deployment 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
$ curl localhost:8080/greet
2
Hello from v
As expected, we are seeing changes done for v2.
Note that for service upgrade, we needed to do a single version change in deployment.yaml and everything else is taken care of by Kubernetes.
Let’s make another change in service and upgrade to v3.
As expected, we can see the following API response:$ curl localhost:8080/greet
Hello from v3
Rollout history for a Deployment can be seen by:$ kubectl rollout history deployments spring-service-deployment
deployment.apps/spring-service-deployment
REVISION CHANGE-CAUSE
1
2
3
Kubernetes assigns a revision number for every new deployment. It helps in the rollback of deployment to a particular version.
Rollback
Let’s say something went wrong in v3 of service and want to rollback to the previous version. Is it possible?
Yes!
$ kubectl rollout undo deployments spring-service-deployment
deployment.apps/spring-service-deployment rolled back
Now, if you see API response, it will be for v2:$ curl localhost:8080/greet
2
Hello from v
Finally, we can delete the deployment with:$ kubectl delete deploy spring-service-deployment
deployment.apps "spring-service-deployment" deleted
Or declaratively:
$ kubectl delete -f k8s/deployment.yaml
Deletion of a deployment deletes associated ReplicaSet and Pods.
Thus, for scale and flexible deployments, we need to have Deployment resources. Effectively, we need to define only Deployment resources and Pod template, which will, in turn, define ReplicaSet and Pod.
Code for the above discussion is available here.
The way to roll out the changes with no/less downtime is a topic for a separate discussion.