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 环境准备
- 已创建腾讯云 TKE 集群(或其他 K8s 集群)
- 已配置 kubectl 连接到集群
- 集群节点数 >= 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 # 允许扩容
⚠️ 重要配置说明:
provisioner 名称 :必须使用
com.tencent.cloud.csi.cbs,这是腾讯云 TKE 实际使用的 CSI 驱动名称。如果使用错误的名称(如cbs.csi.tencent.com),PVC 将一直处于 Pending 状态。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 部署文件清单
必需文件:
- deploy.sh - 主部署脚本
- zookeeper-namespace.yaml - 命名空间配置
- zookeeper-configmap.yaml - ZooKeeper 配置(zoo.cfg、log4j.properties、init.sh)
- zookeeper-service.yaml - Service 配置(Headless + ClusterIP)
- zookeeper-statefulset.yaml - StatefulSet 配置
- 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 不匹配
解决方案:
-
检查实际的 CSI 驱动名称:
bashkubectl get csidriver -
修改 StorageClass 配置:
yamlprovisioner: com.tencent.cloud.csi.cbs # 正确的 provisioner 名称 -
删除并重新创建 StorageClass:
bashkubectl delete storageclass cbs-ssd kubectl apply -f storageclass-cbs-ssd-immediate.yaml
9.2 VolumeBinding 超时
现象 :Pod 调度失败,错误信息 VolumeBinding: binding volumes: context deadline exceeded
原因 :WaitForFirstConsumer 模式下,VolumeBinding 插件在绑定卷时超时
解决方案:
-
将 StorageClass 的
volumeBindingMode改为Immediate -
由于
volumeBindingMode不可变,需要删除并重新创建 StorageClass:bashkubectl 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 无法就绪
- 形成死锁循环
解决方案 :
将 readinessProbe 从 exec(检查 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 其他注意事项
- 费用:部署会自动创建 90Gi SSD 云硬盘(3个 Pod × 30Gi),按量计费,约 72-108 元/月
- 停止计费:删除 PVC 即可停止计费并删除云硬盘
- 数据备份:删除 PVC 会永久删除数据,请提前备份
- 节点要求:至少 3 个可用节点,每个节点至少 1 核 CPU、2Gi 内存
- 系统盘:50GB 系统盘通常足够,ZooKeeper 数据存储在独立的云硬盘上
- 连接地址 :客户端应使用 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 集群的完整流程,基于实际部署经验总结。
关键配置要点
-
StorageClass 配置:
- provisioner 必须使用
com.tencent.cloud.csi.cbs(不是cbs.csi.tencent.com) - volumeBindingMode 使用
Immediate模式,避免 VolumeBinding 超时问题
- provisioner 必须使用
-
StatefulSet 配置:
podManagementPolicy: Parallel:并行创建 Pod,避免死锁readinessProbe使用tcpSocket:避免 DNS 解析死锁livenessProbe使用exec:确保 ZooKeeper 服务健康
-
Service 配置:
- Headless Service:用于 Pod 间通信和 DNS 解析
- ClusterIP Service:用于客户端访问,提供负载均衡
部署架构
- 架构设计:使用 Headless Service 实现 Pod 间通信,ClusterIP Service 提供客户端访问
- 配置管理:通过 ConfigMap 管理配置文件和初始化脚本
- 持久化存储:使用 volumeClaimTemplates 自动创建 PVC
- 高可用保证: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
相关阅读: