一、前言

     在前面的系列K8S初级入门系列之六-控制器(RC/RS/Deployment)K8S初级入门系列之七-控制器(Job/CronJob/Daemonset)我们已经介绍了多种控制器,今天我们将介绍最后一种控制器--StatefulSet,顾名思义,即有状态Set,那什么是无状态和有状态呢?有个形象的比如,牛和宠物。对于牛来说,只要能干活,在农场主眼里没有区别,一头牛死掉了,可以用另外一头牛顶替上,农场主不会纠结是否还是之前那头牛。对于宠物来说,他们的主人是能感受到个体之间区别的,一只宠物死掉了,是无法用另外一只替换。

    前面介绍的RC,RS控制器所创建的都是无状态的实例,即实例个体之间没有差别,一旦某个实例挂掉了,可以启动另一个实例顶替上,比如Ngnix实例。但在实际工程中,还有一种是有状态实例,比如数据库,分库1实例和分库2实例是不一样的,分库1挂掉了,是不能直接启动一个实例顶替上的,而是要确保新启的实例在网络标识,主机标识,存储数据等方面保持一致,才能不影响业务运行。

     显然,对于以上有状态的实例控制,RC,RS控制器是无法做到的,针对有状态场景,K8S提供StatuefulSet控制器。

二、StatefulSet原理

    StatefulSet适用于有状态实例,那么它是如何确保状态的一致呢?还是以数据库的分库为例,需要保持网络标识,主机标识,存储卷一致。如果用之前的RS来实现,看下实现的原理图:

      需要为每个实例创建一个ReplicaSet,分配独立的PVC,以及提供对外访问的Service。这个方式虽然可以实现需求,但并不是完美的解决方案,如果分库数量过大(超过100),那么维护的工作量将非常大。

  接下来,我们看下StatefulSet如何实现

    StatefuSet负责创建不同实例的Pod,每个实例按照数字顺序进行标识,同时分配与实例关联的PVC,以及Service。

      当某个实例挂死后,重建实例,并写入旧实例的标识,并关联就实例的PVC,虽然实例的实体发生变化,但是对外业务感知不到的,还是同一实例,可以理解为旧实例的复制体。如下图所示:

 三、StatefulSet创建

在创建前,我们首先预备存储PV,以及StatefulSet所依赖的Headless Service。

1、创建PV

     这里我们创建两个PV,分别为a-pv.yaml和b-pv.yaml,以前一章节创建的NFS为存储卷,内容如下:

  • a-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: a-pv
  labels:
    pv: a-pv
spec:
  capacity:
    storage: 10Mi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /nfs/data/a
    server: 192.168.16.4
  • b-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: b-pv
  labels:
    pv: b-pv
spec:
  capacity:
    storage: 10Mi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /nfs/data/b
    server: 192.168.16.4

执行该文件,并查看状态

[root@k8s-master yaml]# kubectl apply -f a-pv.yaml 
persistentvolume/a-pv created
[root@k8s-master yaml]# kubectl apply -f b-pv.yaml 
persistentvolume/b-pv created
[root@k8s-master yaml]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                     STORAGECLASS   REASON   AGE
a-pv                                       10Mi       RWX            Recycle          Available                                                     13s
b-pv                                       10Mi       RWX            Recycle          Available                                                     4s

PV创建完成,目前处于Available状态。 

 2、创建Headless Service

      StatefulSet需要配套Headless Service,其相关知识可以参见:K8S初级入门系列之八-网络

 其yaml内容如下:

[root@k8s-master yaml]# cat stateful-svc.yaml 
apiVersion: v1
kind: Service
metadata:
  name: stateful-svc
  labels:
    app: stateful-svc
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx-stateful

   其中clusterIP: None表示的就是Headless Service,执行该文件,并查看状态

[root@k8s-master yaml]# kubectl get svc
NAME                           TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
stateful-svc                   ClusterIP      None            <none>          80/TCP         23h

   3、创建StatefulSet

     接下来就来创建今天的主角StatefulSet,其内容如下:

