Kubernetes之StatefulSet有状态控制器
1. StatefulSet 有状态控制器
1.1 有无状态服务概念
1.1.1 有状态服务
StatefulSet是有状态的集合,管理有状态的服务,它所管理的Pod的名称和持久化数据不能随意变化。每一个Pod都有自己独有的数据持久化存储目录。比如MySQL主从、Redis集群(主从、哨兵)等
1.1.2 无状态服务
RS、Deployment、DaemonSet都是管理无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的。个体对整体无影响,通常都是所有pod都是共用一个数据卷的。
1.2 StatefulSet 的特点
- 唯一性:对于包含 N 个副本的 StatefulSet,每个 pod 会被分配一个 [0-(N-1)]范围内的唯一序号。
- 顺序性:StatefulSet 中 pod 的启动、更新、销毁默认都是按顺序进行的。
- 稳定、唯一的网络标识符:pod 的主机名、DNS 地址不会随着 pod 被重新调度而发生变化(必须配合无头服务)。
- 稳定的持久化存储:当 pod 被重新调度后,仍然能挂载原有的 PersistentVolume,保证了数据的完整性和一致性。
1.3 StatefulSet 组成部分
- Headless Service:定义稳定、唯一的网络标识符,生成可解析的DNS记录
- volumeClaimTemplates:存储卷申请模板,自动创建pvc,且pvc由存储类供应
- 定义StatefulSet来创建pod
1.4 Headless Service
Headless Service,即无头服务,与service的区别就是它没有Cluster IP,解析无头服务的域名时将返回该无头服务对应的全部Pod的Endpoint列表(Pod的IP),而解析普通的svc时,只返回svc的ClusterIP,不返回Pod的IP。
不管是普通的svc还是无头服务,它们在创建后都会通过集群中的CoreDNS组件自动的生成一个域名,它们的格式都是:
<Service名称>.<命名空间>.svc.cluster.local
<无头服务名称>.<命名空间>.svc.cluster.local
创建一个无头服务再关联一个statefulset,然后查看svc域名的解析结果
[root@master 1.3]# cat 1.headless.yaml 2.sts.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: myapp
name: headless
spec:
clusterIP: None # None就是无头服务,没有clusterIP
selector:
app: myapp
type: ClusterIP
status:
loadBalancer: {}
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: mysts
spec:
serviceName: headless # 指定无头服务
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: web
image: crpi-j2guy08w46nav7rf.cn-hongkong.personal.cr.aliyuncs.com/xiao_he/nginx:v1
[root@master 1.3]# kubectl apply -f 1.headless.yaml 2.sts.yaml
使用kubectl run --rm -it test --image=crpi-j2guy08w46nav7rf.cn-hongkong.personal.cr.aliyuncs.com/xiao_he/alpine:3.20 -- sh创建一个Pod来验证一下无头服务是否能解析到Pod的IP(因为是在集群中添加的DNS记录,所以在宿主机上是不能解析的)

也可以在宿主机上执行host命令,后面添加DNS服务器,来解析该svc的域名

1.5 Pod 的固定域名格式
StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod副本创建了一个DNS域名,这个域名的格式为:
<Pod名字>.<无头服务名称>.<命名空间>.svc.cluster.local

通过以上的 Headless Service 操作可以看出,无头服务是和 StatefulSet 紧密相连的,无头服务给 StatefulSet 提供了唯一的网络标识符,不会随着 Pod 被重新调度而发生变化。
1.6 创建和删除 StatefulSet
1.6.1 创建
先创建一个资源清单文件,StatefulSet的资源清单文件和Deployment的清单差不多,最大的区别就是多了一个可选的.sepc.serviceName字段
[root@master 1.3]# cat 3.mysts.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: myapp
name: headless
spec:
clusterIP: None # None就是无头服务,没有clusterIP
selector:
app: myapp
type: ClusterIP
status:
loadBalancer: {}
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: mysts
spec:
serviceName: headless # 指定无头服务
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: web
image: crpi-j2guy08w46nav7rf.cn-hongkong.personal.cr.aliyuncs.com/xiao_he/nginx:v1
StatefulSet创建Pod的时候,是从0到副本数减1的序号创建的,只有上一个Pod的状态为Running且Ready了,才开始创建下一个Pod,如果中途有某个Pod没有起来,那么它会一直等待,不会往后创建了,除非查找原因重建sts。

1.6.2 删除

