简介

statefulset也是一种POD控制器,那为什么要放在PV/PVC之后再简介呢?这是因为statefulset是必须也有持久化数据,每个POD所对应的PV都是不一样的。相对于Deployment所创建的POD是无状态的,那statefulset是属于有状态的,即可以保留POD的状态信息。其特点有:

  1. 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
  2. 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现,
  3. 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
  4. 有序收缩,有序删除(即从N-1到0),当Pod被删除时,它们被终止的顺序是从N-1到0。kubectl delete -f pod.yaml然后手动删除pv。
  5. 有序的滚动更新
  6. 有序扩展: 当对Pod执行扩展操作时,与部署一样,它前面的Pod必须都处于Running和Ready状态。

补充:

  1. StatefulSet为每个Pod副本创建了一个DNS域名,这个域名的格式为:S(podname).(headless servername),也就意味着服务间是通过Pod域名来通信而非PodIP,因为当Pod所在Node发生故障时,Pod会被飘移到其它 Node上,PodIP会发生变化,但是Pod域名不会有变化
  2. StatefulSet使用Headless服务来控制Pod的域名,这个域名的FQDN为:S(servicename).$(namespace).svc.cluster.local。其中,“cluster.local”指的是集群的域名

headless service这东西有什么用?这是因为statefulset的特性所决定的:在statefulset中是要求有序的,每一个pod的名称必须是固定的。当节点挂了,重建之后的标识符是不变的,每一个节点的节点名称是不能改变的。pod名称是作为pod识别的唯一标识符,必须保证其标识符的稳定并且唯一。为了实现标识符的稳定,这时候就需要一个headless service 解析直达到pod,还需要给pod配置一个唯一的名称。

Pod名称、PVC和PV关系图如下:
StatefulSet控制器 - 图1

Statefulset使用场景

  • 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC 来实现。
  • 稳定的网络标识符,即Pod 重新调度后其iPodName 和 HostName不变。
  • 有序部署,有序扩展,基于init containers 来实现。
  • 有序收缩。

    NFS实例演示

    在一台机器上面安装NFS

    1. yum install -y nfs-common nfs-utils rpcbind
    2. mkdir /data/nfs{0..3}
    3. chmod 755 /data/nfs{0..3}
    4. chown nfsnobody /data/nfs{0..3}
    5. cat >/etc/exports <<EOF
    6. /data/nfs0 *(rw,no_root_squash,no_all_squash,sync)
    7. /data/nfs1 *(rw,no_root_squash,no_all_squash,sync)
    8. /data/nfs2 *(rw,no_root_squash,no_all_squash,sync)
    9. /data/nfs3 *(rw,no_root_squash,no_all_squash,sync)
    10. EOF
    11. systemctl start rpcbind && systemctl enable rpcbind
    12. systemctl start nfs && systemctl enable nfs
  • /data/nfs0:共享的数据目录

  • *:表示任何人都有权限连接,当然也可以是一个网段,一个 IP,也可以是域名
  • rw:读写的权限
  • sync:表示文件同时写入硬盘和内存
  • no_root_squash:当登录 NFS 主机使用共享目录的使用者是 root 时,其权限将被转换成为匿名使用者,通常它的 UID 与 GID,都会变成 nobody 身份

在另一台机器可以使用showmount测试下。

[root@master ~]# showmount -e 192.168.1.61
Export list for 192.168.1.61:
/data/nfs3 *
/data/nfs2 *
/data/nfs1 *
/data/nfs0 *

再实际挂载测试下:

mkdir /nfs{0..3}
mount -t nfs 192.168.1.61:/data/nfs0 /nfs0
date >/nfs0/index.html
umount /nfs0/

没有出现异常的话,说明NFS工作是正常的了。


或者使用docker来实现NFS:

docker run -d --net=host --privileged --name nfs-server katacoda/contained-nfs-server:centos7 /exports/data-0001 /exports/data-0002

这时以/etc/exports这个配置,就相当于在192.168.1.61创建了一个共享目录,分别为:/data/nfs0 , /data/nfs1 /data/nfs2 , /data/nfs3,这几个以下需要使用到。

statefulset实例

创建一个NFS共享的PV,以下配置申明了一个名为pv-volume0的5G空间,挂载的目录为 192.168.1.61:/data/nfs0。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-volume0
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: slow
#  mountOptions:
#    - hard
#    - nfsvers=4.1
  nfs:
    path: /data/nfs0
    server: 192.168.1.61

再创建一个StatefulSet

