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}}}}'
# 全部更新.
posted @ 2020-07-04 15:13  常见-youmen  阅读(1062)  评论(0编辑  收藏  举报