当前位置: 首页 > news >正文

k8s(9) — zookeeper集群部署(亲和性、污点与容忍测试)

一、部署思路

1、前期设想

zookeeper集群至少需要运行3个pod集群才能够正常运行,考虑到节点会有故障的风险这个3个pod最好分别运行在3个不同的节点上(为了实现这一需要用到亲和性和反亲和性概念),在部署的时候对zookeeper运行的pod打标签加入app=zk,那么假设当zookeeper-1在node1节点上运行,那么zookeeper-2部署的时候发现node1节点上已经存在app=zk标签就不会在再node1节点上运行(这里可能会用到硬策略 +亲和性(NotIn)或者硬策略+反亲和性(In))来实现。3个pod至少需要3台机器而实验环境只用3台机器(一台master节点和2台node节点),因为污点原因master节点不参与集群节点的调度工作,所以为了完成部署可能需要引入污点与容忍概念。

2、实现思路

1、创建一个service类型的无头服务zk-headless(因为zookeeper集群服务可能会进行销毁创建IP不固定,所以zookeeper配置文件中不能配置IP,所以要创建一个clusterIP: None的service表示不分配ip,通过域名进行访问)

产生的域名格式:<pod-name>.<service-name>.<namespace>.svc.cluster.local。

2、要创建一个zookeeper集群配置清单(将上述的生成的域名写入到配置中)。

3、创建PodDisruptionBudget确保集群正常可用。

4、创建StatefulSet有状态服务(绑定上述的无头服务serviceName: zk-headless)配置亲和性或污点容忍策略。

5、创建一个NodePort类型的service用于外部连接测试使用。

二、部署 zookeeper集群

(一)、zookeeper pod部署

1、将master上的污点设置为:PreferNoSchedule

表示 k8s 将尽量避免将 Pod 调度到具有该污点的 Node 上

#将污点设置为PreferNoSchedule表示 k8s 将尽量避免将 Pod 调度到具有该污点的 Node 上
kubectl taint node master  node-role.kubernetes.io/master:PreferNoSchedule

 2、创建PodDisruptionBudget资源对象

2.1、PodDisruptionBudget资源对象简介

在 Kubernetes(K8s)里,PodDisruptionBudget(PDB)是一种非常重要的资源对象,它主要用于保障应用的高可用性,避免因计划内的中断(比如节点维护、集群升级等)而导致过多的 Pod 同时被终止。下面为你详细介绍 PodDisruptionBudget

2.1.1、核心作用

在 Kubernetes 集群中,计划内的中断事件是难以避免的,像节点维护、节点升级、节点驱逐等操作都可能导致 Pod 被终止。PodDisruptionBudget 的作用就是对这些计划内中断进行管控,确保在任何时候都有足够数量的 Pod 处于运行状态,从而保障应用的正常运行和服务的稳定性。

2.1.2、关键概念
  • minAvailable:该参数规定了在计划内中断期间,必须保持运行的最小 Pod 数量或者比例。例如,设置 minAvailable: 3 表示至少要有 3 个 Pod 处于运行状态;设置 minAvailable: 50% 则意味着至少要有一半的 Pod 保持运行。
  • maxUnavailable:此参数定义了在计划内中断期间,允许不可用的最大 Pod 数量或者比例。例如,设置 maxUnavailable: 2 表示最多允许 2 个 Pod 不可用;设置 maxUnavailable: 25% 则表示最多允许 25% 的 Pod 不可用。
2.1.3、配置示例

以下是一个 PodDisruptionBudget 的 YAML 配置示例:

pod-disruption-budget-examplePodDisruptionBudget 配置示例

V1

生成 pod-disruption-budget.yaml

2.1.4、配置解释
  • apiVersion:指定使用的 Kubernetes API 版本,这里使用的是 policy/v1
  • kind:表明资源对象的类型为 PodDisruptionBudget
  • metadata:包含资源对象的元数据,name 为该 PodDisruptionBudget 的名称。
  • spec:定义了 PodDisruptionBudget 的具体规格。
    • minAvailable:设置为 2,表示在计划内中断期间,至少要有 2 个带有 app: my-app 标签的 Pod 保持运行状态。
    • selector:用于筛选要应用该 PodDisruptionBudget 的 Pod,这里通过 matchLabels 选择带有 app: my-app 标签的 Pod。
