云计算百科
云计算领域专业知识百科平台

1027 Kubernetes调度器MetricServer与HPA小白教程

Kubernetes 调度器、Metric Server 与 HPA 小白教程

这节讲两件事:① Pod 创建出来后,kube-scheduler 凭什么决定把它扔到哪个节点;② 怎么根据 Pod 的 CPU/内存使用率自动扩缩副本数(HPA)。


一、Pod 调度全流程

1.1 直接创建 Pod 的七步走

  • 客户端发请求给 kube-apiserver。
  • apiserver 做认证、授权、准入校验,通过后把 Pod 写进 etcd,此时 Pod 状态是 Pending,spec.nodeName 为空。
  • kube-scheduler 通过 List-Watch 监听到这个 Pending Pod,开始干活:
    • 过滤(Filter):先剔除明显不行的节点(资源不够、污点不匹配、节点被 cordon、亲和规则不满足)。
    • 打分(Score):在剩下的候选节点里打分,选得分最高的。
    • 向 apiserver 发起绑定请求,把 Pod 和目标节点绑起来。
  • apiserver 更新 Pod 的 spec.nodeName,写回 etcd。
  • 目标节点上的 kubelet 监听到"有 Pod 绑给我了",开始准备存储(CSI)、网络(CNI)。
  • kubelet 调用容器运行时拉镜像、启动容器。
  • kubelet 确认容器跑起来了,上报状态,Pod 变成 Running。
  • 一句话: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

    项目HPA 横向VPA 纵向
    扩缩方式 加/减 Pod 数量 调 Pod 的 CPU/内存 request/limit
    是否重启 Pod 不重启 一般要重启
    适用场景 流量波动大 资源请求设得不合理
    生产使用 非常普遍 较少
    能否同时开 不能,会冲突 –

    五、环境清理

    kubectl delete ns scheduler # 删调度实验命名空间
    kubectl delete ns metric # 删Metric Server/HPA实验命名空间

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 1027 Kubernetes调度器MetricServer与HPA小白教程
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!