ZooKeeper 集群部署指南(Kubernetes StatefulSet 方式)

ZooKeeper 集群部署指南(Kubernetes StatefulSet 方式)

这个文章中说的方法,是之前按照AI提供的资料,部署过一套,具体里面很多细节还没有仔细研究。这边部署的结果是能用,但是不保证读者按照这个文章的步骤部署了能用,因为可能会有很多因素的影响。放在这里更多的是偏向于个人笔记的作用,读者仅供参考,请读者降低预期。
本文介绍如何在 Kubernetes(腾讯云 TKE)上使用 StatefulSet 部署高可用 ZooKeeper 集群。

本文档基于实际部署经验总结,详细记录了部署过程中遇到的关键问题及解决方案,包括 StorageClass provisioner 配置、VolumeBinding 超时、Pod 创建死锁、DNS 解析死锁等问题。

一、前言

在生产环境中,ZooKeeper 通常需要以集群方式部署以保证高可用性。Kubernetes 的 StatefulSet 非常适合部署 ZooKeeper 这类有状态应用,它能够保证:

  • 稳定的网络标识:每个 Pod 有固定的 DNS 名称
  • 稳定的存储:PVC 与 Pod 生命周期绑定
  • 有序部署和扩缩容:按顺序创建和删除 Pod

本文将详细介绍如何在腾讯云 TKE 上部署 3 节点 ZooKeeper 集群,其他云厂商的 K8s 集群可参考本文进行适配。

二、架构说明

2.1 部署架构

复制代码
                    ┌─────────────────────────────────────┐
                    │           Kubernetes Cluster         │
                    │                                      │
  Client ──────────►│  ┌─────────────────────────────┐    │
       :2181        │  │    Service: zookeeper       │    │
                    │  │    (ClusterIP)              │    │
                    │  └─────────────┬───────────────┘    │
                    │                │                     │
                    │  ┌─────────────┴───────────────┐    │
                    │  │  Headless Service           │    │
                    │  │  (zookeeper-headless)       │    │
                    │  └─────────────────────────────┘    │
                    │         │         │         │       │
                    │    ┌────┴───┐ ┌───┴────┐ ┌──┴─────┐ │
                    │    │  Pod   │ │  Pod   │ │  Pod   │ │
                    │    │  zk-0  │ │  zk-1  │ │  zk-2  │ │
                    │    └───┬────┘ └───┬────┘ └───┬────┘ │
                    │        │          │          │      │
                    │    ┌───┴────┐ ┌───┴────┐ ┌───┴────┐ │
                    │    │  PVC   │ │  PVC   │ │  PVC   │ │
                    │    │20Gi+10Gi│ │20Gi+10Gi│ │20Gi+10Gi│ │
                    │    │(数据+日志)│ │(数据+日志)│ │(数据+日志)│ │
                    │    └────────┘ └────────┘ └────────┘ │
                    └─────────────────────────────────────┘

2.2 资源清单

资源类型 名称 说明
Namespace zk ZooKeeper 专用命名空间
StorageClass cbs-ssd 腾讯云 SSD 云硬盘存储类(Immediate 模式)
ConfigMap zookeeper-config 配置文件和初始化脚本
Service zookeeper 客户端访问服务(ClusterIP)
Service zookeeper-headless Pod 间通信(Headless)
StatefulSet zookeeper 3 副本有状态应用(Parallel 模式)
PVC data-zookeeper-* 数据持久化存储(每 Pod 20Gi)
PVC datalog-zookeeper-* 日志持久化存储(每 Pod 10Gi)

存储总计:每个 Pod 30Gi(20Gi 数据 + 10Gi 日志),3 个 Pod 共 90Gi SSD 云硬盘

三、前置要求

3.1 环境准备

  1. 已创建腾讯云 TKE 集群(或其他 K8s 集群)
  2. 已配置 kubectl 连接到集群
  3. 集群节点数 >= 3(建议 ZooKeeper Pod 分布在不同节点)

3.2 验证集群连接

bash 复制代码
# 检查 kubectl 连接
kubectl cluster-info

# 查看集群节点
kubectl get nodes

四、配置文件详解

4.1 Namespace 配置

zookeeper-namespace.yaml

yaml 复制代码
apiVersion: v1
kind: Namespace
metadata:
  name: zk
  labels:
    app: zookeeper

