之前在K8s上部署MySQL集羣時踩過一個坑:用Deployment部署的三個數據庫實例,每次重啓後名稱和IP都變了,導致主從複製關係頻繁中斷。後來才明白,像數據庫這種有狀態服務,不能用 Deployment 這種管理無狀態應用的控制器,而應該用 StatefulSet。
Kubernetes 中的 StatefulSet 專為有狀態服務設計,它能保證 Pod 的名稱、網絡標識和存儲的穩定性,非常適合部署數據庫、分佈式緩存等需要固定身份和持久化狀態的應用。本文結合 MySQL 主從集羣的部署案例,詳解 StatefulSet 的核心特性、使用方法以及與數據庫部署相關的最佳實踐。
一、StatefulSet 與 Deployment 的核心區別
無狀態服務(如 Web 應用)的實例可以隨意替換,而有狀態服務(如數據庫)需要:
- 固定的身份標識(名稱、網絡地址)
- 穩定的持久化存儲
- 有序的部署和擴展
StatefulSet 與 Deployment 的關鍵差異:
|
特性 |
Deployment |
StatefulSet |
|
Pod 名稱 |
隨機生成(如 app-7f89b456d9) |
固定格式(如 mysql-0、mysql-1) |
|
網絡標識 |
共享 Service 地址 |
每個 Pod 有唯一 DNS 記錄 |
|
存儲 |
共享 PVC 模板 |
每個 Pod 綁定獨立 PVC |
|
部署/擴展順序 |
並行 |
有序(依次創建/刪除) |
對於數據庫來説,固定的名稱和網絡標識是主從複製、集羣通信的基礎,而獨立的存儲則能保證數據不隨 Pod 重建而丟失——這些正是 StatefulSet 的核心優勢。
二、用 StatefulSet 部署 MySQL 主從集羣
以 MySQL 一主兩從架構為例,演示 StatefulSet 的部署過程。
1. 準備工作:創建存儲和網絡
步驟 1:創建持久卷聲明(PVC)模板
StatefulSet 通過 PVC 模板為每個 Pod 自動創建獨立存儲:
# mysql-storage.yaml
apiVersion: v1
kind: StorageClass
metadata:
name: mysql-storage
provisioner: k8s.io/minikube-hostpath # 本地測試用,生產用實際存儲插件
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data # 模板會為每個Pod創建 mysql-data-mysql-0、mysql-data-mysql-1 等
spec:
accessModes:
- ReadWriteOnce
storageClassName: mysql-storage
resources:
requests:
storage: 10Gi
步驟 2:創建 Headless Service
Headless Service 不為服務分配集羣 IP,而是為每個 Pod 提供固定 DNS 記錄(格式:pod-name.service-name.namespace.svc.cluster.local):
# mysql-service.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
selector:
app: mysql
clusterIP: None # 聲明為 Headless Service
ports:
- port: 3306
name: mysql
2. 定義 StatefulSet 配置
# mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql # 關聯 Headless Service
replicas: 3 # 3個實例(1主2從)
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: "root123"
- name: MYSQL_REPLICA_USER
value: "replica"
- name: MYSQL_REPLICA_PASSWORD
value: "replica123"
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
- name: init-scripts
mountPath: /docker-entrypoint-initdb.d # 初始化腳本目錄
volumes:
- name: init-scripts
configMap:
name: mysql-init # 包含主從配置腳本的 ConfigMap
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "mysql-storage"
resources:
requests:
storage: 10Gi
3. 配置主從複製初始化腳本
創建 ConfigMap 存儲主從複製的初始化邏輯:
# mysql-init-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-init
data:
init-replica.sh: |
#!/bin/bash
# 根據Pod名稱判斷角色(mysql-0為主庫,其他為從庫)
if [ "$(hostname)" = "mysql-0" ]; then
# 主庫配置:開啓二進制日誌,創建複製用户
echo "server-id=1" >> /etc/mysql/conf.d/replica.cnf
echo "log_bin=/var/lib/mysql/mysql-bin.log" >> /etc/mysql/conf.d/replica.cnf
mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "CREATE USER '$MYSQL_REPLICA_USER'@'%' IDENTIFIED BY '$MYSQL_REPLICA_PASSWORD'; GRANT REPLICATION SLAVE ON *.* TO '$MYSQL_REPLICA_USER'@'%';"
else
# 從庫配置:設置server-id,連接主庫
echo "server-id=$(echo $(hostname) | awk -F'-' '{print $2+100}')" >> /etc/mysql/conf.d/replica.cnf
# 等待主庫就緒
until mysql -h mysql-0.mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "SELECT 1"; do
sleep 5
done
# 配置主從複製(主庫地址為mysql-0的DNS)
mysql -uroot -p$MYSQL_ROOT_PASSWORD -e "CHANGE MASTER TO MASTER_HOST='mysql-0.mysql', MASTER_USER='$MYSQL_REPLICA_USER', MASTER_PASSWORD='$MYSQL_REPLICA_PASSWORD', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=154; START SLAVE;"
fi
這個腳本通過 Pod 名稱(mysql-0、mysql-1、mysql-2)區分主從角色,從庫通過 Headless Service 的 DNS(mysql-0.mysql)連接主庫,確保網絡標識穩定。
4. 部署並驗證
依次創建上述資源:
kubectl apply -f mysql-storage.yaml
kubectl apply -f mysql-service.yaml
kubectl apply -f mysql-init-configmap.yaml
kubectl apply -f mysql-statefulset.yaml
查看 StatefulSet 狀態:
kubectl get statefulset mysql
kubectl get pods -l app=mysql # 應顯示 mysql-0、mysql-1、mysql-2 三個Pod
驗證主從複製:
# 進入主庫查看狀態
kubectl exec -it mysql-0 -- mysql -uroot -proot123 -e "SHOW MASTER STATUS;"
# 進入從庫查看複製狀態
kubectl exec -it mysql-1 -- mysql -uroot -proot123 -e "SHOW SLAVE STATUS\G"
若從庫的 Slave_IO_Running 和 Slave_SQL_Running 均為 Yes,説明主從複製配置成功。
三、StatefulSet 核心特性解析
1. 穩定的網絡標識
每個 Pod 的 DNS 格式為 <pod-name>.<service-name>.<namespace>.svc.cluster.local,例如 mysql-0.mysql.default.svc.cluster.local。即使 Pod 重建,只要名稱不變,DNS 記錄就不變,避免主從連接中斷。
2. 有序部署與擴展
- 部署時,按
0→1→2的順序創建 Pod,前一個 Pod 就緒後才創建下一個; - 擴展(
kubectl scale statefulset mysql --replicas=4)時,按同樣順序新增; - 縮容時,按
2→1→0的逆序刪除,確保數據一致性。
這種有序性對數據庫集羣至關重要,例如主庫必須先於從庫啓動。
3. 穩定的存儲
通過 volumeClaimTemplates,每個 Pod 會綁定獨立的 PVC(如 mysql-data-mysql-0 對應 mysql-0),即使 Pod 被刪除重建,新 Pod 仍會掛載原 PVC,保證數據不丟失。
四、數據庫部署最佳實踐
1. 數據備份策略
StatefulSet 保證了存儲的穩定性,但仍需定期備份數據:
# 示例:備份 mysql-0 的數據到本地
kubectl exec -it mysql-0 -- mysqldump -uroot -proot123 --all-databases > backup-$(date +%F).sql
生產環境建議結合定時任務(CronJob)自動備份,並將備份文件存儲到外部存儲(如 S3)。
2. 資源限制
為數據庫 Pod 設置 CPU 和內存限制,避免資源爭搶:
spec:
template:
spec:
containers:
- name: mysql
resources:
requests:
cpu: 1000m
memory: 1Gi
limits:
cpu: 2000m
memory: 2Gi
3. 健康檢查
配置存活探針(livenessProbe)和就緒探針(readinessProbe),及時發現並重啓故障實例:
livenessProbe:
exec:
command: ["mysqladmin", "ping", "-uroot", "-p$MYSQL_ROOT_PASSWORD"]
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command: ["mysqladmin", "ping", "-uroot", "-p$MYSQL_ROOT_PASSWORD"]
initialDelaySeconds: 5
periodSeconds: 5
4. 避免單點故障
- 主庫故障時,可通過工具(如 Orchestrator)自動將從庫提升為主庫;
- 結合 StatefulSet 的有序更新(
kubectl rollout restart statefulset mysql),實現無感知升級。
五、StatefulSet 的適用場景
除了數據庫(MySQL、PostgreSQL),StatefulSet 還適合部署這些有狀態服務:
- 分佈式緩存(Redis Cluster、Memcached 集羣)
- 消息隊列(Kafka、RabbitMQ 集羣)
- 分佈式存儲(Ceph、GlusterFS)
這些服務都需要固定的身份標識、持久化存儲或集羣內節點通信,StatefulSet 能很好地滿足這些需求。
總結
StatefulSet 為有狀態服務提供了穩定的身份標識、存儲和部署順序,是 K8s 中部署數據庫等狀態服務的最佳選擇。相比 Deployment,它通過 Headless Service 保證網絡穩定性,通過 PVC 模板實現存儲隔離,通過有序操作確保集羣一致性。
實際部署數據庫時,還需結合備份策略、資源限制和健康檢查,才能構建高可用的數據庫集羣。理解 StatefulSet 的核心特性,不僅能解決數據庫部署難題,也能加深對 K8s 編排能力的理解——畢竟,管好狀態,才能管好服務。