提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
有状态应用管理StatefulSet
1、有状态应用管理StatefulSet
StatefulSet(有状态集,缩写为sts)常用于部署有状态的且需要有序启动的应用程序,比如在进行SpringCloud项目容器化时,Eureka的部署是比较适合用StatefulSet部署方式的,可以给每个Eureka实例创建一个唯一且固定的标识符,并且每个Eureka实例无需配置多余的Service,其余Spring Boot应用可以直接通过Eureka的Headless Service即可进行注册。
Eureka的statefulset的资源名称是eureka,eureka-0 eureka-1 eureka-2
Service:headless service,没有ClusterIP eureka-svc
Eureka-0.eureka-svc.NAMESPACE_NAME eureka-1.eureka-svc …
2、StatefulSet的基本概念
StatefulSet主要用于管理有状态应用程序的工作负载API对象。比如在生产环境中,可以部署ElasticSearch集群、MongoDB集群或者需要持久化的RabbitMQ集群、Redis集群、Kafka集群和ZooKeeper集群等。
和Deployment类似,一个StatefulSet也同样管理着基于相同容器规范的Pod。不同的是,StatefulSet为每个Pod维护了一个粘性标识。这些Pod是根据相同的规范创建的,但是不可互换,每个Pod都有一个持久的标识符,在重新调度时也会保留,一般格式为StatefulSetName-Number。比如定义一个名字是Redis-Sentinel的StatefulSet,指定创建三个Pod,那么创建出来的Pod名字就为Redis-Sentinel-0、Redis-Sentinel-1、Redis-Sentinel-2。而StatefulSet创建的Pod一般使用Headless Service(无头服务)进行通信,和普通的Service的区别在于Headless Service没有ClusterIP,它使用的是Endpoint进行互相通信,Headless一般的格式为:
statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
- serviceName为Headless Service的名字,创建StatefulSet时,必须指定Headless Service名称;
- 0…N-1为Pod所在的序号,从0开始到N-1;
- statefulSetName为StatefulSet的名字;
- namespace为服务所在的命名空间;
- .cluster.local为Cluster Domain(集群域)。
假如公司某个项目需要在Kubernetes中部署一个主从模式的Redis,此时使用StatefulSet部署就极为合适,因为StatefulSet启动时,只有当前一个容器完全启动时,后一个容器才会被调度,并且每个容器的标识符是固定的,那么就可以通过标识符来断定当前Pod的角色。
比如用一个名为redis-ms的StatefulSet部署主从架构的Redis,第一个容器启动时,它的标识符为redis-ms-0,并且Pod内主机名也为redis-ms-0,此时就可以根据主机名来判断,当主机名为redis-ms-0的容器作为Redis的主节点,其余从节点,那么Slave连接Master主机配置就可以使用不会更改的Master的Headless Service,此时Redis从节点(Slave)配置文件如下:
port 6379
slaveof redis-ms-0.redis-ms.public-service.svc.cluster.local 6379
tcp-backlog 511
timeout 0
tcp-keepalive 0
……
其中redis-ms-0.redis-ms.public-service.svc.cluster.local是Redis Master的Headless Service,在同一命名空间下只需要写redis-ms-0.redis-ms即可,后面的public-service.svc.cluster.local可以省略。
3、StatefulSet注意事项
一般StatefulSet用于有以下一个或者多个需求的应用程序:
- 需要稳定的独一无二的网络标识符。
- 需要持久化数据。
- 需要有序的、优雅的部署和扩展。
- 需要有序的自动滚动更新。
如果应用程序不需要任何稳定的标识符或者有序的部署、删除或者扩展,应该使用无状态的控制器部署应用程序,比如Deployment或者ReplicaSet。
StatefulSet是Kubernetes 1.9版本之前的beta资源,在1.5版本之前的任何Kubernetes版本都没有。
Pod所用的存储必须由PersistentVolume Provisioner(持久化卷配置器)根据请求配置StorageClass,或者由管理员预先配置,当然也可以不配置存储。
为了确保数据安全,删除和缩放StatefulSet不会删除与StatefulSet关联的卷,可以手动选择性地删除PVC和PV
StatefulSet目前使用Headless Service(无头服务)负责Pod的网络身份和通信,需要提前创建此服务。
删除一个StatefulSet时,不保证对Pod的终止,要在StatefulSet中实现Pod的有序和正常终止,可以在删除之前将StatefulSet的副本缩减为0。
4、创建一个StatefulSet应用
- 定义一个StatefulSet资源文件
[root@k8s-master ~]# vim nginx-sts.yaml
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: nginx:1.20.2
ports:
- containerPort: 80
name: web
- kind: Service定义了一个名字为Nginx的Headless
Service,创建的Service格式为nginx-0.nginx.default.svc.cluster.local,其他的类似,因为没有指定Namespace(命名空间),所以默认部署在default。 - kind: StatefulSet定义了一个名字为web的StatefulSet,replicas表示部署Pod的副本数,本实例为2。
在StatefulSet中必须设置Pod选择器(.spec.selector)用来匹配其标签(.spec.template.metadata.labels)。在1.8版本之前,如果未配置该字段(.spec.selector),将被设置为默认值,在1.8版本之后,如果未指定匹配Pod Selector,则会导致StatefulSet创建错误。
当StatefulSet控制器创建Pod时,它会添加一个标签statefulset.kubernetes.io/pod-name,该标签的值为Pod的名称,用于匹配Service。
- 创建一个StatefulSet
[root@k8s-master ~]# kubectl create -f nginx-sts.yaml
service/nginx created
statefulset.apps/web created
#查询pod
[root@k8s-master ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 10m
web-1 1/1 Running 0 10m
#查询service
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10d
nginx ClusterIP None <none> 80/TCP 31s
- 扩容service
[root@k8s-master ~]# kubectl get sts
NAME READY AGE
web 2/2 14m
您在 /var/spool/mail/root 中有新邮件
[root@k8s-master ~]# kubectl scale --replicas=3 sts web
statefulset.apps/web scaled
#查看pod
[root@k8s-master ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 17m
web-1 1/1 Running 0 17m
web-2 1/1 Running 0 2m17s
- 新增busybox,解析无头service
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: default
spec:
containers:
- name: busybox
image: busybox:1.28
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
restartPolicy: Always
EOF
#查看busybox启动状态
[root@k8s-master ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 0 28s
web-0 1/1 Running 0 49m
web-1 1/1 Running 0 49m
web-2 1/1 Running 0 33m
#进入busybox容器
[root@k8s-master ~]# kubectl exec -ti busybox -- sh
/ #
#解析IP
/ # nslookup web-0.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 192.169.214.209 web-0.nginx.default.svc.cluster.local
[root@k8s-master ~]# kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox 1/1 Running 0 5m18s 192.161.125.10 k8s-node01 <none> <none>
web-0 1/1 Running 0 53m 192.169.214.209 k8s-node03 <none> <none>
web-1 1/1 Running 0 53m 192.172.82.199 k8s-master <none> <none>
web-2 1/1 Running 0 38m 192.169.214.210 k8s-node03 <none> <none>
#直接通过名称 获取访问 192.169.214.209
/ # ping web-0.nginx
PING web-0.nginx (192.169.214.209): 56 data bytes
64 bytes from 192.169.214.209: seq=0 ttl=62 time=0.785 ms
64 bytes from 192.169.214.209: seq=1 ttl=62 time=0.854 ms
64 bytes from 192.169.214.209: seq=2 ttl=62 time=0.596 ms
64 bytes from 192.169.214.209: seq=3 ttl=62 time=0.758 ms
/ # wget web-0.nginx
Connecting to web-0.nginx (192.169.214.209:80)
index.html 100% |*******************************************************| 612 0:00:00 ETA
5、StatefulSet扩容缩容
- StatefulSet副本启动顺序按照名称0,1,2,只有web-0完全启动之后才会启动web-1,web-1完全启动之后才会启动web-2
- 删除的时候顺序与启动相反,从最后一个序号开始,2,1,0,如果web-2删除过程中,web-0挂掉了,那么web-1不会被删除,必须等待web-0启动状态变为ready之后,才会删除web-1
- StatefulSet滚动更新的时候会先删除旧的副本,再创建新的副本,如果只有一个副本的话,会导致业务不可用,所以要根据自己的实际情况选择使用StatefulSet或者Deployment,如果必须固定主机名或者pod名称,建议使用StatefulSet
- 查看pod 动态加载过程
[root@k8s-master ~]# kubectl scale --replicas=5 sts web
statefulset.apps/web scaled
#显示创建顺序
[root@k8s-master ~]# kubectl get po -l controller-revision-hash=web-759965fcf4 -w
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 7m19s
web-1 1/1 Running 0 89m
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 3s
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-3 0/1 ContainerCreating 0 0s
web-3 1/1 Running 0 2s
web-4 0/1 Pending 0 0s
web-4 0/1 Pending 0 0s
web-4 0/1 ContainerCreating 0 0s
web-4 1/1 Running 0 2s
#缩容
[root@k8s-master ~]# kubectl scale --replicas=2 sts web
statefulset.apps/web scaled
#显示缩容顺序
[root@k8s-master ~]# kubectl get po -l controller-revision-hash=web-759965fcf4 -w
NAME READY STATUS RESTARTS AGE
web-4 1/1 Terminating 0 2m5s
web-4 0/1 Terminating 0 2m6s
web-4 0/1 Terminating 0 2m9s
web-4 0/1 Terminating 0 2m9s
web-3 1/1 Terminating 0 2m11s
web-3 0/1 Terminating 0 2m13s
web-3 0/1 Terminating 0 2m14s
web-3 0/1 Terminating 0 2m14s
web-2 1/1 Terminating 0 2m17s
web-2 0/1 Terminating 0 2m18s
web-2 0/1 Terminating 0 2m19s
web-2 0/1 Terminating 0 2m19s
[root@k8s-master ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 0 44m
web-0 1/1 Running 0 11m
web-1 1/1 Running 0 92m
[root@k8s-master ~]#
#StatefulSet部署名称是固定的
[root@k8s-master ~]# kubectl exec -ti web-0 -- sh
# hostname
web-0
6、StatefulSet更新策略
- RollingUpdate
- OnDelete
- StatefulSet和Deployment一样,有几种更新方式
RollingUpdate
- 查看更新策略
[root@k8s-master ~]# kubectl get sts -o yaml
updateStrategy:
rollingUpdate:
partition: 0
type: RollingUpdate # 默认滚动更新,从下往上更新
- 扩容到5个副本
[root@k8s-master ~]# kubectl scale --replicas=5 sts web
statefulset.apps/web scaled
[root@k8s-master ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 54m
web-1 1/1 Running 0 136m
web-2 1/1 Running 0 6s
web-3 1/1 Running 0 4s
web-4 1/1 Running 0 2s
- 滚动更新顺序是web-2,web-1,web-0,从下往上更新,如果更新过程中web-0挂掉了,则会等待web-0恢复到状态为ready之后再继续从下往上滚动更新
[root@k8s-master ~]# kubectl edit sts web
statefulset.apps/web edited
# 修改镜像
- image: nginx:1.21.6
#查看pod状态
[root@k8s-master ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 58m
web-1 1/1 Running 0 140m
web-2 1/1 Running 0 4m25s
web-3 0/1 Terminating 0 4m23s
web-4 1/1 Running 0 4s
查看更新顺序
[root@k8s-master ~]# kubectl get po -l app=nginx -w
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 57m
web-1 1/1 Running 0 139m
web-2 1/1 Running 0 2m43s
web-3 1/1 Running 0 2m41s
web-4 1/1 Running 0 2m39s
web-4 1/1 Terminating 0 4m14s
web-4 0/1 Terminating 0 4m16s
web-4 0/1 Terminating 0 4m17s
web-4 0/1 Terminating 0 4m17s
web-3 1/1 Terminating 0 4m21s
web-3 0/1 Terminating 0 4m22s
web-3 0/1 Terminating 0 4m23s
web-3 0/1 Terminating 0 4m23s
web-2 1/1 Terminating 0 4m27s
web-2 0/1 Terminating 0 4m29s
web-2 0/1 Terminating 0 4m32s
web-2 0/1 Terminating 0 4m33s
web-1 1/1 Terminating 0 140m
web-1 0/1 Terminating 0 140m
web-1 0/1 Terminating 0 141m
web-1 0/1 Terminating 0 141m
web-0 1/1 Terminating 0 59m
web-0 0/1 Terminating 0 59m
web-0 0/1 Terminating 0 59m
web-0 0/1 Terminating 0 59m
OnDelete
- 修改更新状态为OnDelete
[root@k8s-master ~]# kubectl edit sts web
updateStrategy:
type: OnDelete
- 修改nginx版本
[root@k8s-master ~]# kubectl set image sts web nginx=nginx:1.20.2 --record
Flag --record has been deprecated, --record will be removed in the future
statefulset.apps/web image updated
#状态未改变
[root@k8s-master ~]# kubectl get po web-0 -oyaml | grep image
- image: nginx:1.21.6
imagePullPolicy: IfNotPresent
image: nginx:1.21.6
imageID: docker-pullable://nginx@sha256:19da26bd6ef0468ac8ef5c03f01ce1569a4dbfb82d4d7b7ffbd7aed16ad3eb46
[root@k8s-master ~]# kubectl get po -l app=nginx -w
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 61m
web-1 1/1 Running 0 62m
web-2 1/1 Running 0 62m
web-3 1/1 Running 0 62m
web-4 1/1 Running 0 62m
#删除pod web-0 显示更新过程
[root@k8s-master ~]# kubectl delete po web-0
pod "web-0" deleted
[root@k8s-master ~]# kubectl get po -l app=nginx -w
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 61m
web-1 1/1 Running 0 62m
web-2 1/1 Running 0 62m
web-3 1/1 Running 0 62m
web-4 1/1 Running 0 62m
web-0 1/1 Terminating 0 69m
web-0 0/1 Terminating 0 69m
web-0 0/1 Terminating 0 69m
web-0 0/1 Terminating 0 69m
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 1s
web-0 1/1 Running 0 2s
#web-0 和 web-1 显示 不同的yaml 镜像版本
[root@k8s-master ~]# kubectl get po web-0 -oyaml | grep image
- image: nginx:1.20.2
imagePullPolicy: IfNotPresent
image: nginx:1.20.2
imageID: docker-pullable://nginx@sha256:dc43c7f22a8310cac31cb27d7a3f63c5db1ee5eb0ac7944bc89a52dbd08b6d3c
[root@k8s-master ~]# kubectl get po web-1 -oyaml | grep image
- image: nginx:1.21.6
imagePullPolicy: IfNotPresent
image: nginx:1.21.6
imageID: docker-pullable://nginx@sha256:19da26bd6ef0468ac8ef5c03f01ce1569a4dbfb82d4d7b7ffbd7aed16ad3eb46
- OnDelete 更新策略,只有在删除pod的时候,才会更新。如果同时删除多个,会总 0 开始更新到最后。
7、StatefulSet灰度发布
- 修改配置
[root@k8s-master ~]# kubectl edit sts web```
updateStrategy:
rollingUpdate:
partition: 2 #小于2的不会被更新
type: RollingUpdate
[root@k8s-master ~]# kubectl set image sts web nginx=nginx:1.21.6 --record
Flag --record has been deprecated, --record will be removed in the future
statefulset.apps/web image updated
#显示只更新了大于 1 的 web-4 web-3 web-2
[root@k8s-master ~]# kubectl get po -l app=nginx -w
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 22m
web-1 1/1 Running 0 22m
web-2 1/1 Running 0 22m
web-3 1/1 Running 0 21m
web-4 1/1 Running 0 21m
web-4 1/1 Terminating 0 22m
web-4 0/1 Terminating 0 22m
web-4 0/1 Terminating 0 22m
web-4 0/1 Terminating 0 22m
web-4 0/1 Pending 0 0s
web-4 0/1 Pending 0 1s
web-4 0/1 ContainerCreating 0 1s
web-4 1/1 Running 0 2s
web-3 1/1 Terminating 0 22m
web-3 0/1 Terminating 0 22m
web-3 0/1 Terminating 0 22m
web-3 0/1 Terminating 0 22m
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-3 0/1 ContainerCreating 0 0s
web-3 1/1 Running 0 2s
web-2 1/1 Terminating 0 22m
web-2 0/1 Terminating 0 22m
web-2 0/1 Terminating 0 22m
web-2 0/1 Terminating 0 22m
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
- 可以使用这种机制实现灰度机制,先发布一两个实例,确认没有问题之后再发布所有实例,这就是stateful的分段更新,相当于灰度发布的机制,也可以使用其它的方式,比如服务网格,或者myservices
8、StatefulSet级联删除和非级联删除
- 级联删除:删除sts时同时删除Pod
- 非级联删除:删除sts时不删Pod
- 默认级联删除
[root@k8s-master ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 3 (23m ago) 3h24m
web-0 1/1 Running 0 7m32s
web-1 1/1 Running 0 7m45s
web-2 1/1 Running 0 13m
web-3 1/1 Running 0 13m
web-4 1/1 Running 0 14m
[root@k8s-master ~]# kubectl delete sts web
statefulset.apps "web" deleted
[root@k8s-master ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 3 (24m ago) 3h24m
web-3 0/1 Terminating 0 14m
web-4 0/1 Terminating 0 14m
[root@k8s-master ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 3 (24m ago) 3h24m
- 非级联删除
[root@k8s-master ~]# kubectl create -f nginx-sts.yaml
service/nginx created
statefulset.apps/web created
[root@k8s-master ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 3 (27m ago) 3h27m
web-0 1/1 Running 0 13s
web-1 1/1 Running 0 12s
[root@k8s-master ~]# kubectl delete sts web --cascade=false
warning: --cascade=false is deprecated (boolean value) and can be replaced with --cascade=orphan.
statefulset.apps "web" deleted
[root@k8s-master ~]# kubectl get sts
No resources found in default namespace.
[root@k8s-master ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 3 (29m ago) 3h29m
web-0 1/1 Running 0 2m17s
web-1 1/1 Running 0 2m16s
- 查看pod,可以看到pod依然存在,只是没有sts管理了,再次删除pod不会被重新创建