K8s详细学习笔记 第七章:资源的限制与调度
第七章:资源的限制与调度
Kubernetes不仅仅是一个应用部署平台,更是一个强大的资源管理器。在一个共享的集群环境中,如果不对资源进行管理,一个行为不端的“坏邻居”应用(例如,因内存泄漏而耗尽节点内存)就可能影响到同一节点上所有其他应用的正常运行。同样,如果不能将需要特定硬件(如GPU)的应用调度到正确的节点上,那么应用也无法正常工作。
本章将聚焦于两个核心问题:
- 资源量化与约束:如何为应用定义其所需的CPU和内存,并设置使用上限?
- 智能调度与放置:如何控制Pod应该被调度到哪些(或不应该被调度到哪些)Node节点上?
掌握这些内容,是实现资源隔离、成本控制、高可用部署以及优化集群资源利用率的关键。
7.1 Requests & Limits:为Pod中的容器设置CPU和内存
Kubernetes允许你为Pod中的每一个容器指定两种类型的资源约束:requests(请求值)和limits(限制值)。
-
requests:- 含义: 容器保证可以获得的最小资源量。
- 作用: 主要由**调度器(Scheduler)**在调度时使用。调度器在为Pod选择节点时,会检查节点的可用资源是否能满足Pod中所有容器的
requests总和。如果找不到任何一个节点能满足,Pod将一直处于Pending状态。 - 可以看作是“预订”。你为你的应用预订了这部分资源,集群承诺会为你保留。
-
limits:- 含义: 容器在运行时绝对不能超过的资源使用上限。
- 作用: 主要由Kubelet在容器运行时强制执行。
- 可以看作是“天花板”。无论节点上有多么空闲的资源,你的容器使用量都不能突破这个硬性上限。
CPU和内存的单位与特性
-
CPU:
- 单位: CPU在Kubernetes中是一个绝对量,而不是相对比例。
1CPU单位等同于云提供商的一个vCPU或物理机上的一个超线程核心。你可以使用毫核(millicores)来表示更小的值,例如500m代表0.5个CPU。 - 特性: CPU是一种可压缩(Compressible)资源。如果一个容器尝试使用的CPU超过了它的
limit,它不会被杀死,而是会被节流(Throttled),即它的性能会被强制降低,运行速度变慢。
- 单位: CPU在Kubernetes中是一个绝对量,而不是相对比例。
-
内存 (Memory):
- 单位: 内存的单位是字节,你可以使用
E, P, T, G, M, K或Ei, Pi, Ti, Gi, Mi, Ki等后缀。例如128Mi(128 Mebibytes)、1Gi(1 Gibibyte)。 - 特性: 内存是一种不可压缩(Incompressible)资源。如果一个容器尝试分配的内存超过了它的
limit,它会立即被终止,并被操作系统以**OOMKilled(Out of Memory Killed)**的错误杀死。如果这个Pod由Deployment等控制器管理,它会被重新启动。
- 单位: 内存的单位是字节,你可以使用
示例:在Pod中定义资源
# resources-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: resource-demo-pod
spec:
containers:
- name: my-app-container
image: nginx
resources:
requests:
memory: "64Mi"
cpu: "250m" # 请求 0.25 个CPU核心
limits:
memory: "128Mi"
cpu: "500m" # 最多使用 0.5 个CPU核心
服务质量(QoS)等级:Pod的“优先级”
Kubernetes根据你为Pod设置的requests和limits,自动将其划分为三种服务质量(QoS)等级。这个QoS等级非常重要,它决定了Pod的调度优先级和在节点资源紧张时被驱逐(Evicted)的优先级。
| QoS等级 | 设置条件 | 行为和优先级 |
|---|---|---|
| Guaranteed (有保证的) |
|
|
| Burstable (可突发的) |
requests < limits,或只设置了requests) |
|
| BestEffort (尽力而为的) | Pod中的所有容器都没有设置任何requests或limits。 |
|
核心思想:通过为关键应用设置Guaranteed或Burstable的QoS,你可以确保在集群负载高时,这些关键应用不会被那些不重要的BestEffort应用抢占资源,从而保障了整个系统的稳定性。
7.2 ResourceQuota: 在命名空间级别限制资源使用
requests和limits是在Pod级别对资源进行约束。而ResourceQuota则是集群管理员的工具,用于在**命名空间(Namespace)**的宏观层面上对资源进行限制。这对于多租户环境至关重要,可以防止某个团队或项目过度消耗集群资源。
ResourceQuota可以限制两大类资源:
- 计算资源: 如
requests.cpu,limits.memory,requests.storage(对PVC的总容量请求)等。 - 对象数量: 如
pods,services,configmaps,secrets,persistentvolumeclaims等的最大数量。
示例:为一个命名空间设置配额
# my-namespace-quota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: default-quota
namespace: team-a # 这个配额将作用于 team-a 命名空间
spec:
hard:
# 计算资源配额
requests.cpu: "10" # 该命名空间下所有Pod的CPU requests总和不能超过10核
requests.memory: 20Gi # ...内存requests总和不能超过20GiB
limits.cpu: "20"
limits.memory: 40Gi
# 对象数量配额
pods: "50"
services: "20"
kubectl apply -f my-namespace-quota.yaml
重要行为:如果在某个命名空间中为一个计算资源(如requests.cpu)设置了配额,那么在该命名空间中创建的每一个容器都必须明确指定该资源的requests和limits。否则,Pod的创建请求将被API Server拒绝。这是为了让ResourceQuota能够准确地追踪资源使用情况。
7.3 Pod调度策略
Kubernetes的默认调度器会综合考虑节点资源、Pod的QoS等级等因素,努力将Pod均匀地分布在集群中。但在很多场景下,我们需要更精细的控制,例如:
- 硬件依赖: 将需要GPU的Pod调度到装有GPU卡的节点上。
- 高可用: 将一个服务的多个副本分散到不同的可用区或物理机架上,避免单点故障。
- 数据局部性: 将计算Pod调度到离其所需数据(如存储或缓存)最近的节点上,以降低网络延迟。
- 隔离: 将某个特定租户或应用的所有Pod都限制在一组专用的节点上。
Kubernetes提供了多种强大的机制来实现这些高级调度策略,核心是亲和性(Affinity)和污点/容忍(Taints/Tolerations)。
节点亲和性(Node Affinity)
节点亲和性是Pod的一个属性,它使Pod被吸引到一类特定的Node节点上。这是通过匹配Node上的**标签(Labels)**来实现的。
它有两种类型:
-
requiredDuringSchedulingIgnoredDuringExecution(硬亲和性):- 含义: 必须满足。调度器只会将Pod调度到拥有匹配标签的节点上。如果集群中不存在这样的节点,Pod将永远处于
Pending状态。 IgnoredDuringExecution意味着:一旦Pod被成功调度,即使后来该节点的标签发生了变化(不再满足规则),Pod也不会被驱逐。
- 含义: 必须满足。调度器只会将Pod调度到拥有匹配标签的节点上。如果集群中不存在这样的节点,Pod将永远处于
-
preferredDuringSchedulingIgnoredDuringExecution(软亲和性):- 含义: 偏好满足。调度器会尽量将Pod调度到拥有匹配标签的节点上,但如果找不到,也可以将其调度到其他不匹配的节点上。
- 你可以为每个偏好规则设置一个
weight(权重,1-100),调度器会计算每个节点的总权重,并将Pod调度到得分最高的节点上。
示例:将Pod调度到拥有SSD硬盘的节点
# 1. 首先为节点打上标签
# kubectl label nodes <your-node-name> disktype=ssd
# 2. 在Pod Spec中定义Node Affinity
# node-affinity-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: ssd-pod
spec:
containers:
- name: my-container
image: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 50
preference:
matchExpressions:
- key: availability-zone
operator: In
values:
- zone-a
Pod亲和性/反亲和性(Pod Affinity/Anti-Affinity)
Pod亲和性/反亲和性是更高级的调度策略。它不是根据Node的标签,而是根据已经运行在Node上的其他Pod的标签来决定新Pod的调度位置。
-
Pod亲和性: “物以类聚”。将Pod调度到与某些特定Pod“在一起”的地方。
- 用例: 将一个Web应用Pod和一个缓存Pod(如Redis)调度到同一个节点或同一个可用区,以减少它们之间的网络延迟。
-
Pod反亲和性: “保持距离”。避免将Pod调度到与某些特定Pod“在一起”的地方。
- 用例 (极其重要): 部署一个高可用的服务(如
replicas: 3的Deployment),使用Pod反亲和性来确保这3个副本不会被调度到同一个Node节点上。这样,即使某个节点宕机,也最多只会损失一个副本,服务依然可用。
- 用例 (极其重要): 部署一个高可用的服务(如
这两种策略同样也分为required...(硬性)和preferred...(软性)两种。
一个关键的字段是**topologyKey。它定义了“在一起”或“分开”的拓扑域**。
topologyKey: "kubernetes.io/hostname": 拓扑域是单个Node节点。topologyKey: "topology.kubernetes.io/zone": 拓扑域是可用区。topologyKey: "topology.kubernetes.io/region": 拓扑域是区域。
示例:高可用部署,将Nginx副本分散到不同节点
# pod-antiaffinity-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ha
spec:
replicas: 3
selector:
matchLabels:
app: nginx-ha
template:
metadata:
labels:
app: nginx-ha # Pod必须有标签供反亲和性规则匹配
spec:
containers:
- name: nginx
image: nginx
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx-ha
topologyKey: "kubernetes.io/hostname" # 确保在同一主机名(Node)上,不会有多个app=nginx-ha的Pod
这个配置是实现服务高可用的黄金法则。
Taints(污点)与Tolerations(容忍)
污点和容忍是另一种强大的调度控制机制,它与亲和性逻辑相反。
- Taint (污点): 应用于Node。一个带有污点的Node会“排斥”Pod。默认情况下,任何Pod都不会被调度到带有污点的Node上。
- Toleration (容忍): 应用于Pod。Pod通过声明对某个污点的“容忍”,来表示自己可以被调度到带有该污点的Node上。
污点的组成: key=value:Effect
三种Effect:
NoSchedule: 只影响新调度的Pod。调度器不会将不能容忍此污点的Pod调度到该节点上,但不会影响已经运行在该节点上的Pod。PreferNoSchedule:NoSchedule的软性版本。调度器会尽量避免将Pod调度到该节点上。NoExecute: 最强效果。不仅会阻止新Pod的调度,还会驱逐节点上已经运行的、但不能容忍此污点的Pod。
核心用例:
- 专用节点: 管理员可以为一个节点打上污点,例如
kubectl taint nodes node1 gpu=true:NoSchedule。这样,该节点就成为了GPU专用节点。只有那些明确声明了可以容忍gpu=true这个污点的Pod(通常是需要GPU的应用)才能被调度上去。 - Master节点保护: Kubernetes集群的Master节点默认就被打上了
NoSchedule污点,以防止用户的应用Pod被错误地调度到控制平面上,从而保证控制平面的稳定性。 - 基于节点状况的自动驱逐: Kubernetes自身会使用
NoExecute污点来处理节点故障。例如,当一个节点网络不通时,Node Controller会为该节点自动添加一个node.kubernetes.io/unreachable的NoExecute污点。默认情况下,Pod对这个污点有300秒(5分钟)的容忍时间,如果节点在5分钟内没有恢复,所有Pod就会被自动驱逐到其他健康节点上。
示例:为Pod添加容忍,使其可以调度到Master节点(仅为演示)
# toleration-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: toleration-demo
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists" # Exists 操作符匹配key存在即可,不关心value
effect: "NoSchedule"
【重难点】 理解Requests和Limits对调度和节点资源分配的影响,灵活运用调度策略实现资源隔离和高可用部署
综合理解与应用:
-
Requests是调度的基石,Limits是运行时的护栏。调度器只关心Requests,它用这个值来做“数学题”,计算节点的剩余容量。而Kubelet则同时关心Requests和Limits,它用Limits来设置Cgroup,防止容器失控。一个常见的错误是只设置Limits而不设置Requests,这会导致Pod的QoS等级为BestEffort,并且调度器无法为其预留资源,极不稳定。 -
亲和性是“拉力”,污点是“推力”。亲和性是Pod主动去寻找符合条件的Node。而污点/容忍是Node被动地排斥Pod,只有“有通行证”(Toleration)的Pod才能进入。两者结合可以实现非常复杂的调度逻辑。例如,你可以为一个节点池打上污点
tenant=team-a:NoSchedule,同时,只给team-a的Pod添加对应的容忍,并使用Node亲和性规则,让这些Pod优先选择该节点池中性能最好的机器。这就实现了租户的硬隔离。 -
Pod反亲和性是高可用的核心。在设计任何无状态服务的Deployment或有状态服务的StatefulSet时,都应该默认为其添加基于
kubernetes.io/hostname的Pod反亲和性规则。这是用声明式的方式,以极低的成本,实现跨节点容灾的最简单、最有效的方法。可以进一步使用topology.kubernetes.io/zone来实现跨可用区容灾。 -
QoS等级决定了“牺牲”顺序。在资源紧张时,Kubelet需要驱逐Pod来回收资源。它会严格按照**
BestEffort->Burstable->Guaranteed**的顺序进行驱逐。因此,为你的核心组件(如数据库、监控系统)设置Guaranteed的QoS,是保护它们在极端情况下不被“误杀”的生命线。
通过将资源量化(Requests/Limits/Quotas)与调度策略(Affinity/Taints)结合起来,你就从一个简单的应用部署者,转变为一个能够精细化管理和优化整个Kubernetes集群资源的架构师。
K8s 详细学习笔记 目录
以下是整个系列的10章目录,点击章节标题即可跳转阅读:
- K8s详细学习笔记 第一章:K8s入门与核心概念
- K8s详细学习笔记 第二章:部署你的第一个应用:Pod与Deployment
- K8s详细学习笔记 第三章:应用访问与服务发现:Service与Ingress
- K8s详细学习笔记 第四章:应用配置管理:ConfigMap与Secret
- K8s详细学习笔记 第五章:数据持久化:Volume与Persistent Storage
- K8s详细学习笔记 第六章:应用的健康检查与自愈能力
- K8s详细学习笔记 第七章:资源的限制与调度
- K8s详细学习笔记 第八章:应用的扩缩容与更新策略
- K8s详细学习笔记 第九章:K8s安全机制:认证、授权与准入控制
- K8s详细学习笔记 第十章:集群运维与故障排查
更多推荐
所有评论(0)