Statefulset

如果我们需要部署多个 MySQL 实例,就需要用到 StatefulSet。

StatefulSet 是用来管理有状态的应用。一般用于管理数据库、缓存等。

与 Deployment 类似, StatefulSet 用来管理 pod 集合的部署和扩缩。

Deployment 无序(mysql-xxxxxx-xxx mysql-xxxxxx-xxx mysql-xxxxxx- xxx)用来部署无状态应用。StatefulSet(mysql-xxxxxx-1 mysql-xxxxxx-2 mysql-xxxxxx-3)用来有状态应用。

StatefulSet 的核心功能就是通过某种方式记录这些状态,然后在 Pod 被重新创建时能够为新 Pod 恢复这些状态

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 在 Headless Service 的基础上又为 StatefulSet 控制的每个 Pod 副本创建了一个 DNS 域名,这个域名的格式为:

$(podname).(headless server name)

$(podname).(headless server name).namespace.svc.cluster.local

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,其中,“cluster.local” 指的是集群的域名。

什么是 statefulset

在 k8s 中,StatefulSet 是一种用于管理有状态应用的资源对象。与一般的 deployment 不同,StatefulSet 提供了一种有序部署和管理有状态应用的方式。

StatefulSet 主要用于部署需要持久化存储和唯一标识的应用,例如数据库或分布式存储系统。它为每个 Pod 实例分配一个唯一的标识符,并根据这个标识符进行有序的创建、更新和删除操作。

StatefulSet 在创建和删除 Pod 实例时,会按照一定的顺序进行操作,确保每个 Pod 实例的唯一标识符和网络标识符的稳定性。这使得有状态应用可以保持持久化存储的连接,并且具备可靠的网络标识符,以便于应用之间的通信。

StatefulSet 还提供了一些其他的功能,如有序的缩放、滚动更新、有状态服务的 DNS 解析等。它使用有状态服务的特性,提供了高可用性、可伸缩性和可靠性。

总之,StatefulSet 是 k8s 中用于管理有状态应用的一种资源对象,它为有状态应用提供了有序的部署、更新和删除操作,并保证每个 Pod 实例的唯一标识符和网络标识符的稳定性。

statefulset 优点

k8s 中的 StatefulSet 具有以下优点:

  • 稳定的网络标识符:每个 StatefulSet 的 Pod 实例都具有稳定的网络标识符,这使得其他应用可以通过直接使用该标识符来访问和通信。这对于有状态应用非常重要,因为它们通常需要持久的网络标识符来保持连接。
  • 有序的创建和删除:StatefulSet 按照一定的顺序创建和删除 Pod 实例,这确保了应用在启动和关闭时的有序性。这对于有状态应用非常重要,因为它们通常需要按顺序启动和关闭,以避免数据丢失或损坏。
  • 持久化存储:StatefulSet 可以与持久化存储卷(如PersistentVolume)结合使用,以确保数据的持久性和可靠性。每个 Pod 实例都可以使用独立的存储卷,这对于有状态应用非常重要,因为它们通常需要在重新启动后保留数据。
  • 有序的缩放和滚动更新:StatefulSet 支持有序的缩放和滚动更新,可以根据需要增加或减少 Pod 实例的数量,并确保这些操作按顺序进行。这对于有状态应用非常重要,因为它们的缩放和更新通常需要遵循特定的顺序和规则。
  • 服务发现和 DNS 解析:StatefulSet 可以为每个 Pod 实例创建一个稳定的网络 DNS 名称,使得其他应用可以通过该名称来发现和访问它们。这对于有状态应用非常重要,因为它们通常需要通过名称进行服务发现和通信。

总的来说,StatefulSet 在 k8s 中为有状态应用提供了一种可靠、稳定和有序的部署、管理和操作方式,使得有状态应用可以更好地运行和维护。

statefulset 示例

  1. 编辑 yaml 文件nginx-sf.yaml
# headless service,即无头服务,nginx, ClusterIP: None
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
    - port: 80
      name: web
  clusterIP: None
  selector:
    app: nginx
---
# StatefulSet, web, 3个副本pod nginx,自动创建3个pvc - 自动创建3个pv
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.20.1
          ports:
            - containerPort: 80
              name: web
          volumeMounts:
            - name: www
              mountPath: /usr/share/nginx/html
  # 存储卷申请模版,指定了pvc名称,pvc的大小,申请的类型....
  volumeClaimTemplates: # pvc template
    - metadata:
        name: www
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: "local-path"
        resources:
          requests:
            storage: 1Gi

