尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【Kubernetes从入门到精通】第22篇:StatefulSet——有状态应用的“私人管家“

【Kubernetes从入门到精通】第22篇:StatefulSet——有状态应用的“私人管家“ 上一篇【第21篇】Secret——敏感信息的“保险箱“下一篇【第23篇】DaemonSet——每个节点都要有的守护者摘要Deployment管无状态应用是一把好手——Pod随便删随便建IP随机分配名字随机生成。但你敢让MySQL这么玩吗主库的Pod挂了新Pod起来——IP变了、数据盘没了、主机名也是随机的整个主从复制架构直接炸给你看。有状态应用的命根子是啥网络身份要固定、存储要持久不丢、启动和停止要有顺序——这就是StatefulSet的四大特性。这篇文章咱们就掰开揉碎聊StatefulSet为什么Deployment搞不定有状态应用StatefulSet怎么给每个Pod一个终身IDHeadless Service怎么提供固定DNSvolumeClaimTemplates怎么给每个Pod定制衣柜最后实战部署一个MySQL StatefulSet——看完你就知道数据库上K8s的正确姿势了。一、为什么Deployment搞不定有状态应用1.1 无状态 vs 有状态——“咖啡馆 vs 银行保险柜”【无状态应用咖啡馆模式——Deployment的舒适区】 Deployment: web-server (replicas: 3) ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ web-abc12 │ │ web-def34 │ │ web-ghi56 │ │ IP: 10.0.1.1│ │ IP: 10.0.1.2│ │ IP: 10.0.1.3│ │ 无本地数据 │ │ 无本地数据 │ │ 无本地数据 │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ └────────────────┴────────────────┘ │ ┌───────────────────────┐ │ Service (负载均衡) │ └───────────────────────┘ │ 任意Pod都能处理请求 挂了就新建一个IP变了无所谓 → 跟咖啡馆服务员一样谁端咖啡都一样【有状态应用银行保险柜模式——Deployment搞不定】 StatefulSet: mysql (replicas: 3) ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ mysql-0 │ │ mysql-1 │ │ mysql-2 │ │ 主库Master│ │ 从库Slave│ │ 从库Slave│ │ 自己的数据盘 │ │ 自己的数据盘 │ │ 自己的数据盘 │ │ 固定DNS标识 │ │ 固定DNS标识 │ │ 固定DNS标识 │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ └────────────────┴────────────────┘ 问题 ✗ mysql-0挂了新Pod不能随便替代——它是主库 ✗ 每个Pod有自己的数据——不能共享一个存储 ✗ 启动顺序很重要——从库得等主库好了才能连 ✗ 名字不能随机——mysql-0就是mysql-0全网都能用这个名字找到它需求DeploymentStatefulSetPod名称随机如web-5d8f9c7b6-xyz固定如mysql-0、mysql-1网络标识无固定DNS有固定DNSmysql-0.mysql-headless存储共享VolumePod删了数据可能丢每个Pod独享PVCPod删了PVC还在启动顺序同时启动0→1→2等前一个Ready再启下一个停止顺序同时停止2→1→0从编号最大的开始停扩缩容随机增减按顺序增减适用场景Web服务、API、无状态微服务数据库、消息队列、分布式存储要点无状态应用像咖啡馆服务员——谁来都一样换个新面孔不影响生意。有状态应用像银行保险柜——每个柜子有自己的主人数据有自己的编号不能随便换。这就是为什么Deployment和StatefulSet完全是两套逻辑——一个管可替换的临时工一个管有身份的正式员工。二、StatefulSet四大特性——“有状态应用的刚需”2.1 特性一稳定的网络标识——“终身ID”每个Pod从出生到退休名字永远不变——statefulset-name-ordinal-index。【StatefulSet Pod命名规则】 StatefulSet Name: mysql Replicas: 3 创建顺序mysql-0 → mysql-1 → mysql-2 删除顺序mysql-2 → mysql-1 → mysql-0 ┌─────────────────────────────────────────────────┐ │ mysql-0 │ │ DNS: mysql-0.mysql-headless.default.svc │ │ .cluster.local │ │ ───────────────────────────────────────────── │ │ 格式{pod-name}.{headless-service}.{ns}.svc. │ │ cluster.local │ │ │ │ 即使Pod被重建 │ │ • 名字永远是 mysql-0 │ │ • DNS永远是 mysql-0.mysql-headless... │ │ • 但IP可能变DNS记录会更新 │ └─────────────────────────────────────────────────┘# StatefulSet基本结构apiVersion:apps/v1kind:StatefulSetmetadata:name:mysql# Pod名字mysql-0, mysql-1...spec:serviceName:mysql-headless# ← 必须绑定Headless Servicereplicas:3selector:matchLabels:app:mysqltemplate:metadata:labels:app:mysqlspec:containers:-name:mysqlimage:mysql:8.02.2 特性二稳定的持久化存储——“每人一个衣柜”这是StatefulSet最厉害的设计——volumeClaimTemplates。不像Deployment里手动创建PVC然后引用StatefulSet自动给每个Pod创建专属PVC。【volumeClaimTemplates——自动衣柜配备】 StatefulSet 定义 ┌────────────────────────────────────────┐ │ volumeClaimTemplates: │ │ - metadata: │ │ name: data │ │ spec: │ │ accessModes: [ReadWriteOnce] │ │ storageClassName: standard │ │ resources: │ │ requests: │ │ storage: 10Gi │ └────────────────────────────────────────┘ │ │ StatefulSet Controller 自动创建 ▼ ┌─────────────────────────────────────────────────┐ │ mysql-0 → PVC:>2.3 特性三有序部署——“排队上场”【有序部署 Pod 启动顺序】 Replicas: 3 时间轴 → ───────────────────────────────────────────── mysql-0 启动 ─┬─ Pending ─┬─ Running ─┬─ Ready │ │ │ │ │ │ mysql-1 等待… │ │ 启动 ─┬─ Pending ─┬─ Running ─┬─ Ready │ │ │ │ │ │ │ │ │ │ mysql-2 等待… │ │ 等待… │ │ 启动 ─┬─ Pending ─┬─ Running ─┬─ Ready │ │ │ │ │ │ │ │ │ │ │ │ │ │ ──────────────┴───────────┴────────┴───────────┴────────┴───────────┴───────────┘ 规则等前一个Pod Ready Running才开始创建下一个# 可以控制策略apiVersion:apps/v1kind:StatefulSetmetadata:name:mysqlspec:podManagementPolicy:OrderedReady# 默认有序等Ready推荐# podManagementPolicy: Parallel # 备选同时启动不保证顺序2.4 特性四有序缩容——“从队尾开始走”【有序缩容 Pod 停止顺序】 Replicas: 3 → 缩容到 1 ───────────────────────────────────────────── │ 首先mysql-2 │ 优雅终止 ─┬─ Terminating ─┬─ 消失 │ │ │ │ │ │ 然后mysql-1 │ 等待… │ 优雅终止 ─┬─ Terminating ─┬─ 消失 │ │ │ │ │ │ │ │ mysql-0继续跑│ │ │ │ 继续运行 ─────────────┴───────────┴───────────┴───────────────┘ 规则从编号最大的Pod开始停等它彻底删了才停下一个 好处数据一致性——从库先关主库最后关三、Headless Service——“没有VIP的DNS服务”3.1 为什么StatefulSet必须配Headless Service普通Service有一个Cluster IPVIP流量打到VIP上VIP随机分发给后端Pod——这叫负载均衡。但StatefulSet里的每个Pod都需要被精准定位——你想连mysql-0主库就必须找到mysql-0不能随机分给mysql-1。【普通Service vs Headless Service】 普通ServiceClusterIP ┌─────────────────────────────────────┐ │ Service: mysql-svc │ │ ClusterIP: 10.96.0.100 │ │ │ │ DNS: mysql-svc → 10.96.0.100 │ │ 一个IP代表所有Pod │ │ 你不知道连的是哪个Pod │ └─────────────────────────────────────┘ Headless ServiceclusterIP: None ┌─────────────────────────────────────┐ │ Service: mysql-headless │ │ ClusterIP: None │ │ │ │ DNS解析返回所有Pod的IPA记录 │ │ 还能精确解析单个Pod │ │ mysql-0.mysql-headless → 10.0.1.1│ │ mysql-1.mysql-headless → 10.0.1.2│ │ mysql-2.mysql-headless → 10.0.1.3│ └─────────────────────────────────────┘# Headless Service——就是普通Service clusterIP: NoneapiVersion:v1kind:Servicemetadata:name:mysql-headlesslabels:app:mysqlspec:clusterIP:None# ← 关键这就是Headlessselector:app:mysqlports:-name:mysqlport:3306targetPort:3306# DNS测试——Headless Service返回所有Pod的IPkubectl run-it--rmdns-test--imagebusybox --nslookupmysql-headless# Name: mysql-headless# Address 1: 10.0.1.1 mysql-0.mysql-headless# Address 2: 10.0.1.2 mysql-1.mysql-headless# Address 3: 10.0.1.3 mysql-2.mysql-headless# 精确定位单个Podkubectl run-it--rmdns-test--imagebusybox --nslookupmysql-0.mysql-headless# Name: mysql-0.mysql-headless# Address 1: 10.0.1.1要点Headless Service “没有VIP的导航服务”。普通Service是大堂经理——你跟它说找服务员它随机给你分一个。Headless Service是花名册——它直接告诉你每个服务员的名字和位置你自己决定找谁。3.2 StatefulSet Headless Service 固定DNS寻址【完整DNS链路——StatefulSet Headless Service】 问主库是谁连哪个 ┌─────────────────────────────────────────────────────┐ │ │ │ App 代码里写死 │ │ DB_HOST mysql-0.mysql-headless.default.svc. │ │ cluster.local │ │ │ │ DNS解析链路 │ │ mysql-0.mysql-headless.default.svc.cluster.local │ │ │ │ │ ▼ │ │ CoreDNS → 查Headless Service → 找到mysql-0的A记录 │ │ │ │ │ ▼ │ │ 10.0.1.1mysql-0 Pod IP │ │ │ │ 即使mysql-0重建IP变成10.0.1.99 │ │ DNS记录自动更新App仍然能用同一个域名找到它 │ └─────────────────────────────────────────────────────┘四、实战部署MySQL StatefulSet4.1 完整部署文件# 1. Secret——存MySQL密码apiVersion:v1kind:Secretmetadata:name:mysql-secrettype:OpaquestringData:root-password:MyRootPassword123!user-password:MyUserPassword123!---# 2. Headless Service——DNS导航apiVersion:v1kind:Servicemetadata:name:mysql-headlesslabels:app:mysqlspec:clusterIP:Noneselector:app:mysqlports:-name:mysqlport:3306targetPort:3306---# 3. ConfigMap——MySQL配置文件apiVersion:v1kind:ConfigMapmetadata:name:mysql-configdata:my.cnf:|[mysqld] server-id1 log-binmysql-bin binlog_formatROW max_connections500 character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-authentication-pluginmysql_native_password---# 4. StatefulSet——主角apiVersion:apps/v1kind:StatefulSetmetadata:name:mysqlspec:serviceName:mysql-headless# 绑定Headless Servicereplicas:3selector:matchLabels:app:mysqltemplate:metadata:labels:app:mysqlspec:containers:-name:mysqlimage:mysql:8.0ports:-containerPort:3306name:mysqlenv:-name:MYSQL_ROOT_PASSWORDvalueFrom:secretKeyRef:name:mysql-secretkey:root-passwordvolumeMounts:-name:mysql-data# 持久化存储mountPath:/var/lib/mysql-name:mysql-config# 配置文件mountPath:/etc/mysql/conf.dresources:requests:memory:512Micpu:500mlimits:memory:1Gicpu:1000mlivenessProbe:exec:command:-mysqladmin-ping--uroot--p$(MYSQL_ROOT_PASSWORD)initialDelaySeconds:30periodSeconds:10readinessProbe:exec:command:-mysql--uroot--p$(MYSQL_ROOT_PASSWORD)--e-SELECT 1initialDelaySeconds:10periodSeconds:5volumes:-name:mysql-configconfigMap:name:mysql-configvolumeClaimTemplates:# ← 自动PVC-metadata:name:mysql-dataspec:accessModes:[ReadWriteOnce]storageClassName:standard# 改成你的StorageClassresources:requests:storage:20Gi# 一键部署kubectl apply-fmysql-statefulset.yaml# 观察有序启动——严格按 0→1→2kubectl get pods-w# mysql-0 0/1 Pending 0 0s# mysql-0 0/1 Running 0 10s# mysql-0 1/1 Running 0 45s# mysql-1 0/1 Pending 0 0s ← 等mysql-0 Ready了才创建# mysql-1 0/1 Running 0 10s# mysql-1 1/1 Running 0 45s# mysql-2 0/1 Pending 0 0s ← 等mysql-1 Ready了才创建# mysql-2 1/1 Running 0 45s4.2 验证成果# 查看StatefulSet状态kubectl get statefulset mysql# NAME READY AGE# mysql 3/3 2m# 查看Pod——名字是固定的kubectl get pods-lappmysql# NAME READY STATUS RESTARTS AGE# mysql-0 1/1 Running 0 2m# mysql-1 1/1 Running 0 1m# mysql-2 1/1 Running 0 30s# 查看PVC——每个Pod独立一个kubectl get pvc# NAME STATUS VOLUME CAPACITY# mysql-data-mysql-0 Bound pvc-xxx 20Gi# mysql-data-mysql-1 Bound pvc-yyy 20Gi# mysql-data-mysql-2 Bound pvc-zzz 20Gi# DNS验证——精确找到每个Podkubectl run-it--rmdns-test--imagebusybox --nslookupmysql-0.mysql-headless# 连上mysql-0试试kubectl run-it--rmmysql-client--imagemysql:8.0--restartNever --\mysql-hmysql-0.mysql-headless-uroot-pMyRootPassword123!-eSELECT hostname;# ------------# | hostname |# ------------# | mysql-0 | ← 精准连接到了mysql-0# ------------4.3 测试持久化——“Pod挂了数据还在不在”# 1. 在mysql-0里创建测试数据库kubectlexecmysql-0 -- mysql-uroot-pMyRootPassword123!\-eCREATE DATABASE test_stateful; USE test_stateful; CREATE TABLE t1(id INT); INSERT INTO t1 VALUES(42);# 2. 验证数据已写入kubectlexecmysql-0 -- mysql-uroot-pMyRootPassword123!\-eSELECT * FROM test_stateful.t1;# ------# | id |# ------# | 42 |# ------# 3. 删除mysql-0 Podkubectl delete pod mysql-0# 4. StatefulSet自动重建mysql-0名字还是mysql-0绑同一个PVCkubectl get pods-w# mysql-0 0/1 Running 0 10s# mysql-0 1/1 Running 0 45s# 5. 数据还在kubectlexecmysql-0 -- mysql-uroot-pMyRootPassword123!\-eSELECT * FROM test_stateful.t1;# ------# | id |# ------# | 42 | ← 数据完好无损# ------要点测试结果说明了一切——删除mysql-0 Pod自动重建后连接同一个PVC数据完好无损。这就是volumeClaimTemplates的魔法Pod是暂时的PVC是永久的。除非你显式删除PVC否则数据永远在那儿。五、StatefulSet扩缩容实战5.1 扩容——从3个到5个# 扩容到5个kubectl scale statefulset mysql--replicas5# 观察有序扩容先创建mysql-3等Ready后再mysql-4kubectl get pods-w# mysql-3 0/1 Pending 0 0s ← 先创建编号小的# mysql-3 1/1 Running 0 45s# mysql-4 0/1 Pending 0 0s ← mysql-3 Ready后才创建# mysql-4 1/1 Running 0 45s# 看PVC——自动给新Pod创建独立的kubectl get pvc# mysql-data-mysql-0 Bound 20Gi# mysql-data-mysql-1 Bound 20Gi# mysql-data-mysql-2 Bound 20Gi# mysql-data-mysql-3 Bound 20Gi ← 自动创建# mysql-data-mysql-4 Bound 20Gi ← 自动创建5.2 缩容——从5个到2个注意数据安全# 缩容到2个kubectl scale statefulset mysql--replicas2# 观察有序缩容先停mysql-4再mysql-3再mysql-2kubectl get pods-w# mysql-4 Terminating 0 5s ← 编号大的先停# mysql-3 Terminating 0 5s# mysql-2 Terminating 0 5s# mysql-1 Running 1 3m ← 这两个保留# mysql-0 Running 1 5m# 关键PVC不会自动删除kubectl get pvc# mysql-data-mysql-0 Bound 20Gi# mysql-data-mysql-1 Bound 20Gi# mysql-data-mysql-2 Bound 20Gi ← 还在# mysql-data-mysql-3 Bound 20Gi ← 还在# mysql-data-mysql-4 Bound 20Gi ← 还在# 如果以后扩容回5个# mysql-2会重新绑定 mysql-data-mysql-2 PVC# 之前的数据全在要点缩容不删PVC是StatefulSet的安全设计——防止误操作导致数据丢失。如果你确定要清理不再用的PVC需要手动删除。另外注意podManagementPolicy: OrderedReady在缩容时也是从大到小——在数据库主从架构里一般是先关从库编号大的最后关主库mysql-0保护数据一致性。六、StatefulSet更新策略apiVersion:apps/v1kind:StatefulSetmetadata:name:mysqlspec:updateStrategy:type:RollingUpdate# 默认滚动更新rollingUpdate:partition:0# 0更新全部Pod2只更新编号≥2的Pod# 也支持# updateStrategy:# type: OnDelete # 手动删Pod才更新【partition 更新策略——金丝雀发布】 partition: 2 ┌──────────┐ ┌──────────┐ ┌──────────┐ │ mysql-0 │ │ mysql-1 │ │ mysql-2 │ │ 旧版本v1 │ │ 旧版本v1 │ │ 新版本v2 │ └──────────┘ └──────────┘ └──────────┘ ▲ ▲ ▲ │ │ │ 不受影响 不受影响 只更新编号≥2的Pod 金丝雀没影响到的主库 金丝雀Pod# 灰度更新——先更新mysql-2验证没问题再更新全部kubectl patch statefulset mysql-p{spec:{updateStrategy:{rollingUpdate:{partition:2}}}}# 改参数触发更新...# 验证mysql-2没问题后kubectl patch statefulset mysql-p{spec:{updateStrategy:{rollingUpdate:{partition:0}}}}# 全部更新本篇小结StatefulSet把有状态应用安排得明明白白四大特性稳定网络标识终身ID不丢、持久存储每人独占衣柜、有序部署排队上场、有序缩容从队尾走Headless Service不用VIP——DNS精确解析每个Pod让应用能直连指定实例volumeClaimTemplates自动给每个Pod创建PVCPod删了PVC不删数据永在扩缩容策略扩容从小到大、缩容从大到小partition还能做金丝雀发布适用场景MySQL、MongoDB、Kafka、Redis集群、Elasticsearch——一切需要身份数据顺序的应用StatefulSet是有状态应用上K8s的标准答案但它不是银弹——复杂的集群管理比如MySQL主从切换、数据同步还是需要Operator来接管。下一篇咱们聊DaemonSet——每个节点都要有的守护者。日志收集、监控Agent、网络插件这些每台机器上一个的东西怎么部署上一篇【第21篇】Secret——敏感信息的“保险箱“下一篇【第23篇】DaemonSet——每个节点都要有的守护者
返回列表