在k8s环境部署Apache zookeeper3.9.5,高可用,多pod

说明

在k8s环境部署Apache Zookeeper 3.9.5,高可用,多pod,适配Hadoop。

准备配置文件

注意,配置文件里使用了hadoop作为namespace,需修改为自己期望的namespace

v1.yaml内容如下

bash 复制代码
# ========== ZooKeeper 3节点集群 ==========
# 用于 HDFS NameNode HA 自动故障转移(ZKFC)。若用于其他环境,应当修改启动命令中的hadoop字段为真实namespace
# 使用官方 zookeeper:3.9.5 镜像
# 3.9.x 变化:基于 JDK 17+,配置项 ZOO_4LINEAR_ENABLE 已移除(默认启用)

---
apiVersion: v1
kind: Service
metadata:
  name: zookeeper
  labels:
    app: zookeeper
spec:
  clusterIP: None   # Headless Service,用于 Pod DNS 发现
  publishNotReadyAddresses: true   # 让等待方能在 Pod 未 Ready 时也解析到 IP
  ports:
    - port: 2181
      name: client
    - port: 2888
      name: peer
    - port: 3888
      name: election
  selector:
    app: zookeeper
---
apiVersion: v1
kind: Service
metadata:
  name: zookeeper-external
  labels:
    app: zookeeper
