12 . Kubernetes之Statefulset 和 Operator
Statefulset简介
k8s权威指南这样介绍的
“在Kubernetes系统中,Pod的管理对象RC、Deployment、DaemonSet和Job都面向无状态的服务。但现实中有很多服务是有状态的,特别是一些复杂的中间件集群,例如MySQL集群、MongoDB集群、Akka集群、ZooKeeper集群等,这些应用集群有4个共同点。"
(1)每个节点都有固定的身份ID,通过这个ID,集群中的成员可以相互发现并通信。
(2)集群的规模是比较固定的,集群规模不能随意变动。
(3)集群中的每个节点都是有状态的,通常会持久化数据到永久存储中。
(4)如果磁盘损坏,则集群里的某个节点无法正常运行,集群功能受损。
如果通过RC或Deployment控制Pod副本数量来实现上述有状态的集群,就会发现第1点是无法满足的,因为Pod的名称是随机产生的,Pod的IP地址也是在运行期才确定且可能有变动的,我们事先无法为每个Pod都确定唯一不变的ID。另外,为了能够在其他节点上恢复某个失败的节点,这种集群中的Pod需要挂接某种共享存储,为了解决这个问题,Kubernetes从1.4版本开始引入了PetSet这个新的资源对象,并且在1.5版本时更名为StatefulSet,StatefulSet从本质上来说,可以看作Deployment[…]”
“StatefulSet除了要与PV卷捆绑使用以存储Pod的状态数据,还要与Headless Service配合使用,即在每个StatefulSet定义中都要声明它属于哪个Headless Service。Headless Service与普通Service的关键区别在于,它没有Cluster IP,如果解析Headless Service的DNS域名,则返回的是该Service对应的全部Pod的Endpoint列表。StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod实例都创建了一个DNS域名,这个域名的格式为:
$(podname).(headless server name)
FQDN: $(podname).(headless server name).namespace.svc.cluster.local
比如一个3节点的Kafka的StatefulSet集群对应的Headless Service的名称为kafka,StatefulSet的名称为kafka,则StatefulSet里的3个Pod的DNS名称分别为kafka-0.kafka、kafka-1.kafka、kafka-3.kafka,这些DNS名称可以直接在集群的配置文件中固定下来。”
StatefulSet本质上是Deployment的一种变体,在v1.9版本中已成为GA版本,它为了解决有状态服务的问题,它所管理的Pod拥有固定的Pod名称,启停顺序,在StatefulSet中,Pod名字称为网络标识(hostname),还必须要用到共享存储。
在Deployment中,与之对应的服务是service,而在StatefulSet中与之对应的headless service,headless service,即无头服务,与service的区别就是它没有Cluster IP,解析它的名称时将返回该Headless Service对应的全部Pod的Endpoint列表。
应用场景
StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括
- 1、稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
- 2、稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
- 3、有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现有序收缩,有序删除(即从N-1到0)
特点
# 1. 稳定且唯一的网络标识符
# 2. 稳定且持久的存储.
# 3. 有序、平滑地部署和扩展.
# 4. 有序、平滑的删除和终止.
# 5. 有序的滚动更新
三个组件
# headless service(无头服务)
# statefuleset
# volumeClaimTemplate(存储卷申请模板)
Operator
Kubernetes operator是把专家平常的经验,流程,比如写在wiki里面的诀窍,使用Operator来固化。这些经验就会集成在Operator软件里面。
换句话说,这个叫做Best practices. 也是软件工程里面把logic封装起来的一个例子。It’s a fancy script.
比如failover,用软件来建立模型。使用K8S的原语,比如stateless workload,stateful workload,服务发现等来实现。
好处是到处可以使用,跨平台。
Kubernetes提供了一个Operator框架,供大家使用。控制集群里面的节点。
Operator SDK for Build
Operator Lifecycle manager for Run, 多个Operator的生命周期,比如版本。
Operator Metering for Operate,收集统计信息。
业界Open operators, 提供在github。比如etcd,Spark, MongoDB.例如,mongoDB提供了operator,封装了创造ReplicaSet的best practice。在PROD namespace,用户提供High level configuration,提供Intention about如何部署。Operator会帮助deploy。
另外一个例子,根据Luke Bond的video, Cassandra需要把3节点扩展到5节点的时候,因为是stateful应用,需要做一系列的动作,这个知识就可以封装在operator。就像Kubectl增加instance的数量一样。
Statefulset应用
Example1
创建pv
cat /root/volume/pod-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
labels:
name: pv001
spec:
nfs:
path: /data/volumes/v1
server: nfs1
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
labels:
name: pv002
spec:
nfs:
path: /data/volumes/v2
server: nfs1
accessModes: ["ReadWriteMany"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003
labels:
name: pv003
spec:
nfs:
path: /data/volumes/v3
server: nfs1
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 2Gi
创建StatefulSet
cat pod-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
labels:
app: myapp
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: myapp-pod
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: myapp
spec:
serviceName: myapp
replicas: 3
selector:
matchLabels:
app: myapp-pod
template:
metadata:
labels:
app: myapp-pod
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
ports:
- containerPort: 80
name: web
volumeMounts:
- name: myappdata
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: myappdata
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 2Gi
查看创建的Statefulset
kubectl get svc
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d
# myapp ClusterIP None <none> 80/TCP 44s
kubectl get sts
# NAME READY AGE
# myapp 3/3 46s
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# myapp-0 1/1 Running 0 48s
# myapp-1 1/1 Running 0 40s
# myapp-2 1/1 Running 0 39s
StateFulSet扩缩容
kubectl scale sts myapp --replicas=4
kubectl patch sts myapp -p '{"spec":{"replicas":4}}'
kubectl get pods -w
NAME READY STATUS RESTARTS AGE
myapp-0 1/1 Running 0 15m
myapp-1 1/1 Running 0 15m
myapp-2 1/1 Running 0 15m
myapp-3 0/1 Pending 0 0s
myapp-3 0/1 Pending 0 0s
myapp-3 0/1 ContainerCreating 0 0s
myapp-3 1/1 Running 0 2s
# 我们进入pod创建一个文件,即使我们缩容删掉Pod,但是存储卷数据还在
[root@master statefulset]# kubectl exec -it myapp-0 -- sh
/ # ls
bin etc lib mnt root sbin sys usr
dev home media proc run srv tmp var
/ # ls /usr/share/nginx/html/
/ # echo 1324 > /usr/share/nginx/html/index.html
[root@nfs1 ~]# tree /data/volumes/
/data/volumes/
├── v1
│ └── index.html
├── v2
├── v3
└── v4
[root@master statefulset]# kubectl scale sts myapp --replicas=1
[root@master statefulset]# kubectl scale sts myapp --replicas=3
[root@master statefulset]# kubectl exec -it myapp-0 /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
/ # ls /usr/share/nginx/html/
index.html
控制更新策略
# 默认为滚动更新
kubectl describe sts myapp |grep Update
Update Strategy: RollingUpdate
kubectl patch sts myapp -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":2}}}}'
# 留下两个不更新
# 更换镜像
kubectl set image sts/myapp myapp=ikubernetes/myapp:v2
# 查看镜像版本
kubectl get sts -o wide
NAME READY AGE CONTAINERS IMAGES
myapp 4/4 29m myapp ikubernetes/myapp:v2
# 我们会发现有两个Pod版本还是以前的,可以算金丝雀发布
kubectl patch sts myapp -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":0}}}}'
# 全部更新.