4.2 StorageClass 配置

storageclass-cbs-ssd-immediate.yaml(腾讯云 TKE 专用,实际使用版本)

yaml 复制代码
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: cbs-ssd
  annotations:
    storageclass.kubernetes.io/is-default-class: "false"
provisioner: com.tencent.cloud.csi.cbs  # ⚠️ 关键:必须是 com.tencent.cloud.csi.cbs,不是 cbs.csi.tencent.com
parameters:
  type: CLOUD_SSD              # SSD云硬盘,高性能
  fsType: ext4                 # 文件系统类型
  diskChargeType: POSTPAID_BY_HOUR  # 按量计费
reclaimPolicy: Delete          # PVC删除时自动删除云硬盘
volumeBindingMode: Immediate    # ⚠️ 关键:使用立即绑定模式,避免VolumeBinding超时问题
allowVolumeExpansion: true     # 允许扩容

⚠️ 重要配置说明:

  1. provisioner 名称 :必须使用 com.tencent.cloud.csi.cbs,这是腾讯云 TKE 实际使用的 CSI 驱动名称。如果使用错误的名称(如 cbs.csi.tencent.com),PVC 将一直处于 Pending 状态。

  2. volumeBindingMode :使用 Immediate 模式而不是 WaitForFirstConsumer,原因:

    • WaitForFirstConsumer 在某些 TKE 版本中可能出现 VolumeBinding 超时问题
    • Immediate 模式虽然可能跨可用区,但功能正常且更稳定
    • 如果需要确保同可用区,可以先使用 Immediate 部署成功后再考虑优化

其他云厂商 StorageClass 配置:

  • 阿里云 ACK:provisioner 使用 diskplugin.csi.alibabacloud.com
  • AWS EKS:provisioner 使用 ebs.csi.aws.com
  • 自建 K8s:可使用 local-path 或 NFS 等

4.3 ConfigMap 配置

zookeeper-configmap.yaml

yaml 复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: zookeeper-config
  namespace: zk
data:
  zoo.cfg: |
    # ZooKeeper配置文件
    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/data
    dataLogDir=/datalog
    clientPort=2181
    maxClientCnxns=60
    autopurge.snapRetainCount=3
    autopurge.purgeInterval=1
  
  log4j.properties: |
    zookeeper.root.logger=INFO, CONSOLE
    zookeeper.console.threshold=INFO
    log4j.rootLogger=${zookeeper.root.logger}
    log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
    log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold}
    log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
    log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
  
  init.sh: |
    #!/bin/bash
    set -e
    
    # 从Pod名称中提取序号(例如:zookeeper-0 -> 0)
    HOSTNAME=$(hostname)
    POD_INDEX=$(echo $HOSTNAME | sed -e 's/zookeeper-//')
    MY_ID=$((POD_INDEX + 1))  # ZooKeeper的myid从1开始
    
    # 创建myid文件
    echo $MY_ID > /data/myid
    
    # 生成server列表配置
    DOMAIN="zookeeper-headless"
    NAMESPACE="${POD_NAMESPACE:-zk}"
    REPLICAS="${ZOOKEEPER_REPLICAS:-3}"
    
    # 构建server配置
    SERVER_LIST=""
    for i in $(seq 1 $REPLICAS); do
      POD_INDEX=$((i - 1))
      SERVER_LIST="${SERVER_LIST}server.$i=zookeeper-${POD_INDEX}.${DOMAIN}.${NAMESPACE}.svc.cluster.local:2888:3888\n"
    done
    
    # 将server配置追加到zoo.cfg
    echo -e "\n# 集群配置(自动生成)" >> /conf/zoo.cfg
    echo -e "$SERVER_LIST" >> /conf/zoo.cfg
    
    echo "ZooKeeper初始化完成,MY_ID=$MY_ID"

4.4 Service 配置

zookeeper-service.yaml

yaml 复制代码
# Headless Service - 用于 Pod 之间通信
apiVersion: v1
kind: Service
metadata:
  name: zookeeper-headless
  namespace: zk
  labels:
    app: zookeeper
spec:
  clusterIP: None
  ports:
  - port: 2181
    name: client
  - port: 2888
    name: server
  - port: 3888
    name: leader-election
  selector:
    app: zookeeper