注意:这文件中使用的 storageClassName 为 local-path,是在之前介绍存储类的时候的创建的,如果没有这个 local-path 是无法创建 pvc 的,如果没有,编写 yaml 文件创建 local-path

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-path
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
  1. 执行 yaml 文件
[root@k8s-master k8s]# vi nginx-sf.yaml
[root@k8s-master k8s]# kubectl apply -f nginx-sf.yaml 
service/nginx created
statefulset.apps/web created
  1. 查看 pod 和 pvc
[root@k8s-master k8s]# kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-3050033b-638f-4d03-80d6-be6e270ac02a   1Gi        RWO            local-path     34m
www-web-1   Bound    pvc-e22cd565-5f89-4b57-8be7-c4f648d52214   1Gi        RWO            local-path     9m15s
www-web-2   Bound    pvc-e79aa081-176a-439f-875e-8119c021930e   1Gi        RWO            local-path     8m53s
[root@k8s-master k8s]# kubectl get pod
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          81s
web-1   1/1     Running   0          57s
web-2   1/1     Running   0          48s
  1. pod 和 pvc 创建完毕,名字也是按顺序起的,不是 deployment 那种随机字符串的格式
  2. 进入容器内部,访问首页
[root@k8s-master k8s]# kubectl get pod -o wide
NAME    READY   STATUS    RESTARTS   AGE     IP                NODE        NOMINATED NODE   READINESS GATES
web-0   1/1     Running   0          3m16s   192.169.36.106    k8s-node1   <none>           <none>
web-1   1/1     Running   0          2m52s   192.169.169.161   k8s-node2   <none>           <none>
web-2   1/1     Running   0          2m43s   192.169.36.107    k8s-node1   <none>           <none>
[root@k8s-master k8s]# kubectl exec -it web-0 /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@web-0:/# curl 192.169.36.106:80
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.20.1</center>
</body>
</html>

访问成功,我们创建的没有问题

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

对于有状态的副本集都会用到持久存储,对于分布式系统来讲,它的最大特点是数据是不一样的,所以各个节点不能使用同一存储卷,每个节点有自已的专用存储,但是如果在 Deployment 中的 Pod template 里定义的存储卷,是所有副本集共用一个存储卷,数据是相同的,因为是基于模板来的 ,而 statefulset 中每个 Pod 都要自已的专有存储卷,所以 statefulset 的存储卷就不能再用 Pod 模板来创建了,于是 statefulSet 使用 volumeClaimTemplate,称为卷申请模板,它会为每个 Pod 生成不同的 pvc,并绑定 pv, 从而实现各 pod 有专用存储。这就是为什么要用volumeClaimTemplate 的原因。

稳定的存储:在 StatefulSet 中使用 VolumeClaimTemplate,为每个 Pod 创建持久卷声明(PVC)。 请注意,当 Pod 或者 StatefulSet 被删除时,持久卷声明和关联的持久卷不会被删除。

使用 StatefulSet 创建 pod,按照顺序依次创建。 StatefulSet name 名 - 0/1/2/3/4 依次类推

无论如何删除 pod,再次启动,名称不会发生变化。

动态创建的 pvc 名字也不会发生变化。

在真实的项目中,redis,mysql 持久化存储的 pod 都是使用 StatefulSet 来部署的,创建有状态应用。

无论 pod ip 怎么变化,服务名不变,项目中就可以通过服务名来访问,域名只能在容器内部访问(对应项目启动的 pod)

root@web-0:/# curl web-1.nginx
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.20.1</center>
</body>
</html>
root@web-0:/# curl web-2.nginx.default.svc.cluster.local
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.20.1</center>
</body>
</html>

可以看到,完全可以通过域名来访问

总结

有状态应用 statefulset 无状态应用 deployment

Headless Service 给 deployment 的 pod 分了名字,然后 StatefulSet 将这种名字锁死。

StatefulSet 可以让 pod 的名字不变 + PVC,使得 pod 对应的 volume 不会变,也就是存储了存储状态。

Pod 标识:在具有 N 个副本的 StatefulSet中,每个 Pod 会被分配一个从 0 到 N-1 的整数序号,该序号在此 StatefulSet 上是唯一的。

部署扩缩容前提保证:

  • 对于包含 N 个 副本的 ,当部署 Pod 时,它们是依次创建的,顺序为 0…N-1。
  • 当删除 Pod 时,它们是逆序终止的,顺序为 N-1…0。
  • 在将扩缩操作应用到 Pod 之前,它前面的所有 Pod 必须是 Running 和 Ready 状态。
03-31 00:46