2.1.5、工作机制

当计划内中断事件发生时,Kubernetes 会检查 PodDisruptionBudget 的规则。如果中断操作会导致不符合 minAvailable 或 maxUnavailable 的要求,那么该操作将会被阻止,直到满足 PodDisruptionBudget 的条件为止。

2.1.6、使用场景
  • 关键业务应用:对于像数据库、缓存服务这类关键业务应用,使用 PodDisruptionBudget 可以确保在集群维护或升级期间,有足够数量的 Pod 继续运行,避免服务中断。
  • 多副本应用:对于运行多个副本的应用,PodDisruptionBudget 可以防止过多的副本同时被终止,保证服务的稳定性和可用性。

通过使用 PodDisruptionBudget,你可以在 Kubernetes 集群中更好地管理计划内中断,保障应用的高可用性。

分享

如何创建一个PodDisruptionBudget?

查看PodDisruptionBudget的详细信息。

如何调整PodDisruptionBudget的配置?

2.2、怎样查看PodDisruptionBudget的apiVersion需要填什么
kubectl explain poddisruptionbudget

2.3、创建PodDisruptionBudget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:name: zookeepernamespace: kafka
spec:minAvailable: 2selector:matchLabels:app: zookeeper

3、创建无头服务

创建一个名为zk-headless(因为zookeeper集群服务可能会进行销毁创建IP不固定,所以zookeeper配置文件中不能配置IP,要创建一个clusterIP: None的service表示不分配ip,通过域名进行访问)。

产生的域名格式:<pod-name>.<service-name>.<namespace>.svc.cluster.local。

apiVersion: v1
kind: Service
metadata:name: zk-headlessnamespace: kafka
spec:clusterIP: None  # Headless Service,不分配IPselector:app: zookeeperports:- name: clientport: 2181targetPort: 2181- name: peer-electionport: 3888targetPort: 3888- name: peer-communicationport: 2888targetPort: 2888

4、创建一个zookeeper集群配置清单

apiVersion: v1
kind: ConfigMap
metadata:name: zoo-confnamespace: kafka
data:zoo.cfg: |tickTime=2000dataDir=/var/lib/zookeeper/datadataLogDir=/var/lib/zookeeper/logclientPort=2181initLimit=5syncLimit=2# 动态生成集群节点(如3节点)server.0=zk-0.zk-headless.kafka.svc.cluster.local:2888:3888server.1=zk-1.zk-headless.kafka.svc.cluster.local:2888:3888server.2=zk-2.zk-headless.kafka.svc.cluster.local:2888:3888

后边部署的pod名称是zk是所以这里填入zk-0、zk-1、zk-2 。

5、创建StatefulSet有状态服务

apiVersion: apps/v1
kind: StatefulSet         #部署服务的类型
metadata:name: zknamespace: kafka
spec:replicas: 3  # 集群节点数selector:matchLabels:app: zookeeperserviceName: zk-headless        #绑定的无头服务名称template:metadata:labels:app: zookeeperspec:containers:- name: zookeeperimage: zookeeper:3.8.0  # 官方镜像ports:- containerPort: 2181name: client- containerPort: 3888name: zk-leader- containerPort: 2888name: zk-serverenv:- name: POD_NAMEvalueFrom:fieldRef:fieldPath: metadata.name  # 获取Pod名称(如zk-0)volumeMounts:- name: datamountPath: /var/lib/zookeeper/data- name: logmountPath: /var/lib/zookeeper/log- name: configmountPath: /conf/zoo.cfgsubPath: zoo.cfgresources:requests:cpu: 500mmemory: 1GiinitContainers:- name: write-myidimage: alpine:3.17command: ["sh", "-c"]args:- |# 从Pod名称中提取序号(如zk-0 → 0)#ID=$(echo $(POD_NAME) | cut -d'-' -f2)cat /proc/1/environ|tr '\0' '\n'|grep "HOSTNAME="|awk -F '=' '{print $2}'|cut -d'-' -f2 >  /var/lib/zookeeper/data/myidvolumeMounts:- name: datamountPath: /var/lib/zookeeper/dataaffinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: "app"operator: Invalues:- zookeepertopologyKey: "kubernetes.io/hostname"volumes:- name: configconfigMap:name: zoo-confvolumeClaimTemplates:- metadata:name: dataspec:accessModes:- ReadWriteOnceresources:requests:storage: 5GistorageClassName: nfs-client- metadata:name: logspec:accessModes:- ReadWriteOnceresources:requests:storage: 5GistorageClassName: nfs-client

5.1、配置亲和性策略
      affinity:podAntiAffinity:         #反亲和requiredDuringSchedulingIgnoredDuringExecution:   #硬策略- labelSelector:matchExpressions:- key: "app"            #指定key是appoperator: In      values:- zookeeper           #即匹配app=zookeeper的标签topologyKey: "kubernetes.io/hostname"

6、创建一个NodePort类型的service用于外部连接测试使用

apiVersion: v1
kind: Service
metadata:name: zk-clientnamespace: kafka
spec:selector:app: zookeeperports:- name: clientport: 2181targetPort: 2181type: NodePort  # 如需外部访问,可改为NodePort或LoadBalancer

(二)、部署过程问题排查

1、pod节点没有创建因为变量命名规则大于15个字符导致报错问题处理

#查看描述
kubectl get statefulset -n zookeeper
kubectl describe statefulset -n  zookeeper zk-cluster | tail -n 10

 

2、配置文件名称不一致问题处理

kubectl get  pod -n zookeeper -w
kubectl describe  pod  zk-cluster-0 -n zookeeper|tail -n10
kubectl logs zk-cluster-0 -n zookeeper

3、statefulset服务将pod信息存储在/var/lib/kubelet/pods中删除动态卷下的数据会导致再次部署的时候发生报错。

 报错:

解决:执行kubectl delete -f *yaml 将之前部署到所有yaml清单全部删除。

4、POD_NAME变量获取不到问题处理

在部署zookeeper集群时,需要在每个zookeeper节点的myid中填入对应的值,这里直接将POD_NAME变量名后的数字写入,但是在部署的过程中发现metadata.name值无法获取,所以在这里使用shell获取pod名称编号写入myid,方法如下:

#在实验中POD_NAME变量的名称获取不到env:- name: POD_NAMEvalueFrom:fieldRef:fieldPath: metadata.name  # 获取Pod名称(如zk-0)#解决方法使用如下命令获取pod的名称cat /proc/1/environ|tr '\0' '\n'|grep "HOSTNAME="|awk -F '=' '{print $2}'|cut -d'-' -f2 >  /var/lib/zookeeper/data/myid

(三)、完整的zookeeper集群部署流程清单