spec:
  type: NodePort
  ports:
    - port: 2181
      name: client
      targetPort: 2181
      # nodePort 留空,系统自动分配
  selector:
    app: zookeeper
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: zookeeper-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: zookeeper
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: zookeeper
spec:
  serviceName: zookeeper
  replicas: 3
  # OrderedReady(默认):Pod 按序启动,确保前一个就绪后才启动下一个
  # 必须用 OrderedReady 而非 Parallel:Parallel 会导致 3 个 Pod 同时启动,
  # 此时 Headless Service DNS 尚未完全传播,ZK 节点找不到 Peer 无法形成
  # Quorum,进程崩溃退出 (exit code 14),产生多次无效重启。
  # OrderedReady 保证 zookeeper-1 启动时 zookeeper-0 的 DNS 已就绪。
  podManagementPolicy: OrderedReady
  selector:
    matchLabels:
      app: zookeeper
  template:
    metadata:
      labels:
        app: zookeeper
    spec:
      subdomain: zookeeper
      priorityClassName: hadoop-namenode-high

      # ZK 节点分散到不同 Node(保证仲裁安全)
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: zookeeper
              topologyKey: kubernetes.io/hostname

      terminationGracePeriodSeconds: 30

      containers:
      - name: zookeeper
        # ZK 3.9.5 官方镜像(华为云镜像源)
        # 重要:3.9.5 Docker entrypoint 在使用 ZOO_SERVERS 时不写 clientPort 到 zoo.cfg,
        # 导致 plain 客户端端口被禁用 (plain=disabled),端口 2181 不监听。
        # 解决方案:覆盖 command,手动写完整 zoo.cfg(含 clientPort=2181),然后前台启动 ZK。
        image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/library/zookeeper:3.9.5
        imagePullPolicy: IfNotPresent

        command:
          - /bin/bash
          - -ec
          - |
            # ===== 阶段1:准备环境 =====
            set -e
            mkdir -p /data /datalog /conf /logs

            # 写入完整的 zoo.cfg(显式包含 clientPort=2181)
            cat > /conf/zoo.cfg << 'ZKCFG'
            tickTime=2000
            dataDir=/data
            dataLogDir=/datalog
            clientPort=2181
            initLimit=10
            syncLimit=2
            autopurge.snapRetainCount=3
            autopurge.purgeInterval=1
            server.1=zookeeper-0.zookeeper.hadoop:2888:3888
            server.2=zookeeper-1.zookeeper.hadoop:2888:3888
            server.3=zookeeper-2.zookeeper.hadoop:2888:3888
            ZKCFG

            echo "=== /conf/zoo.cfg ==="
            cat /conf/zoo.cfg
            echo "=== /data/myid ==="
            cat /data/myid

            # JDK 17 反射权限(Hadoop ZKFC 依赖)
            export SERVER_JVMFLAGS="--add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED"

            # ===== 阶段2:SIGTERM 陷阱(转发给 ZK 进程,确保优雅关闭) =====
            ZK_PID=""
            _term() {
              echo "$(date): Received SIGTERM, stopping ZooKeeper..."
              if [ -n "$ZK_PID" ] && kill -0 "$ZK_PID" 2>/dev/null; then
                kill -TERM "$ZK_PID"
                wait "$ZK_PID" 2>/dev/null
              fi
              exit 0
            }
            trap _term TERM INT

            # ===== 阶段3:启动 ZK(带重试,吸收 Quorum 组建期间的偶发崩溃) =====
            # OrderedReady 保证前置 Pod 已就绪,但 ZK 处于 LOOKING 状态时
            # readiness probe 已通过,新 Pod 加入 Quorum 仍可能有瞬态时序竞争,
            # 导致 ZK 偶发退出 (exit code 14)。通过进程内重试吸收,避免 Pod 级重启。
            set +e
            MAX_RETRY=3
            RETRY=0
            while [ $RETRY -lt $MAX_RETRY ]; do
              echo "$(date): Starting ZooKeeper (attempt $((RETRY+1))/$MAX_RETRY)..."
              zkServer.sh start-foreground &
              ZK_PID=$!
              wait $ZK_PID
              EXIT=$?
              ZK_PID=""

              echo "$(date): ZooKeeper exited with code $EXIT"
              if [ $EXIT -eq 0 ]; then
                echo "$(date): ZooKeeper exited normally"
                exit 0
              fi

              RETRY=$((RETRY+1))
              if [ $RETRY -lt $MAX_RETRY ]; then
                echo "$(date): Will retry in 5 seconds..."
                sleep 5
              fi
            done

            echo "$(date): Max retries ($MAX_RETRY) exhausted, giving up"
            exit $EXIT

        ports:
          - containerPort: 2181
            name: client
          - containerPort: 2888
            name: peer
          - containerPort: 3888
            name: election
        resources:
          requests:
            memory: "1Gi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "1000m"
        # startupProbe:ZK 3.9.5 + JDK 17 启动较慢,需要充足启动时间
        # startupProbe 通过后,liveness/readiness 才开始工作
        startupProbe:
          tcpSocket:
            port: 2181
          initialDelaySeconds: 10
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 30   # 10 + 10×30 = 310s 最大启动容忍
        livenessProbe:
          tcpSocket:
            port: 2181
          periodSeconds: 15
          timeoutSeconds: 3
          failureThreshold: 5     # 75s 容忍(startup 通过后 ZK 已稳定)
        readinessProbe:
          tcpSocket:
            port: 2181
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3
        volumeMounts:
          - name: zookeeper-data
            mountPath: /data

      # initContainer:从 Pod 名提取序号写入 /data/myid
      # zoo.cfg 由主容器的 command 直接写入 /conf/zoo.cfg(绕过有 bug 的 Docker entrypoint)
      initContainers:
      - name: fix-myid
        image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/library/zookeeper:3.9.5
        imagePullPolicy: IfNotPresent
        command: ["/bin/bash", "-c"]
        args:
          - |
            # 从 Pod 名提取序号:zookeeper-0 → 1,zookeeper-1 → 2,zookeeper-2 → 3
            POD_NAME=$(hostname)
            ORD=${POD_NAME##*-}
            MY_ID=$((ORD + 1))
            echo "Pod: $POD_NAME -> ZOO_MY_ID=$MY_ID"
            echo $MY_ID > /data/myid
            echo "Written myid=$MY_ID to /data/myid"
            cat /data/myid
        volumeMounts:
          - name: zookeeper-data
            mountPath: /data

  volumeClaimTemplates:
  - metadata:
      name: zookeeper-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      # 不指定 storageClassName,自动使用集群默认 StorageClass
      resources:
        requests:
          storage: 10Gi

部署

再次提醒,我部署到了namespace hadoop里,需改为你期望的namespace,包括文件中配置的hadoop字段都替换。

bash 复制代码
kubectl apply -f v1.yaml -n hadoop
相关推荐
雨声不在2 小时前
macos 12使用docker
macos·docker·容器
运维大师2 小时前
【K8S 运维实战】07-Pod生命周期与故障排查
运维·容器·kubernetes
BullSmall17 小时前
Anolis OS 8.10 完整安装 Docker CE(生产可用,解决 podman 冲突)
docker·容器·podman
heimeiyingwang18 小时前
【架构实战】可观测性三支柱:日志、指标、链路的融合
elasticsearch·架构·kubernetes
CodexDave1 天前
数据库连接池耗尽:排查顺序与三层兜底
服务器·前端·数据库·git·云原生·容器·kubernetes
java_logo1 天前
ELK Docker Compose 部署指南:轻松搭建日志检索平台
elk·elasticsearch·docker·容器·kibana·logstash·轩辕镜像
ShallWeL1 天前
Orin 上用 Docker 跑通目标检测功能
人工智能·目标检测·docker·容器
辰同学ovo1 天前
用“舞台换景”讲清 Docker 的 Restart 与 Recreate
运维·docker·容器
名字还没想好☜1 天前
Kubernetes Ingress 实战:域名路由、TLS 证书与 502/404 排查
运维·云原生·容器·kubernetes·ingress