StatefulSet资源控制器


一、statefulset介绍

什么是Statefulset?

StatefulSet 是为了解决有状态服务的问题而设计的资源控制器。StatefulSet可以管理有状态服务。 前面我们讲到了Deployment、DaemonSet都只适合用来跑无状态的服务pod,那么这里的StatefulSet(简写sts)就是用来跑有状态服务pod的。

那怎么理解有状态服务和无状态服务呢?

  • 无状态服务:

    • 最典型的是WEB服务器的每次http请求,它的每次请求都是全新的,和之前的没有关系;
    • pod都是随机名称,IP每次发生重启也是变化的;
    • 当一个pod因为某些原来被删除掉的时候,K8s会启动一个新的pod来代替它;
    • 无状态服务因为相互之前都是独立的,很适合用横向扩充来增加服务的资源量。
  • 有状态服务:

    • 用网游服务器来举例比较恰当了,每个用户的登陆请求,服务端都是先根据这个用户之前注册过的帐号密码等信息来判断这次登陆请求是否正常;
    • 在K8s上运行的有状态服务的pod,都会被给予一个独立的固定名称;
    • 当有状态服务的pod删除时,K8s会启动一个和先前一模一样名称的pod来代替它。

StatefulSet的特点

  • 创建的每个pod都有自己的唯一标识,故障时,它只能被拥有同一个标识的新实例所取代。
  • 如果有必要,可以为每个pod配置专用的存储卷,且只能是PVC格式。通过pvc模板来为每个pod创建专用PV。

StatefulSet是由什么组成的

  • Headless Service:用来定义pod网路标识,生成可解析的DNS记录
  • StatefulSet:管理pod的
  • volumeClaimTemplates:存储卷申请模板,创建pvc,指定pvc名称大小,自动创建pvc,且pvc由存储类供应。

什么是Headless service

Headless service不分配clusterIP,headless service可以通过解析service的DNS,返回所有Pod的dns和ip地址 (statefulSet部署的Pod才有DNS),普通的service,只能通过解析service的DNS返回service的ClusterIP。

为什么要用headless service

在使用Deployment时,创建的Pod名称是没有顺序的,是随机字符串,在用Statefulset管理pod时要求pod名称必须是有序的 ,每一个pod不能被随意取代,pod重建后pod名称还是一样的。因为pod IP是变化的,所以要用Pod名称来识别。pod名称是pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称。

headless service 会为 service 分配一个域名

<service name>.$<namespace name>.svc.cluster.local

K8s中资源的全局FQDN格式:
  Service_NAME.NameSpace_NAME.Domain.LTD.
  Domain.LTD.=svc.cluster.local.     #这是默认k8s集群的域名。

FQDN 全称 Fully Qualified Domain Name
即全限定域名:同时带有主机名和域名的名称
FQDN = Hostname + DomainName
如 主机名是 Darius
域名是 baidu.com
FQDN= Darius.baidu.com

匹配 Pod name ( 网络标识 ) 的模式为:(statefulset名称)-(序号),比如下面的示例:web-0,web-1,web-2

141.png

StatefulSet 为每个 Pod 副本创建了一个 DNS 域名,这个域名的格式为: $(podname).(headless server name),也就意味着服务间是通过Pod域名来通信而非 Pod IP,因为当Pod所在Node发生故障时, Pod 会被飘移到其它 Node 上,Pod IP 会发生变化,但是 Pod 域名不会有变化

删除 web-0 后查看:

142.png

StatefulSet 使用 Headless 服务来控制 Pod 的域名,这个域名的 FQDN 为:(service name).(namespace).svc.cluster.local,其中,“cluster.local” 指的是集群的域名

143.png

根据 volumeClaimTemplates,为每个 Pod 创建一个 pvc,pvc 的命名规则匹配模式:(volumeClaimTemplates.name)-(pod_name),比如上面的 volumeMounts.name=www, Podname=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状态。

三、StatefulSet使用场景

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

四、实战

有状态服务sts比较常见的mongo复制集 ,redis cluster,rabbitmq cluster等等,这些服务基本都会用StatefulSet模式来运行,当然除了这个,它们内部集群的关系还需要一系列脚本或controller来维系它们间的状态。

1、在一台服务器上安装 NFS 服务器
# CentOS7.9 服务端节点安装:
yum -y install nfs-utils

# Ubuntu22.04 服务端节点安装:
# apt-get -y install nfs-kernel-server

mkdir -p /data/nfs-{1..2}

# 编辑/etc/exports,配置nfs服务
vim /etc/exports
/data/nfs-1 *(rw,no_root_squash,no_all_squash,sync)
/data/nfs-2 *(rw,no_root_squash,no_all_squash,sync)

# rw 表示客户端对共享的文件系统有读写权限。
# no_root_squash 表示在客户端使用 root 权限时,其权限不会被映射到匿名用户,root 拥有对共享目录的完全访问权限。
# no_all_squash 表示在客户端连接时,所有用户在 NFS 共享目录中的权限都将被保留。
# sync 表示 NFS 服务器将在写入完成之前等待数据被存储到磁盘上。

systemctl enable nfs-server
systemctl restart nfs-server

# Ubuntu重启服务
systemctl restart rpcbind.service
systemctl restart nfs-kernel-server.service 
systemctl restart nfs-utils.service 
systemctl restart nfs-server.service 
# 增加NFS-SERVER开机自启动
systemctl enable nfs-server.service 

# CentOS7.9 客户端节点:
# 客户端模拟挂载[所有k8s的节点都需要安装客户端]
# centos
yum install nfs-utils
# 或者(ubuntu)
# apt-get install nfs-common