#创建配置apiVersion: v1
kind: ConfigMap
metadata:name: zoo-confnamespace: zookeeper
data:zoo.cfg: |tickTime=2000dataDir=/var/lib/zookeeper/datadataLogDir=/var/lib/zookeeper/logclientPort=2181initLimit=5syncLimit=2# 动态生成集群节点(如3节点)server.0=zk-0.zk-headless.zookeeper.svc.cluster.local:2888:3888server.1=zk-1.zk-headless.zookeeper.svc.cluster.local:2888:3888server.2=zk-2.zk-headless.zookeeper.svc.cluster.local:2888:3888---
#创建PodDisruptionBudgetapiVersion: policy/v1
kind: PodDisruptionBudget
metadata:name: zookeepernamespace: zookeeper
spec:minAvailable: 2selector:matchLabels:app: zookeeper---
apiVersion: v1
kind: Service
metadata:name: zk-clientnamespace: zookeeper
spec:selector:app: zookeeperports:- name: clientport: 2181targetPort: 2181type: ClusterIP  # 如需外部访问,可改为NodePort或LoadBalancer---#创建无头服务
apiVersion: v1
kind: Service
metadata:name: zk-headlessnamespace: zookeeper
spec:clusterIP: None  # Headless Service,不分配IPselector:app: zookeeperports:- name: clientport: 2181targetPort: 2181- name: peer-electionport: 3888targetPort: 3888- name: peer-communicationport: 2888targetPort: 2888---#创建有状态副本集apiVersion: apps/v1
kind: StatefulSet
metadata:name: zknamespace: zookeeper
spec:replicas: 3  # 集群节点数selector:matchLabels:app: zookeeperserviceName: zk-headlesstemplate:metadata:labels:app: zookeeperspec:containers:- name: zookeeperimage: zookeeper:3.8.0  # 官方镜像ports:- containerPort: 2181name: client- containerPort: 3888name: zk-leader- containerPort: 2888name: zk-serverenv:- name: POD_NAMEvalueFrom:fieldRef:fieldPath: metadata.name  # 获取Pod名称(如zk-0)volumeMounts:- name: datamountPath: /var/lib/zookeeper/data- name: logmountPath: /var/lib/zookeeper/log- name: configmountPath: /conf/zoo.cfgsubPath: zoo.cfgresources:requests:cpu: 500mmemory: 1GiinitContainers:- name: write-myidimage: alpine:3.17command: ["sh", "-c"]args:- |# 从Pod名称中提取序号(如zk-0 → 0)#ID=$(echo $(POD_NAME) | cut -d'-' -f2)cat /proc/1/environ|tr '\0' '\n'|grep "HOSTNAME="|awk -F '=' '{print $2}'|cut -d'-' -f2 >  /var/lib/zookeeper/data/myidvolumeMounts:- name: datamountPath: /var/lib/zookeeper/dataaffinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: "app"operator: Invalues:- zookeepertopologyKey: "kubernetes.io/hostname"volumes:- name: configconfigMap:name: zoo-confvolumeClaimTemplates:- metadata:name: dataspec:accessModes:- ReadWriteOnceresources:requests:storage: 5GistorageClassName: nfs-client- metadata:name: logspec:accessModes:- ReadWriteOnceresources:requests:storage: 5GistorageClassName: nfs-client---
#创建与外部连接的serviceapiVersion: v1
kind: Service
metadata:name: zk-clientnamespace: zookeeper
spec:selector:app: zookeeperports:- name: clientport: 2181targetPort: 2181type: ClusterIP  # 如需外部访问,可改为NodePort或LoadBalancer
[root@master zookeeper]#

(四)、集群访问测试

1、查看集群状态是否正常

#进入集群内部
kubectl exec -it zk-0 -n kafka -- /bin/bash#查看zk-0的状态
echo srvr|nc zk-0.zk-headless.kafka.svc.cluster.local 2181#查看zk-1的状态
echo srvr|nc zk-1.zk-headless.kafka.svc.cluster.local 2181#查看zk-2的状态
echo srvr|nc zk-2.zk-headless.kafka.svc.cluster.local 2181

2、在zookeeper集群中写入数据

编写python代码往zookeeper集群中写入1000个数据

from kazoo.client import KazooClientdef xixi(path, data):hosts = "192.168.72.130:30459"# 创建 KazooClient 实例zk = KazooClient(hosts=hosts)try:# 连接到 ZooKeeper 集群zk.start()print("成功连接到 ZooKeeper 集群")# 要写入的路径# path = "/test_node"# # 要写入的数据# data = b"Hello, ZooKeeper!"# 检查路径是否存在,如果不存在则创建if not zk.exists(path):zk.create(path, data)print(f"成功在路径 {path} 创建节点并写入数据")else:# 如果节点已存在,更新数据zk.set(path, data)print(f"成功更新路径 {path} 下的数据")except Exception as e:print(f"发生错误: {e}")finally:# 关闭连接if zk.connected:zk.stop()print("已断开与 ZooKeeper 集群的连接")if __name__ == '__main__':# ZooKeeper 集群地址hosts = "192.168.72.130:30459"# 创建 KazooClient 实例zk = KazooClient(hosts=hosts)for i in range(1,1000):print(i)path = f"/test_{i}"data_1 = f'zookeeper-{i}'data = data_1.encode('utf-8')xixi(path, data)

