34、StatefulSet-控制器

发布时间 2023-03-29 17:40:07作者: 小粉优化大师

1、基础知识

1.1、HTTP状态介绍

我们知道,http协议是无状态的,而http应用是有状态的。
 所谓的无状态指的是 -- 每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无
直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况。也就是说,对于
http协议本身的请求-应答模式来说它是独立的,彼此之间没有关联关系。
 而我们使用的web程序是需要有大量的业务逻辑相互关联才可以实现最终的目标,也就是说基于http协
议的web应用程序是有状态的。只不过这个状态是需要借助于其他的机制来实现,比如 cookies、session、token以及其他辅助的机制。

1.2、如果提高会话的高效机制

session sticky - 根据用户的行为数据,找到上次响应请求的服务器,直接响应
session cluster - 通过服务集群之间的通信机制实现会话数据的同步
session server - 借助于一个专用的服务器来保存会话信息。

1.3、需求场景

生产中,一些中间件业务集群,比如MySQL集群、MongoDB集群、Zookeeper集群等,这些应用集群有如下特点:
 1、每个节点都有固定的身份ID,集群成员通过身份ID进行通信
 2、集群的规模是比较固定的,一般不能随意变动
 3、节点都是由状态的,而且状态数据会做持久化存储
 4、集群中某个节点出现故障,集群功能肯定受到影响。
像这种状态类型的服务,只要过程中存在一点问题,那么影响及范围都是不可预测。
 到目前为止,我们学习到了Pod的管理对象有RC、Rs、Deployment,这三个都是面向无状态的服务,
满足不了上述的有状态集群的场景需求,所以Kubernetes从V1.4版本引入了集群状态管理的功能,V1.5版本更名为StatefulSet[有状态应用副本集]。 

1.4、原理解析

1.4.1、简介

StatefulSets 旨在与有状态的应用及分布式系统一起使用。然而在 Kubernetes 上管理有状态应
用和分布式系统是一个宽泛而复杂的话题。StatefulSet有以下特点(使用场景):
 - 每个Pod都有稳定、唯一的网络标识,彼此间可以通信
 - StatefulSet控制的Pod副本启动、扩展、删除、更新等操作都是有顺序的。
 - StatefulSet里的Pod采用独立的持久化存储卷,存储状态数据

1.4.2、Stateful流程图

 

Headless service会给一个StatufulSet控制的Pod提供一个唯一的DNS域名来作为每个成员的网络标识,集群内部成员之间使用域名通信。
DNS域名格式:
 $(podname).$(headless service name).$(namespace name).svc.cluster.local

1.5、StatefulSet组件

1.5.1、headless service

自启动/重启pod名称是随机的,而为了statefulset特性一,所以借用
headless service通过唯一的"网络标识"来直接指定的pod应用,所以
它要求我们的dns环境是完好的。
当一个StatefulSet挂掉,新创建的StatefulSet会被赋予跟原来的Pod
一样的名字,通过这个名字来匹配到原来的存储,实现了状态保存。

1.5.2、volumeClaimTemplate

自启动/重启pod的存储是共享的,但是集群中的副本数据是不一样的
(例:redis),如果用共享存储的话,会导致多副本间的数据被覆盖,
为了statefulsed的特性三,我们需要将pod和其申请的数据卷隔离
开,每一种pod都有其对应的数据卷配置模板,来满足该要求。

1.6、不同的集群,其内部的状态机制是完不同

1.6.1、MySQL主从集群

当我向当前数据库集群添加从角色节点的时候,可不仅仅为添加一个唯一的节点标识
及对应的后端存储就完了。我们要提前知道,从角色节点的时间、数据复制的起始位
置(日志文件名、日志位置、时间戳等),然后才可以进行数据的同步。

1.6.2、Redis主从集群

集群中,添加节点的时候,会自动根据slaveof设定的主角色节点上获取最新的数据,
然后直接在本地还原,然后借助于软件专用的机制进行数据的同步机制。

1.6.3、总结

StatefulSet本身的代码无法一一的考虑到所有的集群状态机制,这也是,为什么早期的k8s只能运行
无状态的应用,为了实现所谓的状态集群效果,只能将所有的无状态服务独立管理,然后以自建EndPoint或
者ExternalName的方式引入到k8s集群中,实现所谓的类似状态效果 -- 而这种方法仍然在很多企业中使
用。
StatefulSet由于其特殊性,所以它只能应用于通用的状态管理机制,或者被封装成各种应用程序专用的
exporter,以便于帮助相关企业进行使用k8s,好事者(软件维护者)将这些做好的状态管理工具放到了
awsomes-operators。所以我们如果涉及到一些状态集群场景的话,最好直接使用他们提供好的工具,不要
自己从0开始自己写。
参考资料:
 https://github.com/operator-framework/awesome-operators