[root@k8s-master yaml]# cat nginx-stateful.yaml 
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-statefulset
spec:
  serviceName: "stateful-svc"
  replicas: 2
  selector:
    matchLabels:
      app: nginx-stateful
  template:
    metadata:
      labels:
        app: nginx-stateful
    spec:
      containers:
      - name: nginx-stateful
        image:  nginx:1.14.2
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteMany" ]
      resources:
        requests:
          storage: 10Mi

      StatefulSet与Deployment的结构类似,这里定义了replics为2,也就是两个副本,容器镜像为 nginx:1.14.2,并将目录/usr/share/nginx/html挂载到PVC存储上。

    执行该文件,并查看创建状态:

[root@k8s-master yaml]# kubectl apply -f nginx-stateful.yaml 
statefulset.apps/nginx-statefulset created
[root@k8s-master yaml]# kubectl get statefulset
NAME                READY   AGE
nginx-statefulset   2/2     5m14s

我们再看下pod的状态

[root@k8s-master yaml]# kubectl get pod
NAME                                      READY   STATUS                   RESTARTS           AGE
nginx-statefulset-0                       1/1     Running                  0                  5s
nginx-statefulset-1                       1/1     Running                  0                  3s

此时,成功创建了2个Pod,并按索引顺序作为名称的后缀。我们再来看下PVC和PV

[root@k8s-master yaml]# kubectl get pvc
NAME                      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-nginx-statefulset-0   Bound    a-pv                                       10Mi       RWX                           6m43s
www-nginx-statefulset-1   Bound    b-pv                                       10Mi       RWX                           2m26s
[root@k8s-master yaml]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                             STORAGECLASS   REASON   AGE
a-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-0                           10m
b-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-1                           10m

自动完成了PVC的创建,并且PVC和PV都显示为绑定状态,说明存储卷已经挂载成功。

4、Pod的验证

验证下Pod的访问以及功能是否可用。

K8S初级入门系列之八-Service核心概念章节,安装了dnsutils工具,使用该工具查看下这两个Pod的ip和域名。

[root@k8s-master ~]# kubectl exec -it dnsutils /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # nslookup nginx-statefulset-0.stateful-svc.default.svc.cluster.local
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   nginx-statefulset-0.stateful-svc.default.svc.cluster.local
Address: 10.244.36.71

/ # nslookup nginx-statefulset-1.stateful-svc.default.svc.cluster.local
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   nginx-statefulset-1.stateful-svc.default.svc.cluster.local
Address: 10.244.36.114

      StatefulSet Pod的访问域名格式为Pod名称.Service名称.default.svc.cluster.local。通过nsloolup正确的解析了域名的IP,内部应用就可以使用域名访问不同Pod了。

接下来,我们验证下Pod的访问,首先分别给html写入不同的内容

[root@k8s-master ~]# kubectl exec nginx-statefulset-0 -- sh -c 'echo "this is nginx-statefulset-0" > /usr/share/nginx/html/index.html'
[root@k8s-master ~]# kubectl exec nginx-statefulset-1 -- sh -c 'echo "this is nginx-statefulset-1" > /usr/share/nginx/html/index.html'

为了方便起见,我们使用curl 名称访问这两个Pod

[root@k8s-master ~]# curl http://10.244.36.71
this is nginx-statefulset-0
[root@k8s-master ~]# curl http://10.244.36.114
this is nginx-statefulset-1

能正确的访问到内容。我们再看下NFS挂载目录的内容

[root@k8s-master data]# cd a
[root@k8s-master a]# cat index.html 
this is nginx-statefulset-0
...
[root@k8s-master data]# cd b
[root@k8s-master b]# cat index.html 
this is nginx-statefulset-1

 也已经存储了正确的内容。

四、Pod重建

      前面介绍,当其中一个Pod发生异常,为了确保副本数的一致,StatefulSet重建需要重建该Pod,且新建的Pod会保持主机标识,网络标识,以及存储等状态不变。下面我们将通过删除Pod(nginx-statefulset-0)来模拟。

 为了监控Pod的重建过程,我们打开两个终端

终端1,执行删除Pod指令

[root@k8s-master b]# kubectl delete pod nginx-statefulset-0
pod "nginx-statefulset-0" deleted

终端2,监控Pod的变化。

