在 Kubernetes 中,调度是一个关键过程,负责将 Pods 安排到适当的节点上,以确保它们能够有效运行并满足应用程序的需求。
9.1 调度的基本概念
9.1.1 调度器
Kubernetes 集群的调度器( kube-scheduler )是一个核心组件,负责将未调度的 Pods 分配到适合的节点上。
9.1.2 调度流程
1. 调度过程
调度流程通常包含以下几个步骤:
-
监听 Pod 事件:
- 调度器通过 Kubernetes API Server 监听未调度的 Pods。当一个 Pod 被创建但没有指定节点时,它将被标记为未调度(Pending),并且调度器会开始处理它。
-
预选(过滤节点):
- 调度器会根据 Pod 的资源需求、节点的标签、污点等条件对可用节点进行过滤,排除不合适的节点。
-
优选(评估节点):
- 在过滤后的节点中,调度器会为通过筛选的每个节点打分,考虑多个因素如资源利用率、亲和性等。
-
选择最佳节点:
- 最后,调度器选择得分最高的节点,将 Pod 调度到该节点,并更新 Kubernetes API。
2. 预选策略(Pre-Selection Strategies)
预选阶段负责筛选出适合 Pod 的候选节点。以下是主要的预选策略:
- PodFitsResources:检查节点上剩余的 CPU 和内存是否足够满足 Pod 的资源请求。这是最基本的筛选条件,确保节点能够承载 Pod 的负载。
- PodFitsHost:如果 Pod 指定了
NodeName,则检查节点名称是否与NodeName匹配。如果匹配,Pod 将被调度到该节点上。 - PodFitsHostPorts:确保 Pod 请求的端口不与节点上已有的端口冲突。如果有冲突,则该节点将被排除。
- PodSelectorMatches:根据 Pod 的标签选择节点,只有标签匹配的节点才能被选中。这使得用户可以控制 Pod 的调度到特定节点。
- NoDiskConflict:检查节点上已挂载的卷与 Pod 请求的卷之间是否存在冲突。特别是当两个卷都是只读时,允许它们共享。
- Taints and Tolerations:节点可以有污点,而 Pod 可以有容忍度,确保只有满足条件的 Pods 能够在有污点的节点上调度。
如果在 预选过程中没有合适的节点,pod 会一直在pending状态,不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续 优选 过程: 按照优先级大小对节点排序。
3. 优选策略(Post-Selection Strategies)
在预选阶段筛选出合适的节点后,调度器会根据优选策略对这些节点进行进一步的排序,以选择最合适的节点。主要的优选策略包括:
- LeastRequestedPriority:计算每个节点的 CPU 和内存使用率,倾向于选择资源使用率较低的节点。这样可以帮助均衡集群负载,避免某些节点过载。
- BalancedResourceAllocation:评估节点的 CPU 和内存使用率,使得这两个资源的使用率尽量接近,从而实现资源的平衡分配。
- ImageLocalityPriority:优先选择已经缓存了 Pod 所需镜像的节点。这有助于减少镜像拉取时间,从而提高 Pod 启动速度,特别是在大镜像的情况下。
9.2 nodeSelector
基本概念:nodeSelector 是 Pod 规格中的一个字段,用于定义节点标签的键值对。Kubernetes 只会将 Pod 调度到拥有你所指定的每个标签的节点上。
语法示例:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
nodeSelector:
disktype: ssd
environment: production
containers:
- name: my-container
image: my-image
在这个示例中,Pod 只会调度到那些同时具有 disktype: ssd 和 environment: production 标签的节点上。
在 Kubernetes 中,给节点添加标签可以通过 kubectl 命令来实现。以下是给节点添加标签的具体步骤:
# 查看当前节点
kubectl get nodes
# 给节点添加标签
kubectl label nodes <node-name> <key>=<value>
kubectl label worker-01 disktype=ssd
# 更新标签
kubectl label nodes node1 disktype=hdd --overwrite
# 删除标签
kubectl label nodes <node-name> <key>-
kubectl label nodes node1 disktype-
9.3 Taint 和 Toleration
在使用 Kubernets 过程中,经常会有以下需求:
- Master 节点应仅部署系统组件容器,如 Calico、Metrics 和 Filebeat,而不应运行业务应用。
- 新添加的节点在经过完整性和稳定性测试之前,不应立即允许部署业务容器。
- 某些节点可能需要系统升级或维护,期间需将其上的 Pod 迁移到其他节点以保持可用性。
- 对于 GPU 服务器或其他专用节点,只希望特定的 Pod 能够运行,避免部署其他 Pod。
针对这些需求,Kubernetes 提供了污点(Taint)和容忍(Toleration)机制,使得管理节点和 Pod 的调度变得更加灵活和高效。
9.3.1 基本概念
1. Taint 和 Toleration 是什么
Taint 是 Kubernetes 中的一种机制,用于控制 Pod 的调度。它是附加在节点上的标记,表明该节点不应接受未具备相应容忍(toleration)的 Pod。与 Taint 相对,Toleration 是 Kubernetes 的另一种机制,它与 Taint 配合使用,允许 Pod 在带有特定 Taint 的节点上运行。简单来说,Toleration 表示 Pod 对 Taint 的“容忍”,使得这些 Pod 能够在带有 Taint 的节点上被调度。
要在节点上添加 Taint,可以使用以下命令:
kubectl taint nodes <node-name> <key>=<value>:<effect>
示例:
kubectl taint nodes node1 disktype=ssd:NoSchedule
在这个示例中,节点 node1 被标记为 disktype=ssd,并且不允许没有相应容忍的 Pod 被调度到这个节点。
2. Taint 的组成
一个 Taint 由三个部分组成:
- 键(Key):标识 Taint 的名称,可以是任意字符串。
- 值(Value):与键关联的值,可以是任意字符串。
- 效果(Effect):表示 Taint 的影响,有三种可能的效果。
- NoSchedule:不允许没有相应容忍的 Pod 被调度到该节点。当前已经在节点上运行的 Pod 不会被驱逐。
- PreferNoSchedule:尽量避免在该节点上调度 Pod,但不是强制的。
- NoExecute:已经在该节点上运行的 Pod 会被驱逐,新的 Pod 不能调度到该节点。
你可以给一个节点添加多个污点,也可以给一个 Pod 添加多个容忍度设置。 Kubernetes 处理多个污点和容忍度的过程就像一个过滤器:从一个节点的所有污点开始遍历, 过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了 Pod 是否会被分配到该节点。
需要注意以下情况:
-
如果未被忽略的污点中存在至少一个 effect 值为 NoSchedule 的污点, 则 Kubernetes 不会将 Pod 调度到该节点。
-
如果未被忽略的污点中不存在 effect 值为 NoSchedule 的污点, 但是存在至少一个 effect 值为 PreferNoSchedule 的污点, 则 Kubernetes 会 尝试 不将 Pod 调度到该节点。
-
如果未被忽略的污点中存在至少一个 effect 值为 NoExecute 的污点, 则 Kubernetes 不会将 Pod 调度到该节点(如果 Pod 还未在节点上运行), 并且会将 Pod 从该节点驱逐(如果 Pod 已经在节点上运行)。
3. toleration 的运算符
Kubernetes 中的 toleration 运算符有以下两种:
-
Equal (
=):表示键(key)和值(value)必须完全匹配。tolerations: - key: "key1" operator: "Equal" value: "value1" effect: "NoSchedule" -
Exists:表示只需匹配键(key),而不考虑值(value)。即可以容忍任何值。
Codetolerations: - key: "key1" operator: "Exists" effect: "NoSchedule"
4. 使用 Taint 的场景
- 资源隔离:确保某些节点只运行特定类型的 Pod,如系统组件。
- 故障处理:标记不健康或需要维护的节点,防止新的 Pod 调度到这些节点上。
- 节点特性:例如,GPU 节点仅允许运行需要 GPU 的特定应用。
9.3.2 使用案例
1. 特殊硬件的节点
假设我们的集群中有一些节点配备了 NVIDIA GPU,且我们希望只有需要 GPU 的应用程序(如机器学习训练任务)能够调度到这些节点上。
首先,我们需要为包含 GPU 的节点打上污点。这可以通过 kubectl taint 命令来实现。
kubectl taint nodes <gpu-node-name> gpu=true:NoSchedule
这里的意思是,如果 Pod 没有容忍这个污点(toleration),则无法调度到这个节点上。
然后,我们在需要使用 GPU 的 Pod 的定义中添加相应的容忍度,以便它们可以调度到这些节点上。
以下是一个示例,展示了如何定义一个需要 GPU 的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: gpu-enabled-pod
spec:
containers:
- name: my-gpu-app
image: my-gpu-image:latest
resources:
limits:
nvidia.com/gpu: 1 # 请求 1 个 GPU
tolerations:
- key: "gpu"
operator: "Equal" # 使用 Equal 运算符
value: "true" # 需要匹配的值
effect: "NoSchedule" # 允许调度到被打上此污点的节点
通过以上配置,只有标记为需要 GPU 的 Pod(即 gpu-enabled-pod)能够调度到配备 GPU 的节点上,其他 Pod 则会被阻止,确保资源的合理使用和调度策略的安全性。
2. 基于 Taint 的驱逐
之前提到过的 Taint 的 effect 的值 NoExecute,它会影响已经在节点上运行的 Pod,具体影响如下:
- 如果 Pod 不能容忍这类污点,会马上被驱逐。
- 如果 Pod 能够容忍这类污点,但是在容忍度定义中没有指定 tolerationSeconds, 则 Pod 还会一直在这个节点上运行。
- 如果 Pod 能够容忍这类污点,而且指定了 tolerationSeconds, 则 Pod 还能在这个节点上继续运行这个指定的时间长度。 这段时间过去后,节点生命周期控制器从节点驱除这些 Pod。
Kubernetes 会自动给 Pod 添加一个 key 为 node.kubernetes.io/not-ready 的 Toleration 并配置 tolerationSeconds=300,同样也会给 Pod 添加一个 key 为 node.kubernetes.io/unreacherable 的 Toleration 并配置 tolerationSeconds=300,除非用户自定义了上述 key,否则会采用这个默认值,意识就是说如果节点由于某些原因造成不可用,默认情况下 Pod 会在 5分钟左右才会漂移至其他节点,对于要求高可用率的 Pod 按需修改这两处的 tolerationSeconds 为较短的值,就可以在节点故障后快速进行故障恢复,当然也不能太短,因为可能某些节点是由于网络波动造成的不可用。
默认情况下,自动添加 Toleration 的机制保证了在其中一种问题被检测到时,Pod 默认还能继续停留在当前节点运行 5 分钟。这两个默认 Toleration 是由 DefaultToleration-ationSeconds admission controler 添加的。
DaemonSet 中的 Pod 被创建时,针对以下 Taint 自动添加的 NoExcute 的 Toleration 将不会指定 tolerationSeconds,这保证了出现伤处问题时,DaemonSet 中的 Pod 永远不会被驱逐,当然 DaemonSet 的 Pod 不需要进行漂移。
3. 内置污点
当集群中的节点出现某种问题或满足特定条件时,kubelet会自动给这些节点添加内置污点。以下是一些常见的内置污点:
- node.kubernetes.io/not-ready:节点未准备好,相当于节点状态 Ready 的值为 False。
- node.kubernetes.io/unreachable:Node Controller 访问不到节点,相当于节点状态 Ready 的值为 Unknown。
- node.kubernetes.io/out-of-disk:节点磁盘耗尽。
- node.kubernetes.io/memory-pressure:节点存在内存压力。
- node.kubernetes.io/disk-pressure:节点存在磁盘压力。
- node.kubernetes.io/unschedulable:节点不可调度。
- node.kubernetes.io/network-unavailable:节点网络不可达。
9.3.3 master 节点的污点问题
kubectl get nodes
# 查看节点污点
kubectl describe nodes master-01 | grep -i taint
kubectl taint node xxx-nodename node-role.kubernetes.io/master-
#将 Master 也当作 Node 使用(去除污点)
kubectl taint node xxx-nodename node-role.kubernetes.io/master="":NoSchedule
#将 Master 恢复成 Master Only 状态
9.4 Affinity 亲和性
在 Kubernetes 中,使用 Affinity(亲和性)可以满足更复杂的调度需求,如优先调度到具有特定标签的节点、避免同一类 Pod 部署在统一节点、以及确保相互依赖的 Pod 在同一节点上运行。
Affinity 根据功能不同分为节点亲和性和 Pod 亲和性。
9.4.1 Node Affinity(节点亲和性)
1.基本概念
Node Affinity是根据节点上的标签选择性调度,可以让 Pod 部署在指定标签的节点、不部署在指定标签的节点撒谎给你,调度时是根据节点上的标签进行选择的。节点亲和性配置在 spec.affinity 字段下设置。
节点亲和性可分为两种类型:
- requiredDuringSchedulingIgnoredDuringExecution:Pod 只能调度到满足条件的节点上。如果没有符合条件的节点,Pod 将无法调度。
- preferredDuringSchedulingIgnoredDuringExecution:Pod 更倾向于调度到满足条件的节点上,但如果没有符合条件的节点,仍然可以调度到其他节点。
2. 支持的操作符
| 操作符 | 行为 |
|---|---|
| In | 标签值存在于指定的字符串集中 |
| NotIn | 标签值不在指定的字符串集中 |
| Exists | 对象上存在此键的标签 |
| DoesNotExist | 对象上不存在此键的标签 |
| Gt | 大于指定的值 |
| Lt | 小于指定的值 |
3. 示例
以下是一个示例 YAML 配置:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
- hdd
containers:
- name: my-app-container
image: my-app-image
上述配置确保 Pods 只能调度到具有标签 disktype 且值为 ssd 或 hdd 的节点上。
4. 注意事项
如果同时指定了 nodeSelector 和 nodeAffinity,需要两者都满足才能被调度;
如果配置了多个 nodeSelecetorTerms ,满足其一即可调度到指定节点;
如果配置了多个 matchExpressions,需要全部满才能调度到指定的节点上;
如果删除了被调度节点的标签,Pod 不会被删除,也就是说亲和性配置只有在调度的时候才会起作用。
9.4.2 Pod Affinity 和 Pod Anti-affinity(Pod 亲和性和 Pod 反亲和性)
1. 基本概念
Pod 亲和性和反亲和性是根据其他 Pod 的标签进行匹配的,比如想要 A 服务 的 Pod 不能和具有 servicce=b 标签的 Pod 部署在同一节点上,此时可以使用 Pod 亲和性和反亲和性进行配置,该调度是基于 Pod 的标签进行选择的。Pod 亲和性和 Pod 反亲和性需要配置在.spec.affinity 的 podAffinity 和 podAntiAffinity 字段下。
- Pod Affinity:允许你将 Pods 调度到同一节点或同一拓扑域(如同一机架)上的其他 Pods。
- Pod Anti-affinity:允许你将 Pods 调度到与其他 Pods 不同的节点或不同的拓扑域上,以提高可用性。
2. 支持的操作符
| 操作符 | 行为 |
|---|---|
| In | 标签值存在于指定的字符串集中 |
| NotIn | 标签值不在指定的字符串集中 |
| Exists | 对象上存在此键的标签 |
| DoesNotExist | 对象上不存在此键的标签 |
3. 示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis
topologyKey: "kubernetes.io/hostname"
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 10
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: "kubernetes.io/hostname"
containers:
- name: nginx
image: nginx
resources:
limits:
memory: 1Gi
cpu: 1
requests:
memory: 256Mi
cpu: 100m
该配置文件定义了一个 Nginx 的 Deployment,要求 Pods 在调度时必须与 app: redis 的 Pods 在同一主机上,并且尽量避免与其他 app: nginx 的 Pods 同处于同一主机。通过这样的亲和性和反亲和性配置,可以实现 Pods 的负载均衡和高可用性。
4. 可用区
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: topology.kubernetes.io/zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: topology.kubernetes.io/zone
containers:
- name: with-pod-affinity
image: registry.k8s.io/pause:2.0
亲和性规则规定,只有节点属于特定的区域 且该区域中的其他 Pod 已打上 security=S1 标签时,调度器才可以将示例 Pod 调度到此节点上。 例如,如果我们有一个具有指定区域(称之为 "Zone V")的集群,此区域由带有 topology.kubernetes.io/zone=V 标签的节点组成,那么只要 Zone V 内已经至少有一个 Pod 打了 security=S1 标签, 调度器就可以将此 Pod 调度到 Zone V 内的任何节点。相反,如果 Zone V 中没有带有 security=S1 标签的 Pod, 则调度器不会将示例 Pod 调度给该区域中的任何节点。
反亲和性规则规定,如果节点属于特定的区域 且该区域中的其他 Pod 已打上 security=S2 标签,则调度器应尝试避免将 Pod 调度到此节点上。 例如,如果我们有一个具有指定区域(我们称之为 "Zone R")的集群,此区域由带有 topology.kubernetes.io/zone=R 标签的节点组成,只要 Zone R 内已经至少有一个 Pod 打了 security=S2 标签, 调度器应避免将 Pod 分配给 Zone R 内的任何节点。相
5. 注意事项
由于使用 Pod 亲和性和 Pod 反亲和性时,需要进行大量的计算,会降低大规模集群下大的调度速率,因此在节点数超过数百时,并不建议使用过多的 Pod 亲和性。
9.4.3 应用场景
- 高可用性:通过 Pod Anti-affinity,将 Pods 部署到不同的节点上,降低单点故障风险。
- 负载均衡:通过 Pod Affinity,将某些 Pods 部署到同一节点上,可以提高访问速度和效率。
- 特定硬件需求:通过 Node Affinity,将 Pods 调度到具有特定硬件(如 GPU 或 SSD)的节点上,以满足应用需求。
9.5 HPA
9.5.1 什么是 HPA
自动扩缩容依赖于 Metrics Server 组件。在安装完 Metrics Server 后,该组件负责在集群中采集 Pod 和 Node 的度量指标,包括 Pod 的 CPU 和内存使用率,以及节点的 CPU 和内存使用率。Metrics Server 通过对外暴露接口默认是,将这些数据提供给 Kubernetes 的 HPA(Horizontal Pod Autoscaler)功能,可以根据观察到的数据来实现 Pod 的自动扩容和缩容。
参考链接:
Metrics Server 项目地址:https://github.com/kubernetes-sigs/metrics-server
9.5.2 HPA 实践
案例:电子商务网站的产品搜索服务
假设你在运营一个电子商务网站,用户在高峰时段(如促销活动)进行大量产品搜索。这些搜索请求需要处理较高的流量,因此你希望在流量增加时自动扩展服务,以保持响应时间和性能。
1. 部署应用
首先,你需要在 Kubernetes 中部署一个处理产品搜索请求的应用。可以使用 Deployment 来管理该应用。
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-search
spec:
replicas: 2
selector:
matchLabels:
app: product-search
template:
metadata:
labels:
app: product-search
spec:
containers:
- name: search-container
image: your-docker-repo/product-search:latest
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
2. 设置 HPA
接下来,你可以创建一个 HPA,根据 CPU 使用率自动调整 Pod 的数量。例如,你希望在 CPU 使用率超过 50% 时增加 Pod 的数量,低于 30% 时减少 Pod 的数量。
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: product-search-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: product-search
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 30 # 额外添加的配置来控制缩容
HPA 将根据 Pod 的 CPU 使用情况自动调整 Pod 的数量。你可以使用 kubectl get hpa 命令查看 HPA 的状态和当前的 Pod 数量。
在高峰期,流量增加,HPA 将会自动增加 Pod 的数量。例如,如果 CPU 使用率超过 50%,Pod 的数量可能会增加到 4 个或更多,以处理增加的请求。当流量减小时,HPA 会自动减少 Pod 的数量,以节省资源。
通过使用 HPA,电子商务网站能够在高流量期间保持良好的性能,同时有效地管理资源和成本。这种自动扩展的能力使得应用在面对不确定的流量模式时更具弹性。
9.6 调度的生产案例
9.6.1 场景
k8s 集群,分为三类节点四类服务
- ingress节点:ingress服务、代理服务,如日志代理,监控代理,dns缓存
- coredns节点:coredns服务、代理服务
- 普通业务节点:业务服务、代理服务
9.6.2 实现
- Ingress节点
标签:ingress.node=true,污点:ingress=true:NoSchedule
Ingress服务(DaemonSet):nodeSelector绑定标签,添加对应污点容忍,限制仅此类服务运行。 - CoreDNS节点
标签:coredns.node=true,污点:coredns=true:NoSchedule
CoreDNS服务(DaemonSet):nodeSelector绑定标签,添加对应污点容忍,确保专属运行。 - 普通业务节点
无专用污点,运行业务服务,简单配置pod反亲和。 - 代理服务(日志/监控/DNS缓存等)
全局部署:不设nodeSelector,配置tolerations: [{operator: "Exists"}],容忍所有污点,覆盖三类节点。
评论区