1.7 StatefulSet 扩缩容
和 Deployment 类似,可以通过kubectl edit sts <sts名称>更新 replicas 字段扩容/缩容 StatefulSet,也可以使用 kubectl scale 和 kubectl patch 来扩容/缩容 StatefulSet。
1.7.1 扩容

1.7.2 缩容

1.8 StatefulSet 更新策略
- RollingUpdate 策略 (滚动更新,默认),会自动更新 StatefulSet 中所有的 Pod,采用与序号索引相反(倒序)的顺序进行滚动更新。
- OnDelete 策略(手动删除后重建,1.7 之前的版本的默认策略),StatefulSet 控制器不会自动更新 Pod,必须手动删除 Pod 才能使控制器创建新的 Pod,可以通过
statefulSet.spec.updateStrategy.type字段来修改策略。
1.8.1 RollingUpdate 策略(默认)
更新镜像来触发滚动更新,只要更改.spec.template字段下的内容就会触发更新
[root@master 1.3]# kubectl set image sts mysts web=crpi-j2guy08w46nav7rf.cn-hongkong.personal.cr.aliyuncs.com/xiao_he/nginx:v2

在滚动更新的时候,它是从最大的序号先前开始更新,先删除最大的序号的Pod,然后再创建同序号的Pod(必须Running且Ready),然后以此类推,直到全部Pod都更新完。

那么如果滚动更新的时候,某一个Pod没有起来,那么它会一直等待该Pod,不会往后进行更新,这个时候可以查找原因进行修改,然后把起不来的Pod删除,让他重新更新该Pod
1.8.2 OnDelete 策略
使用kubectl edit sts mysts命令来修改更新的策略,修改为OnDelete

更新Pod的镜像为nginx:v3版本
[root@master 1.3]# kubectl set image sts mysts web=crpi-j2guy08w46nav7rf.cn-hongkong.personal.cr.aliyuncs.com/xiao_he/nginx:v3

这个时候如果想要更新其他的 Pod ,和以上的方式一样,删除即可完成更新。
1.9 StatefulSet 分段更新
可以通过statefulSet.spec.updateStrategy.rollingUpdate.partition字段来配置分段更新的规格(要把更新策略改为默认的 rollingUpdate),默认该字段等于0(代表只有序号大于等于 0 的才会更新),可以使用 patch 或 edit 直接对 StatefulSet 进行设置
这里通过kubectl edit sts mysts把该字段的值修改为1,也就是说只有序号大于等于1的才会被更新

更改Pod的镜像来触发更新

如果更新后都没有问题的话,可以再把 partition 字段的值改为0,实现全部 Pod 的更新,类似于灰度/金丝雀发布。
1.10 StatefulSet 版本回滚
StatefulSet 的更新和回滚与 Deployment 类似,唯一的区别是历史版本在 ControllerRevision 中保存的,Deployment是在 RS 中保存的。
# 查看版本历史
[root@master 1.3]# kubectl rollout history sts mysts
statefulset.apps/mysts
REVISION CHANGE-CAUSE
2 <none>
3 <none>
4 <none>
# 查看指定的历史版本的详细信息,该版本是一个nginx:v1版
[root@master 1.3]# kubectl rollout history sts mysts --revision=3
statefulset.apps/mysts with revision #3
Pod Template:
Labels: app=myapp
Containers:
web:
Image: crpi-j2guy08w46nav7rf.cn-hongkong.personal.cr.aliyuncs.com/xiao_he/nginx:v1
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
# 回滚到指定的版本
[root@master 1.3]# kubectl rollout undo sts mysts --to-revision=3
# 这个时候Pod是按照倒序来依次的回滚的,如有起不来的,会一直等待
[root@master 1.3]# kubectl get pods -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mysts-0 1/1 Running 0 23m 172.16.196.148 node01 <none> <none>
mysts-1 1/1 Running 0 14s 172.16.140.77 node02 <none> <none>
mysts-2 1/1 Running 0 47s 172.16.196.153 node01 <none> <none>
[root@master 1.3]# curl 172.16.140.77
wo shi v1
1.11 StatefulSet 并发 Pod 管理
StatefulSet 可以通过.spec.podManagementPolicy 字段配置 Pod 的管理策略,目前支持如下的两种方式:
- OrderdReady (默认方式):有序管理,Pod 创建和更新按照正序和倒序进行操作,删除是同时删除的
- Parallel:并发管理,Pod 创建和删除同时并行启动和删除(更新不是并发的)。
这里默认的 OrderReady 就不演示了,因为以上的案例都是按照默认的有序方式来操作的,相信大家都已经琢磨明白了。
Parallel 方式示例
[root@master 1.3]# cat 3.mysts.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: myapp
name: headless
spec:
clusterIP: None # None就是无头服务,没有clusterIP
selector:
app: myapp
type: ClusterIP
status:
loadBalancer: {}
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: mysts
spec:
podManagementPolicy: Parallel # 修改Pod管理方式
serviceName: headless # 指定无头服务
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: web
image: crpi-j2guy08w46nav7rf.cn-hongkong.personal.cr.aliyuncs.com/xiao_he/nginx:v1
创建 StatefulSet 查看 Pod 是否是同时创建的,即使 Pod 是同时创建的没有按顺序创建,那么这些 Pod 的名称还是是按顺序命名的

