4. Kubernetes调度与资源管理:资源请求与限制、服务质量等级、节点亲和性与反亲和性
调度和资源管理,说白了就是Kubernetes的“大脑”和“心脏”。我见过太多集群因为资源没规划好,导致线上服务频繁重启。今天咱们就把这块硬骨头啃下来。
4.1 资源请求与限制(Requests & Limits)
先问个问题:你给Pod分配CPU和内存时,到底该填多少?
Requests 是“最低保障”。Kubernetes调度器看到这个值,就知道至少要留这么多资源给Pod。说白了,这是Pod的“保底工资”。
Limits 是“最高上限”。Pod最多能用这么多,超过就会被限制或杀掉。这是Pod的“天花板”。
我个人习惯这样理解:Requests决定了Pod能不能调度到某个节点上,Limits决定了Pod在节点上能“疯”到什么程度。
核心原则:Requests ≤ Limits。如果只设Limits不设Requests,Kubernetes会默认Requests等于Limits。
来看一个实际例子:
apiVersion: v1
kind: Pod
metadata:
name: web-server
spec:
containers:
- name: nginx
image: nginx:1.21
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
这个Pod申请了0.25核CPU和256MB内存,但最多能用0.5核和512MB。我在项目中遇到过,有些团队只设Limits不设Requests,结果调度器以为Pod不需要资源,把一堆Pod塞到同一个节点上,直接OOM。
避坑指南:我曾经踩过一个坑——给Java应用只设了内存Limits没设Requests。结果Pod被调度到内存紧张的节点,JVM启动时直接OOMKilled。后来我强制要求:所有Java应用必须设置Requests,且Requests至少是JVM堆内存的1.5倍。
4.2 服务质量等级(QoS Classes)
Kubernetes根据Requests和Limits的设置,把Pod分成三个等级。节点资源不够时,先杀谁后杀谁,全看这个等级。
| QoS等级 | 设置条件 | 优先级 | 典型场景 |
|---|---|---|---|
| Guaranteed | 所有容器Requests == Limits | 最高 | 数据库、核心API |
| Burstable | 至少一个容器Requests < Limits | 中等 | Web服务、微服务 |
| BestEffort | 完全不设Requests和Limits | 最低 | 批处理、测试任务 |
Guaranteed:说白了就是“VIP客户”。节点内存不够时,最后一个才杀它。我建议核心服务都用这个等级。
Burstable:这是最常见的等级。Pod平时用Requests的资源,高峰期可以“借”一些空闲资源。但节点紧张时,它会被优先杀掉。
BestEffort:嗯,这个等级就是“流浪汉”。没有任何资源保障,节点一紧张第一个被干掉。我见过有人把生产服务设成BestEffort,结果节点内存一高,Pod全挂了。
个人经验:我习惯把数据库、消息队列这类有状态服务设为Guaranteed。无状态Web服务设为Burstable,Requests设成日常负载的70%,Limits设成峰值的120%。至于BestEffort,我只在开发环境或CI/CD的临时任务中用。
4.3 节点亲和性与反亲和性
调度器默认是“随机分配”的。但实际场景中,我们经常需要“定向分配”。比如:GPU任务必须跑到有GPU的节点上,两个高负载服务不能放在同一个节点上。
4.3.1 节点亲和性(Node Affinity)
节点亲和性分两种:硬亲和性和软亲和性。
硬亲和性(requiredDuringScheduling):调度器必须满足这个条件,否则Pod不调度。说白了就是“非你不可”。
软亲和性(preferredDuringScheduling):调度器尽量满足,但实在不行也可以调度到其他节点。这是“最好是你”。
来看一个实际配置:
apiVersion: v1
kind: Pod
metadata:
name: gpu-worker
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu-type
operator: In
values:
- "nvidia-tesla-v100"
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 50
preference:
matchExpressions:
- key: disk-type
operator: In
values:
- "ssd"
containers:
- name: worker
image: tensorflow:2.8
这个Pod必须调度到有V100 GPU的节点上,同时尽量选SSD磁盘的节点。我在项目中遇到过,有个团队把硬亲和性写成了软亲和性,结果GPU任务跑到了CPU节点上,训练速度慢了100倍。
避坑指南:我曾经犯过一个错误——把节点亲和性的key写错了。比如节点标签是"gpu-type",我写成了"gpu_type"。调度器直接忽略了这个规则,Pod调度到了普通节点。后来我养成了习惯:写完后用kubectl describe node确认标签名。
4.3.2 节点反亲和性(Node Anti-Affinity)
反亲和性就是“我不要和你在一起”。典型场景:两个高负载服务不能放在同一个节点上,避免资源争抢。
反亲和性也分硬和软两种:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
replicas: 3
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-server
topologyKey: "kubernetes.io/hostname"
containers:
- name: nginx
image: nginx:1.21
这个配置的意思是:同一个Deployment的Pod,不能调度到同一个节点上。说白了就是“打散部署”。
我建议:对于高可用服务,一定要用反亲和性把Pod打散到不同节点。否则一个节点挂了,所有副本全挂。
个人经验:我习惯把反亲和性写成软性的。比如preferredDuringScheduling,权重设成100。这样调度器会尽量打散,但如果节点不够,也能调度到同一个节点上。硬反亲和性在节点数少时可能导致Pod无法调度。
4.4 综合实战建议
最后,分享几个我在生产环境中的经验:
- 资源规划要留余量:Requests总和不要超过节点总资源的80%。我见过有人填到95%,结果节点一扩容就出问题。
- QoS等级要匹配业务:核心服务用Guaranteed,普通服务用Burstable,临时任务用BestEffort。别搞反了。
- 亲和性要写对标签:先
kubectl get nodes --show-labels确认标签名,再写配置。我吃过这个亏。 - 反亲和性要考虑节点数:如果只有3个节点,副本数却设了5个,硬反亲和性会导致Pod调度失败。
嗯,资源管理这块其实不难,但细节很多。你想想看,一个Requests写错了,可能导致整个集群的Pod调度失败。所以我的建议是:先小规模验证,再大规模推广。