---
# ClusterIP Service - 用于客户端访问
apiVersion: v1
kind: Service
metadata:
  name: zookeeper
  namespace: zk
  labels:
    app: zookeeper
spec:
  type: ClusterIP
  ports:
  - port: 2181
    targetPort: 2181
    protocol: TCP
    name: client
  selector:
    app: zookeeper

4.5 StatefulSet 配置

zookeeper-statefulset.yaml

yaml 复制代码
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: zookeeper
  namespace: zk
  labels:
    app: zookeeper
spec:
  serviceName: zookeeper-headless
  replicas: 3
  podManagementPolicy: Parallel  # ⚠️ 关键:并行创建Pod,避免死锁问题
  selector:
    matchLabels:
      app: zookeeper
  template:
    metadata:
      labels:
        app: zookeeper
    spec:
      # Pod 反亲和性,尽量分布在不同节点
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - zookeeper
              topologyKey: kubernetes.io/hostname
      
      # 初始化容器
      initContainers:
      - name: init-config
        image: busybox:1.36
        command:
        - sh
        - -c
        - |
          cp /configmap/zoo.cfg /shared/zoo.cfg
          cp /configmap/log4j.properties /shared/log4j.properties
          cp /configmap/init.sh /shared/init.sh
          chmod +x /shared/init.sh
        volumeMounts:
        - name: configmap
          mountPath: /configmap
        - name: shared
          mountPath: /shared
      
      # 主容器
      containers:
      - name: zookeeper
        image: zookeeper:3.9.1
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 2181
          name: client
        - containerPort: 2888
          name: server
        - containerPort: 3888
          name: leader-election
        env:
        - name: ZOOKEEPER_REPLICAS
          value: "3"
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        command:
        - sh
        - -c
        - |
          cp /shared/zoo.cfg /conf/zoo.cfg
          cp /shared/log4j.properties /conf/log4j.properties
          /shared/init.sh
          exec /docker-entrypoint.sh zkServer.sh start-foreground
        volumeMounts:
        - name: config
          mountPath: /conf
        - name: data
          mountPath: /data
        - name: datalog
          mountPath: /datalog
        - name: shared
          mountPath: /shared
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        # 存活探针
        livenessProbe:
          exec:
            command:
            - sh
            - -c
            - "zkServer.sh status || exit 1"
          initialDelaySeconds: 90
          periodSeconds: 15
          timeoutSeconds: 5
          failureThreshold: 5
        # 就绪探针
        readinessProbe:
          tcpSocket:
            port: 2181  # ⚠️ 关键:使用TCP探针而不是exec,避免DNS解析死锁
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3
      
      volumes:
      - name: config
        emptyDir: {}
      - name: configmap
        configMap:
          name: zookeeper-config
      - name: shared
        emptyDir: {}
  
  # 持久化存储声明模板
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "cbs-ssd"
      resources:
        requests:
          storage: 20Gi
  - metadata:
      name: datalog
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "cbs-ssd"
      resources:
        requests:
          storage: 10Gi

五、一键部署

5.1 部署脚本

创建 deploy.sh 部署脚本:

bash 复制代码
#!/bin/bash
set -e

echo "=========================================="
echo "ZooKeeper 集群部署脚本"
echo "=========================================="

# 检查 kubectl
if ! command -v kubectl &> /dev/null; then
    echo "错误: 未找到 kubectl 命令"
    exit 1
fi

# 检查集群连接
if ! kubectl cluster-info &> /dev/null; then
    echo "错误: 无法连接到 Kubernetes 集群"
    exit 1
fi

echo "✓ kubectl 连接正常"

# 1. 创建 Namespace
echo ""
echo "创建 Namespace..."
kubectl apply -f zookeeper-namespace.yaml
echo "✓ Namespace 已创建"

# 2. 创建 StorageClass
echo ""
echo "创建 StorageClass..."
if kubectl get storageclass cbs-ssd &>/dev/null; then
    echo "✓ StorageClass 'cbs-ssd' 已存在"
else
    # ⚠️ 注意:使用 immediate 版本,避免 VolumeBinding 超时问题
    kubectl apply -f storageclass-cbs-ssd-immediate.yaml
    echo "✓ StorageClass 已创建"
