在k8s中工作负载资源StatefulSet用于管理有状态应用。
什么是无状态?
组成一个应用的pod是对等的,它们之前没有关联和依赖关系,不依赖外部存储。
即我们上篇小作文中deployment创建的nginx pod ,他们是完全一样的,任何一个pod 被移除后依然可以正常工作。由于不依赖外部存储,它们可以被轻易的调度到任何 node 上。
什么是有状态?
显然无状态的反面就是有状态了,pod之间可能包含主从、主备的相互依赖关系,甚至对启动顺序也有要求。更关键的是这些pod 需要外部存储,一旦pod被清除或调度后,怎么把pod 和原来的外部数据联系起来?这就是StatefulSet厉害的地方。
StatefulSet将这些状态应用进行记录,在需要的时候恢复。
StatefulSet如何展开这些工作?
一、维护应用拓扑状态
通过dns记录为 pod 分配集群内唯一、稳定的网络标识。即只要保证pod 的名称不变,pod被调度到任何节点或者ip如何变更都能被找到。
在 k8s 中Service用来来将一组 Pod 暴露给外界访问的一种机制。当创建的service 中clusterIP为None 时(headless 无头服务), 不会进行负载均衡,也不会为该服务分配集群 IP。仅自动配置 DNS。
这样我们集群中的 一个pod 将被绑定到一条DNS记录:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
通过解析这个地址就能找到pod的IP 。
下面我们创建一个headless service,将clusterIP
配置为None
:
#headless-service.yml
apiVersion: v1
kind: Service
metadata:
name: nginx-headless
spec:
ports:
- name: nginx-service-port
port: 80
targetPort: 9376
clusterIP: None
selector:
app: nginx
#headless-service.yml apiVersion: v1 kind: Service metadata: name: nginx-headless spec: ports: - name: nginx-service-port port: 80 targetPort: 9376 clusterIP: None selector: app: nginx
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 18d
nginx-headless ClusterIP None <none> 80/TCP 4h48m
nginx-headless 这个headless service创建成功了。接着我们创建一个StatefulSet:
#nginx-statefulset.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-statefulset
spec:
serviceName: "nginx-headless"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx-web
image: nginx:1.17
ports:
- containerPort: 82
nginx-statefulset 将会绑定我们前面的service nginx-headless
并创建三个nginx pod。
我们查看创建的pod ,StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。同时statefulset创建出来的pod 名称以(StatefulSetname)−(order)开始编号。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-statefulset-0 1/1 Running 0 18s
nginx-statefulset-1 1/1 Running 0 15s
nginx-statefulset-2 1/1 Running 0 12s
$ kubectl exec nginx-statefulset-0 -- sh -c hostname
nginx-statefulset-0
其实他们的创建顺序也是从0-2,当我们删除这些pod时,statefulset 马上重建出相同名称的Pod 。
我们通过statefulset 的event可以观测到这个过程:
$ kubectl describe nginx-statefulset
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 7m43s statefulset-controller create Pod nginx-statefulset-0 in StatefulSet nginx-statefulset successful
Normal SuccessfulCreate 7m40s statefulset-controller create Pod nginx-statefulset-1 in StatefulSet nginx-statefulset successful
Normal SuccessfulCreate 7m37s statefulset-controller create Pod nginx-statefulset-2 in StatefulSet nginx-statefulset successful
现在我们来看一下 pod 是否存在于 DNS 记录中:
kubectl run -it --image busybox busybox --rm /bin/sh
运行一个一次性 pod busybox ,接着使用 ping 命令查询之前提到的规则构建名称
nginx-statefulset-0.nginx-headless.default.svc.cluster.local
解析的IP与如下nginx-statefulset-0
相符。
这样我们使用pod名称通过DNS就可以找到这个pod 再加上StatefulSet可以按顺序创建出不变名称的 pod ,即一个应用通过StatefulSet准确维护其拓扑状态
二、维护应用存储状态
**k8s为应对应用的数据存储需求提供了卷的概念(volume)以及提供持久化存储的PVC( PersistentVolumeClaim)PV( PersistentVolume)当一个pod 和 PVC绑定后,即使pod 被移除,PVC和PV仍然保留在集群中,pod 再次被创建后会自动绑定到之前的PVC。**他们看起来是这样的:
rs_pv_pvc
这里我们以讨论statefulset持久化存储为主,对于k8s存储本身不了解的同学可以参考k8s官方文档存储章节storage[1]
首先我们创建存储目录 /data/volumes/ 以及一个本地的local
类型(使用节点上的文件或目录来模拟网络附加存储)的PV:
#pv-local.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-local
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /data/volumes/
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- minikube
PV是集群中的一块存储,它声明了后端使用的真实存储,通常会由K8S管理员创建。我们在pv-local
中声明了后端存储类型为local
挂载到目录 /data/volumes/ , 存储卷类名为local-storage
,1Gb容量,访问模式ReadWriteMany
-- 卷可以被多个个节点以读写方式挂载。亲和的节点为minikube
我们通过get来查看这个PV:
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 5Gi RWX Delete Available local-storage 25m
此时PV的状态为available
,还未与任何PVC绑定。我们通过创建PV使集群得到了一块存储资源,但此时还不属于你的应用,我们需要通过PVC去构建一个使用它的”通道“。
#app1-pvc.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app1-pvc
spec:
storageClassName: local-storage
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
现在我们开辟好一个5Gb容量的存储通道(PVC),此时PV和PVC已通过 storageClassName
自动形成绑定。这样PV和PVC的status 皆为Bound
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 5Gi RWX Delete Bound default/app-pvc local-storage 25m
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
app-pvc Bound pv-local 5Gi RWX local-storage 27m
上面我们创建好通道,接下来要在我们statefuset中绑定这个通道,才能顺利使用存储。
# nginx-statefulset.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-statefulset
spec:
serviceName: "nginx-headless"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
nodeName: minikube
volumes:
- name: app-storage
persistentVolumeClaim:
claimName: app-pvc
containers:
- name: nginx-web
image: nginx:1.17
ports:
- containerPort: 80
name: nginx-port
volumeMounts:
- mountPath: /usr/share/nginx/html
name: app-storage
与之前的statefulset相比我们在pod 模板中添加了volume
已经 volumeMounts
,这样使用这个statefulset 所创建的pod都将挂载 我们前面定义的PVC app-pvc
,应用nginx-statefulset.yml
后我们进入到pod 检验一下目录是否被正确挂载。
$ kubectl exec -it nginx-statefulset-0 -- /bin/bash
root@nginx-statefulset-0:/# cat /usr/share/nginx/html/index.html
hello pv
接着我们在pod 中修改index.html内容为并将pod删除,检验重载后的 pod 存储数据是否能被找回。
root@nginx-statefulset-0:/# echo "pod data" > /usr/share/nginx/html/index.html
删除带有标签app=nginx
的pod ,由于statefulset的控制器使pod按顺序被重建:
$ kubectl delete pod -l app=nginx
pod "nginx-statefulset-0" deleted
pod "nginx-statefulset-1" deleted
pod "nginx-statefulset-2" deleted
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-statefulset-0 1/1 Running 0 9s
nginx-statefulset-1 1/1 Running 0 6s
nginx-statefulset-2 0/1 ContainerCreating 0 3s
毫无疑问,pod 数据完好无损:
$ kubectl exec -it nginx-statefulset-0 -- /bin/bash
root@nginx-statefulset-0:/# cat /usr/share/nginx/html/index.html
pod data
也就是说虽然我们的pod被删除了,但是PV已经PV依然保留在集群中,当pod 被重建后,它依然会去找定义的claimName: app-pvc
这个PVC,接着挂载到容器中。
这里我们一个PVC 绑定了多个节点,其实可以为每一个 statefulset中的pod 创建PVC,可以自行了解。
k8s存储可操作性非常强,这里只在statefulset下做了简单的演示。后续我们会对k8s存储做更深入的了解。
三、总结
这篇小作文我们一起学习了k8s中工作负载资源StatefulSet是如何管理有状态应用的,主要从维护应用拓扑状态和存储状态两个方面做了简单介绍。这样我们对statefulset这个工作资源有了大体了解:StatefulSet与Deployment 相比,它为每个管理的 Pod 都进行了编号,使Pod有一个稳定的启动顺序,并且是集群中唯一的网络标识。有了标识后使用PV、PVC对存储状态进行维护。