# 创建一个名为nginx的headless svc,端口80为内部使用,注意跟nodePort的区别
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
# 副本为3的StatefulSet,serviceName名为nginx
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.18.0
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  # volumeClainTemplate作用:当在使用statefulset创建pod时,会自动生成一个PVC,从而请求绑定一个PV,从而有自己专用的存储卷。
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "slow"
      resources:
        requests:
          storage: 1Gi

无注释版本

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.18.0
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "slow"
      resources:
        requests:
          storage: 1Gi

由于StatefulSet是3个副本,但是看到只创建了web-0。在创建web-1时出错了。这是由于没有可用的PV了。所以一个POD是绑定一个PVC的,每个POD所对应的存储空间是不一样的。

[root@master nfs]# kubectl get po -o wide
NAME          READY   STATUS    RESTARTS   AGE     IP          NODE     NOMINATED NODE   READINESS GATES
web-0         1/1     Running   0          3m44s   10.32.0.5   master   <none>           <none>
web-1         0/1     Pending   0          42s     <none>      <none>   <none>           <none>

按上面的创建PV的模板修改一下,再创建2个PV。之后就可以看到正常运行了。

[root@master nfs]# kubectl get po -o wide
NAME          READY   STATUS    RESTARTS   AGE     IP          NODE     NOMINATED NODE   READINESS GATES
web-0         1/1     Running   0          4m2s    10.32.0.5   master   <none>           <none>
web-1         1/1     Running   0          3m56s   10.44.0.5   node1    <none>           <none>
web-2         1/1     Running   0          60s     10.32.0.7   master   <none>           <none>
[root@master nfs]# curl 10.32.0.5
/data/nfs0 ---> web-0
[root@master nfs]# curl 10.44.0.5
/data/nfs1 ---> web-1
[root@master nfs]# curl 10.32.0.7
/data/nfs2 ---> web-2

查看一下PV以及PVC

[root@master nfs]# kubectl get pv
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
pv-volume0   5Gi        RWO            Recycle          Bound    default/www-web-0   slow                    26m
pv-volume1   8Gi        RWO            Retain           Bound    default/www-web-1   slow                    8m10s
pv-volume2   10Gi       RWO            Retain           Bound    default/www-web-2   slow                    7m25s
[root@master nfs]# kubectl get pvc
NAME        STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pv-volume0   5Gi        RWO            slow           11m
www-web-1   Bound    pv-volume1   8Gi        RWO            slow           10m
www-web-2   Bound    pv-volume2   10Gi       RWO            slow           8m3s

删除pvc时,回收策略为Recycle,会自动删除文件,而Retain,会保存文件。

清空PV绑定信息CLAIM

现在所有的PV都有default/www-web-xx,那其他的pvc就无法和pv进行绑定,即使我们删除与之绑定的pvc,状态会变成Released,但是CLAIM信息还在,其他pvc依旧无法进行绑定,因此需要清空PV绑定信息CLAIM

kubectl edit pv pv-volume0

image.png
image.png

statefulset总结

  • 匹配 Pod name ( 网络标识 ) 的模式为:$(statefulset名称)-$(序号),从零开始,比如上面的示例:web-0,web-1, web-2
  • StatefulSet 为每个 Pod 副本创建了一个 DNS 域名,这个域名的格式为: $(podname).(headless server name),也就意味着服务间是通过Pod域名来通信而非 Pod IP,因为当Pod所在Node发生故障时, Pod 会 被飘移到其它 Node 上,Pod IP 会发生变化,但是 Pod 域名不会有变化
  • StatefulSet 使用 Headless 服务来控制 Pod 的域名,这个域名的 FQDN 为:

$(service name).$(namespace).svc.cluster.local

  • 根据 volumeClaimTemplates,为每个 Pod 创建一个 pvc,pvc 的命名规则匹配模式: (volumeClaimTemplates.name)-(pod_name),比如上面的 volumeMounts.name=www, Pod name=web-[0-2],因此创建出来的 PVC 是 www-web-0、www-web-1、www-web-2
  • 删除 Pod 不会删除其 pvc,手动删除 pvc 将自动释放 pv

Statefulset的启停顺序:

  • 有序部署:部署StatefulSet时,如果有多个Pod副本,它们会被顺序地创建(从0到N-1),并且,在下一个 Pod运行之前所有之前的Pod必须都是Running和Ready状态。
  • 有序删除:当Pod被删除时,它们被终止的顺序是从N-1到0。
  • 有序扩展:当对Pod执行扩展操作时,与部署一样,它前面的Pod必须都处于Running和Ready状态。