在 Kubernetes 上为 ClickHouse Operator 部署 ZooKeeper:从快速启动到高级配置实战指南
【免费下载链接】clickhouse-operatorAltinity Kubernetes Operator for ClickHouse creates, configures and manages ClickHouse® clusters running on Kubernetes项目地址: https://gitcode.com/GitHub_Trending/cl/clickhouse-operator
本文是 clickhouse-operator 仓库中 ZooKeeper 环境搭建的完整实战指南,覆盖从"一键快速启动"(Quick Start)到"逐项精细配置"(Advanced Setup)两条部署路径,并深入讲解 Service、Headless Service、PodDisruptionBudget、StorageClass、StatefulSet 等关键资源的配置要点与 ZooKeeper 集群的验证方法。读完本文,你将能够在 Kubernetes 上独立完成单节点或三节点 ZooKeeper 集群的部署、存储方案选型与运行状态检查,为 ClickHouse 复制表等高可用特性提供可靠的协调服务底座。
一、为什么 ClickHouse 集群需要 ZooKeeper
ClickHouse 的复制表(Replicated Table)依赖 ZooKeeper 来协调副本间的元数据、日志序号与 leader 选举。在 docs/replication_setup.md 与docs/chi-examples/目录下的04-replication-zookeeper-*.yaml系列示例中,均可以看到 ZooKeeper 作为复制协调服务被显式引用。因此,在生产环境为 ClickHouse Operator 部署一套高可用的 ZooKeeper 是前置条件之一。
本文档对应的部署脚本与清单全部位于 deploy/zookeeper/zookeeper-manually 目录,采用手工(manually)方式部署。仓库同时提供 zookeeper-with-zookeeper-operator 的 Operator 方式部署目录,可自行对比选用。
本文描述的 ZooKeeper 安装会创建/配置以下 Kubernetes 资源:
| 资源 | 作用 |
|---|---|
| Service | 为客户端访问 ZooKeeper 提供统一入口 |
| Headless Service | 为每个 ZooKeeper 节点提供独立 DNS 名称 |
| PodDisruptionBudget(中断预算) | 限制任意时刻可离线的 ZooKeeper Pod 数量 |
| StorageClass(可选) | 指定 ZooKeeper 数据存储使用的存储类 |
| StatefulSet | 管理并伸缩 ZooKeeper Pod 集合 |
ZooKeeper 安装分为两种方式:
- Quick start(快速启动):直接运行,不做过多询问,适合测试。
- Advanced setup(高级设置):精细配置存储类、副本数等内部细节,适合生产。
二、快速启动(Quick Start)
快速启动提供两种存储风格:
- 持久化卷(Persistent Volume):适合 AWS 等云环境,文件位于 deploy/zookeeper/zookeeper-manually/quick-start-persistent-volume。
- 本地 emptyDir 存储:适合单机本地运行,但没有真正的持久化能力(Pod 重建即数据丢失),文件位于 deploy/zookeeper/zookeeper-manually/quick-start-volume-emptyDir。
每种风格都提供两种集群规模:
- 1 节点 ZooKeeper 集群(
zookeeper-1-前缀文件):不提供故障切换(failover)能力。 - 3 节点 ZooKeeper 集群(
zookeeper-3-前缀文件):提供故障切换能力,生产推荐。
选型建议:
- 需要在 AWS 或任何其他云厂商测试:推荐使用 quick-start-persistent-volume 的持久化存储。
- 本地测试:可以使用 quick-start-volume-emptyDir 的
emptyDir。
2.1 脚本方式安装
以 AWS 上 1 节点 ZooKeeper 集群为例,使用 quick-start-persistent-volume 目录下的脚本。仓库同时提供创建和删除两个脚本:
- 创建:zookeeper-1-node-create.sh
- 删除:zookeeper-1-node-delete.sh
从脚本实现可以看到命名空间的可定制机制(默认zoo1ns):
# zookeeper-1-node-create.sh ZK_NAMESPACE="${ZK_NAMESPACE:-zoo1ns}" kubectl create namespace "${ZK_NAMESPACE}" kubectl --namespace="${ZK_NAMESPACE}" apply -f "${CUR_DIR}/zookeeper-1-node.yaml"即默认会创建名为zoo1ns的命名空间并应用zookeeper-1-node.yaml。若需自定义命名空间,可在执行前设置环境变量:
ZK_NAMESPACE=myzkns ./zookeeper-1-node-create.sh删除脚本则直接删除整个命名空间:
# zookeeper-1-node-delete.sh kubectl delete namespace "${ZK_NAMESPACE}"3 节点集群对应使用 zookeeper-3-nodes-create.sh 与 zookeeper-3-nodes-delete.sh,emptyDir风格目录下也有对应脚本。
2.2 手动方式安装
如果希望手动部署 ZooKeeper,按以下步骤执行。
创建命名空间
kubectl create namespace zoo1ns部署 ZooKeeper
kubectl apply -f zookeeper-1-node.yaml -n zoo1ns其中zookeeper-1-node.yaml位于 deploy/zookeeper/zookeeper-manually/quick-start-persistent-volume/zookeeper-1-node.yaml。该清单文件是一个多文档 YAML,一次性包含了 Service、Headless Service、PodDisruptionBudget 与 StatefulSet 四类资源(这也是快速启动"一条命令全部搞定"的体现)。
现在 ZooKeeper 应该已经启动运行,可以前往本文"验证 ZooKeeper 集群"一节查看如何确认集群状态。
IMPORTANT:快速启动的 ZooKeeper 安装主要面向测试目的。如需精细化调优的 ZooKeeper 部署,请参考下一节"高级设置"。
三、高级设置(Advanced Setup)
高级设置的清单文件位于 deploy/zookeeper/zookeeper-manually/advanced 目录,所有资源被拆分到不同的独立文件中,便于单独修改和按需配置。
高级设置同样提供两种存储选项:
- 持久化卷(Persistent Volume)
- emptyDir 卷(Volume)
每种选项都提供创建与删除脚本:
- 持久化卷:zookeeper-persistent-volume-create.sh 与 zookeeper-persistent-volume-delete.sh
- emptyDir 卷:zookeeper-volume-emptyDir-create.sh 与 zookeeper-volume-emptyDir-delete.sh
从创建脚本可以清楚看到资源的应用顺序(以持久化卷为例):
# zookeeper-persistent-volume-create.sh ZK_NAMESPACE="${ZK_NAMESPACE:-zoons}" YAML_FILES_LIST="\ 01-service-client-access.yaml \ 02-headless-service.yaml \ 03-pod-disruption-budget.yaml \ 04-storageclass-zookeeper.yaml \ 05-stateful-set-persistent-volume.yaml\ " source "${CUR_DIR}/zookeeper-create-universal.sh"即默认命名空间为zoons,按编号顺序依次应用 5 个清单文件(通用逻辑封装在 zookeeper-create-universal.sh 中)。以下为逐步手动执行的详细说明。
3.1 创建命名空间
kubectl create namespace zoons后续所有资源都会创建在该命名空间内。
3.2 Zookeeper Service(客户端访问服务)
kubectl apply -f 01-service-client-access.yaml -n zoons预期输出:
service/zookeeper created该 Service 为客户端访问所有 ZooKeeper 节点提供统一的 DNS 名称(形如zookeeper.zoons)。清单位于 01-service-client-access.yaml,核心配置如下:
apiVersion: v1 kind: Service metadata: # DNS would be like zookeeper.zoons name: zookeeper labels: app: zookeeper spec: ports: - port: 2181 name: client - port: 7000 name: prometheus selector: app: zookeeper what: node其中 2181 是 ZooKeeper 客户端端口,7000 是 Prometheus 指标端口,selector 与 StatefulSet 的 Pod 标签app: zookeeper, what: node相对应。
3.3 Zookeeper Headless Service(无头服务)
kubectl apply -f 02-headless-service.yaml -n zoons预期输出:
service/zookeeper-nodes created无头服务(Headless Service,clusterIP: None)为每个 ZooKeeper 节点提供独立的 DNS 名称,是 StatefulSet 稳定网络标识的基础。清单位于 02-headless-service.yaml:
apiVersion: v1 kind: Service metadata: # DNS would be like zookeeper-0.zookeepers.etc name: zookeepers labels: app: zookeeper spec: ports: - port: 2888 name: server - port: 3888 name: leader-election clusterIP: None selector: app: zookeeper what: node其中 2888 是 ZooKeeper 节点间数据同步端口,3888 是 leader 选举端口,均用于节点间通信而非客户端访问。
3.4 PodDisruptionBudget(中断预算)
kubectl apply -f 03-pod-disruption-budget.yaml -n zoons预期输出:
poddisruptionbudget.policy/zookeeper-pod-distribution-budget created中断预算向 k8s 声明任意时刻最多允许多少个 ZooKeeper 节点离线,用于在节点维护等自愿中断场景下保护 ZooKeeper 的法定人数(quorum)。清单位于 03-pod-disruption-budget.yaml:
apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: zookeeper-pod-distribution-budget spec: selector: matchLabels: app: zookeeper maxUnavailable: 13.5 StorageClass(存储类)
这部分并非直接照抄即可,可能需要与 k8s 实例管理员沟通确认。
首先需要决定 ZooKeeper 使用哪种存储:
- Persistent Volume(持久化卷):走
05-stateful-set-persistent-volume.yaml。 - emptyDir 卷:走
05-stateful-set-volume-emptyDir.yaml。
简单来说,StorageClass 用于将 Persistent Volume 绑定在一起——这些 PV 要么由 k8s 管理员手工创建,要么由 Provisioner(动态供应)自动创建。无论哪种方式,PV 都是由 k8s 在外部提供给待部署应用的。因此应用必须在自己的持久化卷声明(Persistent Volume Claim)中向 k8s 请求一个具体的StorageClass 名称,该名称需要向 k8s 管理员索取,并填写在 StatefulSet 配置的.spec.volumeClaimTemplates.storageClassName参数中。对应清单文件为:
- emptyDir 版:05-stateful-set-volume-emptyDir.yaml
- 持久化卷版:05-stateful-set-persistent-volume.yaml
仓库的 04-storageclass-zookeeper.yaml 提供了一个默认全部注释掉的 StorageClass 模板,用于在集群没有默认存储类时启用:
#apiVersion: storage.k8s.io/v1 #kind: StorageClass #metadata: # name: storageclass-zookeeper #provisioner: kubernetes.io/no-provisioner ## Choose desired 'volumeBindingMode' ##volumeBindingMode: WaitForFirstConsumer #volumeBindingMode: Immediate注意其 provisioner 为kubernetes.io/no-provisioner,即该模板面向"管理员预先准备 PV"的静态供应场景;如需动态供应,应替换为集群实际的 provisioner,并选择合适的volumeBindingMode(WaitForFirstConsumer或Immediate)。
3.6 StatefulSet(有状态应用集)
根据存储偏好编辑对应清单:
- 05-stateful-set-volume-emptyDir.yaml
- 05-stateful-set-persistent-volume.yaml
选择 emptyDir 存储时:确保.spec.template.spec.containers.volumes生效,形如:
volumes: - name: datadir-volume emptyDir: medium: "" #accepted values: empty str (means node's default medium) or Memory sizeLimit: 1Gi同时确保.spec.volumeClaimTemplates处于注释状态。
选择 Persistent Volume 存储时:确保.spec.template.spec.containers.volumes处于注释状态,并取消.spec.volumeClaimTemplates的注释:
volumeClaimTemplates: - metadata: name: datadir-volume spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi ## storageClassName has to be coordinated with k8s admin and has to be created as a `kind: StorageClass` resource storageClassName: storageclass-zookeeper并确保storageClassName(本例为storageclass-zookeeper)已按"存储类"一节正确填写。关于storageClassName字段还有两个值得注意的语义(清单文件注释中已明确):
- 不指定
storageClassName:表示使用默认存储类; - 指定为空字符串
"":表示不使用动态供应(do not use dynamic provisioning)。
StatefulSet 内部结构速览
持久化卷版与 emptyDir 版两份 StatefulSet 清单(05-stateful-set-persistent-volume.yaml)除存储部分外主体一致,关键设计如下,可帮助理解其工作原理:
apiVersion: apps/v1 kind: StatefulSet metadata: # nodes would be named as zookeeper-0, zookeeper-1, zookeeper-2 name: zookeeper spec: selector: matchLabels: app: zookeeper serviceName: zookeepers replicas: 3 updateStrategy: type: RollingUpdate podManagementPolicy: ParallelserviceName: zookeepers绑定到前面创建的无头服务,Pod 因此获得zookeeper-0.zookeepers.zoons这类稳定 DNS;replicas: 3对应三节点 ZooKeeper 集群;podManagementPolicy: Parallel允许并行创建/更新所有 Pod(区别于快速启动版使用的OrderedReady,快速启动 zookeeper-1-node.yaml 中为OrderedReady,即按顺序逐个就绪)。
容器启动命令会动态生成 ZooKeeper 配置/conf/zoo.cfg,包括clientPort=2181、tickTime=2000、initLimit=300、syncLimit=10、maxClientCnxns=2000、maxSessionTimeout=60000000、autopurge.snapRetainCount=10、autopurge.purgeInterval=1、4lw.commands.whitelist=*等参数,并通过解析 Pod 名称序号(zookeeper-0→ myid=1)写入myid文件、追加server.N=host:port:port配置,最终以zkServer.sh start-foreground前台方式启动。同时启用 Prometheus 指标(7000 端口)并通过 Pod 注解prometheus.io/scrape: 'true'、prometheus.io/port: '7000'暴露抓取端点。此外还配置了:
- 探针:readiness 与 liveness 均通过向
127.0.0.1:2181发送ruok并检查返回imok来实现,用于判断 ZooKeeper 是否存活并就绪; - 安全上下文:
runAsUser: 1000与fsGroup: 1000,以非特权用户运行; - 反亲和:
podAntiAffinity基于kubernetes.io/hostname强制要求不同 ZooKeeper Pod 调度到不同节点,避免单点故障; - 数据目录:
volumeMounts将datadir-volume挂载到/var/lib/zookeeper。
应用 StatefulSet
清单准备就绪后,用kubectl应用:
kubectl apply -f 05-stateful-set.yaml -n zoons预期输出:
statefulset.apps/zookeeper-node created(注:实际清单文件名为05-stateful-set-persistent-volume.yaml或05-stateful-set-volume-emptyDir.yaml,按所选存储方案替换上面命令中的文件名即可。)
现在可以查看部署在 k8s 中的 ZooKeeper 集群了。
四、验证 ZooKeeper 集群(Explore ZooKeeper Cluster)
4.1 DNS 名称
以高级设置的三节点集群为例,在zoons命名空间内,预期有 3 个 Pod,名称分别为:
zookeeper-0 zookeeper-1 zookeeper-2这些 Pod 拥有如下短 DNS 名称:
zookeeper-0.zookeepers.zoons zookeeper-1.zookeepers.zoons zookeeper-2.zookeepers.zoons其中zookeepers是 ZooKeeper 无头服务(Headless Service)的名称,zoons是 ZooKeeper 命名空间的名称。
完整 DNS 名称(FQDN)为:
zookeeper-0.zookeepers.zoons.svc.cluster.local zookeeper-1.zookeepers.zoons.svc.cluster.local zookeeper-2.zookeepers.zoons.svc.cluster.local这些稳定的网络标识正是 ZooKeeper 集群节点间互相发现、以及 ClickHouse 复制表协调时所依赖的地址基础。若 ClickHouse 需要接入该 ZooKeeper,可在 CHI 的spec.configuration.clusters中通过zookeeper配置段引用这些地址(参见 04-replication-zookeeper-01-minimal.yaml 等复制示例)。
4.2 资源检查
查看 Pod
kubectl get pod -n zoons预期输出类似:
NAME READY STATUS RESTARTS AGE zookeeper-0 1/1 Running 0 9m2s zookeeper-1 1/1 Running 0 9m2s zookeeper-2 1/1 Running 0 9m2s查看 Service
kubectl get service -n zoons预期输出类似:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE zookeeper ClusterIP 10.108.36.44 <none> 2181/TCP 168m zookeepers ClusterIP None <none> 2888/TCP,3888/TCP 31m可以看到zookeeper(客户端访问入口)暴露 2181 端口,zookeepers(无头服务)为ClusterIP: None并暴露 2888/3888 节点间通信端口。
查看 StatefulSet
kubectl get statefulset -n zoons预期输出类似:
NAME READY AGE zookeepers 3/3 10m如果一切正常,说明 ZooKeeper 集群已经成功运行。
五、与 ClickHouse Operator 的集成与进一步参考
- 本文对应的全部手工部署清单与脚本位于 deploy/zookeeper/zookeeper-manually,其中 advanced 目录还提供了
zookeeper-watch.sh等辅助脚本供观察部署过程。 - 若希望以 Operator 方式管理 ZooKeeper,可参考 zookeeper-with-zookeeper-operator 目录下的安装脚本与 CR 示例(1 节点/3 节点,含自定义探针版本)。
- 将 ZooKeeper 与 ClickHouse 复制表结合使用的完整流程,请阅读 docs/replication_setup.md 及 docs/chi-examples/ 中
04-replication-zookeeper-*.yaml系列示例。 - 监控方面,ZooKeeper 已通过 7000 端口暴露 Prometheus 指标,仓库的 deploy/prometheus/prometheus-alert-rules-zookeeper.yaml 提供了现成的告警规则示例,Grafana 仪表盘模板见 grafana-dashboard/Zookeeper_dashboard.json。
- 若计划从 ZooKeeper 迁移到 ClickHouse Keeper,可参考 docs/keeper_migration_from_23_to_24.md 与 docs/keeper_reference.md。
总结
在 clickhouse-operator 项目中,ZooKeeper 的部署路径清晰可循:测试场景走 快速启动(一条命令即可拉起 1 节点或 3 节点集群),生产场景走 高级设置(按 Service → Headless Service → PDB → StorageClass → StatefulSet 的顺序逐项精细配置,并显式声明存储方案)。部署完成后,通过 Pod/Service/StatefulSet 的kubectl get输出以及ruok/mntr等四字命令即可确认集群健康状态,随后即可将 ZooKeeper 地址接入 ClickHouse 复制集群,构建完整的高可用数据链路。
【免费下载链接】clickhouse-operatorAltinity Kubernetes Operator for ClickHouse creates, configures and manages ClickHouse® clusters running on Kubernetes项目地址: https://gitcode.com/GitHub_Trending/cl/clickhouse-operator
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考