4004 字
20 分钟
04.Kubernetes 学习笔记:StatefulSet 有状态应用管理

六、StatefulSet - 有状态应用管理#

1. StatefulSet 基础概念#

1.1 什么是 StatefulSet?#

回顾一个问题:

在第五章中,我们用 Deployment 部署了 Nginx、PHP 应用,它们有一个共同特点:无状态

无状态应用的特点:
- Pod 之间完全相同,可以互相替换
- Pod 名称是随机的(nginx-xxxx-yyyyy)
- Pod 重启后 IP 会变化,但不影响使用
- 可以任意顺序启动、停止、删除
- 不需要持久化存储(或使用共享存储)

但是,如果我们要部署 MySQL 集群呢?

MySQL 主从集群的要求:
1. 每个实例需要有固定的网络标识(主库是master-0,从库是slave-1、slave-2)
2. 必须先启动主库,再启动从库
3. 每个实例需要独立的持久化存储(不能共享数据目录)
4. 从库需要知道主库的固定地址来同步数据
5. Pod 重启后,必须保持原来的身份和数据

Deployment 做不到这些!这时候就需要 StatefulSet


StatefulSet 是什么?

StatefulSet 是 Kubernetes 中专门用于管理有状态应用的控制器。

用一个生活化的比喻来理解:

Deployment 就像工厂流水线上的工人:
- 每个工人穿着统一的工作服
- 谁干活都一样,可以随意替换
- 编号是临时的(工人-a1b2c3)
- 换班顺序无所谓
StatefulSet 就像学校里的学生:
- 每个学生有固定的学号(student-0, student-1, student-2)
- 学号是按顺序分配的,不会重复
- 每个学生有自己的课桌(独立存储)
- 排队必须按学号顺序来
- 转学回来后,还是用原来的学号和课桌

1.2 StatefulSet vs Deployment#

核心区别对比:

特性DeploymentStatefulSet
Pod 名称随机后缀(nginx-7d8-9f2)有序编号(web-0, web-1, web-2)
Pod 启动顺序并发启动按序启动(0→1→2)
Pod 停止顺序随机停止反序停止(2→1→0)
网络标识不稳定(IP 变化)稳定(固定 DNS 名称)
存储共享存储或无状态每个 Pod 独立的 PVC
适用场景Web 应用、API 服务数据库、消息队列、Zookeeper
Pod 替换新 Pod 是全新的新 Pod 继承原身份和存储

详细说明:

1. Pod 名称 - 固定且有序

Terminal window
# Deployment 的 Pod 名称(随机)
nginx-deployment-7d8b9f2c-x7k9m
nginx-deployment-7d8b9f2c-p4q2n
nginx-deployment-7d8b9f2c-m8w5t
# StatefulSet 的 Pod 名称(固定编号)
mysql-0 # 第一个 Pod,编号从 0 开始
mysql-1 # 第二个 Pod
mysql-2 # 第三个 Pod

为什么需要固定名称?

  • MySQL 主从配置中,从库需要连接 mysql-0.mysql-service(主库的固定 DNS)
  • Redis 集群中,节点需要通过固定名称相互发现
  • Zookeeper 需要固定的 server.id 标识

2. 启动和停止顺序

graph TD subgraph Deployment["Deployment 启动(并发)"] D1["Pod0<br/>同时启动"] D2["Pod1<br/>同时启动"] D3["Pod2<br/>同时启动"] end subgraph StatefulSet["StatefulSet 启动(顺序)"] S1["Pod0<br/>先启动"] --> S2["Pod1<br/>Pod0 Ready后启动"] S2 --> S3["Pod2<br/>Pod1 Ready后启动"] end

StatefulSet 停止(反序): 先删除 Pod2 → 再删除 Pod1 → 最后删除 Pod0

为什么需要顺序?

  • MySQL 主从:必须先启动主库(Pod0),从库才能连接它
  • Etcd 集群:需要先有 leader 节点,其他节点才能加入
  • 优雅关闭:先关闭从节点,最后关闭主节点,避免数据丢失

3. 稳定的网络标识(固定 DNS)

StatefulSet 必须配合 Headless Service 使用,每个 Pod 有固定的 DNS 名称:

DNS 格式:
<pod-name>.<service-name>.<namespace>.svc.cluster.local
示例:
mysql-0.mysql-service.default.svc.cluster.local
mysql-1.mysql-service.default.svc.cluster.local
mysql-2.mysql-service.default.svc.cluster.local

即使 Pod 重启,DNS 名称也不变!

Terminal window
# Pod mysql-1 崩溃前的 IP
mysql-1: 10.244.1.100
# Pod mysql-1 重启后的 IP
mysql-1: 10.244.2.200 # IP 变了
# 但 DNS 名称不变,其他 Pod 仍然可以通过名称访问
nslookup mysql-1.mysql-service
# 自动解析到新 IP: 10.244.2.200

4. 独立的持久化存储

Deployment(共享存储或无存储):
┌─────┐ ┌─────┐ ┌─────┐
│ Pod0│ │ Pod1│ │ Pod2│
└─────┘ └─────┘ └─────┘
└───────┴───────┘
┌────────────┐
│ Shared PVC │ ← 所有 Pod 共享一个存储
└────────────┘
StatefulSet(独立存储):
┌─────┐ ┌─────┐ ┌─────┐
│ Pod0│ │ Pod1│ │ Pod2│
└─────┘ └─────┘ └─────┘
↓ ↓ ↓
┌──────┐ ┌──────┐ ┌──────┐
│ PVC-0│ │ PVC-1│ │ PVC-2│ ← 每个 Pod 有独立的 PVC
└──────┘ └──────┘ └──────┘

为什么需要独立存储?

  • MySQL 的每个实例必须有自己的数据目录
  • Pod 重启后,新 Pod 会自动绑定到原来的 PVC,数据不丢失
  • 扩容时,新 Pod 会自动创建新的 PVC

1.3 StatefulSet 适用场景#

✅ 适合使用 StatefulSet:

  1. 数据库集群

    • MySQL 主从/MGR 集群
    • PostgreSQL 集群
    • MongoDB 副本集
    • Redis 主从/Sentinel/Cluster
  2. 分布式存储

    • Ceph
    • GlusterFS
    • MinIO 集群
  3. 分布式协调服务

    • Etcd 集群
    • Zookeeper 集群
    • Consul 集群
  4. 消息队列

    • Kafka 集群
    • RabbitMQ 集群
    • Pulsar 集群
  5. 有状态的大数据组件

    • Elasticsearch 集群
    • Cassandra 集群
    • HBase 集群

❌ 不适合使用 StatefulSet:

  1. 无状态 Web 应用 - Nginx、Apache、前端静态资源服务
  2. 无状态 API 服务 - RESTful API、微服务(不需要固定标识的)
  3. 批处理任务 - 使用 Job/CronJob 更合适

1.4 快速实验:对比 Deployment 和 StatefulSet#

实验目标: 直观感受两者的区别

步骤1:创建 Deployment

Terminal window
cd /root/k8s-yaml
cat > nginx-deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
replicas: 3
selector:
matchLabels:
app: nginx-deploy
template:
metadata:
labels:
app: nginx-deploy
spec:
containers:
- name: nginx
image: nginx:1.20
ports:
- containerPort: 80
EOF
kubectl apply -f nginx-deployment.yaml
# 查看 Pod 名称(随机后缀)
kubectl get pods -l app=nginx-deploy

预期输出:

NAME READY STATUS RESTARTS AGE
nginx-deploy-7d8b9f2c-x7k9m 1/1 Running 0 10s
nginx-deploy-7d8b9f2c-p4q2n 1/1 Running 0 10s
nginx-deploy-7d8b9f2c-m8w5t 1/1 Running 0 10s

步骤2:创建 StatefulSet

Terminal window
# 先创建 Headless Service(必须的!)
cat > nginx-headless.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: nginx-sts
spec:
clusterIP: None # Headless Service
selector:
app: nginx-sts
ports:
- port: 80
EOF
kubectl apply -f nginx-headless.yaml
# 创建 StatefulSet
cat > nginx-statefulset.yaml <<EOF
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-sts
spec:
serviceName: nginx-sts # 关联 Headless Service
replicas: 3
selector:
matchLabels:
app: nginx-sts
template:
metadata:
labels:
app: nginx-sts
spec:
containers:
- name: nginx
image: nginx:1.20
ports:
- containerPort: 80
EOF
kubectl apply -f nginx-statefulset.yaml
# 观察 Pod 启动过程(会看到按顺序启动)
watch kubectl get pods -l app=nginx-sts

预期输出(注意启动顺序):