1.7、属性解析

apiVersion: apps/v1              # API群组及版本;
kind: StatefulSet                # 资源类型的特有标识
metadata:
  name <string>                   # 资源名称,在作用域中要唯一
  namespace <string>              # 名称空间;StatefulSet隶属名称空间级别
spec:
  replicas <integer>              # 期望的Pod副本数,默认为1
  selector <object>               # 标签选择器,须匹配Pod模板中的标签,必选字段
  template <object>               # Pod模板对象,必选字段
  revisionHistoryLimit <integer>  # 滚动更新历史记录数量,默认为10
  updateStrategy <Object>         # 滚动更新策略
    type <string>                 # 滚动更新类型,可用值有OnDelete和Rollingupdate
    rollingUpdate <Object>        # 滚动更新参数,专用于RollingUpdate类型
      partition <integer>         # 分区指示索引值,默认为0,一般用于版本分区域更新场景
  serviceName <string>            # 相关的Headless Service的名称,必选字段
  volumeClaimTemplates <[]Object> # 存储卷申请模板
    apiVersion <string>           # PVC资源所属的API群组及版本,可省略
    kind <string>                 # PVC资源类型标识,可省略
    metadata <Object>             # 卷申请模板元数据
    spec <Object>                 # 期望的状态,可用字段同PVC
 podManagementPolicy <string> # Pod管理策略,默认的“OrderedReady”表示顺序创建并逆序删除,另一可用值“Parallel”表示并行模式

2、PV & StatefulSet-实践

2.1、NFS安装与配置

2.1.1、安装NFS

2.1.2、配置共享目录

mkdir /nfs-data/v{1,2,3,4} -p
cat >/etc/exports<<'EOF'
/nfs-data/v1 *(rw,no_root_squash,sync)
/nfs-data/v2 *(rw,no_root_squash,sync)
/nfs-data/v3 *(rw,no_root_squash,sync)
/nfs-data/v4 *(rw,no_root_squash,sync)
EOF

注意:
    *:表示允许所有连接,该值可以是一个网段|IP|域名的形式
    rw 表示读写共享权限
    no_root_squash 表示root登录nfs资源的时候,虽然是以nobody身份,单不压缩其权限
    sync 所有操作数据会同时写入硬盘和内存


# 重启服务
systemctl restart nfs

#命令验证是否正常
register ]# exportfs 
/nfs-data/v1    <world>
/nfs-data/v2    <world>
/nfs-data/v3    <world>
/nfs-data/v4    <world>

#查询可以挂载的目录
register]# showmount -e 192.168.10.33
Export list for 192.168.10.33:
/nfs-data/v4 *
/nfs-data/v3 *
/nfs-data/v2 *
/nfs-data/v1 *

# 注意:如果客户端想要使用nfs的功能,必须提前安装软件和启动服务,否则会发生找不到资源的报错。

2.2、创建pv资源

2.2.1、定义资源配置清单

cat >statefulset-pv.yml<<'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
spec:
  nfs:
    path: /nfs-data/v1
    server: 192.168.10.33
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv002
spec:
  nfs:
    path: /nfs-data/v2
    server: 192.168.10.33
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv003
spec:
  nfs:
    path: /nfs-data/v3
    server: 192.168.10.33
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv004
spec:
  nfs:
    path: /nfs-data/v4
    server: 192.168.10.33
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 2Gi
EOF

2.2.2、应用资源配置清单

[root@master1 stateful-test]# kubectl apply -f statefulset-pv.yml 
persistentvolume/pv001 created
persistentvolume/pv002 created
persistentvolume/pv003 created
persistentvolume/pv004 created
[root@master1 stateful-test]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv001   1Gi        RWO            Retain           Available                                   7s
pv002   1Gi        RWO            Retain           Available                                   7s
pv003   2Gi        RWO            Retain           Available                                   7s
pv004   2Gi        RWO            Retain           Available                                   7s

2.3、创建pod和service资源

2.3.1、定义资源配置清单

cat >statefulset-service.yml<<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: statefulset-headless
spec:
  ports: 
  - port: 80
  clusterIP: None
  selector:
    app: myapp-pod
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myapp
spec:
  serviceName: statefulset-headless
  replicas: 3
  selector:
    matchLabels:
      app: myapp-pod
  template:
    metadata:
      labels:
        app: myapp-pod
    spec:
      containers:
      - name: myapp
        image: 192.168.10.33:80/k8s/my_nginx:v1
        volumeMounts:
        - name: myappdata
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: myappdata
    spec:
      accessModes: [ "ReadWriteOnce" ]  
      resources:
        requests:
          storage: 1Gi