systemctl enable nfs-server
systemctl restart nfs-server
# 查看nfs节点
showmount -e 192.168.8.128
        Export list for 192.168.8.128:
        /data/nfs-2 *
        /data/nfs-1 *


# 2、创建pv
# -------------------------------------------
root@node1:~# vim web-pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: web-pv0
  labels:
    type: web-pv0
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: my-storage-class
  nfs:
    path: /data/nfs-1
    server: 192.168.8.128
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: web-pv1
  labels:
    type: web-pv1
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: my-storage-class
  nfs:
    path: /data/nfs-2
    server: 192.168.8.128
kubectl apply -f web-pv.yaml
# 3、创建pvc
# -------------------------------------------
# 如果创建的PVC的名称和StatefulSet中的名称没有对应上,那么StatefulSet中的Pod就肯定创建不成功.

# 我们在这里创建了一个叫做www-web-0和www-web-1的PVC,
# 这个yaml里并没有提到PV的名字,所以PV和PVC是怎么bound起来的呢?
# 是通过labels标签下的key:value键值对来进行匹配的,
# 我们在创建PV时指定了label的键值对,在PVC里通过selector可以指定label。

# 然后再回到这个PVC的名称定义:www-web-0
# 首先我们看到StatefulSet的name叫web,设置的replicas为2个
# volumeMounts和volumeClaimTemplates的name必须相同,为www
# 所以StatefulSet创建的第一个Pod的name应该为web-0,第二个为web-1。
# 这里StatefulSet中的Pod与PVC之间的绑定关系是通过名称来匹配的,即:
# PVC_name  =  volumeClaimTemplates_name + "-" + pod_name
# www-web-0     =       www               + "-" +   web-0
# www-web-1     =       www               + "-" +   web-1

root@node1:~# vim web-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: www-web-0
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: my-storage-class
  selector:
    matchLabels:
      type: web-pv0
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: www-web-1
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: my-storage-class
  selector:
    matchLabels:
      type: web-pv1
kubectl apply -f web-pvc.yaml 
# 3、创建Service 和 StatefulSet
# -------------------------------------------
# 在上一步中我们已经创建了名为www-web-0的PVC了,接下来创建一个service和statefulset,
# service的名称可以随意取,但是statefulset的名称已经定死了,为web,
# 并且statefulset中的volumeClaimTemplates_name必须为www,volumeMounts_name也必须为www。
# 只有这样,statefulset中的pod才能通过命名来匹配到PVC,否则会创建失败。

root@node1:~# vim web.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-headless
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---

apiVersion: v1
kind: Service
metadata:
  name: web
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  selector:
    app: nginx

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # has to match .spec.template.metadata.labels
  serviceName: "web-headless"
  replicas: 2 # by default is 1
  template:
    metadata:
      labels:
        app: nginx # has to match .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: nginx:1.21.6
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi
# 创建web应用
kubectl apply -f web.yaml
# 查看pv,pvc,pod的创建效果
kubectl get pv,pvc,pod

image-20240519093319490

# 运行一个busybox容器来测试下dns解析
kubectl run -it --rm busybox --image=registry.cn-shanghai.aliyuncs.com/acs/busybox:v1.29.2 -- sh

wget web-0.web-headless.default

wget web-1.web-headless.default

image-20240519094255033

访问web-1.web-headless.default显示403,原因是/nfs-2目录为空,没有index.html文件

五、StatefulSet配置规范

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”表示并行模式

六、 StatefulSet扩容和缩容

扩容

当我们需要增加StatefulSet的Pod数量时,可以使用以下命令:

kubectl scale statefulset <statefulset-name> --replicas=<new-replica-count>

例如,如果我们要将名为web的StatefulSet的副本数量增加到5个,可以运行以下命令:

kubectl scale statefulset web --replicas=5

Kubernetes会按照以下顺序执行扩容的过程:

创建新的Pod

StatefulSet会自动为新增的Pod分配一个唯一的网络标识符,并按照一定的顺序启动它。新的Pod将在Headless Service中注册,并成为Endpoints列表的一部分。

检查状态

StatefulSet会检查新的Pod的就绪状态,以确保它已经准备好为服务提供服务。

更新状态

StatefulSet会更新它的状态,将新增的Pod的信息添加到Pod列表中,并更新副本数量。

缩容

当我们需要减少StatefulSet的Pod数量时,可以使用以下命令:

kubectl scale statefulset <statefulset-name> --replicas=<new-replica-count>

例如,如果我们要将名为web的StatefulSet的副本数量减少到2个,可以运行以下命令:

kubectl scale statefulset web --replicas=2

Kubernetes会按照以下顺序执行缩容的过程:

选择要删除的Pod

StatefulSet会选择要删除的Pod。通常情况下,StatefulSet会选择最后启动的Pod进行删除,但可以使用Pod的标识符来控制删除的顺序。

清除服务发现信息

StatefulSet会从Headless Service的Endpoints列表中移除要删除的Pod的IP地址和端口信息。

删除Pod

Kubernetes会停止要删除的Pod容器中的进程,并卸载磁盘卷。

删除磁盘卷

如果要删除的Pod中使用了磁盘卷,StatefulSet会在删除Pod后删除这些磁盘卷。

更新状态

StatefulSet会检查删除操作是否成功,并将删除的Pod从Pod列表中删除。最后,它会更新副本数量。

删除STS步骤

kubectl delete sts web    # 删除sts后pod会被自动被删除
kubectl delete pvc --all  # 删除所有的pvc
kubectl delete pv  --all  # 最后删除所有pv