[root@k8s-master yaml]# kubectl get pod -w -l app=nginx-stateful
NAME                  READY   STATUS    RESTARTS   AGE
nginx-statefulset-0   1/1     Running   0          23h
nginx-statefulset-1   1/1     Running   0          23h
nginx-statefulset-0   1/1     Terminating   0          23h
nginx-statefulset-0   1/1     Terminating   0          23h
nginx-statefulset-0   0/1     Terminating   0          23h
nginx-statefulset-0   0/1     Terminating   0          23h
nginx-statefulset-0   0/1     Terminating   0          23h
nginx-statefulset-0   0/1     Pending       0          0s
nginx-statefulset-0   0/1     Pending       0          0s
nginx-statefulset-0   0/1     ContainerCreating   0          0s
nginx-statefulset-0   0/1     ContainerCreating   0          1s
nginx-statefulset-0   1/1     Running             0          2s

     可以看到,老的Pod变为Terminating后,新的Pod开始重建,状态变化Pending->ContainerCreating->Running。

    重建过程中,Pod的名称保持不变,再看下重建后,新Pod的网络标识。

[root@k8s-master ~]# kubectl exec -it dnsutils /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # nslookup nginx-statefulset-0.stateful-svc.default.svc.cluster.local
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   nginx-statefulset-0.stateful-svc.default.svc.cluster.local
Address: 10.244.36.70

IP发生了变化,域名没有变化,所以在内部访问时,要使用域名访问,不能使用IP

继续看下挂载的PVC,可以使用详情查看

[root@k8s-master b]# kubectl describe pod nginx-statefulset-0
...
Volumes:
  www:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  www-nginx-statefulset-0
    ReadOnly:   false
....

绑定到了之前的PVC www-nginx-statefulset-0。再访问index内容

[root@k8s-master ~]# curl http://10.244.36.70
this is nginx-statefulset-0

也能正确访问到老Pod写入的内容。由此可见,重建的Pod保持了存储标识的一致。

五、扩缩容

     StatefulSet与RS一样,支持扩缩容。

1、扩容

先来看下扩容场景,我们需要扩容两个副本,首先创建两个PV,其yaml内容如下:

  • c-pv.yaml
[root@k8s-master yaml]# cat c-pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: c-pv
  labels:
    pv: c-pv
spec:
  capacity:
    storage: 10Mi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /nfs/data/c
    server: 192.168.16.4
  • d-pv.yaml
[root@k8s-master yaml]# cat d-pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: d-pv
  labels:
    pv: d-pv
spec:
  capacity:
    storage: 10Mi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /nfs/data/d
    server: 192.168.16.4

执行完成后,查看状态

[root@k8s-master yaml]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                             STORAGECLASS   REASON   AGE
a-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-0                           2d10h
b-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-1                           2d10h
c-pv                                       10Mi       RWX            Recycle          Available                                                             6m35s
d-pv                                       10Mi       RWX            Recycle          Available                                                             2m9s

新创建的两个PV处于Available状态。接下来启动两个终端

终端1,执行扩容指令

[root@k8s-master yaml]# kubectl scale  sts nginx-statefulset --replicas=4
statefulset.apps/nginx-statefulset scaled

终端2,监控pod的变化

[root@k8s-master yaml]# kubectl get pod -w -l app=nginx-stateful
NAME                  READY   STATUS    RESTARTS   AGE
nginx-statefulset-0   1/1     Running   0          2d11h
nginx-statefulset-1   1/1     Running   0          3d11h
nginx-statefulset-2   0/1     Pending   0          0s
nginx-statefulset-2   0/1     Pending   0          0s
nginx-statefulset-2   0/1     Pending   0          1s
nginx-statefulset-2   0/1     ContainerCreating   0          1s
nginx-statefulset-2   0/1     ContainerCreating   0          2s
nginx-statefulset-2   1/1     Running             0          3s
nginx-statefulset-3   0/1     Pending             0          0s
nginx-statefulset-3   0/1     Pending             0          0s
nginx-statefulset-3   0/1     Pending             0          1s
nginx-statefulset-3   0/1     ContainerCreating   0          1s
nginx-statefulset-3   0/1     ContainerCreating   0          2s
nginx-statefulset-3   1/1     Running             0          3s

 可以看到,以索引递增的命名方式依次新扩容了两个Pod。

再看下PVC和PV的状态

