概述

概念

StatefulSet 是为了解决有状态服务的问题(对应 Deployments 和 ReplicaSets 是为无状态服务而设计),其应用场景包括

  • 稳定的持久化存储,即 Pod 重新调度后还是能访问到相同的持久化数据,基于 PVC 来实现
  • 稳定的网络标志,即 Pod 重新调度后其 PodName 和 HostName 不变,基于 Headless Service(即没有 Cluster IP 的 Service)来实现
  • 有序部署,有序扩展,即 Pod 是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依序进行(即从 0 到 N-1,在下一个 Pod 运行之前所有之前的 Pod 必须都是 Running 和 Ready 状态),基于 init containers 来实现
  • 有序收缩,有序删除(即从 N-1 到 0)

从上面的应用场景可以发现,StatefulSet 由以下几个部分组成:

  • 用于定义网络标志(DNS domain)的 Headless Service
  • 用于创建 PersistentVolumes 的 volumeClaimTemplates
  • 定义具体应用的 StatefulSet

StatefulSet 中每个 Pod 的 DNS 格式为 statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local,其中

  • serviceName 为 Headless Service 的名字
  • 0..N-1 为 Pod 所在的序号,从 0 开始到 N-1
  • statefulSetName 为 StatefulSet 的名字
  • namespace 为服务所在的 namespace,Headless Service 和 StatefulSet 必须在相同的 namespace
  • .cluster.local 为 Cluster Domain

限制

  • 给定 Pod 的存储必须由 PersistentVolume Provisioner 根据请求的 storage class 进行配置,或由管理员预先配置。
  • 删除或 scale StatefulSet 将不会删除与 StatefulSet 相关联的 volume。 这样做是为了确保数据安全性,这通常比自动清除所有相关 StatefulSet 资源更有价值。
  • StatefulSets 目前要求 Headless Service 负责 Pod 的网络身份。 您有责任创建此服务。

部署Statefulset服务

首先我们下面使用的是用前面博文中使用到的NFS做动态供给PVC作为持久化存储。那么他的创建步骤这里不在赘述,我们在编排yaml文件中申请即可。

获取动态卷信息

1
2
3
4
5
6
7
8
9
10
11
$ kubectl describe storageclass managed-nfs-storage
Name: managed-nfs-storage
IsDefaultClass: No
Annotations: <none>
Provisioner: fuseim.pri/ifs
Parameters: archiveOnDelete=false
AllowVolumeExpansion: <unset>
MountOptions: <none>
ReclaimPolicy: Delete
VolumeBindingMode: Immediate
Events: <none>

编写service

一个名为 nginx 的 headless service,用于控制网络域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ cat >> statefulset_service.yaml << EOF
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
EOF

# 执行创建
$ kubectl create -f statefulset_service.yaml
service/nginx created

编写statefulset yaml

  • 一个名为 web 的 StatefulSet,它的 Spec 中指定在有 2 个运行 nginx 容器的 Pod。
  • volumeClaimTemplates 使用 PersistentVolume Provisioner 提供的 PersistentVolumes 作为稳定存储。

volumeClaimTemplates: 表示一类PVC的模板,系统会根据Statefulset配置的replicas数量,创建相应数量的PVC。这些PVC除了名字不一样之外其他配置都是一样的。

下面storageClassName配置为我之前通过NFS创建的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ cat >> statefulset.yaml << EOF
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www-disk
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www-disk
annotations:
volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "managed-nfs-storage"
resources:
requests:
storage: 1Gi
EOF

验证服务伸缩性

创建Statefulset服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 执行创建
$ kubectl create -f statefulset.yaml
statefulset.apps/web created

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
pod/nfs-client-provisioner-6c6997c674-z7m9r 1/1 Running 0 18m
pod/web-0 1/1 Running 0 13m
pod/web-1 1/1 Running 0 12m

$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/www-disk-web-0 Bound pvc-a440540e-01dd-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 13m
persistentvolumeclaim/www-disk-web-1 Bound pvc-af5c7006-01dd-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 12m

扩容服务到3个Pod,显示会创建新的云盘卷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ kubectl scale sts web --replicas=3
statefulset.apps/web scaled

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6c6997c674-z7m9r 1/1 Running 0 19m
web-0 1/1 Running 0 14m
web-1 1/1 Running 0 14m
web-2 1/1 Running 0 40s

$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-disk-web-0 Bound pvc-a440540e-01dd-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 14m
www-disk-web-1 Bound pvc-af5c7006-01dd-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 14m
www-disk-web-2 Bound pvc-97afce59-01df-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 38s