fi

# 3. 部署 ConfigMap
echo ""
echo "部署 ConfigMap..."
kubectl apply -f zookeeper-configmap.yaml
echo "✓ ConfigMap 已创建"

# 4. 部署 Service
echo ""
echo "部署 Service..."
kubectl apply -f zookeeper-service.yaml
echo "✓ Service 已创建"

# 5. 部署 StatefulSet
echo ""
echo "部署 StatefulSet..."
kubectl apply -f zookeeper-statefulset.yaml
echo "✓ StatefulSet 已创建"

# 6. 等待 Pod 就绪
echo ""
echo "等待 Pod 就绪(最多5分钟)..."
kubectl wait --for=condition=ready pod -l app=zookeeper -n zk --timeout=300s

echo ""
echo "=========================================="
echo "部署完成!"
echo "=========================================="
echo ""
echo "Pod 状态:"
kubectl get pods -l app=zookeeper -n zk
echo ""
echo "连接地址: zookeeper.zk.svc.cluster.local:2181"

5.2 部署文件清单

必需文件

  1. deploy.sh - 主部署脚本
  2. zookeeper-namespace.yaml - 命名空间配置
  3. zookeeper-configmap.yaml - ZooKeeper 配置(zoo.cfg、log4j.properties、init.sh
  4. zookeeper-service.yaml - Service 配置(Headless + ClusterIP)
  5. zookeeper-statefulset.yaml - StatefulSet 配置
  6. storageclass-cbs-ssd-immediate.yaml - StorageClass 配置(Immediate 模式,实际使用

⚠️ 重要 :必须使用 storageclass-cbs-ssd-immediate.yaml,不要使用 storageclass-cbs-ssd.yaml(WaitForFirstConsumer 模式可能导致 VolumeBinding 超时)。

5.3 执行部署

bash 复制代码
# 1. 上传文件到服务器
# 确保所有必需文件都在同一目录下

# 2. 设置执行权限
chmod +x deploy.sh

# 3. 执行部署
./deploy.sh

# 4. 验证部署
kubectl get pods -n zk
kubectl exec zookeeper-0 -n zk -- zkServer.sh status

5.4 手动分步部署

如果不使用脚本,可以按以下顺序手动部署:

bash 复制代码
# 1. 创建命名空间
kubectl apply -f zookeeper-namespace.yaml

# 2. 创建存储类(腾讯云 TKE)
# ⚠️ 注意:必须使用 immediate 版本,避免 VolumeBinding 超时问题
kubectl apply -f storageclass-cbs-ssd-immediate.yaml

# 3. 创建配置
kubectl apply -f zookeeper-configmap.yaml

# 4. 创建服务
kubectl apply -f zookeeper-service.yaml

# 5. 创建 StatefulSet
kubectl apply -f zookeeper-statefulset.yaml

六、验证部署

6.1 检查资源状态

bash 复制代码
# 查看 Pod 状态
kubectl get pods -l app=zookeeper -n zk

# 查看 StatefulSet
kubectl get statefulset zookeeper -n zk

# 查看 Service
kubectl get svc -n zk

# 查看 PVC
kubectl get pvc -n zk

期望输出:

复制代码
NAME          READY   STATUS    RESTARTS   AGE
zookeeper-0   1/1     Running   0          5m
zookeeper-1   1/1     Running   0          5m
zookeeper-2   1/1     Running   0          5m

6.2 验证集群状态

bash 复制代码
# 检查每个节点的角色
for i in 0 1 2; do
  echo "=== zookeeper-$i ==="
  kubectl exec zookeeper-$i -n zk -- zkServer.sh status
done

期望输出(1 个 leader,2 个 follower):

复制代码
=== zookeeper-0 ===
Mode: follower

=== zookeeper-1 ===
Mode: leader

=== zookeeper-2 ===
Mode: follower

6.3 测试客户端连接

bash 复制代码
# 进入任意 Pod 测试
kubectl exec -it zookeeper-0 -n zk -- zkCli.sh

# 在 zkCli 中执行
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]

[zk: localhost:2181(CONNECTED) 1] create /test "hello-cluster"
Created /test

[zk: localhost:2181(CONNECTED) 2] get /test
hello-cluster

[zk: localhost:2181(CONNECTED) 3] quit

七、客户端连接方式

7.1 Kubernetes 集群内部连接

同一命名空间(zk)内:

yaml 复制代码
env:
  - name: ZOOKEEPER_HOSTS
    value: "zookeeper:2181"  # 使用 ClusterIP Service 名称

其他命名空间:

yaml 复制代码
env:
  - name: ZOOKEEPER_HOSTS
    value: "zookeeper.zk.svc.cluster.local:2181"  # 完整 DNS 名称

重要说明 :连接地址中的 zookeeper 指的是 ClusterIP Service 的名称,不是 StatefulSet 名称。使用 Service 名称可以获得负载均衡,推荐用于生产环境。

7.2 Java 客户端示例

java 复制代码
import org.apache.zookeeper.ZooKeeper;

public class ZkClusterClient {
    public static void main(String[] args) throws Exception {
        // 从其他命名空间连接
        String zkHosts = "zookeeper.zk.svc.cluster.local:2181";
        
        // 或者直接连接所有节点
        // String zkHosts = "zookeeper-0.zookeeper-headless.zk.svc.cluster.local:2181,"
        //                + "zookeeper-1.zookeeper-headless.zk.svc.cluster.local:2181,"
        //                + "zookeeper-2.zookeeper-headless.zk.svc.cluster.local:2181";
        
        ZooKeeper zk = new ZooKeeper(zkHosts, 3000, null);
        System.out.println("连接状态: " + zk.getState());
        zk.close();
    }
}

八、运维管理

8.1 查看日志

bash 复制代码
# 查看所有 Pod 日志
kubectl logs -l app=zookeeper -n zk --tail=100

# 查看特定 Pod 日志
kubectl logs zookeeper-0 -n zk -f

8.2 扩缩容

bash 复制代码
# 扩容到 5 节点
kubectl scale statefulset zookeeper -n zk --replicas=5

# 缩容到 3 节点
kubectl scale statefulset zookeeper -n zk --replicas=3

注意:ZooKeeper 集群建议使用奇数节点(3、5、7),缩容前请确保数据已同步。

8.3 数据备份

bash 复制代码
# 备份数据
kubectl exec zookeeper-0 -n zk -- tar czf /tmp/zk-backup.tar.gz /data

# 复制到本地
kubectl cp zk/zookeeper-0:/tmp/zk-backup.tar.gz ./zk-backup.tar.gz

8.4 监控资源使用

bash 复制代码
# 查看 Pod 资源使用
kubectl top pods -l app=zookeeper -n zk

九、常见问题及解决方案

本文档基于实际部署经验总结,以下是部署过程中可能遇到的关键问题及解决方案。

9.1 StorageClass provisioner 名称错误

现象:PVC 一直处于 Pending 状态,CSI 驱动无法创建云硬盘

错误信息

复制代码
Events:
  Type     Reason              Age   From                         Message
  ----     ------              ----  ----                         -------
  Warning  ProvisioningFailed  5m    cbs.csi.tencent.com          failed to provision volume...

原因 :StorageClass 使用的 provisioner 名称 cbs.csi.tencent.com 与实际的 CSI 驱动名称 com.tencent.cloud.csi.cbs 不匹配

解决方案

  1. 检查实际的 CSI 驱动名称:

    bash 复制代码
    kubectl get csidriver
  2. 修改 StorageClass 配置:

    yaml 复制代码
    provisioner: com.tencent.cloud.csi.cbs  # 正确的 provisioner 名称
  3. 删除并重新创建 StorageClass:

    bash 复制代码
    kubectl delete storageclass cbs-ssd
    kubectl apply -f storageclass-cbs-ssd-immediate.yaml

9.2 VolumeBinding 超时

现象 :Pod 调度失败,错误信息 VolumeBinding: binding volumes: context deadline exceeded

原因WaitForFirstConsumer 模式下,VolumeBinding 插件在绑定卷时超时

解决方案

  1. 将 StorageClass 的 volumeBindingMode 改为 Immediate

  2. 由于 volumeBindingMode 不可变,需要删除并重新创建 StorageClass:

    bash 复制代码
    kubectl delete storageclass cbs-ssd
    kubectl apply -f storageclass-cbs-ssd-immediate.yaml

注意Immediate 模式虽然可能跨可用区,但功能正常且更稳定。如果需要确保同可用区,可以先部署成功后再考虑优化。

9.3 Pod 创建死锁

现象 :只有 zookeeper-0 Pod 创建,其他 Pod 无法创建

原因 :StatefulSet 默认按序创建(OrderedReady),必须等前一个 Pod 就绪才创建下一个。但 zookeeper-0 无法就绪,因为它需要连接其他 Pod 才能形成集群。

解决方案

在 StatefulSet 中添加 podManagementPolicy: Parallel,让所有 Pod 并行创建:

yaml 复制代码
spec:
  podManagementPolicy: Parallel  # 并行创建 Pod,避免死锁

9.4 DNS 解析死锁

现象 :Pod 无法解析其他 Pod 的 DNS 名称,日志显示 UnknownHostException

原因分析

  • Pod 未就绪 → Headless Service 不创建 DNS 记录
  • DNS 记录不存在 → ZooKeeper 无法解析其他 Pod
  • 无法连接其他 Pod → 无法形成集群 → readiness probe 失败 → Pod 无法就绪
  • 形成死锁循环

解决方案

readinessProbeexec(检查 ZooKeeper 状态)改为 tcpSocket(只检查端口是否监听):

yaml 复制代码
readinessProbe:
  tcpSocket:
    port: 2181  # 使用 TCP 探针,只检查端口是否监听
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 3
  failureThreshold: 3

这样 ZooKeeper 启动并监听端口后,Pod 即可就绪。Pod 就绪后,Headless Service 创建 DNS 记录,ZooKeeper 可以连接其他 Pod 并形成集群。

说明 :虽然 TCP 探针无法确保 ZooKeeper 完全就绪,但 livenessProbe 会检查 ZooKeeper 状态,确保服务健康。

十、故障排查

10.1 Pod 无法启动

bash 复制代码
# 查看 Pod 详情
kubectl describe pod zookeeper-0 -n zk

# 检查 PVC 状态
kubectl get pvc -n zk

# 检查 StorageClass
kubectl get storageclass

10.2 集群无法选举 Leader

bash 复制代码
# 检查 Pod 间网络连通性
kubectl exec zookeeper-0 -n zk -- nc -zv zookeeper-1.zookeeper-headless.zk.svc.cluster.local 2888

# 检查 myid 配置
kubectl exec zookeeper-0 -n zk -- cat /data/myid

# 检查 zoo.cfg 配置
kubectl exec zookeeper-0 -n zk -- cat /conf/zoo.cfg

# 检查 DNS 解析
kubectl exec zookeeper-0 -n zk -- nslookup zookeeper-1.zookeeper-headless.zk.svc.cluster.local

10.3 PVC Pending

bash 复制代码
# 检查 StorageClass 是否正确
kubectl get storageclass

# 检查 StorageClass 的 provisioner 是否匹配
kubectl get storageclass cbs-ssd -o yaml | grep provisioner

# 检查 CSI 插件是否正常
kubectl get pods -n kube-system | grep cbs

# 查看 PVC 事件
kubectl describe pvc data-zookeeper-0 -n zk

10.4 检查部署配置

bash 复制代码
# 验证关键配置
echo "=== StorageClass ==="
kubectl get storageclass cbs-ssd -o yaml | grep -E "provisioner|volumeBindingMode"

echo "=== StatefulSet ==="
kubectl get statefulset zookeeper -n zk -o yaml | grep -E "podManagementPolicy|readinessProbe" -A 5

echo "=== Service ==="
kubectl get svc -n zk

echo "=== Endpoints ==="
kubectl get endpoints zookeeper-headless -n zk

十一、卸载清理

bash 复制代码
# 删除 StatefulSet
kubectl delete statefulset zookeeper -n zk

# 删除 Service
kubectl delete svc zookeeper zookeeper-headless -n zk

# 删除 ConfigMap
kubectl delete configmap zookeeper-config -n zk

# 删除 PVC(会删除数据!)
kubectl delete pvc -l app=zookeeper -n zk

# 删除命名空间
kubectl delete namespace zk

# 删除 StorageClass(可选)
kubectl delete storageclass cbs-ssd

十二、关键配置说明与注意事项

12.1 StorageClass provisioner

重要 :必须使用 com.tencent.cloud.csi.cbs,不是 cbs.csi.tencent.com

  • 错误的 provisioner 名称会导致 PVC 一直处于 Pending 状态
  • 可以通过 kubectl get csidriver 查看实际的 CSI 驱动名称

12.2 volumeBindingMode

当前使用Immediate 模式

  • 优点:避免 VolumeBinding 超时问题,部署更稳定
  • 缺点:可能跨可用区(但功能正常)

备选WaitForFirstConsumer 模式

  • 优点:确保云硬盘与 Pod 在同一可用区
  • 缺点:在某些 TKE 版本中可能超时

建议 :先使用 Immediate 模式部署成功,后续如需优化可用区分布,再考虑调整。

12.3 podManagementPolicy

当前使用Parallel 模式

  • 优点:所有 Pod 同时创建,避免死锁
  • 缺点:可能同时创建多个 Pod,资源消耗较大

说明 :ZooKeeper 集群需要多个节点才能形成集群,如果使用默认的 OrderedReady 模式,第一个 Pod 无法就绪(因为无法连接其他 Pod),会导致后续 Pod 无法创建,形成死锁。

12.4 readinessProbe

当前使用 :TCP 探针(tcpSocket

  • 优点:只检查端口是否监听,不检查 ZooKeeper 状态,避免 DNS 死锁
  • 缺点:无法确保 ZooKeeper 完全就绪(但 livenessProbe 会检查)

说明 :如果使用 exec 探针检查 ZooKeeper 状态,会出现以下死锁:

  • Pod 未就绪 → Headless Service 不创建 DNS 记录
  • DNS 记录不存在 → ZooKeeper 无法解析其他 Pod
  • 无法连接其他 Pod → 无法形成集群 → readiness probe 失败 → Pod 无法就绪

使用 TCP 探针可以打破这个循环。

12.5 其他注意事项

  1. 费用:部署会自动创建 90Gi SSD 云硬盘(3个 Pod × 30Gi),按量计费,约 72-108 元/月
  2. 停止计费:删除 PVC 即可停止计费并删除云硬盘
  3. 数据备份:删除 PVC 会永久删除数据,请提前备份
  4. 节点要求:至少 3 个可用节点,每个节点至少 1 核 CPU、2Gi 内存
  5. 系统盘:50GB 系统盘通常足够,ZooKeeper 数据存储在独立的云硬盘上
  6. 连接地址 :客户端应使用 ClusterIP Service 名称 zookeeper,不是 StatefulSet 名称

十三、费用说明(腾讯云)

资源 规格 数量 预估费用
CBS 云硬盘 SSD 20Gi(数据) 3 ~54元/月
CBS 云硬盘 SSD 10Gi(日志) 3 ~27元/月
存储总计 ~81元/月
计算资源 CPU 250m-500m,内存 512Mi-1Gi 3 Pod 包含在节点费用中

注意事项

  • 以上为按量计费参考价格,实际费用以腾讯云账单为准
  • 部署会自动创建 90Gi SSD 云硬盘(3个 Pod × 30Gi),按量计费
  • 删除 PVC 即可停止计费并删除云硬盘
  • 重要:删除 PVC 会永久删除数据,请提前备份

十四、客户端连接说明

13.1 连接地址

客户端连接地址(使用 ClusterIP Service):

  • 完整地址zookeeper.zk.svc.cluster.local:2181
  • 简写zookeeper.zk:2181(同一集群内)
  • 同一命名空间zookeeper:2181(如果客户端也在 zk 命名空间)

重要说明 :上述连接地址中的 zookeeper 指的是 ClusterIP Service 的名称(不是 StatefulSet 名称)。

13.2 Service 说明

  • zookeeper (ClusterIP Service):用于客户端访问,提供负载均衡,推荐用于生产环境
  • zookeeper-headless (Headless Service):用于 StatefulSet 内部 Pod 间通信和 DNS 解析,不用于客户端连接
  • StatefulSet 名称zookeeper(仅用于管理,不用于连接)

13.3 客户端连接测试

使用 zkCli.sh 连接 ZooKeeper 进行测试:

bash 复制代码
# 连接到 ZooKeeper(在 Pod 内部)
kubectl exec -it zookeeper-0 -n zk -- zkCli.sh -server localhost:2181

# 或者从集群外部通过 Service 连接(如果配置了 NodePort/LoadBalancer)
zkCli.sh -server zookeeper.zk.svc.cluster.local:2181

正常连接成功的标志

  • ✅ 看到 Session establishment complete
  • ✅ 看到 [zk: localhost:2181(CONNECTED) 0] 提示符
  • ✅ 看到 WatchedEvent state:SyncConnected
  • ✅ 可以执行 ZooKeeper 命令(如 ls /

退出 CLI

  • 输入 quit 或按 Ctrl+C 退出
  • 退出时显示 command terminated with exit code 130 是正常的(表示被 SIGINT 中断)

十五、总结

本文详细介绍了在 Kubernetes(腾讯云 TKE)上使用 StatefulSet 部署 ZooKeeper 集群的完整流程,基于实际部署经验总结。

关键配置要点

  1. StorageClass 配置

    • provisioner 必须使用 com.tencent.cloud.csi.cbs(不是 cbs.csi.tencent.com
    • volumeBindingMode 使用 Immediate 模式,避免 VolumeBinding 超时问题
  2. StatefulSet 配置

    • podManagementPolicy: Parallel:并行创建 Pod,避免死锁
    • readinessProbe 使用 tcpSocket:避免 DNS 解析死锁
    • livenessProbe 使用 exec:确保 ZooKeeper 服务健康
  3. Service 配置

    • Headless Service:用于 Pod 间通信和 DNS 解析
    • ClusterIP Service:用于客户端访问,提供负载均衡

部署架构

  1. 架构设计:使用 Headless Service 实现 Pod 间通信,ClusterIP Service 提供客户端访问
  2. 配置管理:通过 ConfigMap 管理配置文件和初始化脚本
  3. 持久化存储:使用 volumeClaimTemplates 自动创建 PVC
  4. 高可用保证:Pod 反亲和性确保分布在不同节点

部署优势

  • ✅ 自动故障恢复
  • ✅ 方便扩缩容
  • ✅ 数据持久化
  • ✅ 统一管理
  • ✅ 解决了实际部署中的关键问题(provisioner 名称、VolumeBinding 超时、Pod 死锁、DNS 死锁)

部署检查清单

部署成功后,应该看到:

bash 复制代码
# Pod 状态
$ kubectl get pods -n zk
NAME          READY   STATUS    RESTARTS   AGE
zookeeper-0   1/1     Running   0          5m
zookeeper-1   1/1     Running   0          5m
zookeeper-2   1/1     Running   0          5m

# 集群状态(1 leader + 2 followers)
$ kubectl exec zookeeper-0 -n zk -- zkServer.sh status
Mode: follower  # 或 leader

# Endpoints
$ kubectl get endpoints zookeeper-headless -n zk
NAME                 ENDPOINTS                                                    AGE
zookeeper-headless   10.21.72.185:2888,10.21.72.77:2888,10.21.72.95:2888 + 6 more   57m

相关阅读:

相关推荐
Anastasiozzzz2 小时前
RabbitMQ介绍与基础架构
分布式·rabbitmq
开发者联盟league2 小时前
k8s 创建 serviceAccount 并配置自定义ClusterRole 再授权用于 api-server 访问
云原生·容器·kubernetes
【赫兹威客】浩哥2 小时前
【赫兹威客】完全分布式HBase测试教程
数据库·分布式·hbase
Wpa.wk3 小时前
Docker原理和使用场景(网络模式和分布式UI自动化环境部署)
linux·经验分享·分布式·测试工具·docker·性能监控
2401_840192273 小时前
ZooKeeper 单机部署指南
分布式·zookeeper·云原生
yumgpkpm3 小时前
Cloudera CDP/CDH/Hadoop 信创大模型AI时代何去何从?
人工智能·hive·hadoop·elasticsearch·zookeeper·kafka·cloudera
weixin_462446233 小时前
一键安装单节点 Zookeeper 3.8.5(附完整 Bash 脚本)
zookeeper·debian·bash
Go高并发架构_王工3 小时前
Kafka监控体系构建:指标收集与可视化方案
分布式·kafka·linq
【赫兹威客】浩哥3 小时前
【赫兹威客】完全分布式Hive(on Spark)测试教程
hive·分布式·spark