1. Introduction
The unequal relationship between the instances and the applications where the instances have dependencies on external data are called "stateful applications".
The so-called unequal relationship between instances means that for distributed applications, there are often relatively large dependencies between each instance and each application. For example, an application must be started before other applications, otherwise other applications will not be started.
The most significant application that has a dependency on external data is the database application. For database applications, we need to store its data persistently. If it is a stateless application, restarting the data in the database and the application will lose contact, which is obviously a violation Our original intention was not to put it into production.
Therefore, in order to solve the effective support of stateful applications in Kubernetes, Kubernetes uses StatefulSet to orchestrate and manage stateful applications.
StatefulSet is similar to ReplicaSet. The difference is that it can control the startup sequence of Pods, and it sets a unique identifier for each Pod. It has the following functions:
-
Stable, unique network identifier
-
Stable, persistent storage
-
Orderly, elegant deployment and scaling
- Orderly, automatic rolling update
The design of StatefulSet is easy to understand. It abstracts the real world into the following two situations:
(1) Topological state. This means that there is an asymmetric relationship between applications. Applications must be started in a certain order. Even if the application is restarted, it must be restarted in the specified order, and its network identification must be the same as the original after restart, so that the original access can be guaranteed The user can access the new Pod through the same method;
(2), storage state. This means that the application is bound to stored data, no matter when, no matter what the situation, for the application, as long as the data in the storage does not change, the data read should be the same;
So the core function of StatefulSet is to record the state of the Pod in a certain way, and then restore its state in some way when the Pod is re-created.
二、Headless Service
Before introducing StatefulSet, let's first understand what Headless Service is.
We know that in Kubernetes, Service is a way to provide external access to a group of Pods. Usually, we use Service to access Pod in the following two ways:
(1) Through Cluster IP, this Cluster IP is equivalent to VIP. When we visit this IP, the request will be forwarded to the backend Pod;
(2) Through DNS method, in this way, you must first ensure that there is DNS service in the Kubernetes cluster. At this time, as long as we access "my-service.my-namespace.svc,cluster.local", we can access the backend Pod proxied by the Service named my-service;
For the second method, there are the following two processing methods:
(1) Normal Service, that is, resolve the domain name, and get the Cluster IP, and then access according to method one;
(2), Headless Service, that is, resolve the domain name to get Is the IP address of a Pod in the backend, so that it can be accessed directly;
For the second processing method, we can see that there is no need for Cluster IP, but the IP address of the backend Pod is directly resolved through the domain name for access.
The following is a simple YAML file definition of Headless Service:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
It can be seen from the above YAML file that there is not much difference from our ordinary Service definition. The only difference is that the clusterIP is set to None, that is, no cluster is needed. We create this Service through kubectl apply -f, and then check the service's Cluster IP is None.
The Service created in this way is a Headless Service, it does not have a VIP, so it will expose the Pod it proxied in the form of DNS records.
Three, use StatefulSet
Create the PV before creating the StatefulSet, as follows:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv01
labels:
release: stable
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
hostPath:
path: /tmp/data
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv02
labels:
release: stable
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
hostPath:
path: /tmp/data
Then execute kubectl apply -f to start PV, as follows:
[root@master statefulset]# kubectl apply -f pv.yaml
persistentvolume/pv01 created
persistentvolume/pv02 created
[root@master statefulset]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv01 1Gi RWO Recycle Available 10s
pv02 1Gi RWO Recycle Available 9s
You can see that the states of the two PVs are both available, and then write the YAML file of the StatefulSet:
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
role: stateful
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
role: stateful
spec:
containers:
- name: nginx
image: cnych/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
Note that the above YAML file associated with volumeMounts is a new attribute: volumeClaimTemplates, this attribute will automatically declare a pvc object and pv for management, and serviceName: "nginx" means that the headless of nginx is used when the control loop is executed. Service to save the resolvable identity of the Pod.
Then here we open two terminal windows. In the first terminal, use kubectl get to view the creation of the Pods of the StatefulSet.
$ kubectl get pods -w -l role=stateful
In another terminal, use kubectl create to create the Headless Service and StatefulSet defined in statefulset-demo.yaml.
$ kubectl create -f statefulset-demo.yaml
service "nginx" created
statefulset.apps "web" created
Then we observe the startup sequence of the Pod:
[root@master ~]# kubectl get pods -w -l role=stateful
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 3m12s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 1s
web-1 0/1 ContainerCreating 0 1s
web-1 1/1 Running 0 5s
Through the above creation process, we can see that StatefulSet numbers the Pods it manages. Its naming rule is [statefulset-name]-[index], and its index starts from 0, which is the same as each Pod instance of StatefulSet. One correspondence, never repeat. More importantly, the Pod creation process is sequential. As above, web-1 enters the pending state after web-0 enters the running state.
We use the command to view the results of its creation:
[root@master statefulset]# kubectl get statefulset web
NAME READY AGE
web 2/2 17m
[root@master statefulset]# kubectl get pods -l role=stateful
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 17m
web-1 1/1 Running 0 14m
[root@master statefulset]# kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 17m
When both Pods enter the running state, you can view their respective network identities. We can view them through kubectl exec, as follows:
[root@master statefulset]# kubectl exec web-0 -- sh -c 'hostname'
web-0
[root@master statefulset]# kubectl exec web-1 -- sh -c 'hostname'
web-1
You can see that the hostname and pod name of the two pods are the same, and they are all assigned corresponding numbers. Next, we use DNS to access the Headless Service.
We first start a Pod with the following command:
kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh
Then use nslookup to parse the Headless Service corresponding to a Pod in this Pod:
kubectl run -i --tty --image centos dns-test --restart=Never --rm /bin/sh
sh-4.2# yum install bind-utils -y
sh-4.2# nslookup web-0.nginx
Server: 10.68.0.2
Address: 10.68.0.2#53
Name: web-0.nginx.default.svc.cluster.local
Address: 172.20.2.63
sh-4.2# nslookup web-1.nginx
Server: 10.68.0.2
Address: 10.68.0.2#53
Name: web-1.nginx.default.svc.cluster.local
Address: 172.20.2.64
From the analysis of the results of nslookup, when accessing web-0.nginx, the IP of the Pod web-0 is parsed, and the other is the same.
At this time, if we delete the two Pods, and then observe the process of restarting the Pod:
[root@master statefulset]# kubectl delete pod -l role=stateful
pod "web-0" deleted
pod "web-1" deleted
Then check the restart sequence as follows:
[root@master ~]# kubectl get pods -w -l role=stateful
NAME READY STATUS RESTARTS AGE
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 2s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 2s
We can see that after we delete the Pod, its restart sequence is still restarted according to the original number, and its network identification remains the same as before.
Through this strict correspondence rule, StatefulSet guarantees the stability of the Pod's network identity. Through this method, the Pod's topological state can be fixed according to the Pod's name + number. In addition, Kubernetes also provides a fixed and unique access entry for each Pod, that is, the Pod's DNS record.
We can also check the binding of PV and PVC:
[root@master statefulset]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv01 1Gi RWO Recycle Bound default/www-web-0 129m
pv02 1Gi RWO Recycle Bound default/www-web-1 129m
[root@master statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pv01 1Gi RWO 124m
www-web-1 Bound pv02 1Gi RWO 116m
Therefore, we sort out StatefulSet as follows:
(1), StatefulSet directly manages Pod. This is because the Pod instances in the StatefulSet are not exactly the same as the Pod instances in the ReplicaSet. They are slightly different. For example, the name and hostname of each Pod are different, and the way that the StatefulSet distinguishes these instances is to add the Pod Number;
(2), Kubernetes generates a record with the same number in the DNS server for this numbered Pod through the Headless Service. As long as the StatefulSet can ensure that the number of this Pod remains unchanged, the DNS record similar to web-0.nginx.default.svc.cluster.local in the Service will not change, and the Pod IP address resolved by this record will follow. It is automatically updated with Pod re-creation;
(3) StatefulSet can also allocate and create a PVC with the same number as the Pod for each Pod. In this way, Kubernetes can bind the corresponding PV to this PVC through the Persitent Volume mechanism, thereby ensuring that each Pod has an independent Volume. In this case, even if the Pod is deleted, its corresponding PVC and PV will still be retained, so when the Pod is recreated, Kubernetes will find the PVC with the same number for it, and mount the volume corresponding to the PVC. Get the previous data of the previous Volume;
Four, summary
One of the main functions of the StatefulSet controller is to number them when creating Pods using the Pod template, and complete the tasks in the order of the numbers. When the control loop of the StatefulSet finds that the actual state of the Pod is inconsistent with the expected state, it will also Operate the Pod in order.
Of course, StatefulSet also has other features. In actual projects, we still rarely go back and deploy our stateful services directly through StatefulSet, unless you can fully hold it yourself. For some specific services, we may use more Advanced Operators are deployed, such as etcd-operator, prometheus-operator, etc. These applications can manage stateful services very well, instead of just using a StatefulSet to deploy a Pod, because for stateful applications The most important thing is data recovery, failover and so on.
Finish