# 第一阶段:只有 Pod-0 启动
NAME READY STATUS RESTARTS AGE
nginx-sts-0 1/1 Running 0 5s
# 第二阶段:Pod-0 Ready 后,Pod-1 启动
NAME READY STATUS RESTARTS AGE
nginx-sts-0 1/1 Running 0 15s
nginx-sts-1 1/1 Running 0 5s
# 第三阶段:Pod-1 Ready 后,Pod-2 启动
NAME READY STATUS RESTARTS AGE
nginx-sts-0 1/1 Running 0 25s
nginx-sts-1 1/1 Running 0 15s
nginx-sts-2 1/1 Running 0 5s

步骤3:测试固定 DNS

Terminal window
# 测试 StatefulSet 的 Pod(有固定 DNS)
kubectl exec -it nginx-sts-0 -- nslookup nginx-sts-1.nginx-sts
# 预期输出:
# Server: 10.96.0.10
# Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
#
# Name: nginx-sts-1.nginx-sts
# Address 1: 10.244.2.100 nginx-sts-1.nginx-sts.default.svc.cluster.local
# 每个 StatefulSet Pod 都有固定 DNS
kubectl exec -it nginx-sts-0 -- nslookup nginx-sts-0.nginx-sts
kubectl exec -it nginx-sts-0 -- nslookup nginx-sts-2.nginx-sts

步骤4:测试删除 Pod 后的行为

Terminal window
# 删除 Deployment 的一个 Pod
DEPLOY_POD=$(kubectl get pods -l app=nginx-deploy -o jsonpath='{.items[0].metadata.name}')
kubectl delete pod $DEPLOY_POD
kubectl get pods -l app=nginx-deploy
# 输出:新 Pod 名称完全不同
# 删除 StatefulSet 的一个 Pod
kubectl delete pod nginx-sts-1
kubectl get pods -l app=nginx-sts
# 输出:新 Pod 名称完全相同
# nginx-sts-1 ← 名称不变!

步骤5:清理实验资源

Terminal window
kubectl delete deployment nginx-deploy
kubectl delete statefulset nginx-sts
kubectl delete svc nginx-sts

实验总结:

特性Deployment 表现StatefulSet 表现
Pod 名称随机后缀固定编号(0、1、2)
启动顺序并发启动顺序启动(0→1→2)
固定 DNS❌ 不支持✅ 支持
Pod 重建新名称名称不变

2. StatefulSet 设计原理#

2.1 StatefulSet 的组成部分#

一个完整的 StatefulSet 应用需要三个对象:

┌─────────────────────────────────────────────────┐
│ StatefulSet 架构 │
├─────────────────────────────────────────────────┤
│ 1. Headless Service(必须) │
│ 提供稳定的网络标识 │
│ 2. StatefulSet(核心) │
│ 管理 Pod 的创建、更新、删除 │
│ 3. PersistentVolumeClaim(可选) │
│ 为每个 Pod 自动创建独立的存储 │
└─────────────────────────────────────────────────┘

1. Headless Service

apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
clusterIP: None # 关键!设置为 None
selector:
app: mysql
ports:
- port: 3306

