Kubernetes 调度器、Metric Server 与 HPA 小白教程
这节讲两件事:① Pod 创建出来后,kube-scheduler 凭什么决定把它扔到哪个节点;② 怎么根据 Pod 的 CPU/内存使用率自动扩缩副本数(HPA)。
一、Pod 调度全流程
1.1 直接创建 Pod 的七步走
- 过滤(Filter):先剔除明显不行的节点(资源不够、污点不匹配、节点被 cordon、亲和规则不满足)。
- 打分(Score):在剩下的候选节点里打分,选得分最高的。
- 向 apiserver 发起绑定请求,把 Pod 和目标节点绑起来。
一句话:scheduler 只管选节点,kubelet 才管启动容器,controller-manager 只管按副本数创建/删除 Pod。
1.2 通过 Deployment 创建 Pod 多了谁
跟直接创建 Pod 相比,多了一个 kube-controller-manager:
Client → apiserver → etcd → controller-manager → scheduler → kubelet → 容器运行时
controller-manager 里跑着 Deployment 控制器,它对比"期望副本数"和"现有 Pod 数",差几个就调 apiserver 创建几个新 Pod。新 Pod 出来之后,后面的流程跟上面一模一样。
二、五种控制 Pod 调度位置的手段
| nodeName | Pod 指定节点 | 最硬,绕过调度器 |
| nodeSelector | Pod 选节点标签 | 简单硬匹配 |
| nodeAffinity | Pod 选节点标签 | 支持 In/NotIn/Exists,支持优先策略 |
| podAffinity / podAntiAffinity | Pod 选其他 Pod 在哪 | 按已运行的 Pod 标签决定位置 |
| taint / toleration | 节点选 Pod | 节点主动拒绝不匹配的 Pod |
2.1 nodeName(最暴力)
直接在 Pod spec 里写死节点名,调度器连打分都不做,直接扔过去。缺点:节点不存在就调度失败,节点资源不够也直接报错。生产基本不用。
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
nodeName: worker32.lcm.cloud # 直接写死节点名
containers:
– image: hub.lcm.cloud/library/nginx
name: nginx
kubectl apply -f deploy-with-nodeName.yaml
kubectl get pods -o wide | awk '{print $1,$3,$7}' # 看Pod是不是都跑在worker32
2.2 nodeSelector(最简单的标签匹配)
先给节点打标签:
kubectl get node –show-labels # 看节点现有标签
kubectl label node worker31.lcm.cloud disktype=ssd # 给worker31打disktype=ssd标签
kubectl get node -L disktype # 验证标签打上了
在 Pod spec 里写 nodeSelector:
spec:
nodeSelector:
disktype: ssd # 只调度到带disktype=ssd标签的节点
containers:
– image: hub.lcm.cloud/library/nginx
name: nginx
kubectl apply -f deploy-with-nodeSelector.yaml
kubectl get pods -o wide | awk '{print $1,$3,$7}' # Pod全跑在worker31
注意一个坑:删掉节点标签不会触发 Pod 重新调度,Pod 继续在原节点跑;只有删掉 Deployment 里的 nodeSelector 才会触发重建。
kubectl label node worker31.lcm.cloud disktype- # 删标签,Pod不会动
kubectl patch deployment webapp –type json -p '[{"op":"remove","path":"/spec/template/spec/nodeSelector"}]' # 删nodeSelector,触发重建
2.3 nodeAffinity(节点亲和性,加强版 nodeSelector)
两种模式:
-
requiredDuringSchedulingIgnoredDuringExecution:必须满足,跟 nodeSelector 一样硬,只是支持更复杂的表达式。
-
preferredDuringSchedulingIgnoredDuringExecution:优先满足,打分时倾向匹配的节点,但不会强制。
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
– matchExpressions:
– key: CPU
operator: In
values: [L1, L2] # 必须打了CPU=L1或L2标签
preferredDuringSchedulingIgnoredDuringExecution:
– weight: 1 # 权重1-100
preference:
matchExpressions:
– key: MEM
operator: In
values: [L1, L2] # 优先选带MEM=L1/L2的节点
operator 支持:In、NotIn、Exists、DoesNotExist、Gt、Lt。
打标签实验:
kubectl label nodes worker31.lcm.cloud CPU=L1 # worker31打CPU=L1
kubectl label nodes worker32.lcm.cloud CPU=L2 # worker32打CPU=L2
kubectl rollout restart deployment web # 重建,Pod会分散到两个节点
kubectl label nodes worker32.lcm.cloud MEM=L2 # 给worker32加MEM=L2偏好
kubectl rollout restart deployment web # 重启后Pod优先堆在worker32
2.4 podAffinity / podAntiAffinity(Pod 间亲和/反亲和)
不是看节点标签,而是看"这个节点上已经跑了什么 Pod":
- 亲和:我想跟某个 Pod 待在同一个拓扑域里。
- 反亲和:我不想跟某个 Pod 待在同一个拓扑域里。
拓扑域用 topologyKey 表示,最常用的是 kubernetes.io/hostname(按节点隔离)。
示例:反亲和让同服务的 Pod 分散到不同节点
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
– labelSelector:
matchExpressions:
– key: app
operator: In
values: [store]
topologyKey: "kubernetes.io/hostname" # 同一个hostname节点上只能跑一个
效果:3 个副本的 Pod 会被打散到 3 个不同节点,节点不够就 Pending。
示例:亲和让 web 跟 store 待在一起
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
– labelSelector:
matchExpressions:
– key: app
operator: In
values: [store]
topologyKey: topology.kubernetes.io/zone # 同一个zone里有store Pod的节点才能调度
2.5 taint(污点)和 toleration(容忍)
前面都是"Pod 选节点",taint 反过来:节点主动拉黑不匹配的 Pod。
一句话记忆:nodeAffinity 是 Pod 挑节点,taint 是节点挑 Pod。
给节点打污点:
kubectl taint nodes worker31.lcm.cloud CPU=L1:NoSchedule # worker31加污点:CPU=L1,效果NoSchedule
三种 effect:
- NoSchedule:新 Pod 没匹配容忍度就别想上来,已经在跑的 Pod 不动。
- PreferNoSchedule:软拒绝,尽量不调度,但实在没地方也会塞上去。
- NoExecute:不仅不让新 Pod 上来,已经在跑的不匹配 Pod 直接驱逐。
Pod 里写容忍度:
spec:
tolerations:
– key: "CPU"
operator: "Equal"
value: "L1"
effect: "NoSchedule"
operator 两种:
- Equal:键值都要对得上。
- Exists:只要 key 存在就行,不写 value(等于容忍所有该 key 的污点)。
删污点:
kubectl taint nodes worker31.lcm.cloud CPU:NoSchedule- # 末尾加横杠就是删
2.6 cordon / uncordon / drain(节点维护三连)
kubectl cordon worker31.lcm.cloud # 标记节点为SchedulingDisabled,不让新Pod上来
kubectl get nodes # 状态变成Ready,SchedulingDisabled
kubectl uncordon worker31.lcm.cloud # 恢复可调度
kubectl drain worker31.lcm.cloud –ignore-daemonsets # 驱逐节点上所有Pod,同时自动cordon
# –ignore-daemonsets:忽略DaemonSet管理的Pod(calico、kube-proxy这些不能删)
kubectl uncordon worker31.lcm.cloud # 维护完恢复
三、Metric Server(监控指标采集)
3.1 它是干嘛的
K8s 本身不知道每个 Pod 用了多少 CPU、多少内存。Metric Server 就是从每个节点的 kubelet 那里抓指标,汇总成 metrics.k8s.io API,供 kubectl top 和 HPA 使用。
3.2 部署
wget -O metrics-server-components.yaml https://lcm.cloud/class/course-materials/softwares/stage03/metrics-server-components-v0.7.1.yaml
sed -i 's/registry.k8s.io/hub.lcm.cloud/g' metrics-server-components.yaml # 换镜像
kubectl apply -f metrics-server-components.yaml
kubectl get pods -n kube-system | grep metrics # 等Pod变Running
3.3 用法
kubectl top node # 看每个节点的CPU/内存使用率
kubectl top pods -n kube-system # 看每个Pod的CPU/内存
单位记住:1 CPU = 1000m,所以 Pod 用了 50m 就是 0.05 核。
四、HPA(水平自动扩缩)
4.1 基本概念
HPA 盯着 Deployment,根据 Pod 的 CPU 或内存使用率自动加减副本数:
- CPU 超过阈值 → 加 Pod。
- CPU 低于阈值持续一段时间 → 减 Pod。
- 副本数卡在 minReplicas 和 maxReplicas 之间。
4.2 创建 HPA(CPU 模式)
先起 Deployment:
kubectl create deployment web –image=hub.lcm.cloud/library/nginx
创建 HPA:
kubectl autoscale deployment web –max=5 –min=2 –cpu-percent=80 # 最少2个,最多5个,CPU超80%就扩
看 HPA 状态:
kubectl get hpa
输出里 TARGETS 列格式是 当前值/目标值,比如 cpu: 0%/80%。
4.3 TARGETS 为什么显示 <unknown>
这是新手最常踩的坑。HPA 要算 CPU 百分比,必须知道"100% 是多少",这个基准就是容器的 resources.limits.cpu。如果你没写 limits,HPA 就不知道分母,只能显示 unknown。
修复:编辑 Deployment 加 resources:
kubectl edit deployments.apps web
在容器下面加:
resources:
limits:
cpu: 100m # 这就是100%的基准,跑满100m就是100%
memory: 200Mi
保存后滚动重建 Pod,再 kubectl get hpa,TARGETS 就从 <unknown>/80% 变成 0%/80% 了。
4.4 TARGETS 怎么读
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
web Deployment/web cpu: 100%/80% 2 5 2
- 斜杠前面 100%:所有 Pod 的平均 CPU 实际使用率。
- 斜杠后面 80%:你设的阈值。
- 前面 > 后面 → 扩容;前面 < 后面 → 冷却后缩容。
- REPLICAS 是当前实际副本数。
4.5 压测看扩容
开一个监控窗口:
while true; do clear; date; kubectl get hpa; kubectl get pod; kubectl top pods; sleep 1; done | tee hpa.log
另一个窗口上压力:
apt install -y apache2-utils # 装ab压测工具
kubectl expose deployment web –port=80 –target-port=80 # 先建Service
while true; do ab -n 300000 -c 100 http://10.100.255.92/; sleep 1; done # 狂打
观察:CPU 冲到 100% 后约 20 秒,HPA 自动加一个 Pod;加完后每个 Pod 负载降下来,TARGETS 回落到 95% 左右。一直加到 5 个 Pod 封顶。
4.6 内存模式 HPA(v2 API)
CPU 用 v1 就够,内存要写 v2:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
spec:
minReplicas: 2
maxReplicas: 5
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
metrics:
– resource:
name: memory
target:
type: Utilization
averageUtilization: 60 # 内存平均用超60%就扩
type: Resource
4.7 HPA 精细管控(scaleUp / scaleDown)
K8s 1.18 之后可以控制扩缩容的节奏:
扩容 scaleUp:
scaleUp:
stabilizationWindowSeconds: 0 # 扩容不等待,立刻扩
policies:
– type: Percent
value: 100 # 每15秒最多翻倍
periodSeconds: 15
– type: Pods
value: 4 # 每15秒最多加4个Pod
periodSeconds: 15
selectPolicy: Max # 两条策略取大值
缩容 scaleDown:
scaleDown:
stabilizationWindowSeconds: 300 # 缩容冷却5分钟,防止流量抖动来回扩缩
policies:
– type: Percent
value: 100
periodSeconds: 15
selectPolicy: Max
记忆要点:
- 扩容默认立刻扩(stabilization=0),缩容默认等 5 分钟(stabilization=300)。
- 扩容窗口内取最小期望副本数,缩容窗口内取最大期望副本数——都是为了避免抖动。
4.8 HPA vs VPA
| 扩缩方式 | 加/减 Pod 数量 | 调 Pod 的 CPU/内存 request/limit |
| 是否重启 Pod | 不重启 | 一般要重启 |
| 适用场景 | 流量波动大 | 资源请求设得不合理 |
| 生产使用 | 非常普遍 | 较少 |
| 能否同时开 | 不能,会冲突 | – |
五、环境清理
kubectl delete ns scheduler # 删调度实验命名空间
kubectl delete ns metric # 删Metric Server/HPA实验命名空间
网硕互联帮助中心






评论前必须登录!
注册