三、污点与容忍

(一)、不设置污点容忍master节点无法参与调度

将master主节点上的污点修改成NoSchedule (修改之后zookeeper pod将不能再调度到master节点上,这意味着剩下两个节点中的有一个节点要运行两个pod,而我们在yaml中配置了亲和性所以已经存在zookeeper标签的节点不能再运行,因此3个副本中只有两个副本是正常的,还有一个处于异常状态)

为了验证上述的说法,我开始以下测试

步骤一:将之前部署的zookeeper集群删除掉(环境清理)

执行脚本: sh deploy.sh delete

deploy.sh脚本内容如下:

##创建命名空间
#kubectl create namespace zookeeper
#
##根据配置文件创建configmapx
#kubectl create configmap zoo.cfg --from-file=zoo.conf
#
##创建PodDisruptionBudget只能指定集群运行节点的最小数
#kubectl apply -f  podDisruptionBudget.yaml
#
##创建一个service使得pod之间产生一个可用互相访问的域名用于 StatefulSet 的稳定 DNS 解析,格式为 pod-name.service-name.namespace.svc.cluster.local
#
apply ()
{kubectl apply -f podDisruptionBudget.yamlkubectl apply -f zk-client-service.yamlkubectl apply -f zk-headless-service.yamlkubectl apply -f zoo-config.yamlkubectl apply -f zk-statefulset.yaml
}delete ()
{kubectl delete -f podDisruptionBudget.yamlkubectl delete -f zk-client-service.yamlkubectl delete -f zk-headless-service.yamlkubectl delete -f zoo-config.yamlkubectl delete -f zk-statefulset.yaml
}main ()
{case $1 inapply)apply;;delete)deleteesac
}main $1

步骤二:将master污点设置成:NoSchedule

#将污点设置为:NoSchedule
kubectl taint node master node-role.kubernetes.io/master:NoSchedule#查看污点是否设置成功
kubectl describe  nodes master|grep -i taint

步骤三:执行脚本sh deploy.sh apply (重新部署zookeeper集群)

测试结果:master节点无法参与调度

只有两个pod成功运行,一个pod处于Pending状态

报错信息与设想的结果一致 

(二)、设置污点容忍master上可以参与调度

假设将zookeeper集群的yaml文件中配置污点容忍,那么master节点也可以参与调度(之前3个副本只能运行两个,如果添加了污点容忍那么master节点上也可以参与调度,即3个pod都能正常运行)

1、污点容忍配置

在 Kubernetes(k8s)里,污点(Taints)和容忍度(Tolerations)是用来控制 Pod 调度到特定节点的机制。污点应用于节点,而容忍度则设置在 Pod 上,允许 Pod 调度到带有特定污点的节点。下面详细介绍如何设置污点和容忍度。

1.1. 设置节点污点

可以使用 kubectl taint 命令为节点添加污点。污点的格式为 key=value:effect,其中 effect 有三种类型:

  • NoSchedule:新的 Pod 不会被调度到带有该污点的节点,但已在该节点上运行的 Pod 不受影响。
  • PreferNoSchedule:系统会尽量避免将新的 Pod 调度到带有该污点的节点,但不是强制的。
  • NoExecute:新的 Pod 不会被调度到带有该污点的节点,并且如果已在该节点上运行的 Pod 没有相应的容忍度,它们会被驱逐。

以下是添加污点的示例命令:

# 添加一个 NoSchedule 类型的污点
kubectl taint nodes <node-name> key1=value1:NoSchedule# 添加一个 PreferNoSchedule 类型的污点
kubectl taint nodes <node-name> key2=value2:PreferNoSchedule# 添加一个 NoExecute 类型的污点
kubectl taint nodes <node-name> key3=value3:NoExecute

要将 <node-name> 替换成实际的节点名称。

1.2. 删除节点污点

若要删除节点上的污点,可以使用以下命令:

kubectl taint nodes <node-name> key1:NoSchedule-

这里的 - 表示删除该污点。

1.3. 设置 Pod 容忍度