删除同理

1.12 部署 Eureka 集群
可以通过以下的资源清单来创建 Eureka 集群,清单中的 env 下的 EUREKA_SERVER_ADDRESS 变量是指定哪些实例来创建 Eureka 集群,所以它的 value 值就是每个 Pod 的唯一网络标识符(域名)加端口和路径
生产环境中一定要加 Pod 的健康检查
[root@master 1.3]# cat 4.eureka_sts.yaml
apiVersion: v1
kind: Service
metadata:
name: eureka-headless
spec:
clusterIP: None # None就是无头服务,没有clusterIP
selector:
app: eureka
type: ClusterIP
status:
loadBalancer: {}
---
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: eureka
spec:
serviceName: eureka-headless # 指定无头服务
replicas: 3
selector:
matchLabels:
app: eureka
template:
metadata:
labels:
app: eureka
spec:
containers:
- name: eureka
image: crpi-j2guy08w46nav7rf.cn-hongkong.personal.cr.aliyuncs.com/xiao_he/eureka:demo
ports:
- containerPort: 8761
name: eureka
protocol: TCP
env:
- name: SPRING_PROFILES_ACTIVE
value: k8s
- name: SERVER_PORT
value: '8761'
- name: EUREKA_SERVER_ADDRESS
# 这里要是sts管理的Pod的域名,注意不要写错
value: http://eureka-0.eureka-headless:8761/eureka/,http://eureka-1.eureka-headless:8761/eureka/,http://eureka-2.eureka-headless:8761/eureka/
startupProbe:
tcpSocket: # 四种检查方式,每个探针只能使用一个
port: 8761 # 必填
initialDelaySeconds: 20 # 容器启动后,多少秒开始进行探测
timeoutSeconds: 2 # 探测超时的秒数,默认是1秒
periodSeconds: 5 # 执行探测的频率,默认10秒,最小可配置1秒
successThreshold: 1 # 检测成功1次表示就绪,默认1次
failureThreshold: 2 # 检测失败3次表示未就绪,默认3次,最小可配置1次
#readinessProbe:
# httpGet:
# port: 8761 # 必填
# path: /eureka/ # 选填,检查的路径,该容器是没有该路径的
# scheme: HTTP # 默认是HTTP
# initialDelaySeconds: 10
# timeoutSeconds: 2
# periodSeconds: 3 # 在Pod的整个生命周期中运行,对故障敏感的服务,该值可以设小一点
# successThreshold: 1
# failureThreshold: 2
livenessProbe:
tcpSocket:
port: 8761
initialDelaySeconds: 10
timeoutSeconds: 2
periodSeconds: 3 # 在Pod的整个生命周期中运行,对故障敏感的服务,该值可以设小一点
successThreshold: 1
failureThreshold: 2
[root@master 1.3]# kubectl apply -f 4.eureka_sts.yaml
[root@master 1.3]# kubectl get pods
NAME READY STATUS RESTARTS AGE
eureka-0 1/1 Running 0 6m54s
eureka-1 1/1 Running 0 6m28s
eureka-2 1/1 Running 0 6m3s
# 这个时候我们要让外部能访问我们的集群,这时可以再创建一个svc来暴露服务
[root@master 1.3]# cat 5.eureka_svc.yaml
apiVersion: v1
kind: Service
metadata:
name: eureka-svc
spec:
selector:
app: eureka # 这里要匹配sts定义的Pod的标签,写错就会导致代理不到后端的Pod
type: NodePort # 每个节点都会暴露一个高位端口,供外部访问
ports:
- name: eureka
protocol: TCP
port: 8761
targetPort: 8761
status:
loadBalancer: {}
[root@master 1.3]# kubectl apply -f 5.eureka_svc.yaml

这个时候就可以通过每个节点的IP+31877端口访问我们的 Eureka 集群了

更多推荐
所有评论(0)