缩容服务到1个Pod,显示pvc/pv并不会一同删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ kubectl scale sts web --replicas=1
statefulset.apps/web scaled

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6c6997c674-z7m9r 1/1 Running 0 21m
web-0 1/1 Running 0 16m

$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-disk-web-0 Bound pvc-a440540e-01dd-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 16m
www-disk-web-1 Bound pvc-af5c7006-01dd-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 16m
www-disk-web-2 Bound pvc-97afce59-01df-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 2m

再次扩容到3个Pod,新的pod会复用原来的PVC/PV:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ kubectl scale sts web --replicas=3
statefulset.apps/web scaled

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
pod/nfs-client-provisioner-6c6997c674-z7m9r 1/1 Running 1 46m
pod/web-0 1/1 Running 0 3m
pod/web-1 1/1 Running 0 1m
pod/web-2 1/1 Running 0 18s

$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/www-disk-web-0 Bound pvc-a440540e-01dd-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 41m
persistentvolumeclaim/www-disk-web-1 Bound pvc-af5c7006-01dd-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 40m
persistentvolumeclaim/www-disk-web-2 Bound pvc-97afce59-01df-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 27m

删除一个pod/web0前,Pod引用PVC:www-disk-web-0

1
2
kubectl describe pod/web-0 | grep ClaimName
ClaimName: www-disk-web-0

删除Pod后,重新创建的Pod名字与删除的一致,且使用同一个PVC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ kubectl delete pod/web-0
pod "web-0" deleted

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
pod/nfs-client-provisioner-6c6997c674-z7m9r 1/1 Running 1 49m
pod/web-0 1/1 Running 0 10s
pod/web-1 1/1 Running 0 4m
pod/web-2 1/1 Running 0 3m

$ kubectl describe pod/web-0 | grep ClaimName
ClaimName: www-disk-web-0

$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/www-disk-web-0 Bound pvc-a440540e-01dd-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 44m
persistentvolumeclaim/www-disk-web-1 Bound pvc-af5c7006-01dd-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 43m
persistentvolumeclaim/www-disk-web-2 Bound pvc-97afce59-01df-11e9-b006-00505621dd5b 1Gi RWO managed-nfs-storage 30m

验证服务高可用性

共享持久卷中新建文件

1
2
$ kubectl exec web-1 ls /usr/share/nginx/html
statefulset

删除Pod,验证数据持久性:

1
2
3
4
5
6
7
8
$ kubectl delete pod web-1
pod "web-1" deleted

# 待新的pod创建之后再次检查文件是否还在
$ kubectl exec web-1 ls /usr/share/nginx/html
statefulset

# 以上说明删除pod对pvc没有任何的影响,我们的数据还是保存着的。

数据保存:

上面也说啦, 删除或 scale StatefulSet 将不会删除与 StatefulSet 相关联的 volume。 这样做是为了确保数据安全性,这通常比自动清除所有相关 StatefulSet 资源更有价值。

所以我到NFS Server上面check一下:

可见 我们在pod/web-1上创建的文件还是保存着的。

附:

该yaml为华为云那边的PV申请,以及StatefulSet应用的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
---
# PVC存储申请
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-evs-auto-example
namespace: default
annotations:
volume.beta.kubernetes.io/storage-class: sata
volume.beta.kubernetes.io/storage-provisioner: flexvolume-huawei.com/fuxivol
labels:
failure-domain.beta.kubernetes.io/region: cn-north-1
failure-domain.beta.kubernetes.io/zone: cn-north-1a
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
---
# StatefulSet应用创建
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cce21days-app11-guomaoqiu
namespace: default
spec:
podManagementPolicy: OrderedReady
serviceName: cce21days-app11-guomaoqiu
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: cce21days-app11-guomaoqiu
failure-domain.beta.kubernetes.io/region: cn-north-1
failure-domain.beta.kubernetes.io/zone: cn-north-1a
template:
metadata:
labels:
app: cce21days-app11-guomaoqiu
failure-domain.beta.kubernetes.io/region: cn-north-1
failure-domain.beta.kubernetes.io/zone: cn-north-1a
spec:
affinity: {}
containers:
- image: 100.125.0.198:20202/guomaoqiu/tank:1.0.1
imagePullPolicy: IfNotPresent
name: container-0
resources: {}
volumeMounts:
- mountPath: /tmp
name: pvc-evs-example
dnsPolicy: ClusterFirst
imagePullSecrets:
- name: default-secret
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
volumes:
- name: pvc-evs-example
persistentVolumeClaim:
claimName: pvc-evs-auto-example
updateStrategy:
type: RollingUpdate