[root@k8s-master yaml]# kubectl get pvc
NAME                      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
nfs-storage-pvc           Bound    pvc-f795e09b-1583-436b-869b-88a68d28ce64   1Gi        RWX            nfs-client     11d
www-nginx-statefulset-0   Bound    a-pv                                       10Mi       RWX                           3d11h
www-nginx-statefulset-1   Bound    b-pv                                       10Mi       RWX                           3d11h
www-nginx-statefulset-2   Bound    c-pv                                       10Mi       RWX                           6m27s
www-nginx-statefulset-3   Bound    d-pv                                       10Mi       RWX                           6m24s
[root@k8s-master yaml]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                             STORAGECLASS   REASON   AGE
a-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-0                           3d11h
b-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-1                           3d11h
c-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-2                           25h
d-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-3                           25h

 PVC状态正确,并与PV绑定。

同样,验证下新扩容的nginx-statefulset-2是否可用

[root@k8s-master yaml]# kubectl exec nginx-statefulset-2 -- sh -c 'echo "this is nginx-statefulset-2" > /usr/share/nginx/html/index.html'
[root@k8s-master yaml]# curl http://10.244.36.85 
this is nginx-statefulset-2

 能正确的写入和读取,扩容成功。

2、缩容

再将这两个Pod缩容掉,同样启用两个终端

终端1,执行缩容指令

[root@k8s-master yaml]# kubectl scale  sts nginx-statefulset --replicas=2
statefulset.apps/nginx-statefulset scaled

终端2,监控pod的变化

[root@k8s-master yaml]# kubectl get pod -w -l app=nginx-stateful
NAME                  READY   STATUS    RESTARTS   AGE
nginx-statefulset-0   1/1     Running   0          2d11h
nginx-statefulset-1   1/1     Running   0          3d11h
nginx-statefulset-2   1/1     Running   0          15m
nginx-statefulset-3   1/1     Running   0          14m

nginx-statefulset-3   1/1     Terminating   0          16m
nginx-statefulset-3   1/1     Terminating   0          16m
nginx-statefulset-3   0/1     Terminating   0          16m
nginx-statefulset-3   0/1     Terminating   0          16m
nginx-statefulset-3   0/1     Terminating   0          16m
nginx-statefulset-2   1/1     Terminating   0          16m
nginx-statefulset-2   1/1     Terminating   0          16m
nginx-statefulset-2   0/1     Terminating   0          16m
nginx-statefulset-2   0/1     Terminating   0          16m
nginx-statefulset-2   0/1     Terminating   0          16m

  可以看到,Pod按照倒序索引依次终止。

六、滚动更新

     StatefulSet支持更新,可以通过配置相关的更新策略,默认为"RollingUpdate"(滚动更新),如下所示:

spec:
  updateStrategy:
    type: RollingUpdate
 ....

接下来,我们通过滚动更新的方式将nginx的镜像版本升级到最新状态

终端1,执行更新执行,修改镜像的版本

[root@k8s-master yaml]# kubectl set image sts nginx-statefulset nginx-stateful=nginx:latest
statefulset.apps/nginx-statefulset image updated

终端2,监控Pod的变化

[root@k8s-master ~]# kubectl get pod -w -l app=nginx-stateful
NAME                  READY   STATUS    RESTARTS   AGE
nginx-statefulset-0   1/1     Running   0          2d13h
nginx-statefulset-1   1/1     Running   0          3d12h
nginx-statefulset-1   1/1     Terminating   0          3d12h
nginx-statefulset-1   1/1     Terminating   0          3d12h
nginx-statefulset-1   0/1     Terminating   0          3d12h
nginx-statefulset-1   0/1     Terminating   0          3d12h
nginx-statefulset-1   0/1     Terminating   0          3d12h
nginx-statefulset-1   0/1     Pending       0          0s
nginx-statefulset-1   0/1     Pending       0          0s
nginx-statefulset-1   0/1     ContainerCreating   0          0s
nginx-statefulset-1   0/1     ContainerCreating   0          1s
nginx-statefulset-1   1/1     Running             0          71s
nginx-statefulset-0   1/1     Terminating         0          2d13h
nginx-statefulset-0   1/1     Terminating         0          2d13h
nginx-statefulset-0   0/1     Terminating         0          2d13h
nginx-statefulset-0   0/1     Terminating         0          2d13h
nginx-statefulset-0   0/1     Terminating         0          2d13h
nginx-statefulset-0   0/1     Pending             0          0s
nginx-statefulset-0   0/1     Pending             0          0s
nginx-statefulset-0   0/1     ContainerCreating   0          0s
nginx-statefulset-0   0/1     ContainerCreating   0          1s
nginx-statefulset-0   1/1     Running             0          3s

  可以看到,按照倒序依次更新。待上一个Pod处于running状态后,才会启动下一个的更新。

 查看下升级后Pod实例的版本

