教程云原生容器编排【免费下载链接】kubernetes-handbookKubernetes 架构与生态从云原生到 AI 原生基础设施的构建指南项目地址https://gitcode.com/gh_mirrors/ku/kubernetes-handbook点击查看免费下载StatefulSet 是 Kubernetes 中专为有状态应用设计的控制器能够为 Pod 提供稳定的身份标识hostname、启动顺序、DNS 名称等解决数据库、消息队列等有状态服务在容器化编排中的持久化与网络标识难题。本文以 Kubernetes 1.6 环境中部署 Zookeeper 与 Kafka 集群为实战主线完整讲解 StatefulSet 的核心机制、Headless Service、PodDisruptionBudget、亲和性调度与探针配置并深入仓库源码说明镜像构建脚本与配置生成逻辑帮助你掌握一套可直接落地的有状态应用部署方案。StatefulSet 解决了什么问题StatefulSet 作为 Controller 为 Pod 提供唯一的标识可以保证部署和 scale 的顺序。与为无状态服务设计的 Deployment 和 ReplicaSet 不同StatefulSet 专门面向有状态服务其应用场景包括稳定的持久化存储Pod 重新调度后仍能访问到相同的持久化数据基于 PVC 实现稳定的网络标志Pod 重新调度后其 PodName 和 HostName 不变基于 Headless Service即没有 Cluster IP 的 Service实现有序部署、有序扩展Pod 按照定义的顺序从 0 到 N-1 依次创建在下一个 Pod 运行之前所有之前的 Pod 必须都是 Running 和 Ready 状态有序收缩、有序删除从 N-1 到 0 逆序进行。从上述应用场景可以看出一个完整的 StatefulSet 方案由以下几个部分组成用于定义网络标志DNS domain的 Headless Service用于创建 PersistentVolumes 的volumeClaimTemplates定义具体应用的 StatefulSet。StatefulSet 中每个 Pod 的 DNS 格式为statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local其中组成部分含义serviceNameHeadless Service 的名字0..N-1Pod 所在的序号从 0 开始到 N-1statefulSetNameStatefulSet 的名字namespace服务所在的 namespaceHeadless Service 和 StatefulSet 必须在相同的 namespace.cluster.localCluster Domain部署 Zookeeper 集群下面以在 Kubernetes 1.6 版本中部署 Zookeeper 为例讲解 StatefulSet 的使用。Zookeeper 镜像基于 CentOS 系统的 JDK 制作示例中为私人镜像harbor-001.jimmysong.io/library/zookeeper:3.4.6完整的 Dockerfile 和配置文件位于仓库的 manifests/zookeeper 目录。镜像构建与脚本设计Dockerfile 从远程获取 Zookeeper 的安装文件并解压安装随后将三个辅助脚本复制到/opt/zookeeper/bin/见 manifests/zookeeper/DockerfilezkGenConfig.sh生成 zookeeper 配置文件zkMetrics.sh获取 zookeeper 的 metricszkOk.sh用来做 ReadinessProbe。在zkGenConfig.shmanifests/zookeeper/zkGenConfig.sh中脚本通过正则从 hostname 中提取序数生成集群中每个节点的myid与zoo.cfgHOSThostname -s if [[ $HOST ~ (.*)-([0-9])$ ]]; then NAME${BASH_REMATCH[1]} ORD${BASH_REMATCH[2]} else echo Failed to extract ordinal from hostname $HOST exit 1 fi MY_ID$((ORD1))该脚本的核心思路是StatefulSet 创建的 Pod hostname 形如zk-0、zk-1、zk-2脚本通过print_servers()函数以server.$i$NAME-$((i-1)).$DOMAIN:$ZK_SERVER_PORT:$ZK_ELECTION_PORT的格式把整个 Zookeeper ensemble 的节点地址写入zoo.cfg从而让每个节点都知道其他成员的位置同时向$ZK_DATA_DIR/myid写入唯一的节点 ID序数 1。脚本还负责创建数据目录、生成log4j.properties与java.env通过JVMFLAGS-Xmx$ZK_HEAP_SIZE -Xms$ZK_HEAP_SIZE控制堆内存完整流程为validate_env create_config create_log_props create_data_dirs create_java_env。zkMetrics.sh脚本实际上执行的是下面的命令通过 Zookeeper 的四字命令mntr获取监控指标$ echo mntr | nc localhost $ZK_CLIENT_PORT 1 zk_version 3.4.6-1569965, built on 02/20/2014 09:09 GMT zk_avg_latency 0 zk_max_latency 5 zk_min_latency 0 zk_packets_received 427879 zk_packets_sent 427890 zk_num_alive_connections 3 zk_outstanding_requests 0 zk_server_state leader zk_znode_count 18 zk_watch_count 3 zk_ephemerals_count 4 zk_approximate_data_size 613 zk_open_file_descriptor_count 29 zk_max_file_descriptor_count 1048576 zk_followers 1 zk_synced_followers 1 zk_pending_syncs 0zkOk.sh脚本实际上执行的是下面的命令通过ruok四字命令判断实例是否健康manifests/zookeeper/zkOk.sh当服务端返回imok时退出码为 0否则为 1恰好作为 exec 型探针的判断依据$ echo ruok | nc 127.0.0.1 $ZK_CLIENT_PORT imokzookeeper.yaml 详解下面是启动三个 zookeeper 实例的 YAML 配置文件完整文件见 manifests/zookeeper/zookeeper.yaml--- apiVersion: v1 kind: Service metadata: name: zk-svc labels: app: zk-svc spec: ports: - port: 2888 name: server - port: 3888 name: leader-election clusterIP: None selector: app: zk --- apiVersion: v1 kind: ConfigMap metadata: name: zk-cm data: jvm.heap: 1G tick: 2000 init: 10 sync: 5 client.cnxns: 60 snap.retain: 3 purge.interval: 0 --- apiVersion: policy/v1beta1 kind: PodDisruptionBudget metadata: name: zk-pdb spec: selector: matchLabels: app: zk minAvailable: 2 --- apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: zk spec: serviceName: zk-svc replicas: 3 template: metadata: labels: app: zk spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - zk topologyKey: kubernetes.io/hostname containers: - name: k8szk imagePullPolicy: Always image: harbor-001.jimmysong.io/library/zookeeper:3.4.6 resources: requests: memory: 2Gi cpu: 500m ports: - containerPort: 2181 name: client - containerPort: 2888 name: server - containerPort: 3888 name: leader-election env: - name : ZK_REPLICAS value: 3 - name : ZK_HEAP_SIZE valueFrom: configMapKeyRef: name: zk-cm key: jvm.heap - name : ZK_TICK_TIME valueFrom: configMapKeyRef: name: zk-cm key: tick - name : ZK_INIT_LIMIT valueFrom: configMapKeyRef: name: zk-cm key: init - name : ZK_SYNC_LIMIT valueFrom: configMapKeyRef: name: zk-cm key: tick - name : ZK_MAX_CLIENT_CNXNS valueFrom: configMapKeyRef: name: zk-cm key: client.cnxns - name: ZK_SNAP_RETAIN_COUNT valueFrom: configMapKeyRef: name: zk-cm key: snap.retain - name: ZK_PURGE_INTERVAL valueFrom: configMapKeyRef: name: zk-cm key: purge.interval - name: ZK_CLIENT_PORT value: 2181 - name: ZK_SERVER_PORT value: 2888 - name: ZK_ELECTION_PORT value: 3888 command: - sh - -c - zkGenConfig.sh zkServer.sh start-foreground readinessProbe: exec: command: - zkOk.sh initialDelaySeconds: 10 timeoutSeconds: 5 livenessProbe: exec: command: - zkOk.sh initialDelaySeconds: 10 timeoutSeconds: 5 securityContext: runAsUser: 1000 fsGroup: 1000这个 YAML 中包含了 StatefulSet 部署有状态应用的所有关键元素逐一说明如下Headless Servicezk-svcclusterIP: None使其成为无头服务只为 Pod 提供稳定的 DNS 域名而不做负载均衡。它声明了 Zookeeper 集群内部通信使用的两个端口2888server用于 Follower 与 Leader 之间的数据同步和 3888leader-election用于 Leader 选举。ConfigMapzk-cm将 Zookeeper 的调优参数与镜像解耦通过configMapKeyRef注入到容器环境变量中。各参数与zkGenConfig.sh中读取的环境变量一一对应tick对应tickTime基本时间单元单位毫秒、init对应initLimitFollower 连接并同步到 Leader 的初始化时间上限单位 tick、sync对应syncLimitFollower 与 Leader 通信的同步时间上限单位 tick、client.cnxns对应maxClientCnxns单客户端最大连接数、snap.retain对应autopurge.snapRetainCount保留的快照数量、purge.interval对应autopurge.purgeInterval自动清理快照的间隔小时数。注意示例中ZK_SYNC_LIMIT误取了key: tick实际生产环境中应指向key: sync。PodDisruptionBudgetzk-pdbminAvailable: 2保证集群在任何时候至少有 2 个 Zookeeper 实例可用避免节点维护voluntary disruptions时破坏 Quorum。StatefulSetzkserviceName: zk-svc将 StatefulSet 与 Headless Service 绑定replicas: 3创建 3 个实例。Pod 反亲和性podAntiAffinity使用requiredDuringSchedulingIgnoredDuringExecutiontopologyKey: kubernetes.io/hostname强制要求三个 Zookeeper Pod 调度到不同的节点上保证单个节点宕机不会导致整个集群不可用。这是分布式有状态应用常见的打散策略。启动命令command: [sh, -c, zkGenConfig.sh zkServer.sh start-foreground]先在容器启动时生成配置再以前台方式启动 Zookeeper容器主进程必须前台运行否则 Pod 会立即退出。就绪与存活探针readinessProbe 与 livenessProbe 均使用exec执行zkOk.sh。就绪探针决定 Pod 是否进入 Ready 状态只有 Ready 后 StatefulSet 才会继续创建下一个 Pod存活探针决定 Pod 是否需要被重启initialDelaySeconds: 10给 JVM 启动留出缓冲时间。securityContextrunAsUser: 1000, fsGroup: 1000与 Dockerfile 中useradd zookeeperUID/GID 均为 1000见 manifests/zookeeper/Dockerfile保持一致确保数据目录的属主权限匹配。注示例中的 YAML 没有配置持久化存储未使用volumeClaimTemplates且镜像为作者私人镜像、外部无法访问。生产环境建议参考 Kubernetes 官方 Zookeeper StatefulSet 教程为每个 Pod 挂载独立 PVC详见下文组件与稳定存储。部署 Kafka 集群Kafka 的 docker 镜像制作跟 zookeeper 类似都是从远程下载安装包后解压安装Dockerfile 与配置文件见 manifests/kafka 目录。与 zookeeper 不同的是Kafka 侧只需要一个脚本但它依赖于上一步安装的 zookeeper。kafkaGenConfig.sh从 hostname 推导 broker IDkafkaGenConfig.shmanifests/kafka/kafkaGenConfig.sh用来生成 kafka 的配置文件#!/bin/bash HOSThostname -s if [[ $HOST ~ (.*)-([0-9])$ ]]; then NAME${BASH_REMATCH[1]} ORD${BASH_REMATCH[2]} else echo Failed to extract ordinal from hostname $HOST exit 1 fi MY_ID$((ORD1)) sed -i s/broker.id0/broker.id$MY_ID/g /opt/kafka/config/server.properties sed -i s/zookeeper.connectlocalhost:2181/zookeeper.connectzk-0.zk-svc.brand.svc:2181,zk-1.zk-svc.brand.svc:2181,zk-2.zk-svc.brand.svc:2181/g /opt/kafka/config/server.properties该脚本根据 StatefulSet 生成的 Pod 的 hostname 的后半截数字部分作为 broker IDbroker.id 序数 1保证每个 broker 拥有全局唯一的整数 ID同时将server.properties中默认的zookeeper.connectlocalhost:2181替换为三个 Zookeeper 节点的完整 DNS 地址。这里用到的zk-0.zk-svc.brand.svc正是 StatefulSet Pod DNS 命名规则statefulSetName-{0..N-1}.serviceName.namespace的实际体现——zk是 StatefulSet 名、zk-svc是 Headless Service 名、brand是 namespace。Zookeeper 在zkGenConfig.sh中通过print_servers()生成 ensemble 配置Kafka 侧则直接在连接串中写死三个 Zookeeper 的 DNS两者相互印证了以稳定 DNS 名称替代 Pod IP这一有状态应用的核心设计。kafka.yaml 详解下面是创建 3 个 kafka 实例的 YAML 配置manifests/kafka/kafka.yaml--- apiVersion: v1 kind: Service metadata: name: kafka-svc labels: app: kafka spec: ports: - port: 9093 name: server clusterIP: None selector: app: kafka --- apiVersion: policy/v1beta1 kind: PodDisruptionBudget metadata: name: kafka-pdb spec: selector: matchLabels: app: kafka minAvailable: 2 --- apiVersion: apps/v1beta1 kind: StatefulSet metadata: name: kafka spec: serviceName: kafka-svc replicas: 3 template: metadata: labels: app: kafka spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - kafka topologyKey: kubernetes.io/hostname podAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - zk topologyKey: kubernetes.io/hostname terminationGracePeriodSeconds: 300 containers: - name: k8skafka imagePullPolicy: Always image: harbor-001.jimmysong.io/library/kafka:2.10-0.8.2.1 resources: requests: memory: 1Gi cpu: 500m env: - name: KF_REPLICAS value: 3 ports: - containerPort: 9093 name: server command: - /bin/bash - -c - /opt/kafka/bin/kafkaGenConfig.sh /opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/server.properties env: - name: KAFKA_HEAP_OPTS value : -Xmx512M -Xms512M - name: KAFKA_OPTS value: -Dlogging.levelDEBUG readinessProbe: tcpSocket: port: 9092 initialDelaySeconds: 15 timeoutSeconds: 1与 Zookeeper 配置相比Kafka 的 YAML 有几个值得注意的差异双重亲和性组合在podAntiAffinity强制要求三个 Kafka broker 分到不同节点之外还使用了podAffinity的软策略preferredDuringSchedulingIgnoredDuringExecution权重 1倾向让 Kafka 与 Zookeeperapp: zk调度到同一节点以减少跨节点访问 Zookeeper 的网络开销——这是反亲和打散 亲和就近的经典组合。terminationGracePeriodSeconds: 300Kafka 关闭时需要将内存中的日志段刷盘并完成分区 leader 迁移因此设置了 300 秒的宽限期避免 Pod 被强制删除导致数据不一致。注意 StatefulSet 官方建议不要把该值设为 0这会导致强制删除 Pod 从而破坏有状态应用的数据安全。启动命令与探针启动命令依次执行配置生成与前台启动readinessProbe 使用tcpSocket探测 9092 端口Kafka 实际监听端口定义于 manifests/kafka/server.properties 的port9092initialDelaySeconds: 15等待 broker 完成启动注册。JVM 参数注入KAFKA_HEAP_OPTS将堆内存固定为 512M-Xmx512M -Xms512M防止堆动态伸缩带来的停顿KAFKA_OPTS开启 DEBUG 级别日志便于排障。Kafka 的server.propertiesmanifests/kafka/server.properties中还包含了num.partitions每 topic 默认分区数默认 1、log.retention.hours日志保留时间默认 168 小时、log.segment.bytes单日志段大小等可调参数生产环境可按需通过 ConfigMap 注入。集群外部访问 StatefulSet 的 Pod在 Kubernetes 集群外部调试 StatefulSet 中有序的 Pod 时如何访问这些 Pod方法是为 Pod 设置 label然后用kubectl expose将其以 NodePort 的方式暴露到集群外部。以上面的 zookeeper 为例下面使用命令的方式暴露其中的两个 zookeeper 节点也可以写一个 service 配置 yaml 文件kubectl label pod zk-0 zkInst0 kubectl label pod zk-1 zkInst1 kubectl expose po zk-0 --port2181 --target-port2181 --namezk-0 --selectorzkInst0 --typeNodePort kubectl expose po zk-1 --port2181 --target-port2181 --namezk-1 --selectorzkInst1 --typeNodePort这样在 Kubernetes 集群外部就可以根据 Pod 所在的主机所映射的端口来访问了。查看zk-0这个 service 可以看到如下结果NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE zk-0 10.254.98.14 nodes 2181:31693/TCP 5m集群外部就可以使用所有 node 中的任何一个IP:31693来访问这个 zookeeper 实例。这种方式的本质是StatefulSet 保证 Pod 名称与身份稳定不变因此可以为某个特定序数的 Pod 打上专属 label 并单独暴露这在调试单个节点、验证 Leader/Follower 角色等场景下非常实用。深入理解 StatefulSet 的组件、顺序保证与更新策略在动手部署之外理解 concepts/statefulset.md 中阐述的 StatefulSet 底层机制有助于在生产环境正确设计有状态服务。组件Headless Service、StatefulSet 与 volumeClaimTemplates一个典型 StatefulSet 由三部分组成Headless Service控制网络域、StatefulSetspec中指定副本数、volumeClaimTemplates使用 PersistentVolume Provisioner 提供的 PersistentVolumes 作为稳定存储。volumeClaimTemplates会为每个 Pod 自动创建一个 PVC命名格式为claimName-statefulSetName-ordinal例如www-web-0当 Pod 重新调度到其他节点时volumeMounts会挂载与 PVC 相关联的同一个 PersistentVolume从而保证数据不丢失。需要注意删除或 scale StatefulSet 不会删除与其关联的 volume。这是为了确保数据安全通常比自动清除所有相关 StatefulSet 资源更有价值但意味着不再使用的 PVC 需要手动清理。此外StatefulSet 目前要求必须有 Headless Service 负责 Pod 的网络身份创建该 Service 是使用者的责任。部署与 Scale 的保证对于有 N 个副本的 StatefulSetPod 将按照 {0..N-1} 的顺序被创建和部署删除 Pod 时按照逆序 {N-1..0} 终结对 Pod 执行 scale 操作之前它所有的前任必须处于 Running 和 Ready 状态在终止 Pod 前它所有的继任者必须处于完全关闭状态。例如上面的 nginx 示例详见 manifests/test/web.yaml创建后3 个 Pod 将按照 web-0、web-1、web-2 的顺序创建web-0 进入 Running 和 Ready 之前web-1 不会被部署若 web-0 在 web-1 就绪后失败web-2 将不会启动直到 web-0 成功重启并恢复就绪。缩容时顺序相反replicas1时 web-2 先被终止web-2 完全关闭之前 web-1 不会被终止。Pod 管理策略与更新策略Kubernetes 1.7 及之后版本StatefulSet 通过.spec.podManagementPolicy放开顺序保证OrderedReady默认实现上述有序启动/终止行为Parallel并行的启动和终止 Pod在启动/终止其他 Pod 之前不会等待 Pod 变成运行并就绪或完全终止。更新策略由.spec.updateStrategy控制OnDeleteKubernetes 1.6 及以前的遗留行为未指定时默认控制器不会自动更新 Pod用户必须手动删除 Pod 才能以新 template 重建RollingUpdate控制器按与终止相同的顺序从最大序数到最小序数每次更新一个 Pod等待其 Running 和 Ready 后再更新前身分区partition通过.spec.updateStrategy.rollingUpdate.partition指定后只有序数大于或等于 partition 的 Pod 会被更新序数小于 partition 的 Pod 不受影响。这在金丝雀发布或分阶段发布中非常有用若 partition 大于 replicas则 template 的更新不会传播到任何 Pod。小结本文以 Zookeeper 与 Kafka 两个经典有状态中间件为例完整展示了 StatefulSet 在 Kubernetes 中的落地方式通过 Headless Service 提供稳定的 DNS 身份、通过启动脚本从 hostname 推导集群成员与节点 ID、通过 PodDisruptionBudget 与亲和性调度保障集群可用性、通过探针控制有序滚动。所有脚本与配置均可在仓库的 manifests/zookeeper 与 manifests/kafka 目录中找到concepts/statefulset.md 则提供了更完整的组件、顺序保证与更新策略说明。需要指出的是示例中的镜像为作者私人镜像且未配置持久化存储生产环境应替换为可访问的镜像仓库并通过volumeClaimTemplates为每个 Pod 挂载独立 PVC以获得真正的数据持久化保障。赞分享教程云原生容器编排【免费下载链接】kubernetes-handbookKubernetes 架构与生态从云原生到 AI 原生基础设施的构建指南项目地址https://gitcode.com/gh_mirrors/ku/kubernetes-handbook点击查看免费下载相关推荐kubernetes-handbook 实战StatefulSet 原理与有状态应用部署指南kubernetes handbook 实战StatefulSet 原理与有状态应用部署指南 StatefulSet 是 Kubernetes 中唯一面向有状教程云原生容器编排Toonflow 二次元场景图生成实战成熟都市言情风格约束手册与源码级调用链解析Toonflow 二次元场景图生成实战成熟都市言情风格约束手册与源码级调用链解析 导读 本文以 Toonflow 开源仓库中「成熟都市言情二次元动画」风格包的人工智能大模型AI 应用AI Agent媒体生成后端桌面应用设计冲刺与敏捷开发如何将两者结合提升产品效率设计冲刺与敏捷开发如何将两者结合提升产品效率 设计冲刺与敏捷开发是现代产品开发中两种高效的方法论它们各自拥有独特的优势。设计冲刺通过五天的集中式协作快速解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
