Creating ReplicaSets
Let's take a look at a ReplicaSet based on the Pod we created in the previous chapter:
cat rs/go-demo-2.yml
The output is as follows:
apiVersion: apps/v1beta2 kind: ReplicaSet metadata: name: go-demo-2 spec: replicas: 2 selector: matchLabels: type: backend service: go-demo-2 template: metadata: labels: type: backend service: go-demo-2 db: mongo language: go spec: containers: - name: db image: mongo:3.3 - name: api image: vfarcic/go-demo-2 env: - name: DB value: localhost livenessProbe: httpGet: path: /demo/hello port: 8080
The apiVersion, kind, and metadata fields are mandatory with all Kubernetes objects. ReplicaSet is no exception.
We specified that the apiVersion is apps/v1beta2. At the time of this writing, ReplicaSet is still in beta. Soon it will be considered stable, and you'll be able to replace the value with apps/v1. The kind is ReplicaSet and metadata has the name key set to go-demo-2. We could have extended ReplicaSet metadata with labels. However, we skipped that part since they would serve only for informational purposes. They do not affect the behavior of the ReplicaSet.
You should be familiar with the three fields since we already explored them when we worked with Pods. In addition to them, the spec section is mandatory as well.
The first field we defined in the spec section is replicas. It sets the desired number of replicas of the Pod. In this case, the ReplicaSet should ensure that two Pods should run concurrently. If we did not specify the value of the replicas, it would default to 1.
The next spec section is the selector. We use it to select which pods should be included in the ReplicaSet. It does not distinguish between the Pods created by a ReplicaSet or some other process. In other words, ReplicaSets and Pods are decoupled. If Pods that match the selector exist, ReplicaSet will do nothing. If they don't, it will create as many Pods to match the value of the replicas field. Not only that ReplicaSet creates the Pods that are missing, but it also monitors the cluster and ensures that the desired number of replicas is (almost) always running. In case there are already more running Pods with the matching selector, some will be terminated to match the number set in replicas.
We used spec.selector.matchLabels to specify a few labels. They must match the labels defined in the spec.template. In our case, ReplicaSet will look for Pods with type set to backend and service set to go-demo-2. If Pods with those labels do not already exist, it'll create them using the spec.template section.
The last section of the spec field is the template. It is the only required field in the spec, and it has the same schema as a Pod specification. At a minimum, the labels of the spec.template.metadata.labels section must match those specified in the spec.selector.matchLabels. We can set additional labels that will serve informational purposes only. ReplicaSet will make sure that the number of replicas matches the number of Pods with the same labels. In our case, we set type and service to the same values and added two additional ones (db and language).
It might sound confusing that the spec.template.spec.containers field is mandatory. ReplicaSet will look for Pods with the matching labels created by other means. If we already created a Pod with labels type: backend and service: go-demo-2, this ReplicaSet would find them and would not create a Pod defined in spec.template. The main purpose of that field is to ensure that the desired number of replicas is running. If they are created by other means, ReplicaSet will do nothing. Otherwise, it'll create them using the information in spec.template.
Finally, the spec.template.spec section contains the same containers definition we used in the previous chapter. It defines a Pod with two containers (db and api).
In the previous chapter, I claimed that those two containers should not belong to the same Pod. The same is true for the containers in Pods managed by the ReplicaSet. However, we did not yet have the opportunity to explore ways to allow containers running in different Pods to communicate with each other. So, for now, we'll continue using the same flawed Pods definition.
Let's create the ReplicaSet and experience its advantages first hand.
kubectl create -f rs/go-demo-2.yml
We got the response that the replicaset "go-demo-2" was created. We can confirm that by listing all the ReplicaSets in the cluster.
kubectl get rs
The output is as follows:
NAME DESIRED CURRENT READY AGE go-demo-2 2 2 0 14s
We can see that the desired number of replicas is 2 and that it matches the current value. The value of the ready field is still 0 but, after the images are pulled, and the containers are running, it'll change to 2.
Instead of retrieving all the replicas in the cluster, we can retrieve those specified in the rs/go-demo-2.yml file.
kubectl get -f rs/go-demo-2.yml
The output should be the same since, in both cases, there is only one ReplicaSet running inside the cluster.
All the other kubectl get arguments we explored in the previous chapter also apply to ReplicaSets or, to be more precise, to all Kubernetes objects. The same is true for kubectl describe command:
kubectl describe -f rs/go-demo-2.yml
The last lines of the output are as follows:
... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal SuccessfulCreate 3m replicaset-controller Created pod:
go-demo-2-v59t5 Normal SuccessfulCreate 3m replicaset-controller Created pod:
go-demo-2-5fd54
Judging by the events, we can see that ReplicaSet created two Pods while trying to match the desired state with the actual state.
Finally, if you are not yet convinced that the ReplicaSet created the missing Pods, we can list all those running in the cluster and confirm it:
kubectl get pods --show-labels
To be on the safe side, we used the --show-labels argument so that we can verify that the Pods in the cluster match those created by the ReplicaSet.
The output is as follows:
NAME READY STATUS RESTARTS AGE LABELS go-demo-2-5fd54 2/2 Running 0 6m db=mongo,language=go,service=go-demo-2,type=backend go-demo-2-v59t5 2/2 Running 0 6m db=mongo,language=go,service=go-demo-2,type=backend
The sequence of events that transpired with the kubectl create -f rs/go-demo-2.yml command is as follows:
- Kubernetes client (kubectl) sent a request to the API server requesting the creation of a ReplicaSet defined in the rs/go-demo-2.yml file.
- The controller is watching the API server for new events, and it detected that there is a new ReplicaSet object.
- The controller creates two new pod definitions because we have configured replica value as 2 in rs/go-demo-2.yml file.
- Since the scheduler is watching the API server for new events, it detected that there are two unassigned Pods.
- The scheduler decided to which node to assign the Pod and sent that information to the API server.
- Kubelet is also watching the API server. It detected that the two Pods were assigned to the node it is running on.
- Kubelet sent requests to Docker requesting the creation of the containers that form the Pod. In our case, the Pod defines two containers based on the mongo and api image. So in total four containers are created.
- Finally, Kubelet sent a request to the API server notifying it that the Pods were created successfully.
The sequence we described is useful when we want to understand everything that happened in the cluster from the moment we requested the creation of a new ReplicaSet. However, it might be too confusing so we'll try to explain the same process through a diagram that more closely represents the cluster.
Let's see which types of operations we can perform on ReplicaSets.