之前在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-0mysql-1mysql-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_RunningSlave_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 編排能力的理解——畢竟,管好狀態,才能管好服務。