前言
当我们构建一个有状态的应用的时候, 例如 mysql / ftp / 包含session的管理界面等. 我们可能会有下列预期:
- 稳定的、唯一的网络标识符。
- 稳定的、持久的存储。
- 有序的、优雅的部署和缩放。
- 有序的、优雅的删除和终止。
- 有序的、自动的滚动更新。
StatefulSet 就是干这个的。不过在实际的项目中,其实我们还是很少会去直接通过 StatefulSet 来部署我们的有状态服务的,除非你自己能够完全能够 hold 住,对于一些特定的服务,我们可能会使用更加高级的 Operator 来部署,比如 etcd-operator、prometheus-operator 等等,这些应用都能够很好的来管理有状态的服务,而不是单纯的使用一个 StatefulSet 来部署一个 Pod 就行,因为对于有状态的应用最重要的还是数据恢复、故障转移等等。
例子
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:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: k8s.gcr.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
- svc对象必须先创建
- StatefulSet.spec.serviceName 设定 svc 名称
- StatefulSet.spec.volumeClaimTemplates PVC模板,可以自动创建一个PVC.
- 如果没有空闲的PV或者没有StorageClass,则需要先提供。
特点
- Pod 标识:拥有唯一的有序索引和稳定的网络身份。pod 被部署的时候其名字是按照
<statefulset.name>-<index.num>
创建的,例如(web-0, web-1)。这个名字就是他们的hostname,且不管 pod 如何调度,都永不变。 - Service 必须是 headless。StatefulSet 使用 headless 结合Pod标识来构建 Pod 子域名。其Pod子域名格式:
$(statefulset.name)-$(index.name).$(svc.name).$(ns.name).svc.cluster.local
- pod 不管怎么删, pod与pvc之间的关系都不会混乱.
- pod 之间可以通过固定的Pod子域名互相访问.
- 启动 pod 的时候, 按照索引, 从 0 开始. 一个一个启动. 不会同时启动多个. (这是默认行为)
- 删除 pod 的时候, 按照索引倒序删除, 从最后一个开始, 一个一个删除. 不会同时删除多个. (这是默认行为)
- 删除 pod 的时候, pvc 和 pv 并不会删除。且PVC的名称也是有序的。$(VCT.name)-$(statefulset.name)-${index.num}
你可以通过设置statefulset管理策略, 来改变上述默认行为
https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set/#pod-management-policy
StatefulSet.spec.podManagementPolicy
设定为:Parallel 。就可以并发的创建Pod和删除Pod。默认这个值是:OrderedReady
扩展/缩放
扩展: kubectl scale sts web --replicas=5
缩放: kubectl patch sts web -p '{"spec":{"replicas":3}}'
当然你也可以通过修改配置文件里 replicas 的值来进行变更.
更新
有状态应用的更新和无状态应用的更新有些差异.
有状态应用的更新策略(spec.updateStrategy)是 RollingUpdate 和 OnDelete. 其中 RollingUpdate 是默认策略, 这个是自动滚动更新. 而OnDelete的意思是只有你手动删除了pod才会更新.
更新是针对pod模板的. 例如:
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"gcr.io/google_containers/nginx-slim:0.8"}]'
这里更新了nginx的镜像.
更新遵循以下原则:
-
所有的 pod 需要是就绪状态.
-
更新采用索引倒序进行(即从索引最后一个号码开始更新), 并且是一个一个的更新.
-
当更新失败的时候, 已经收到更新的pod将保持更新后的版本, 没有开始更新的pod将恢复到更新前的版本.
🌟需要注意的是, 如果你的更新文件(二进制文件故障或者配置文件)有问题从而导致更新失败, 你可能需要强制回滚. 以下是说明信息:
即你恢复了更新前的模板,却发现statefulset依然不正常, 则你需要手动删除所有由错误模板产出的pod.
在这之后, statefulset将会采用更新前的模板重建pod.
(https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#forced-rollback)
阶段更新(staging an update)
有时候我们可能并不想一次更新所有, 此时可以进行阶段更新.
阶段更新的意思就是通过在索引上设置一个点. 当pod的索引大于等于这个点的时候, 才会更新. (默认点是索引0,即所有Pod都更新)
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
这个意思是设置索引3为阶段分割点.
如果你想回到默认更新, 则只需要调整分割点, 重新执行上述命令即可.
删除
删除分为联级删除和非联级删除.
联级删除就是 statefulset 和 pod 都删除. 这是默认行为.
非联级删除, 只会删除 statefulset. 你可以通过删除的时候附加--cascade=false
开启它.