[root@k8s-master yaml]# kubectl describe pod nginx-statefulset-1
...
Containers:
  nginx-stateful:
    Container ID:   docker://4caa6d45b6e6423d61bea97f93a680f7171c56462eaaa5e8af83bbe2d1917a6b
    Image:          nginx:latest

... 

可以看到,Pod已经升级到最新版本。

除了 RollingUpdate,还支持

  • OnDelete,删除升级策略,StatefulSet并不主动触发升级,而是要用户删除Pod,StatefulSet重建Pod时进行升级,实际是一种手动升级的方式。
  • partition,分区升级策略,用户指定一个序号,大于此序号的Pod实例全部升级,而小于此序号的Pod实例则依旧保持老版本不变,即使是这些Pod重建,依然使用老的版本。此策略一般用于按计划分步骤的升级,如金丝雀发布。

七,删除

      StatefulSet 同时支持级联和非级联删除。使用非级联方式删除 StatefulSet 时,StatefulSet 的 Pod 不会被删除。使用级联删除时,StatefulSet 和它的 Pod 都会被删除。

   这里我们看下级联删除

 [root@k8s-master yaml]# kubectl delete sts nginx-statefulset
statefulset.apps "nginx-statefulset" deleted
[root@k8s-master yaml]# kubectl get sts
No resources found in default namespace.
[root@k8s-master yaml]# kubectl get pod -l app=nginx-stateful
No resources found in default namespace.

此时Pod已经被删除,再看下svc,pvc,pv的状态

[root@k8s-master yaml]# kubectl get svc
NAME                           TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
stateful-svc                   ClusterIP      None            <none>          80/TCP         4d13h
[root@k8s-master yaml]# kubectl get pvc
NAME                      STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE

www-nginx-statefulset-0   Bound    a-pv                                       10Mi       RWX                           3d13h
www-nginx-statefulset-1   Bound    b-pv                                       10Mi       RWX                           3d13h
www-nginx-statefulset-2   Bound    c-pv                                       10Mi       RWX                           128m
www-nginx-statefulset-3   Bound    d-pv                                       10Mi       RWX                           127m
[root@k8s-master yaml]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                             STORAGECLASS   REASON   AGE
a-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-0                           3d13h
b-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-1                           3d13h
c-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-2                           27h
d-pv                                       10Mi       RWX            Recycle          Bound       default/www-nginx-statefulset-3                           27h

 与之相关联的Service,PVC,PV并没有删除,需要手动删除。 

非级联删除,相对于级联删除,指令上需要增加"--cascade=orphan"后缀参数。

 [root@k8s-master yaml]# kubectl delete sts nginx-statefulset  --cascade=orphan

 八、总结

     本章节介绍了有状态控制器StatefulSet,有状态实例有自己的主机标识,网络标识,存储标识的一致,实例之间不能相互替代,Pod实例重建时需要保持状态的一致,使得业务无感。          StatefulSet能根据一份容器镜像完成多个有状态的副本的状态,并自动创建PVC,并绑定PV,同时,与Deployment一样,StatefulSet控制器支持副本重建,扩缩容,滚动更新等。 

 附:

K8S初级入门系列之一-概述

K8S初级入门系列之二-集群搭建

K8S初级入门系列之三-Pod的基本概念和操作

K8S初级入门系列之四-Namespace/ConfigMap/Secret

K8S初级入门系列之五-Pod的高级特性

K8S初级入门系列之六-控制器(RC/RS/Deployment)

K8S初级入门系列之七-控制器(Job/CronJob/Daemonset)

K8S初级入门系列之八-网络

K8S初级入门系列之九-共享存储

K8S初级入门系列之十-控制器(StatefulSet)

K8S初级入门系列之十一-安全

K8S初级入门系列之十二-计算资源管理

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