一、部署思路
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博客