作用:为每个 Pod 创建固定的 DNS 记录(<pod-name>.<service-name>

2. StatefulSet 定义

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-service # 关联 Headless Service
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
volumeClaimTemplates: # PVC 模板(可选)
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi

3. 自动创建的 PVC

Terminal window
# 命名规则:<pvc-name>-<statefulset-name>-<ordinal>
data-mysql-0 # mysql-0 的 PVC
data-mysql-1 # mysql-1 的 PVC
data-mysql-2 # mysql-2 的 PVC

2.2 管理策略#

1. Pod 管理策略(podManagementPolicy)

spec:
podManagementPolicy: OrderedReady # 或 Parallel
  • OrderedReady(默认):按顺序启动(0→1→2),前一个 Ready 才启动下一个
  • Parallel:并发启动,不等待

2. 更新策略(updateStrategy)

spec:
updateStrategy:
type: RollingUpdate # 或 OnDelete
rollingUpdate:
partition: 0 # 金丝雀发布
  • RollingUpdate:自动滚动更新,反序更新(2→1→0)
  • OnDelete:手动删除 Pod 才更新

3. Partition 金丝雀发布

partition: 2 # 只更新编号 >= 2 的 Pod
假设有 5 个 Pod(0-4),partition: 2
更新后:
mysql-0 (v1) mysql-1 (v1) mysql-2 (v2) mysql-3 (v2) mysql-4 (v2)

2.3 升级和回滚#

升级:

Terminal window
kubectl set image statefulset/mysql mysql=mysql:8.0.35
kubectl rollout status statefulset/mysql

回滚:

StatefulSet 不支持 kubectl rollout undo,需手动回滚:

Terminal window
kubectl set image statefulset/mysql mysql=mysql:8.0.30

为什么不支持自动回滚?

  • 有状态应用的回滚涉及数据兼容性
  • 需要人工判断和干预

2.4 扩缩容#

扩容:

Terminal window
kubectl scale statefulset mysql --replicas=5
# 顺序创建:mysql-3 → mysql-4

缩容:

Terminal window
kubectl scale statefulset mysql --replicas=3
# 反序删除:mysql-4 → mysql-3
# PVC 不会被删除(数据保留)

清理 PVC:

Terminal window
kubectl delete statefulset mysql
kubectl get pvc # PVC 仍然存在
kubectl delete pvc -l app=mysql # 手动删除

3. 实战:用 StatefulSet 搭建 MySQL 主从集群#

3.1 实验架构#

┌──────────────────────────────────────────────────┐
│ MySQL 主从复制集群 │
├──────────────────────────────────────────────────┤
│ mysql-0 (主库 Master) ← 写入数据 │
│ ↓ │
│ mysql-1 (从库 Slave) ← 同步数据 │
│ mysql-2 (从库 Slave) ← 同步数据 │
│ │
│ 读写分离: │
│ - 写操作 → mysql-0 │
│ - 读操作 → mysql-1, mysql-2(负载均衡) │
└──────────────────────────────────────────────────┘

实验目标:

  1. 部署 1 主 2 从的 MySQL 集群
  2. 配置主从复制
  3. 验证数据同步
  4. 测试 Pod 故障恢复

3.2 步骤1:创建 ConfigMap#

Terminal window
cd /root/k8s-yaml
cat > mysql-configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
master.cnf: |
[mysqld]
log-bin=mysql-bin # 开启 binlog
server-id=1 # 主库 ID
slave.cnf: |
[mysqld]
server-id=2 # 从库 ID(会被动态修改)
read_only=1 # 只读模式
EOF
kubectl apply -f mysql-configmap.yaml

3.3 步骤2:创建 Service#

Terminal window
cat > mysql-service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
clusterIP: None # Headless Service
selector:
app: mysql
ports:
- name: mysql
port: 3306
---
apiVersion: v1
kind: Service
metadata:
name: mysql-read # 读请求负载均衡
spec:
selector:
app: mysql
ports:
- name: mysql
port: 3306
EOF
kubectl apply -f mysql-service.yaml

两个 Service 的作用:

  • mysql: Headless,提供固定 DNS
  • mysql-read: 普通 Service,读请求负载均衡

3.4 步骤3:创建 StatefulSet#

Terminal window
cat > mysql-statefulset.yaml <<'EOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: init-mysql
image: reg.westos.org/library/mysql:8.0
command:
- bash
- "-c"
- |
set -ex
# 提取 Pod 编号
[[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo "Pod ordinal: $ordinal"
# mysql-0 用主库配置,其他用从库配置
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/master.cnf /mnt/conf.d/
else
cp /mnt/config-map/slave.cnf /mnt/conf.d/
# 动态设置 server-id
sed -i "s/server-id=2/server-id=$((100 + $ordinal))/" /mnt/conf.d/slave.cnf
fi
cat /mnt/conf.d/*.cnf
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
- name: config-map
mountPath: /mnt/config-map
containers:
- name: mysql
image: reg.westos.org/library/mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
value: "Westos123"
- name: MYSQL_DATABASE
value: "testdb"
ports:
- name: mysql
containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
livenessProbe:
exec:
command: ["mysqladmin", "ping", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command: ["mysql", "-uroot", "-p${MYSQL_ROOT_PASSWORD}", "-e", "SELECT 1"]
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql-config
- name: data
emptyDir: {} # 简化版:使用 emptyDir
EOF
kubectl apply -f mysql-statefulset.yaml

关键点说明:

1. initContainers 的作用:

  • 根据 Pod 编号生成不同配置
  • mysql-0 使用主库配置
  • mysql-1/2 使用从库配置

2. Pod 编号提取:

Terminal window
[[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}

3. 动态 server-id:

  • mysql-0: server-id = 1
  • mysql-1: server-id = 101
  • mysql-2: server-id = 102

3.5 步骤4:观察启动过程#

Terminal window
# 观察 Pod 启动顺序
watch kubectl get pods -l app=mysql
# 查看 init 容器日志
kubectl logs mysql-0 -c init-mysql
kubectl logs mysql-1 -c init-mysql
# 查看 MySQL 日志
kubectl logs mysql-0 -c mysql

预期输出(按顺序启动):

NAME READY STATUS RESTARTS AGE
mysql-0 1/1 Running 0 30s ← 先启动
NAME READY STATUS RESTARTS AGE
mysql-0 1/1 Running 0 60s
mysql-1 1/1 Running 0 25s ← mysql-0 Ready 后启动
NAME READY STATUS RESTARTS AGE
mysql-0 1/1 Running 0 90s
mysql-1 1/1 Running 0 55s
mysql-2 1/1 Running 0 20s ← mysql-1 Ready 后启动

3.6 步骤5:配置主从复制#

在主库创建复制用户:

Terminal window
kubectl exec -it mysql-0 -c mysql -- mysql -uroot -pWestos123 <<EOF
CREATE USER 'repl'@'%' IDENTIFIED BY 'Repl123!@#';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
SHOW MASTER STATUS;
EOF

记录输出:

+------------------+----------+
| File | Position |
+------------------+----------+
| mysql-bin.000003 | 157 |
+------------------+----------+

配置从库(mysql-1 和 mysql-2):

Terminal window
# 配置 mysql-1
kubectl exec -it mysql-1 -c mysql -- mysql -uroot -pWestos123 <<EOF
STOP SLAVE;
CHANGE MASTER TO
MASTER_HOST='mysql-0.mysql',
MASTER_USER='repl',
MASTER_PASSWORD='Repl123!@#',
MASTER_LOG_FILE='mysql-bin.000003',
MASTER_LOG_POS=157;
START SLAVE;
SHOW SLAVE STATUS\G
EOF
# 配置 mysql-2
kubectl exec -it mysql-2 -c mysql -- mysql -uroot -pWestos123 <<EOF
STOP SLAVE;
CHANGE MASTER TO
MASTER_HOST='mysql-0.mysql',
MASTER_USER='repl',
MASTER_PASSWORD='Repl123!@#',
MASTER_LOG_FILE='mysql-bin.000003',
MASTER_LOG_POS=157;
START SLAVE;
SHOW SLAVE STATUS\G
EOF

检查从库状态:

Terminal window
kubectl exec -it mysql-1 -c mysql -- mysql -uroot -pWestos123 -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO_Running|Slave_SQL_Running"

预期输出:

Slave_IO_Running: Yes ← 必须是 Yes
Slave_SQL_Running: Yes ← 必须是 Yes

3.7 步骤6:测试主从同步#

在主库写入数据:

Terminal window
kubectl exec -it mysql-0 -c mysql -- mysql -uroot -pWestos123 <<EOF
USE testdb;
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('Charlie');
SELECT * FROM users;
EOF

在从库查询数据:

Terminal window
# 从库1
kubectl exec -it mysql-1 -c mysql -- mysql -uroot -pWestos123 -e "SELECT * FROM testdb.users;"
# 从库2
kubectl exec -it mysql-2 -c mysql -- mysql -uroot -pWestos123 -e "SELECT * FROM testdb.users;"

预期输出(数据已同步):

+----+---------+---------------------+
| id | name | created_at |
+----+---------+---------------------+
| 1 | Alice | 2024-01-15 10:30:00 |
| 2 | Bob | 2024-01-15 10:30:00 |
| 3 | Charlie | 2024-01-15 10:30:00 |
+----+---------+---------------------+

继续插入,观察实时同步:

Terminal window
# 主库插入
kubectl exec -it mysql-0 -c mysql -- mysql -uroot -pWestos123 -e \
"INSERT INTO testdb.users (name) VALUES ('David'), ('Eve');"
# 从库查询
kubectl exec -it mysql-1 -c mysql -- mysql -uroot -pWestos123 -e \
"SELECT * FROM testdb.users;"

3.8 步骤7:测试读写分离#

创建测试 Pod:

Terminal window
kubectl run mysql-client --image=reg.westos.org/library/mysql:8.0 --rm -it --restart=Never -- bash

在测试 Pod 中:

Terminal window
# 写操作 → 主库
mysql -h mysql-0.mysql -uroot -pWestos123 -e \
"INSERT INTO testdb.users (name) VALUES ('Frank');"
# 读操作 → 负载均衡
mysql -h mysql-read -uroot -pWestos123 -e "SELECT * FROM testdb.users;"
# 多次查询观察负载均衡
for i in {1..5}; do
mysql -h mysql-read -uroot -pWestos123 -e \
"SELECT @@hostname AS pod_name, COUNT(*) AS count FROM testdb.users;"
done

3.9 步骤8:测试 Pod 故障恢复#

删除从库 Pod:

Terminal window
kubectl delete pod mysql-2
watch kubectl get pods -l app=mysql

验证:

  • Pod 名称不变(mysql-2)
  • DNS 名称不变(mysql-2.mysql)
  • 由于使用 emptyDir,数据丢失(需重新配置主从)

生产环境使用 PVC:

  • Pod 重建自动挂载原 PVC
  • 数据不丢失,MySQL 自动恢复

3.10 步骤9:扩缩容#

扩容到 4 个副本:

Terminal window
kubectl scale statefulset mysql --replicas=4
watch kubectl get pods -l app=mysql
# 配置新从库 mysql-3
kubectl exec -it mysql-3 -c mysql -- mysql -uroot -pWestos123 <<EOF
CHANGE MASTER TO
MASTER_HOST='mysql-0.mysql',
MASTER_USER='repl',
MASTER_PASSWORD='Repl123!@#',
MASTER_LOG_FILE='mysql-bin.000003',
MASTER_LOG_POS=157;
START SLAVE;
EOF

缩容回 3 个:

Terminal window
kubectl scale statefulset mysql --replicas=3
# mysql-3 被删除

3.11 清理资源#

Terminal window
kubectl delete statefulset mysql
kubectl delete svc mysql mysql-read
kubectl delete configmap mysql-config

4. 总结#

4.1 核心要点#

StatefulSet 三大特性:

  1. 固定标识:Pod名称和DNS不变(mysql-0, mysql-1, mysql-2)
  2. 有序管理:按序启动/停止/更新(0→1→2 或 2→1→0)
  3. 独立存储:每个Pod自动创建独立PVC,Pod重建后数据保留

vs Deployment:

  • Deployment:无状态,Pod可替换,并发管理
  • StatefulSet:有状态,Pod不可替换,有序管理

4.2 适用场景#

✅ 使用 StatefulSet:

  • 数据库集群(MySQL、PostgreSQL、MongoDB)
  • 消息队列(Kafka、RabbitMQ)
  • 分布式存储(Ceph、MinIO)
  • 协调服务(Etcd、Zookeeper)

❌ 不要使用:

  • Web应用、API服务 → 用Deployment
  • 批处理任务 → 用Job/CronJob

4.3 重要限制#

StatefulSet 只提供基础能力,不提供:

  • ❌ 自动故障切换(主库挂了不会自动提升从库)
  • ❌ 自动备份和恢复
  • ❌ 自动监控告警
  • ❌ 业务逻辑处理(如主从复制配置)

解决方案:

  • 简单场景:手动配置 + StatefulSet
  • 生产环境:使用 Operator(Percona、Vitess、Strimzi等)
  • 关键业务:考虑托管服务(RDS、云数据库)

4.4 常用命令#

Terminal window
# 基本操作
kubectl get statefulset
kubectl describe sts <name>
kubectl get pods -l app=mysql
# 扩缩容
kubectl scale sts mysql --replicas=5
# 更新
kubectl set image sts/mysql mysql=mysql:8.0.35
kubectl rollout status sts/mysql
# 回滚(手动)
kubectl set image sts/mysql mysql=mysql:8.0.30
# 删除(保留PVC)
kubectl delete sts mysql
kubectl get pvc
kubectl delete pvc -l app=mysql # 手动清理PVC

4.5 生产最佳实践#

# 1. 必须使用持久化存储
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: fast-ssd
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
# 2. 配置健康检查
livenessProbe:
exec:
command: ["mysqladmin", "ping"]
readinessProbe:
exec:
command: ["mysql", "-e", "SELECT 1"]
# 3. 设置资源限制
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
# 4. Pod反亲和(分散到不同节点)
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: mysql
topologyKey: kubernetes.io/hostname

第七章:daemonset和job

1.介绍daemonset,他的作用和适用场景,他的实现逻辑,需要配置什么,他的更新和回滚策略 2.介绍job和cronjob,介绍作用和适用场景,介绍配置项 3.实战 搭建一个daemonset实例,如Fluentd 创建一个job执行一次性任务 学会用cronjob进行定期备份etcd 提前备份系统快照,故意弄坏etcd,学会用备份恢复etcd


04.Kubernetes 学习笔记:StatefulSet 有状态应用管理
https://dev-null-sec.github.io/posts/04-k8s学习笔记-statefulset有状态应用管理/
作者
DevNull
发布于
2024-11-10
许可协议
CC BY-NC-SA 4.0