目录
- 概述
- 创建StatefulSet
- Pod 的管理策略
- 扩容缩容
- 镜像更新
- 滚动更新
- 分区更新
- 删除更新
- 删除
概述
Stateful 翻译为 有状态的
,也是pod控制器的一种。
StatefulSet 是为了解决有状态应用
的问题,Deployment是为无状态应用
设计的。
-
无状态应用:网络可能会变(IP 地址)、存储可能会变(卷)、顺序可能会变(启动的顺序)。应用场景:业务代码,如:使用 SpringBoot 开发的商城应用的业务代码。
-
有状态应用:网络不变、存储不变、顺序不变。应用场景:中间件,如:MySQL 集群、Zookeeper 集群、Redis 集群、MQ 集群。
Stateful 具有以下特点:
- 稳定的持久化存储,即 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 解析条目,并且每个 Pod 都是唯一的。
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 Servic 和 StatefulSet 必须在相同的 namespace
- .cluster.local 为 Cluster Domain
我们可以通过这个dns域名直接访问,也可以省略的使用statefulSetName-{0…N-1}.serviceName就可以访问,后面我们会验证。
注意事项:
- kubernetes v1.5 版本以上才支持
- 给定 Pod 的存储必须由 PersistentVolume 驱动 基于所请求的 storage class 来提供,或者由管理员预先提供。
- 删除或者收缩 StatefulSet 并不会删除它关联的存储卷。 这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。
- StatefulSet 需要一个 Headless Service 来定义 DNS domain,需要在 StatefulSet 之前创建好。
- 当删除 StatefulSets 时,StatefulSet 不提供任何终止 Pod 的保证。 为了实现 StatefulSet 中的 Pod 可以有序地且体面地终止,可以在删除之前将 StatefulSet 缩放为 0。
- 在默认
Pod 管理策略
(OrderedReady) 时使用 滚动更新,可能进入需要人工干预才能修复的损坏状态。
创建StatefulSet
一般我们还是通过yaml文件来创建StatefulSet。
创建nginx-sts.yaml文件,内容如下:
---
# 在yaml中,---中间的内容,属于嵌套配置,这里用于将 StatefulSet 加入网络
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None # 不分配 ClusterIP ,形成无头服务,整个集群的 Pod 能直接访问,但是浏览器不可以访问。
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: nginx # 使用哪个service来管理dns,,这个字段需要和 service 的 metadata.name 相同
replicas: 2
selector: # 选择器,用于定义管理的指定资源的标签(Service和Pod)
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports: # 容器内部暴漏的端口
- containerPort: 80 # 具体的端口
name: web # 端口的名字
volumeMounts: # 数据卷挂载
- name: www # 数据卷名字
mountPath: /usr/share/nginx/html # 加载到容器内的哪个目录
volumeClaimTemplates: # 数据卷模板
- metadata:
name: www # 数据卷名称
annotations: # 注解信息
volume.alpha.kubernetes.io/storage-class: anything
spec: # 数据卷配置
accessModes: [ "ReadWriteOnce" ] # 访问模式
resources: # 资源配置
requests:
storage: 1Gi
然后执行创建命令:
kubectl create -f nginx-sts.yaml
# 结果如下,service和statefulset都创建出来了
service/nginx created
statefulset.apps/web created
查看service:
kubectl get svc
# 结果如下,第一个是默认的,不用管,第二个ngin使我们创建出来的
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9d
nginx ClusterIP None <none> 80/TCP 103s
查看statefulset:
kubectl get sts
# 结果如下
NAME READY AGE
web 2/2 3m5s
查看存储卷PersistentVolumes:
kubectl get pvc
# 结果如下
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-bdd3c8f0-8a9d-4c3c-ac3e-2fe6701d6c01 1Gi RWO hostpath 3m59s
www-web-1 Bound pvc-54a1dab2-59ea-4611-9d73-312789c3d5f7 1Gi RWO hostpath 3m56s
查看po:
kubectl get po
# 结果如下
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 7m29s
web-1 1/1 Running 0 7m26s
可以看到pod的名字后缀和deployment不一样,这里是数字,deployment是一串字符串。
接着我们新创建一个容器,看看能否在新容器中通过name访问到这个容器。
# busybox是一个linux的工具包镜像;--restart=Never表示不重启, --rm:运行完的时候删除
kubectl run -it --image busybox dns-test --restart=Never --rm /bin/sh
# 成功后进入容器的终端控制台
If you don't see a command prompt, try pressing enter.
/ #
我们在容器内ping下刚刚创建的web-0 pod:
# web-0是pod的名字,nginx是svc的名字
ping web-0.nginx
# 结果如下
PING web-0.nginx (10.1.0.80): 56 data bytes
64 bytes from 10.1.0.80: seq=0 ttl=64 time=0.601 ms
64 bytes from 10.1.0.80: seq=1 ttl=64 time=0.190 ms
64 bytes from 10.1.0.80: seq=2 ttl=64 time=0.325 ms
64 bytes from 10.1.0.80: seq=3 ttl=64 time=0.294 ms
同样的,pod web-1也是可以ping通的。
这样就证明了service为pod提供了稳定网络,固定的dns,可以通过dns直接访问。
Pod 的管理策略
StatefulSet 的 Pod 的管理策略(podManagementPolicy)分为:
- OrderedReady(有序启动,默认值):也就是pod的创建是按照顺序来的,启动后,我们看到后缀数字就是其顺序。
- Parallel(并发一起启动):pod的创建没有顺序。
配置如下:
...
spec:
podManagementPolicy: OrderedReady # 控制 Pod 创建、升级以及扩缩容逻辑。Parallel(并发一起启动) 和
...
一般我们不用去修改。
扩容缩容
与deployment一样,statefulSet也可以实现扩容缩容。也是通过修改我们的副本数即可。
方式也是一样,这里我们再回顾一下:
- 使用 K
ubectl edit
命令,修改spec:replicas:n
即可
kubectl edit sts web
- 使用
scale
命令实现扩缩容
kubectl scale sts web --replicas=5
- 修改 yaml 文件中 spec.replicas 字段,随后使用
kubectl apply -f xxx.yaml
命令即可
vim nginx-sts.yaml
kubectl apply -f nginx-sts.yaml
- 使用patch命令
kubectl patch statefulset web -p '{"spec":{"replicas":3}}'
示例:
使用下面命令扩容:
kubectl scale sts web --replicas=5
# statefulset.apps/web scaled
查看statefulset:
kubectl get sts
# 结果如下
NAME READY AGE
web 5/5 37m
可以看到结果变成了5个。
通过describe查看下Events信息:
kubectl describe sts web
结果如下:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 38m statefulset-controller create Claim www-web-0 Pod web-0 in StatefulSet web success
Normal SuccessfulCreate 38m statefulset-controller create Pod web-0 in StatefulSet web successful
Normal SuccessfulCreate 37m statefulset-controller create Claim www-web-1 Pod web-1 in StatefulSet web success
Normal SuccessfulCreate 37m statefulset-controller create Pod web-1 in StatefulSet web successful
Normal SuccessfulCreate 91s statefulset-controller create Claim www-web-2 Pod web-2 in StatefulSet web success
Normal SuccessfulCreate 91s statefulset-controller create Pod web-2 in StatefulSet web successful
Normal SuccessfulCreate 88s statefulset-controller create Claim www-web-3 Pod web-3 in StatefulSet web success
Normal SuccessfulCreate 88s statefulset-controller create Pod web-3 in StatefulSet web successful
Normal SuccessfulCreate 85s statefulset-controller create Claim www-web-4 Pod web-4 in StatefulSet web success
Normal SuccessfulCreate 85s statefulset-controller create Pod web-4 in StatefulSet web successful
可以看到pod后缀依次从0到4,共5个。
再进行缩容看下:
kubectl scale sts web --replicas=3
# statefulset.apps/web scaled
直接查看Events:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 41m statefulset-controller create Claim www-web-0 Pod web-0 in StatefulSet web success
Normal SuccessfulCreate 41m statefulset-controller create Pod web-0 in StatefulSet web successful
Normal SuccessfulCreate 41m statefulset-controller create Claim www-web-1 Pod web-1 in StatefulSet web success
Normal SuccessfulCreate 41m statefulset-controller create Pod web-1 in StatefulSet web successful
Normal SuccessfulCreate 5m13s statefulset-controller create Claim www-web-2 Pod web-2 in StatefulSet web success
Normal SuccessfulCreate 5m13s statefulset-controller create Pod web-2 in StatefulSet web successful
Normal SuccessfulCreate 5m10s statefulset-controller create Claim www-web-3 Pod web-3 in StatefulSet web success
Normal SuccessfulCreate 5m10s statefulset-controller create Pod web-3 in StatefulSet web successful
Normal SuccessfulCreate 5m7s statefulset-controller create Claim www-web-4 Pod web-4 in StatefulSet web success
Normal SuccessfulCreate 5m7s statefulset-controller create Pod web-4 in StatefulSet web successful
Normal SuccessfulDelete 104s statefulset-controller delete Pod web-4 in StatefulSet web successful
Normal SuccessfulDelete 103s statefulset-controller delete Pod web-3 in StatefulSet web successful
可以看到最后两行,依次把web-4和web-3删除了。也就是删除也是从后往前删除。
镜像更新
StatefulSet支持两种镜像更新策略:滚动更新(RollingUpdate)
和删除更新(OnDelete)
。默认是RollingUpdate。
其配置如下:
...
spec:
updateStrategy: # 更新策略
type: RollingUpdate # OnDelete 删除之后才更新;RollingUpdate 滚动更新
rollingUpdate:
partition: 0 # 更新索引 >= partition 的 Pod ,默认为 0
...
滚动更新
StatefulSet的镜像更新目前还不支持set命令直接更新 image,需要 patch 来间接实现,如下:
kubectl patch sts web --type='json' -p='[{"op": "replace", "path":"/spec/template/spec/containers/0/image", "value":"nginx:1.9.1"}]'
# statefulset.apps/web patched
执行完命令后看下我们StatefulSet:
kubectl get sts
# 结果如下
NAME READY AGE
web 3/3 56m
查看版本:
kubectl rollout history sts web
statefulset.apps/web
REVISION CHANGE-CAUSE
1 <none>
2 <none>
可以看到已经有两个版本了。
查看版本2的信息:
kubectl rollout history sts web --revision=2
# 结果如下:看到更新到版本1.9.1了
statefulset.apps/web with revision #2
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.9.1
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts:
/usr/share/nginx/html from www (rw)
Volumes: <none>
然后查看Events:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
# 只看最后6行即可
Normal SuccessfulDelete 39s statefulset-controller delete Pod web-2 in StatefulSet web successful
Normal SuccessfulCreate 38s (x2 over 19m) statefulset-controller create Pod web-2 in StatefulSet web successful
Normal SuccessfulDelete 37s statefulset-controller delete Pod web-1 in StatefulSet web successful
Normal SuccessfulCreate 36s (x2 over 56m) statefulset-controller create Pod web-1 in StatefulSet web successful
Normal SuccessfulDelete 35s statefulset-controller delete Pod web-0 in StatefulSet web successful
Normal SuccessfulCreate 34s (x2 over 56m) statefulset-controller create Pod web-0 in StatefulSet web successful
可以看到滚定更新全过程,是删除一个,再创建一个新的,依次一个一个地更新。
由 默认的,pod 是有序的,在 StatefulSet 中更新时也是基于 pod 的顺序倒序更新的。
分区更新
上面我们知道在配置更新策略时,还有一个partition
的概念。
例如我们有 5 个 pod,如果当前 partition 设置为 3,那么此时滚动更新时,只会更新那些 序号 >= 3 的 pod。 partition 默认为0,也就是全部更新。
利用该机制,我们可以通过控制 partition 的值,来决定只更新其中一部分 pod,确认没有问题后再主键增大更新的 pod 数量,最终实现全部 pod 更新。
利用 滚动更新的partition 可以实现类似灰度发布/金丝雀发布的效果。
灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。AB test就是一种灰度发布方式,让一部用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
接下来看下如何实现灰度发布。
首先还使用我们前面创建的,修改副本数为5:
kubectl scale sts web --replicas=5
# statefulset.apps/web scaled
然后通过edit命令修改sts:
kubectl edit sts web
修改我们的 partition值为3,即只有序号大于等于3的才会更新;修改镜像版本为1.7.9(原来为1.9.1):
编辑好后保存并退出。
查看pod:
kubectl get po
# 结果如下
NAME READY STATUS RESTARTS AGE
dns-test 1/1 Running 0 79m
web-0 1/1 Running 0 37m
web-1 1/1 Running 0 37m
web-2 1/1 Running 0 37m
web-3 1/1 Running 0 15s
web-4 1/1 Running 0 17s
这样看不出效果,再查看具体pod web-3的信息:
kubectl describe po web-3
结果如下,看到版本已经变为1.7.9。
再查看具体pod web-2的信息:
kubectl describe po web-2
结果如下,看到版本还是1.9.1:
同理,可以查看其他pod。得出结论:pod后面序号为0,1,2的没有更新,序号为3和4的更新了。也就验证了partition的作用。
接着如果发现序号为3和4的使用没问题,我们可以把partition改为0,实现全部更新。这里就不在演示了。
以上步骤就可以实现灰度发布的效果了。
删除更新
当使用删除更新的策略时,我们修改了镜像版本,并不会马上去更新。当我们手动删除某个pod时,就会吃席创建出一样的pod,这个时候,就会更新成最新的镜像了。
删除更新不常用,就不再演示了。
删除
创建StatefulSet时,会创建出三个资源:StatefulSet、Service、Pod。
当我们需要删除时,
执行如下命令:
kubectl delete statefulset web
注意:当我们删除statefulset时,会级联删除下面所有的Pod。
但是Service不会被级联删除,需要单独删除,如下:
kubectl delete service nginx
如果不想级联删除Pod,可以使用如下命令:
kubectl deelte sts web --cascade=false