在 Pod 的 YAML 文件中添加容忍度设置,以下是一个示例:

pod-with-tolerations带有容忍度设置的 Pod YAML 文件

生成 pod-with-tolerations.yaml

apiVersion: v1
kind: Pod
metadata:name: pod-with-tolerations
spec:containers:- name: nginximage: nginx:1.14.2ports:- containerPort: 80tolerations:- key: "key1"operator: "Equal"value: "value1"effect: "NoSchedule"- key: "key2"operator: "Equal"value: "value2"effect: "PreferNoSchedule"- key: "key3"operator: "Equal"value: "value3"effect: "NoExecute"tolerationSeconds: 3600  # 表示在节点添加该污点后,Pod 还能继续运行 3600 秒    

在这个示例中:

  • key:对应节点上污点的键。
  • operator:有 Equal 和 Exists 两种取值。Equal 表示容忍度的 key 和 value 必须与污点的 key 和 value 完全匹配;Exists 表示只要节点上存在该 key 的污点,不管 value 是什么,都能容忍。
  • value:对应节点上污点的值。
  • effect:对应节点上污点的效果。
  • tolerationSeconds:仅在 effect 为 NoExecute 时有效,表示在节点添加该污点后,Pod 还能继续运行的时间。

1.4. 部署带有容忍度的 Pod

将上述 YAML 文件保存为 pod-with-tolerations.yaml,然后使用以下命令部署 Pod:

kubectl apply -f pod-with-tolerations.yaml

通过上述步骤,你就可以设置节点的污点和 Pod 的容忍度,从而控制 Pod 的调度。

2、在zookeeper部署的yaml中配置污点容忍

步骤一:查看污点所对应的key值
#查看污点所对应的key
kubectl get nodes master -o yaml

步骤二:在yaml中加入污点容忍

#在pod的spec下加入tolerations:- key: "node-role.kubernetes.io/master"operator: "Exists"effect: "NoSchedule"- key: "node-role.kubernetes.io/master"operator: "Exists"effect: "PreferNoSchedule"

步骤三:部署3个pod的zookeeper集群
sh -x deploy.sh apply

测试结果:master节点参与调度,污点容忍策略生效

参考博客链接:k8s设置容器环境变量&service服务无法获取到环境变量的解决方法_yaml <podname> 变量-CSDN博客

相关文章:

  • ESG跨境电商如何为国内的跨境电商企业打开国外的市场
  • 探秘 roadmap.sh:GitHub 最受欢迎的开发者学习路线图项目
  • 组件化开发
  • 「零配置陷阱」:现代全栈工具链的复杂度管控实践
  • 安全测试之SQL注入深度解析
  • Action:Update your application‘s configuration
  • QuecPython+GNSS:实现快速定位
  • 【Java学习笔记】二维数组
  • Linux NIO 原理深度解析:从内核到应用的高性能 I/O 之道
  • 从eslint切换到biome你的Jetbrains下的Webstorm还习惯吗
  • 每日AI必读 - 2025年4月25日(晚报)
  • 4.25学习——文件上传之00截断
  • 人工智能与机器学习,谁是谁的子集 —— 再谈智能的边界与演进路径
  • 自学新标日第二十二课(复习)
  • 并发设计模式实战系列(7):Thread Local Storage (TLS)
  • 命令行指引的尝试
  • 初一试后担忧
  • 在虚拟机中安装Linux详细教程
  • PyQt6基础_QTableWidget
  • 题目 3320: 蓝桥杯2025年第十六届省赛真题-产值调整
  • 刘非履新浙江省委常委、杭州市委书记,曾在吉湘云多省任职
  • 光线传媒:正与部分重点地区洽谈主题乐园合作,首款3A游戏预计三年左右推出
  • 海南:谈话提醒9名缺点明显或有苗头性、倾向性问题的省管干部
  • 央行副行长陆磊:国际化程度有效提升是上海国际金融中心建设的一个主要方向
  • 正荣地产旗下“H20正荣2”债未能于宽限期内支付分期偿付款,尚未就新兑付方案达成一致
  • 董明珠卸任格力电器总裁,张伟接棒