EOF

2.3.2、应用资源配置清单

master1 ]# kubectl apply -f statefulset-service.yml && kubectl get pod -w
service/statefulset-headless created
statefulset.apps/myapp created
NAME      READY   STATUS              RESTARTS   AGE
myapp-0   0/1     ContainerCreating   0          0s
myapp-0   1/1     Running             0          1s
myapp-1   0/1     Pending             0          0s
myapp-1   0/1     Pending             0          0s
myapp-1   0/1     Pending             0          2s
myapp-1   0/1     ContainerCreating   0          2s
myapp-1   1/1     Running             0          3s
myapp-2   0/1     Pending             0          0s
myapp-2   0/1     Pending             0          0s
myapp-2   0/1     Pending             0          2s
myapp-2   0/1     ContainerCreating   0          2s
myapp-2   1/1     Running             0          3s

master1 ]# kubectl describe statefulsets.apps myapp
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 14s statefulset-controller create Pod myapp-0 in StatefulSet myapp successful
Normal SuccessfulCreate 12s statefulset-controller create Pod myapp-1 in StatefulSet myapp successful
Normal SuccessfulCreate 11s statefulset-controller create Pod myapp-2 in StatefulSet myapp successful
总结:
    所有的资源对象(pod+pv)都是按照顺序创建的,而且每个pv都有自己独有的标识符

2.3.3、删除资源观察总结

master1 ]# kubect delete -f statefulset-service.yml && kubectl get pod -w
service "statefulset-headless" deleted
statefulset.apps "myapp" deleted
NAME      READY   STATUS        RESTARTS   AGE
myapp-0   1/1     Terminating   0          2m30s
myapp-1   1/1     Terminating   0          2m28s
myapp-2   1/1     Terminating   0          2m27s

关闭的时候他们是有先后顺序的,只不过因为容器所在结点是不一样的,所以删除的时间有些偏差
具体的效果我们可以在缩容扩容的实践中看到详细的效果

2.3.4、检查数据是否持久化到硬盘

master1 ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                       STORAGECLASS   REASON   AGE
pv001   1Gi        RWO            Retain           Bound       default/myappdata-myapp-0                           120m
pv002   1Gi        RWO            Retain           Bound       default/myappdata-myapp-1                           120m
pv003   2Gi        RWO            Retain           Bound       default/myappdata-myapp-2                           120m
pv004   2Gi        RWO            Retain           Available                                                       120m

master1 ~]# kubectl get pvc
NAME                STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
myappdata-myapp-0   Bound    pv001    1Gi        RWO                           114m
myappdata-myapp-1   Bound    pv002    1Gi        RWO                           98m
myappdata-myapp-2   Bound    pv003    2Gi        RWO                           98m


pod的删除,不影响pv和pvc的效果,说明pod的状态数据没有丢失,而且pvc指定的名称不变,只要是同一个 statufulset创建的pod,
会自动找到根据指定的pvc找到具体的pv,pvc 的名称是 <VCT_name>-<POD_name>的组合,所以pod可以直接找到绑定的pvc。

2.3.5、使用busybox-检查pod的域名解析

master1 ]# kubectl get pods -o wide
NAME      READY   STATUS    RESTARTS   AGE   IP             NODE    NOMINATED NODE   READINESS GATES
client    1/1     Running   0          65s   10.244.3.23    node1   <none>           <none>
myapp-0   1/1     Running   0          8s    10.244.3.24    node1   <none>           <none>
myapp-1   1/1     Running   0          6s    10.244.4.107   node2   <none>           <none>
myapp-2   1/1     Running   0          5s    10.244.3.25    node1   <none>           <none>


master1 ~]# kubectl run -it client --image=192.168.10.33:80/k8s/busybox:latest --rm /bin/sh
# DNS反向解析
/ # nslookup 10.244.3.24 
Server:         10.96.0.10
Address:        10.96.0.10:53

24.3.244.10.in-addr.arpa        name = myapp-0.statefulset-headless.default.svc.cluster.local

# DNS正向解析
/ # nslookup -query=A myapp-0.statefulset-headless.default.svc.cluster.local
Server:         10.96.0.10
Address:        10.96.0.10:53

Name:   myapp-0.statefulset-headless.default.svc.cluster.local
Address: 10.244.3.24


注意:
    自己可以直接解析自己的pod名称,解析其他pod的名称必须携带其无头服务的完整名称
    完整名称格式:myapp-1.statefulset-headless.default.svc.cluster.local
               <pod_name>.<headless_name>.<ns_name>.svc.cluster.local