K8s调度实战全景-Pod调度与资源管控

【K8s 调度实战全景】从 Pod 诞生到 HPA 弹性伸缩:一文吃透 Kubernetes 调度与资源管控

摘要 :你有没有好奇过:一条 kubectl apply 敲下去,集群内部到底发生了什么?Pod 是怎么被"挑中"某个节点的?为什么有时候 Pod 一直 Pending 就是起不来?又怎么让服务在流量洪峰自动扩容、多团队共享集群时不互相"抢食"?这篇文章用一套环境 + 20 多个可复现实验,把 Kubernetes 调度体系从「Pod 诞生过程」到「调度算法」再到「资源配额管控」讲个通透。看完动手练一遍,调度这块基本就稳了。


一、环境准备

全文实验都在这个命名空间下操作,方便做完后一次性清理:

bash 复制代码
root@master30:~# kubectl create ns scheduler
root@master30:~# kubectl config set-context --current --namespace scheduler

二、调度是什么?先认识 kube-scheduler

调度 ,就是把 Pod放置到合适的节点上的过程。kube-scheduler是集群的默认调度器。它通过 Watch 监听机制发现集群中未被调度到节点上的 Pod,然后把这些"无家可归"的 Pod 安排到一个合适的节点上运行。

一句话记住它的边界 :kube-scheduler 只负责选节点,不负责启动容器。启动容器是目标节点 kubelet + 容器运行时的活。


三、Pod 调度全流程拆解

3.1 直接创建 Pod 的完整链路

bash 复制代码
# 客户端发起请求 → apiserver 认证授权准入 → 写入 etcd → scheduler 监听并绑定 → kubelet 启动容器

七个核心组件各司其职:

组件 职责
客户端 (kubectl) 组装请求,与 API Server 交互
kube-apiserver 集群唯一入口,认证/授权/准入校验,读写 etcd
etcd 键值数据库,持久化集群所有资源状态
kube-scheduler 监听 Pending Pod,过滤+打分选最优节点,发起绑定
kubelet 节点代理,监听绑定到本机的 Pod,调用 CNI/CSI/CRI 启动容器
容器运行时 containerd/docker,拉镜像、创建容器、运行业务进程

精简版:7 步记住全流程

  1. 客户端发送创建 Pod 请求至 kube-apiserver;
  2. apiserver 完成认证、授权、准入校验,合法则把 Pod 存入 etcd,Pod 为 Pending;
  3. kube-scheduler 监听到未调度 Pod,执行过滤、打分选出最优节点,向 apiserver 发起绑定;
  4. apiserver 更新 Pod,写入节点名称;
  5. 对应节点 kubelet 感知 Pod 绑定到本机,调用 CNI 准备网络、CSI 准备存储;
  6. kubelet 调用容器运行时拉取镜像、启动容器;
  7. kubelet 检测容器正常运行,通知 apiserver 将 Pod 状态更新为 Running。

3.2 通过控制器(Deployment)创建 Pod 的链路

bash 复制代码
Client → kube-apiserver → etcd → kube-controller-manager → kube-scheduler → kubelet → 容器运行时

关键区别:Deployment 控制器只负责"创建/删除 Pod",不参与调度、不启动容器。它通过对比期望副本数与实际 Pod 数量,缺失时调用 apiserver 创建新的 Pending Pod。

精简版流程

  1. 用户通过kubectl提交Deployment创建请求至apiserver;
  2. apiserver完成认证授权准入校验后将Deployment存入etcd;
  3. kube-controller-manager内的Deployment控制器监听到Deployment,对比期望副本数与现有Pod数量,缺失则调用apiserver创建Pending状态Pod存入etcd;
  4. kube-scheduler监听到未调度Pod,筛选选出最优节点并发起绑定;
  5. apiserver更新Pod绑定节点名称;
  6. 对应节点kubelet感知Pod调度到本机,准备存储与网络,调用容器运行时拉取镜像启动容器;
  7. kubelet检测容器正常运行后更新Pod状态为Running。

3.3 调度器内部两步走

第一步:过滤(Filter / Predicates)

把所有满足 Pod 调度需求的节点选出来,得出一个"可调度节点列表"。通常这个列表不止一个节点;如果列表为空,这个 Pod 就不可调度(会一直 Pending)。

Predicate 作用
PodFitsHostPorts 节点是否有空闲端口供 Pod 请求使用
PodFitsHost Pod 是否通过主机名指定了特定节点
PodFitsResources 节点的空闲资源(CPU/内存)能否满足 Pod 的 request
MatchNodeSelector 节点的标签是否匹配 Pod 的 nodeSelector
NoVolumeZoneConflict 考虑存储的故障区域限制,Pod 请求的卷在该节点是否可用
NoDiskConflict Pod 请求的卷与节点上已挂载的卷是否冲突
MaxCSIVolumeCount CSI 卷挂载数量是否超过配置的限制
PodToleratesNodeTaints Pod 的 toleration 能否容忍节点的 taint
CheckVolumeBinding Pod 请求的 PVC(已绑定/未绑定)能否适配该节点

第二步:打分(Score / Priorities)

根据当前启用的打分规则给每个可调度节点算分,得分最高者胜出 。如果有多个并列第一,kube-scheduler 从中随机选取一个。

Priority 作用
SelectorSpreadPriority 把属于同一 Service / StatefulSet / ReplicaSet 的 Pod 打散到不同主机
InterPodAffinityPriority 实现 Pod 间亲和性与反亲和性的软策略
LeastRequestedPriority 倾向资源请求较少的节点(节点上 Pod 越多、用得越多,得分越低)
MostRequestedPriority 倾向资源请求最多的节点(让工作负载集中在尽可能少的节点上)
RequestedToCapacityRatioPriority 基于 requests/capacity 比率的资源分配评分
BalancedResourceAllocation 倾向 CPU 与内存使用比例均衡的节点
NodePreferAvoidPodsPriority 根据节点注解 scheduler.alpha.kubernetes.io/preferAvoidPods 调整优先级
NodeAffinityPriority 根据 nodeAffinity 中 preferred 规则的首选项打分
TaintTolerationPriority 按节点上不可容忍污点的数量调整节点排名
ImageLocalityPriority 优先选择已缓存了该容器镜像的节点(加快启动)
ServiceSpreadingPriority 确保同一 Service 的 Pod 分布在不同节点,提升容灾能力
EqualPriority 给所有节点相同权重
EvenPodsSpreadPriority 实现 Pod 拓扑分布约束的首选策略

实用技巧 :Pod 一直 Pending 时,先 kubectl describe pod <pod名> 看 Events。过滤失败的事件信息非常直白(如 0/3 nodes are available: 1 node(s) had untolerated taint...),对照上面的 Predicate 表就能快速定位根因。


四、手动干预:控制 Pod 的运行位置

通常不需要干预------调度器自己会做合理放置(把 Pod 分散、避开资源不足的节点)。但有些场景必须干预:大量磁盘 I/O 的 Pod 想跑在 SSD 节点上;需要 GPU 的 Pod 必须落在 GPU 节点上。

可用的四种方式:

  • 与节点名匹配的 nodeName
  • 与节点标签匹配的 nodeSelector
  • 亲和性与反亲和性(nodeAffinity / podAffinity / podAntiAffinity)
  • Pod 拓扑分布约束(topologySpreadConstraints)

4.1 nodeName:最简单、也最不推荐

nodeName 是 PodSpec 的一个字段,值是节点名称。它是优先级最高的节点选择方式,通常不使用。

使用限制:

  • 指定的节点不存在 → Pod 不会运行,甚至会被自动删除
  • 指定节点资源不够 → 调度失败,原因明确(如 OutOfmemory、OutOfcpu)
  • 云环境中节点名称并非总是可预测或稳定的
bash 复制代码
root@master30:~# kubectl create deployment webapp --image nginx --replicas 2 -o yaml --dry-run=client > deploy-with-nodeName.yaml

root@master30:~# vim deploy-with-nodeName.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: webapp
  name: webapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webapp
  strategy: {}
  template:
    metadata:
      labels:
        app: webapp
    spec:
      # 添加nodeName配置
      nodeName: worker32.laoma.cloud
      containers:
      - image: hub.laoma.cloud/library/nginx
        name: nginx

root@master30:~# kubectl apply -f deploy-with-nodeName.yaml

root@master30 ~# kubectl get pods -o wide | awk '{print $1,$3,$7}'
NAME STATUS NODE
webapp-55c6b896bc-ns9m5 Running worker32.laoma.cloud
webapp-55c6b896bc-qr892 Running worker32.laoma.cloud

# 清理资源
root@master30:~# kubectl delete deployments.apps webapp 

两个副本被死死钉在同一个节点上------零容错、零弹性。

4.2 nodeSelector:推荐的简单做法

nodeSelector 包含键值对的映射。节点的标签中必须包含这里的每个键值对(也可以有额外标签),Pod 才能调度上去。

提示:nodeSelector 是节点选择约束的推荐形式。

bash 复制代码
# 查看node标签
root@master30:~# kubectl get node --show-labels 
NAME                  STATUS   ROLES           AGE    VERSION   LABELS
master30.laoma.cloud   Ready    control-plane   7d8h   v1.30.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master30.laoma.cloud,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node.kubernetes.io/exclude-from-external-load-balancers=
worker31.laoma.cloud   Ready    <none>          7d8h   v1.30.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=worker31.laoma.cloud,kubernetes.io/os=linux
worker32.laoma.cloud   Ready    <none>          7d8h   v1.30.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=worker32.laoma.cloud,kubernetes.io/os=linux

# 标注worker31 disktype是ssd
root@master30:~# kubectl label node worker31.laoma.cloud disktype=ssd

root@master30:~# kubectl get node -L disktype
NAME                   STATUS   ROLES           AGE   VERSION   DISKTYPE
master30.laoma.cloud   Ready    control-plane   11d   v1.30.2   
worker31.laoma.cloud   Ready    <none>          11d   v1.30.2   ssd
worker32.laoma.cloud   Ready    <none>          11d   v1.30.2   

在 Pod 模板的 spec 里指定跑在 SSD 节点上:

bash 复制代码
root@master30:~# vim deploy-with-nodeSelector.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: webapp
  name: webapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      # 添加 nodeSelector
      nodeSelector:
        disktype: ssd
      containers:
      - image: hub.laoma.cloud/library/nginx
        name: nginx

root@master30:~# kubectl apply -f deploy-with-nodeSelector.yaml

root@master30:~# kubectl get pods -o wide | awk '{print $1,$3,$7}'
NAME NODE
webapp-7d675f9d9c-rz9hx Running worker31.laoma.cloud
webapp-7d675f9d9c-zrj97 Running worker31.laoma.cloud
⚠️ 一个极易踩坑的细节

删除节点标签,不会触发重新部署------已经在跑的 Pod 会继续在原地跑:

bash 复制代码
# 删除worker31标签
root@master30:~# kubectl label node worker31.laoma.cloud disktype-
root@master30:~# kubectl get node -L disktype
NAME                   STATUS   ROLES           AGE   VERSION   DISKTYPE
master30.laoma.cloud   Ready    control-plane   11d   v1.30.2   
worker31.laoma.cloud   Ready    <none>          11d   v1.30.2   
worker32.laoma.cloud   Ready    <none>          11d   v1.30.2   

# 此时不会触发重新部署deployment

而删除 Deployment 的 nodeSelector 会自动触发重新部署:

bash 复制代码
# 删除deployment的nodeSelector
root@master30:~# kubectl patch deployment webapp --type json \
-p '[{"op":"remove","path":"/spec/template/spec/nodeSelector"}]'

# 自动触发重新部署
root@master30:~# kubectl get pods -o wide | awk '{print $1,$3,$7}'
NAME NODE
webapp-7fb6dc88f9-hj6m6 Running worker31.laoma.cloud
webapp-7fb6dc88f9-qcvtr Running worker32.laoma.cloud

# 清理资源
root@master30:~# kubectl delete deployments.apps webapp 

核心结论 :选择/亲和性规则只在调度那一刻生效。节点标签后续怎么变,都不会驱逐已运行的 Pod。

4.3 nodeAffinity:nodeSelector 的超集升级版

支持两种强度:

  • requiredDuringSchedulingIgnoredDuringExecution:必须满足(硬约束,类似 nodeSelector)
  • preferredDuringSchedulingIgnoredDuringExecution:优先满足(软偏好,不强制)

注意:如果节点标签在运行时发生变更,Pod 仍继续在该节点上运行。

配置位置:.spec.affinity.nodeAffinity

bash 复制代码
root@master30:~# vim deploy-with-nodeAffinity.yaml
apiVersion: apps/v1  # 资源API版本,Deployment属于apps/v1
kind: Deployment     # 资源类型:Deployment控制器
metadata:
  labels:
    app: web         # Deployment自身标签
  name: web          # Deployment名称
spec:
  replicas: 2        # 期望Pod副本数量:2个
  selector:
    matchLabels:
      app: web       # 匹配Pod标签,用来管理Pod
  template:          # Pod模板,定义创建出来的Pod配置
    metadata:
      labels:
        app: web     # Pod的标签,和selector对应
    spec:
      affinity:      # 亲和性配置
        nodeAffinity: # 节点亲和性
          requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和,调度必须满足
            nodeSelectorTerms:
            - matchExpressions:
              - key: CPU                  # 节点标签key
                operator: In              # 运算符:包含
                values:
                - L1
                - L2                      # 节点标签CPU值必须是L1或L2,否则Pod Pending
          preferredDuringSchedulingIgnoredDuringExecution: # 软亲和,优先满足,不满足也能调度
          - weight: 1                     # 权重1,多个偏好时用于打分排序(范围1~100)
            preference:
              matchExpressions:
              - key: MEM                  # 节点标签key
                operator: In              # 运算符:包含
                values:
                - L1
                - L2                      # 优先选MEM=L1/L2的节点,没有也可以调度到其他符合硬亲和的节点
      containers:
      - image: hub.laoma.cloud/library/nginx  # 容器镜像地址
        name: nginx                           # 容器名称

规则解读 :Pod 只能放在带 CPU=L1 或 CPU=L2 标签的节点上;在满足条件的节点中,优先选带 MEM=L1/L2 标签的。

三组必备规则(面试常考)
场景 结果
同时指定 nodeSelector 和 nodeAffinity 两者都必须满足
多个 nodeSelectorTerms 任意一个满足即可(OR)
同一个 term 里的多个 matchExpressions 必须全部满足(AND)

operator 支持:In、NotIn、Exists、DoesNotExist、Gt、Lt。用 NotIn / DoesNotExist 可以实现节点反亲和效果。

实验 1:节点无标签 → 全部 Pending
bash 复制代码
root@master30:~# kubectl apply -f deploy-with-nodeAffinity.yaml
root@master30:~# kubectl get pods
NAME                  READY   STATUS    RESTARTS   AGE
web-854cd4986-598lb   0/1     Pending   0          2s
web-854cd4986-mp266   0/1     Pending   0          2s
实验 2:给节点打 CPU 标签
bash 复制代码
root@master30:~# kubectl label nodes worker31.laoma.cloud CPU=L1
root@master30:~# kubectl label nodes worker32.laoma.cloud CPU=L2

# 再次查看pod调度情况
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-854cd4986-598lb Running worker31.laoma.cloud
web-854cd4986-mp266 Running worker31.laoma.cloud
# worker31打上标签后,满足pod调度条件,此时所有pod调度到worker31节点。

# 重新部署
root@master30:~# kubectl rollout restart deployment web
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-85c8ffff8-q2j7r Running worker31.laoma.cloud
web-85c8ffff8-rkq7s Running worker32.laoma.cloud
# worker31和worker32都打上标签后,满足pod调度条件,此时pod均衡调度到worker31和worker32节点。
实验 3:给 worker32 打 MEM 标签 → 软偏好立即生效
bash 复制代码
root@master30:~# kubectl label nodes worker32.laoma.cloud MEM=L2
# 重新部署
root@master30:~# kubectl rollout restart deployment web

# 只在worker32上运行
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-6c45779498-glxbg Running worker32.laoma.cloud
web-6c45779498-sn996 Running worker32.laoma.cloud
权重 weight:1~100 的打分机制

每条 preferred 规则的 weight 取值范围 1~100 。调度器遍历节点满足的所有偏好规则,把匹配的权重求和 ,再叠加到该节点的其他优先级评分上,总分最高者胜出。

bash 复制代码
apiVersion: v1                          # 资源版本,Pod 属于 v1
kind: Pod                                # 资源类型:Pod
metadata:
  name: with-affinity-anti-affinity     # Pod名称
spec:
  affinity:                              # 亲和性配置
    nodeAffinity:                        # 节点亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 节点硬亲和(强制条件)
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os        # 标签key:操作系统
            operator: In                 # 运算符:In(属于)
            values:
            - linux                      # 节点标签 kubernetes.io/os 的值必须是 linux,不满足则Pod Pending
      preferredDuringSchedulingIgnoredDuringExecution: # 节点软亲和(偏好打分)
      - weight: 1                        # 权重1,分值低,优先级低
        preference:
          matchExpressions:
          - key: label-1
            operator: In
            values:
            - key-1                      # 匹配 label-1=key-1,命中 +1分
      - weight: 50                       # 权重50,分值高,优先级更高
        preference:
          matchExpressions:
          - key: label-2
            operator: In
            values:
            - key-2                      # 匹配 label-2=key-2,命中 +50分
  containers:
  - name: with-node-affinity             # 容器名称
    image: hub.laoma.cloud/library/nginx # 使用的nginx镜像

上例中,带 label-2: key-2 的节点(weight=50)会比带 label-1: key-1(weight=1)的节点获得高得多的加分。

bash 复制代码
# 清理环境
root@master30:~# kubectl delete deployments.apps web
root@master30:~# kubectl label nodes worker31.laoma.cloud CPU-
root@master30:~# kubectl label nodes worker32.laoma.cloud CPU-
root@master30:~# kubectl label nodes worker32.laoma.cloud MEM-

4.4 Pod 间亲和性与反亲和性

前面的规则全部基于节点标签 ;Pod 间亲和/反亲和则是基于已在节点上运行的 Pod 的标签:

如果 X 拓扑域上已运行了一个或多个满足规则 Y 的 Pod:

  • 亲和性:本 Pod 应该运行在 X
  • 反亲和性:本 Pod 不应该运行在 X
  • X :节点 / 机架 / 可用区 / 地理区域等拓扑域,由 topologyKey(节点标签的键)表达
  • Y:标签选择算符表达的匹配规则

配置位置:.affinity.podAffinity、.affinity.podAntiAffinity

⚠️ 两条注意事项:

  1. Pod 间亲和/反亲和会消耗大量计算资源 ,显著拖慢大规模集群调度,不建议在数百节点以上的集群使用
  2. 使用反亲和时,集群中每个节点都必须有匹配 topologyKey 的标签,否则会出现意外行为
语法模板
bash 复制代码
apiVersion: v1                          # Pod资源API版本
kind: Pod                                # 资源类型:Pod
metadata:
  name: pod-with-podaffinity             # Pod名称
spec:
  affinity:                              # 亲和性配置
    podAffinity:                         # Pod亲和性(和别的Pod放一块)
      requiredDuringSchedulingIgnoredDuringExecution: # Pod硬亲和,强制规则
      - labelSelector:
          matchExpressions:
          - key: security                # 匹配Pod标签key
            operator: In
            values:
            - S1                         # 寻找标签 security=S1 的Pod
        topologyKey: topology.kubernetes.io/zone # 拓扑域:可用区zone
    podAntiAffinity:                     # Pod反亲和(尽量不和某些Pod放一起)
      preferredDuringSchedulingIgnoredDuringExecution: # Pod软反亲和,偏好规则
      - weight: 100                      # 权重100,优先级最高
        podAffinityTerm:                 # ✅ 软的这里要包一层 podAffinityTerm
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2                       # 匹配标签 security=S2 的Pod
          topologyKey: topology.kubernetes.io/zone # 拓扑域:可用区zone
  containers:
  - name: pod-with-podaffinity           # 容器名称
    image: hub.laoma.cloud/library/nginx # nginx镜像
  • 亲和规则 :只有当某 zone 内已有 security=S1 的 Pod,本 Pod 才能调度到该 zone 内的任意节点
  • 反亲和规则 :若某 zone 内有 security=S2 的 Pod,尽量避免把本 Pod 调度到该 zone

合法操作符:In、NotIn、Exists、DoesNotExist。topologyKey 不允许为空。

实验 1:同 zone 聚拢(亲和性)
bash 复制代码
# node节点打标签
root@master30:~# kubectl label node worker31.laoma.cloud \
topology.kubernetes.io/zone=v
root@master30:~# kubectl label node worker32.laoma.cloud \
topology.kubernetes.io/zone=v

# 创建两个pod
root@master30:~# vim deploy-with-podaffinity.yaml
bash 复制代码
---
apiVersion: apps/v1                          # Deployment API版本
kind: Deployment                              # 资源类型:Deployment
metadata:
  name: web1                                  # Deployment名称web1
spec:
  selector:
    matchLabels:
      app: web1                               # 管理标签app=web1的Pod
  replicas: 1                                 # 副本数1
  template:
    metadata:
      labels:
        app: web1                             # Pod标签 app=web1
    spec:
      nodeSelector:                           # 节点选择器(简单硬匹配)
        topology.kubernetes.io/zone: v        # 只能调度到zone=v这个可用区
      containers:
      - name: web-server                      # 容器名
        image: hub.laoma.cloud/library/nginx  # nginx镜像
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web2                                  # Deployment名称web2
spec:
  selector:
    matchLabels:
      app: web2                               # 管理标签app=web2的Pod
  replicas: 1                                 # 副本数1
  template:
    metadata:
      labels:
        app: web2                             # Pod标签 app=web2
    spec:
      affinity:
        podAffinity:                          # Pod亲和性
          requiredDuringSchedulingIgnoredDuringExecution: # Pod硬亲和【强制】
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web1                        # 寻找标签 app=web1 的Pod
            topologyKey: topology.kubernetes.io/zone # 拓扑域:可用区zone
      containers:
      - name: web-server                      # 容器名
        image: hub.laoma.cloud/library/nginx  # nginx镜像
bash 复制代码
root@master30:~# kubectl apply -f deploy-with-podaffinity.yaml 
root@master30 ~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web1-849b694c75-t2spf Running worker31.laoma.cloud
web2-84fdf8f999-vn5l9 Running worker32.laoma.cloud

思考题:把 worker32 的 zone 标签删掉,重启 web2 后会去哪?

bash 复制代码
root@master30:~# kubectl label nodes worker32.laoma.cloud \
topology.kubernetes.io/zone-
root@master30 ~# kubectl rollout restart deployment web2

# 调度到worker31节点
root@master30 ~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web1-849b694c75-t2spf Running worker31.laoma.cloud
web2-7947994f89-pwnkv Running worker31.laoma.cloud

结论:Pod 亲和性必须同时满足两个条件

  1. node 必须具有相应标签(把节点定义成同一机架、同一机房等物理区域)
  2. 该拓扑域内至少运行着一个带相应标签的 Pod
bash 复制代码
# 清理环境
root@master30:~# kubectl delete -f deploy-with-podaffinity.yaml 
root@master30:~# kubectl label nodes worker31.laoma.cloud \
topology.kubernetes.io/zone-
实验 2:每节点一副本(hostname 反亲和)------高可用标配
bash 复制代码
# 准备deployment
root@master30:~# vim deploy-with-podAntiAffinity.yaml
apiVersion: apps/v1                          # Deployment API版本
kind: Deployment                              # 资源类型:Deployment
metadata:
  name: store                                 # Deployment名称 store
spec:
  selector:
    matchLabels:
      app: store                              # 筛选管理标签 app=store 的Pod
  replicas: 3                                 # 期望副本数:3个
  template:
    metadata:
      labels:
        app: store                            # Pod自身标签 app=store
    spec:
      affinity:
        podAntiAffinity:                      # Pod反亲和
          requiredDuringSchedulingIgnoredDuringExecution: # 硬反亲和(强制约束)
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store                       # 匹配 app=store 的Pod
            topologyKey: "kubernetes.io/hostname" # 拓扑域:单台节点(hostname)
      containers:
      - name: web-server                      # 容器名称
        image: hub.laoma.cloud/library/nginx  # nginx镜像
# 表示同一台节点上,不允许同时存在 2 个 app=store 的 Pod
bash 复制代码
# 查看pod调度情况
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-9z5ch Pending <none>
store-5596cf4c84-dswkq Running worker32.laoma.cloud
store-5596cf4c84-ww2l4 Running worker31.laoma.cloud

结果 :每个节点的 hostname 唯一,所以每个节点只能跑一个副本,第三个副本找不到可用节点 → Pending。

生产建议:replicas 别超过可用节点数,否则副本会一直 Pending。

实验 3:亲和 + 反亲和的组合冲突
bash 复制代码
# 缩容 deployment
root@master30:~# kubectl scale deployment store --replicas 1
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-dswkq Running worker32.laoma.cloud

# 准备新deployment
root@master30:~# vim deploy-with-multi-Affinity.yaml
apiVersion: apps/v1                          # Deployment API版本
kind: Deployment                              # 资源类型:Deployment
metadata:
  name: web-store                             # Deployment名称 web-store
spec:
  selector:
    matchLabels:
      app: web-store                          # 管理标签 app=web-store 的Pod
  replicas: 1                                 # 副本数:1
  template:
    metadata:
      labels:
        app: web-store                        # Pod自身标签 app=web-store
    spec:
      affinity:
        podAntiAffinity:                      # Pod硬反亲和
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store                   # 匹配 app=web-store 的Pod
            topologyKey: "kubernetes.io/hostname" # 拓扑域:单节点hostname
        podAffinity:                          # Pod硬亲和
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store                       # 匹配 app=store 的Pod
            topologyKey: "kubernetes.io/hostname" # 拓扑域:单节点hostname
      containers:
      - name: web-app                         # 容器名称
        image: hub.laoma.cloud/library/nginx  # nginx镜像
# web-store Pod 必须跑到一台【有 store Pod、同时又没有 web-store Pod】的节点上
bash 复制代码
root@master30:~# kubectl apply -f deploy-with-multi-Affinity.yaml
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-f4dfb Running worker32.laoma.cloud
web-store-7b4779c958-vpl58 Running worker32.laoma.cloud

# 扩容:验证新副本位置
root@master30:~# kubectl scale deployment web-store --replicas 2
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-f4dfb Running worker32.laoma.cloud
web-store-7b4779c958-bswws Pending <none>
web-store-7b4779c958-vpl58 Running worker32.laoma.cloud

为什么第二个副本挂起?

  • 亲和性:必须和 store 同节点 → 只有 worker32 合格
  • 反亲和性:不能和已有 web-store 副本同节点 → worker32 被排除
  • 交集为空 → Pending

这就是设计 YAML 时最容易掉进去的坑:亲和 + 反亲和取交集,别让结果变成空集。


五、Taint 与 Toleration:反过来的"节点选 Pod"

前面都是 Pod 挑节点 ,污点/容忍则是节点挑 Pod:

  • Taint(污点):打在节点上,让"不对味"的 Pod 进不来
  • Toleration(容忍度):配在 Pod 上,让自己"够资格"进这些节点

🤔 nodeAffinity 和 Taint 到底啥区别?

机制 本质
nodeAffinity Pod 选 Node 的必要条件
Taint Node 选 Pod 的必要条件

5.1 管理节点污点

bash 复制代码
# 给节点增加污点:键名 CPU,键值 L1,效果 NoSchedule
root@master30:~# kubectl taint nodes worker31.laoma.cloud \
CPU=L1:NoSchedule

# 移除污点(键名:effect-)
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU:NoSchedule-

5.2 Pod 侧配置容忍度

bash 复制代码
tolerations:
- key: "CPU"
  operator: "Equal"        # 默认值:键值对完全相同才匹配
  value: "L1"
  effect: "NoSchedule"

# 只匹配 key,不关心 value 时:
tolerations:
- key: "CPU"
  operator: "Exists"
  effect: "NoSchedule"

effect 三种效果:

effect 行为
NoSchedule 不能容忍的新 Pod 不会被调度上来;已在运行的 Pod 不会被驱逐
PreferNoSchedule 软性版本,尽量避免调度过来,但不保证
NoExecute 无法容忍的已运行 Pod 立刻被驱逐 ;能容忍但未指定 tolerationSeconds 的一直运行;指定了则运行超时后被驱逐

⚠️ 高危配置 :容忍度的 key 为空且 operator 为 Exists,表示容忍任意 taint,慎用。

5.3 实验:单污点 vs 单容忍度

bash 复制代码
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule
root@master30:~# kubectl taint nodes worker32.laoma.cloud CPU=L2:NoSchedule

第一次尝试 :故意用不匹配的容忍度(CPU=L3)

bash 复制代码
apiVersion: apps/v1                          # Deployment资源API版本
kind: Deployment                              # 资源类型:Deployment控制器
metadata:
  labels:
    app: web                                  # Deployment自身标签
  name: web                                   # Deployment名称
spec:
  replicas: 2                                 # 期望Pod副本数量:2个
  selector:
    matchLabels:
      app: web                                # 匹配Pod标签,管理Pod
  template:
    metadata:
      labels:
        app: web                              # Pod标签
    spec:
      tolerations:                            # 容忍(污点容忍)配置
      - key: "CPU"                            # 污点key
        operator: "Equal"                     # 运算符:Equal(等于)
        value: "L3"                           # 污点value
        effect: "NoSchedule"                  # 污点效果:不允许调度
      containers:
      - name: nginx                           # 容器名称
        image: hub.laoma.cloud/library/nginx  # 镜像地址
        imagePullPolicy: IfNotPresent         # 拉取策略:本地有镜像就不拉取
bash 复制代码
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
web-5bf7b7b98f-kh7hv   0/1     Pending   0          2s
web-5bf7b7b98f-km7w5   0/1     Pending   0          2s

# 事件表明3个节点上的污点与pod不匹配,所以无法创建pod
root@master30:~# kubectl describe pod web-5bf7b7b98f-kh7hv
......
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  29s   default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {CPU: L1}, 1 node(s) had untolerated taint {CPU: L2}, 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.

顺带解释了:为什么普通 Pod 永远跑不到 master 上------control-plane 节点天生带 node-role.kubernetes.io/control-plane 污点。

第二次尝试 :改成匹配的 CPU=L1

bash 复制代码
root@master30:~# kubectl delete deployments.apps web
root@master30:~# vim deploy-with-tolerations.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      tolerations:
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoSchedule"
      containers:
      - name: nginx
        image: hub.laoma.cloud/library/nginx
        imagePullPolicy: IfNotPresent
bash 复制代码
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pods -o wide
NAME                 READY   STATUS    RESTARTS   AGE   IP               NODE                   NOMINATED NODE   READINESS GATES
web-7c99d767-w9rzt   1/1     Running   0          5s    10.224.162.142   worker31.laoma.cloud   <none>           <none>
web-7c99d767-wwjhk   1/1     Running   0          5s    10.224.19.13     worker31.laoma.cloud   <none>           <none>

# Pod调度成功,而且分配到了worker31

# 清理环境
root@master30:~# kubectl taint node worker31.laoma.cloud CPU:NoSchedule-
root@master30:~# kubectl taint node worker32.laoma.cloud CPU:NoSchedule-
root@master30:~# kubectl delete deployments.apps web

关键认知 :容忍度只是"准入许可证",不是定向指令 。它能上 worker31,同时也保留了上其他节点的可能性。想"只跑在某类节点",必须污点 + 节点亲和性配合使用。

5.4 多污点 + 多容忍度的过滤器逻辑

处理过程就像一个过滤器:遍历节点所有污点,过滤掉被 Pod 容忍的,剩下的决定结果:

  • 剩余污点含 NoSchedule → 不分配
  • 无 NoSchedule 但含 PreferNoSchedule → 尽量不分配
  • 剩余污点含 NoExecute → 不分配(未运行)或驱逐(已运行)

实验:worker31 打三个污点,Pod 只容忍前两个

bash 复制代码
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoExecute
root@master30:~# kubectl taint nodes worker31.laoma.cloud MEM=L2:NoSchedule
root@master30:~# kubectl taint nodes worker32.laoma.cloud CPU=L2:NoSchedule
bash 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      tolerations:
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoSchedule"
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoExecute"
      containers:
      - name: nginx
        image: hub.laoma.cloud/library/nginx
        imagePullPolicy: IfNotPresent
bash 复制代码
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
web-6844d698c4-b8cz7   0/1     Pending   0          4s
web-6844d698c4-wrpkj   0/1     Pending   0          4s

# 事件表明节点上的3个污点与pod不匹配,所以无法创建pod
root@master30:~# kubectl describe pod web-6844d698c4-b8cz7 
......
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  50s   default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {CPU: L2}, 1 node(s) had untolerated taint {MEM: L2}, 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.

补上第三条容忍度后成功:

bash 复制代码
root@master30:~# kubectl delete deployments.apps web
root@master30:~# vim deploy-with-tolerations.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      tolerations:
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoSchedule"
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoExecute"
      - key: "MEM"
        operator: "Equal"
        value: "L2"
        effect: "NoSchedule"
      containers:
      - name: nginx
        image: hub.laoma.cloud/library/nginx
        imagePullPolicy: IfNotPresent
bash 复制代码
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pod -o wide
NAME                   READY   STATUS    RESTARTS   AGE   IP             NODE                   NOMINATED NODE   READINESS GATES
web-77d5464ccf-656fd   1/1     Running   0          5s    10.224.19.18   worker31.laoma.cloud   <none>           <none>
web-77d5464ccf-wjk6m   1/1     Running   0          5s    10.224.19.27   worker31.laoma.cloud   <none>           <none>

# 清理环境
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule-
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoExecute-
root@master30:~# kubectl taint nodes worker31.laoma.cloud MEM=L2:NoSchedule-
root@master30:~# kubectl taint nodes worker32.laoma.cloud CPU=L2:NoSchedule-
root@master30:~# kubectl delete deployments.apps web

5.5 生产用例:专用节点与 GPU 节点

用户专用节点

bash 复制代码
# 给专用节点打污点
kubectl taint nodes <node> dedicated=groupName:NoSchedule

然后给这组用户的 Pod 加对应 toleration。注意 :这样配置的 Pod 既能上专用节点,也能上集群其他节点。

若要求 Pod 只能上专用节点,必须再叠加:

bash 复制代码
# 1. 同时给专用节点加标签
kubectl label nodes <node> dedicated=groupName

# 2. 给 Pod 加节点亲和性,限定只能调度到带该标签的节点

特殊硬件节点(GPU)

bash 复制代码
# 给 GPU 节点打污点,避免不需要 GPU 的 Pod 占用资源
kubectl taint nodes <gpu-node> special=true:NoSchedule
# 再给真正需要 GPU 的任务加匹配的 toleration

5.6 基于污点的驱逐:K8s 自动加了哪些?

节点异常时,节点控制器会自动给节点加污点:

内置污点 含义
node.kubernetes.io/not-ready 节点未就绪(Ready=False)
node.kubernetes.io/unreachable 节点控制器访问不到节点(Ready=Unknown)
node.kubernetes.io/memory-pressure 内存压力
node.kubernetes.io/disk-pressure 磁盘压力
node.kubernetes.io/pid-pressure PID 压力
node.kubernetes.io/network-unavailable 网络不可用
node.kubernetes.io/unschedulable 节点不可调度
node.cloudprovider.kubernetes.io/uninitialized 外部云平台驱动未初始化

为保证节点问题引发的驱逐速率可控,系统以限定速率添加污点,避免主控与 worker 通信中断时 Pod 被大量驱逐。

利用 tolerationSeconds 争取缓冲时间:

bash 复制代码
tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000

✨ 两个必须知道的内置行为:

  1. K8s 会自动给所有 Pod 注入 not-ready 和 unreachable 容忍度(tolerationSeconds=300)→ 节点异常时 Pod 默认还能坚持 5 分钟
  2. DaemonSet 的 Pod 对这两个污点的容忍不设 tolerationSeconds → 保证节点级 Agent(如 CNI、kube-proxy)永不被驱逐
bash 复制代码
# 环境清理
root@master30:~# kubectl delete ns scheduler

六、节点维护三件套:cordon / drain / uncordon

6.1 cordon:标记为不可调度

bash 复制代码
root@master30:~# kubectl cordon -h
Mark node as unschedulable.

Usage:
  kubectl cordon NODE [options]

root@master30:~# kubectl get nodes
NAME                  STATUS   ROLES           AGE   VERSION
master30.laoma.cloud   Ready    control-plane   9d    v1.30.2
worker31.laoma.cloud   Ready    <none>          9d    v1.30.2
worker32.laoma.cloud   Ready    <none>          9d    v1.30.2

# 标记worker31.laoma.cloud为SchedulingDisabled
root@master30:~# kubectl cordon worker31.laoma.cloud 
root@master30:~# kubectl get nodes
NAME                  STATUS                     ROLES           AGE   VERSION
master30.laoma.cloud   Ready                      control-plane   9d    v1.30.2
worker31.laoma.cloud   Ready,SchedulingDisabled   <none>          9d    v1.30.2
worker32.laoma.cloud   Ready                      <none>          9d    v1.30.2

# 创建一个deployment
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/nginx --replicas=2
root@master30:~# kubectl get pod -o wide
NAME                   READY   STATUS    RESTARTS   AGE   IP              NODE               NOMINATED NODE   READINESS GATES
web-59b9bb7664-flhvx   1/1     Running   0          10s   10.224.225.69   worker32.laoma.cloud   <none>           <none>
web-59b9bb7664-ss586   1/1     Running   0          10s   10.224.225.68   worker32.laoma.cloud   <none>           <none>

注意:cordon 只影响新 Pod,已经在 worker31 上跑的 Pod 不会被赶走。

6.2 uncordon:恢复调度

bash 复制代码
root@master30:~# kubectl uncordon worker31.laoma.cloud 
root@master30:~# kubectl get nodes
NAME                    STATUS   ROLES    AGE   VERSION
master30.laoma.cloud    Ready    control-plane   36d   v1.30.2
worker31.laoma.cloud    Ready    <none>   36d   v1.30.2
worker32.laoma.cloud    Ready    <none>   36d   v1.30.2

root@master30:~# kubectl scale deployment web --replicas=4
root@master30:~# kubectl get pod -o wide
NAME                   READY   STATUS    RESTARTS   AGE    IP              NODE               NOMINATED NODE   READINESS GATES
web-59b9bb7664-flhvx   1/1     Running   0          2m6s   10.224.225.69   worker32.laoma.cloud   <none>           <none>
web-59b9bb7664-jkb5q   1/1     Running   0          5s     10.224.51.133   worker31.laoma.cloud   <none>           <none>
web-59b9bb7664-ss586   1/1     Running   0          2m6s   10.224.225.68   worker32.laoma.cloud   <none>           <none>
web-59b9bb7664-w6mbs   1/1     Running   0          5s     10.224.51.132   worker31.laoma.cloud   <none>           <none>

6.3 drain:安全地清空节点(维护前必做)

内核升级、硬件维护之前,用 kubectl drain 把节点上的 Pod 安全地驱逐走。驱逐过程允许 Pod 容器体面终止,并确保满足 PodDisruptionBudget。

前置条件:

  1. 节点清空期间,确保应用具有高可用性
  2. 已为需要的应用配置了 PodDisruptionBudget(建议设置 AlwaysAllow 不健康 Pod 驱逐策略)
bash 复制代码
root@master30:~# kubectl drain -h
Drain node in preparation for maintenance.

 The given node will be marked unschedulable to prevent new pods from arriving.
'drain' evicts the pods if the APIServer supports.

Usage:
  kubectl drain NODE [options]
  
# 默认不驱逐 DaemonSet 控制的pod
root@master30:~# kubectl drain worker31.laoma.cloud 
node/worker31.laoma.cloud cordoned
error: unable to drain node "worker31.laoma.cloud", aborting command...

There are pending nodes to be drained:
 worker31.laoma.cloud
error: cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): kube-system/calico-node-jwdzx, kube-system/kube-proxy-98qkl

# 使用--ignore-daemonsets驱逐DaemonSet控制的pod
root@master30:~# kubectl drain worker31.laoma.cloud --ignore-daemonsets 

实用选项:

bash 复制代码
# 只驱逐特定标签的 Pod
  --pod-selector='': Label selector to filter pods on the node
  -l, --selector='': Selector (label query) to filter on,supports '=', '==', and '!='.(e.g. -l CPU=L1,MEM=L2). Matching objects must satisfy all of the specified label constraints.

root@master30:~# kubectl get node
NAME                   STATUS                        ROLES        AGE   VERSION
master30.laoma.cloud   Ready                         control-plane   36d   v1.30.2
worker31.laoma.cloud   Ready,SchedulingDisabled   <none>       36d   v1.30.2
worker32.laoma.cloud   Ready                      <none>       36d   v1.30.2

# 删除worker31上SchedulingDisabled标记
root@master30:~# kubectl uncordon worker31.laoma.cloud 

cordon vs drain 一句话区别:cordon = 只关门(不让新人进);drain = 关门 + 清人(把现有 Pod 都赶走)。


七、Metrics-Server:让 kubectl top 和 HPA 有数据可用

7.1 它解决什么问题

  • 如何监控 node 计算资源使用情况?
  • 如何监控 pod 计算资源使用情况?
  • 如何根据 pod 计算资源使用情况自动扩展?

Metrics Server 通过 Kubelet 收集资源指标,再通过 Metrics API 公开在 Kubernetes apiserver 中,供 HPA 和 VPA 使用。

7.2 环境准备

bash 复制代码
root@master30:~# kubectl create ns metric
root@master30:~# kubectl config set-context --current --namespace metric

7.3 部署

bash 复制代码
# 下载 Metrics-Server
root@master30:~# wget -O metrics-server-components.yaml https://laoma.cloud/class/course-materials/softwares/stage03/metrics-server-components-v0.7.1.yaml

# 修改 Metrics-Server,不校验tls(实验环境自签证书必需)
root@master30:~# sed -i '/metric-resolution/a\        - --kubelet-insecure-tls' metrics-server-components.yaml

# 修改镜像为私有仓库
root@master30:~# grep image: metrics-server-components.yaml
        image: registry.k8s.io/metrics-server/metrics-server:v0.7.1
root@master30:~# sed -i 's/registry.k8s.io/hub.laoma.cloud/g' metrics-server-components.yaml

# 部署
root@master30:~# kubectl apply -f metrics-server-components.yaml
serviceaccount/metrics-server created
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
clusterrole.rbac.authorization.k8s.io/system:metrics-server created
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
service/metrics-server created
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created

# 查看 Metrics-Server 状态
root@master30:~# kubectl get pods -n kube-system |grep metrics
metrics-server-6556f8cb6c-78mhq             1/1     Running   0          45s

7.4 使用

bash 复制代码
root@master30:~# kubectl top node
NAME                  CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%   
master30.laoma.cloud   97m          4%     1328Mi          35%       
worker31.laoma.cloud   49m          2%     912Mi           24%       
worker32.laoma.cloud   45m          2%     926Mi           24% 

root@master30:~# kubectl top pods -n kube-system 
NAME                                           CPU(cores)   MEMORY(bytes)   
calico-kube-controllers-6dfcd885bf-8rhg2       2m           11Mi           
calico-node-crfr7                              27m          57Mi           
calico-node-sbmjt                              22m          57Mi           
calico-node-z9dvd                              24m          56Mi           
coredns-6d56c8448f-nhjpg                       3m           12Mi           
coredns-6d56c8448f-w2f48                       2m           10Mi           
etcd-master30.laoma.cloud                      16m          54Mi           
kube-apiserver-master30.laoma.cloud            46m          355Mi           
kube-controller-manager-master30.laoma.cloud   11m          50Mi           
kube-proxy-hx566                               1m           18Mi           
kube-proxy-t2r4x                               1m           18Mi           
kube-proxy-x6k8h                               1m           18Mi           
kube-scheduler-master30.laoma.cloud            4m           22Mi           
metrics-server-6556f8cb6c-78mhq                1m           11Mi    

单位换算 :1 CPU = 1000m


八、HPA(Horizontal Pod Autoscaler):Pod 水平自动扩缩容

控制器从 metrics.k8s.io、custom.metrics.k8s.io、external.metrics.k8s.io 获取度量值(前者通常由 Metrics Server 提供),根据定义的指标动态伸缩 Pod 数量,支持 Deployment、ReplicationController、ReplicaSet。

适用场景:

  • 流量波动大的无状态服务(nginx、api、网关、微服务)
  • 需要提高并发、削峰填谷

核心特点:

  • 不修改 Pod 配置,只改副本数
  • 实时、秒级扩缩
  • 最常用、最稳定

支持指标:CPU 使用率、内存使用率、自定义指标(对接 Prometheus)、QPS/延迟等

8.1 三个核心调参(实验环境建议调小)

Pod 数量扩缩容由 kube-controller-manager 管理:

参数 默认值 作用
--horizontal-pod-autoscaler-sync-period 15s 采样周期:拉取指标 → 计算期望副本 → 判断是否扩容
--horizontal-pod-autoscaler-initial-readiness-delay 30s Pod 启动后这段时间内视为"启动中",不参与 HPA 计算
--horizontal-pod-autoscaler-downscale-stabilization 300s(5 分钟) 缩容冷却时间
bash 复制代码
root@master30:~# vim /etc/kubernetes/manifests/kube-controller-manager.yaml 
spec:
  containers:
  - command:
    - kube-controller-manager
    - --allocate-node-cidrs=true
......
    # 增加下面三行参数
    - --horizontal-pod-autoscaler-sync-period=10s
    - --horizontal-pod-autoscaler-initial-readiness-delay=20s
    - --horizontal-pod-autoscaler-downscale-stabilization=30s

保存退出,静态 Pod 会自动重启生效。

实际扩容 cooling 时间范围约 20s~30s:最短 20s(Pod 启动刚好赶上采指立刻扩),最长 30s(启动后还要等一个采样周期)。

8.2 实验:基于 CPU 使用率伸缩

准备资源

bash 复制代码
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/nginx
root@master30:~# kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
web-68b95c775c-xksf4   1/1     Running   0          4s

创建 HPA

bash 复制代码
Usage:  kubectl autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS]--max=MAXPODS [--cpu-percent=CPU] [options]

# 示例
root@master30:~# kubectl autoscale deployment web --max=5 --min=2 --cpu-percent=80 -o yaml
# 省略部分不重要配置
# 注意这里版本是v1
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: web
  namespace: metric
spec:
  maxReplicas: 5
  minReplicas: 2
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  targetCPUUtilizationPercentage: 80
bash 复制代码
# 稍等片刻,hpa会自动创建一个新的pod(补齐 minReplicas)
root@master30:~# kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
web-68b95c775c-48ngs   1/1     Running   0          13s
web-68b95c775c-xksf4   1/1     Running   0          41s

root@master30:~# kubectl get hpa
NAME   REFERENCE        TARGETS              MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   cpu: <unknown>/80%   2         5         2          34s
# CPU 目标为unknown

⚠️ TARGETS 显示 <unknown> 的排查清单 (必看):

必须同时满足两个条件 ------① 已安装 Metrics Server;② Pod 设置了资源限制 limits(没有 limit 就没有百分比的分母)。

bash 复制代码
root@master30:~# kubectl edit deployments.apps web
# 修改spec.template.spec.containers.[N].resources属性,添加limit属性,如下:
        resources:
          limits: 
            cpu: 100m
            memory: 200Mi

# 再次查看hpa
root@master30:~# kubectl get hpa
NAME   REFERENCE        TARGETS       MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   cpu: 0%/80%   2         5         2          83s

root@master30:~# kubectl expose deployment web --port=80 --target-port=80
root@master30:~# kubectl get svc
NAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   10.100.255.92   <none>        80/TCP    6s

压力测试

bash 复制代码
# 打开一个监控窗口
root@master30:~# \
while true
do
  clear;
  date;echo
  kubectl get hpa;echo
  kubectl get pod;echo
  kubectl top pods
  sleep 1
done | tee hpa.log

# 上 CPU 压力
root@master30:~# apt install -y apache2-utils
root@master30:~# while true ;do ab -n 300000 -c 100 http://10.100.255.92/;done
#  -n 300000,总请求数
#  -c 100,每次并发数

扩容过程实录(每轮约 20 秒新增一个副本):

bash 复制代码
Every 2.0s: kubectl get hpa;echo;kubectl top pods                     master30.laoma.cloud: Sun Apr 19 09:57:22 2026

NAME   REFERENCE        TARGETS        MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   cpu: 100%/80%   2         5         2          8m32s

NAME                   CPU(cores)   MEMORY(bytes)   
web-6dcdc45c94-6gwd2   101m         3Mi             
web-6dcdc45c94-dvd2m   100m         3Mi 
bash 复制代码
Every 2.0s: kubectl get hpa;echo;kubectl top pods                     master30.laoma.cloud: Sun Apr 19 09:58:04 2026

NAME   REFERENCE        TARGETS        MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   cpu: 95%/80%   2         5         3          9m14s

NAME                   CPU(cores)   MEMORY(bytes)   
web-6dcdc45c94-2zbmb   93m          3Mi             
web-6dcdc45c94-6gwd2   100m         3Mi             
web-6dcdc45c94-dvd2m   90m          3Mi 
bash 复制代码
Every 2.0s: kubectl get hpa;echo;kubectl top pods                     master30.laoma.cloud: Sun Apr 19 09:58:59 2026

NAME   REFERENCE        TARGETS        MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   cpu: 99%/80%   2         5         4          11m

NAME                   CPU(cores)   MEMORY(bytes)   
web-6dcdc45c94-2zbmb   100m         3Mi             
web-6dcdc45c94-6gwd2   100m         3Mi             
web-6dcdc45c94-dvd2m   97m          3Mi             
web-6dcdc45c94-tsjkr   99m          3Mi
bash 复制代码
Every 2.0s: kubectl get hpa;echo;kubectl top pods                     master30.laoma.cloud: Sun Apr 19 09:59:59 2026

NAME   REFERENCE        TARGETS        MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   cpu: 100%/80%   2         5         5          11m

NAME                   CPU(cores)   MEMORY(bytes)   
web-6dcdc45c94-2zbmb   101m         3Mi             
web-6dcdc45c94-6gwd2   100m         3Mi             
web-6dcdc45c94-dvd2m   100m         3Mi             
web-6dcdc45c94-hpg45   98m          3Mi             
web-6dcdc45c94-tsjkr   101m         3Mi 

观察结论:

  1. 随着 Pod CPU 使用率上升,自动扩展 Pod 数量
  2. 正常情况下约 20 秒创建一个新 Pod
  3. 即使 CPU 超过阈值,Pod 数量不会超过 maxReplicas(5)
  4. 停止压力测试,负载降低后,Pod 数量会缩回 minReplicas(2)
bash 复制代码
# 清理资源
root@master30:~# kubectl delete hpa web 
root@master30:~# kubectl delete deployment web
# 保留 svc,后续使用

8.3 实验:基于 Memory 使用率伸缩

想让 Nginx 内存涨,最简单方法是让它返回一个超大响应体。

准备资源

bash 复制代码
# worker节点创建big.img
[root@worker31 ~]# mkdir /www
[root@worker31 ~]# dd if=/dev/zero of=/www/big.img bs=1M count=200
[root@worker32 ~]# mkdir /www
[root@worker32 ~]# dd if=/dev/zero of=/www/big.img bs=1M count=200

root@master30:~# vim deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: big-file
          mountPath: /usr/share/nginx/html
        resources:
          limits:
            cpu: 100m
            memory: 200Mi
      volumes:
      - name: big-file
        hostPath:
          path: /www

root@master30:~# kubectl apply -f deployment.yaml

创建 HPA(注意这里是 v2 版本)

bash 复制代码
root@master30:~# vim hpa-mem.yaml
# 注意这里版本为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
        # 使用20% 效果比较明显,这里实用60%
        averageUtilization: 60
    type: Resource

root@master30:~# kubectl apply -f hpa-mem.yaml

# 稍等片刻:目标值中内存才会正常显示
root@master30:~# kubectl get hpa
NAME   REFERENCE        TARGETS                 MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   memory: 2%/60%   2         5         1          13m

# 稍等片刻,创建一个新的pod
root@master30:~# kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
web-6dcdc45c94-2zj8s   1/1     Running   0          13m
web-6dcdc45c94-w62pr   1/1     Running   0          13s

压力测试

bash 复制代码
# 打开一个监控窗口
root@master30:~# \
while true
do
  clear;
  date;echo
  kubectl get hpa;echo
  kubectl get pod;echo
  kubectl top pods
  sleep 1
done | tee hpa.log

# 上 MEM 压力
root@master30:~# while true ;do ab -n 300000 -c 100 http://10.99.129.137/big.img;sleep 1;done

排雷提示 :本机作为压测客户端向外大量发起 HTTP 请求会产生海量 TCP 收发包。若 master30 硬件配置低,会导致自身 CPU 性能瓶颈------用 top 看会发现 si(软中断)指标偏高,特定 CPU 核心接近 100%,这是软中断全堆在单核处理导致的。

bash 复制代码
Every 4.0s: kubectl get hpa;echo;kubectl top pods                     master30.laoma.cloud: Sun Apr 19 11:13:29 2026

NAME   REFERENCE        TARGETS           MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   memory: 86%/60%   2         5         2          16m

NAME                  CPU(cores)   MEMORY(bytes)
web-88cff78bb-2b7vh   15m          174Mi
web-88cff78bb-76pg2   15m          171Mi
bash 复制代码
Every 4.0s: kubectl get hpa;echo;kubectl top pods                     master30.laoma.cloud: Sun Apr 19 11:13:46 2026

NAME   REFERENCE        TARGETS           MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   memory: 58%/60%   2         5         3          16m

NAME                  CPU(cores)   MEMORY(bytes)
web-88cff78bb-2b7vh   15m          174Mi
web-88cff78bb-4hcm5   5m           3Mi
web-88cff78bb-76pg2   15m          171Mi
bash 复制代码
Every 4.0s: kubectl get hpa;echo;kubectl top pods                     master30.laoma.cloud: Sun Apr 19 11:15:46 2026

NAME   REFERENCE        TARGETS           MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   memory: 58%/60%   2         5         4          16m

NAME                  CPU(cores)   MEMORY(bytes)
web-88cff78bb-2b7vh   15m          174Mi
web-88cff78bb-4hcm5   5m           3Mi
web-88cff78bb-76pg2   15m          171Mi
web-88cff78bb-ws6t4   1m           3Mi  
bash 复制代码
Every 4.0s: kubectl get hpa;echo;kubectl top pods                     master30.laoma.cloud: Sun Apr 19 11:17:46 2026

NAME   REFERENCE        TARGETS           MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   memory: 58%/60%   2         5         5          16m

NAME                  CPU(cores)   MEMORY(bytes)
web-88cff78bb-2b7vh   15m          174Mi
web-88cff78bb-4hcm5   5m           3Mi
web-88cff78bb-76pg2   15m          171Mi
web-88cff78bb-ws6t4   1m           3Mi  
web-88cff78bb-t4q2p   100m         3Mi

观察结论:

  1. 随着 Pod 内存使用率上升,自动扩展 Pod 数量
  2. 正常情况下约 20 秒创建一个新 Pod
  3. 即使内存超过阈值,Pod 数量不会超过 5
  4. 停止压测后,副本数缩回 2
  5. 太高的负载可能导致 Pod 出现 OOMKilled
bash 复制代码
# 清理资源
root@master30:~# kubectl delete hpa web 
root@master30:~# kubectl delete deployment web
root@master30:~# kubectl delete svc web 

8.4 HPA 精细管控:behavior(K8s 1.18+)

扩容 scaleUp
bash 复制代码
scaleUp:
  # 扩容稳定窗口:负载突增后等待多久再扩容
  stabilizationWindowSeconds: 0
  # 扩容限速策略:控制每个周期最多新增多少Pod
  policies:
  - type: Percent
    value: 数值
    periodSeconds: 周期时长
  - type: Pods
    value: 数值
    periodSeconds: 周期时长
  # 多策略选择逻辑
  selectPolicy: Max / Min / Disabled

① stabilizationWindowSeconds :用于过滤瞬时突刺流量,避免瞬间飙升立刻扩容

  • 取值逻辑:窗口内取指标计算的最小副本数作为目标,只有持续高负载才会扩容
  • 默认值:0(无等待,负载一超标立刻尝试扩容)

② policies:每条策略三要素

  • type:Percent(周期内最多新增当前副本百分比)/ Pods(周期内最多新增固定数量 Pod)
  • value:阈值
  • periodSeconds:策略生效周期(最大 1800 秒)

③ selectPolicy

  • Max:取新增 Pod 更多的策略(激进,默认)
  • Min:取新增 Pod 更少的策略(保守,生产推荐)
  • Disabled:完全禁止

默认扩容策略

bash 复制代码
scaleUp:                          # HPA 扩容(加Pod)行为配置
  stabilizationWindowSeconds: 0   # 稳定窗口:0秒,指标一满足立刻扩容,不等待防抖
  policies:                       # 扩容限制策略(两条同时生效,按规则选一条)
  - type: Percent                 # 策略1:百分比限制
    value: 100                    # 每15s,最多新增 当前副本数的100%(翻倍)
    periodSeconds: 15             # 周期:15秒
  - type: Pods                    # 策略2:固定Pod数量限制
    value: 4                      # 每15s,最多新增 4个Pod
    periodSeconds: 15             # 周期:15秒
  selectPolicy: Max               # 策略选择规则:两个策略算出来,取【扩容数量更大】的那个

解读:无等待先格子满分为参考;两条 15s 滑动窗口策略------每 15s 最多翻倍(Percent 100%)或最多新增 4 个(Pods 4),取允许扩更多的那条作为本轮上限。

⚠️ policy 是上限,最终扩容数量由指标计算公式决定,不是强制每次扩这么多。

扩容执行流程

  1. Metrics Server 15s 采集一次指标
  2. HPA 计算目标副本数
  3. 读取稳定窗口内历史推荐值,取最小值作为候选副本
  4. 判断需要扩容 → 用限速策略计算本周期允许新增 Pod 上限
  5. 按限速分步扩容,不会一次性拉满 maxReplicas
  6. 下个周期重新采集指标,逐步扩容

三个典型配置

bash 复制代码
# 案例1:禁止扩容
scaleUp:
  policies:
  - type: Percent
    value: 0
    periodSeconds: 3600
  selectPolicy: Max

# 案例2:激进扩容(无等待,每 15s 扩 50%)
scaleUp:
  stabilizationWindowSeconds: 0
  policies:
  - type: Percent
    value: 50
    periodSeconds: 15
  selectPolicy: Max

# 案例3:平缓扩容(等 60s 确认持续高负载;每 30s 最多扩 20% 或 4 个,取更保守的)
scaleUp:
  stabilizationWindowSeconds: 60
  policies:
  - type: Percent
    value: 20
    periodSeconds: 30
  - type: Pods
    value: 4
    periodSeconds: 30
  selectPolicy: Min
缩容 scaleDown
bash 复制代码
scaleDown:
  stabilizationWindowSeconds: 0
  policies:
  - type: Percent
    value: 数值
    periodSeconds: 周期时长
  - type: Pods
    value: 数值
    periodSeconds: 周期时长
  selectPolicy: Max / Min / Disabled

① stabilizationWindowSeconds

  • 取值逻辑:窗口内取最大期望副本作为目标副本;只有持续低负载超过窗口时长才真正缩容,防止瞬时流量下跌删 Pod 导致反弹
  • 默认值:300(5 分钟),取值范围 0~3600(1 小时)
  • 举例:当前 10 副本,指标瞬时建议缩到 6,但 5 分钟窗口内历史最大值仍是 10 → 不缩;5 分钟后高值过期才按更低副本缩

默认缩容策略

bash 复制代码
scaleDown:
  stabilizationWindowSeconds: 300
  policies:
  - type: Percent
    value: 100
    periodSeconds: 15
  selectPolicy: Max
# 只有单条策略,selectPolicy字段存在但不产生取舍效果

缩容执行流程

  1. Metrics Server 每 15s 采集 CPU/内存指标
  2. HPA 计算理论期望副本
  3. 查询稳定窗口历史推荐值,取最大值作为候选目标
  4. 对比当前副本,判断需要缩容
  5. 执行 policies 限速规则,计算本周期允许删除的最大 Pod 数
  6. 按限速值缩减副本,不会一次性缩到最低
  7. 下个周期重复计算,分步缓慢下降

三个典型配置

bash 复制代码
# 案例1:禁止缩容
scaleDown:
  stabilizationWindowSeconds: 300
  policies:
  - type: Percent
    value: 0
    periodSeconds: 3600
  selectPolicy: Max

# 案例2:激进缩容
scaleDown:
  stabilizationWindowSeconds: 30
  policies:
  - type: Percent
    value: 100
    periodSeconds: 15
  selectPolicy: Max

# 案例3:平缓缩容(等 10 分钟确认低负载;每分钟最多缩 10% 或删 2 个,取删得更少的)
scaleDown:
  stabilizationWindowSeconds: 600
  policies:
  - type: Percent
    value: 10
    periodSeconds: 60
  - type: Pods
    value: 2
    periodSeconds: 60
  selectPolicy: Min
扩容 vs 缩容完整对比
配置项 scaleUp(扩容) scaleDown(缩容)
stabilizationWindowSeconds 默认值 0 300(5 分钟)
稳定窗口取值逻辑 窗口内取最小期望副本 窗口内取最大期望副本
默认 policies 条数 2 条(Percent + Pods) 1 条(仅 Percent)
默认策略内容 15s 最多扩容 100% / 最多新增 4 Pod 15s 最多删除 100% 副本
默认 selectPolicy Max Max(单策略无实际效果)
selectPolicy 支持选项 Max / Min / Disabled Max / Min / Disabled
触发阈值 利用率 > target × 1.1 利用率 < target × 0.9
设计思想 优先快速扩容保障业务 谨慎缩容,避免流量反弹

8.5 VPA:纵向扩容,了解一下就行

根据 VPA 中定义的指标动态调整 Pod 的 requests 和 limits(不改数量)。

适用场景 :资源配置不合理的服务、长期运行且流量稳定的服务、Java/Go 等内存占用固定的应用、不适合频繁扩缩的服务。

核心特点 :不改变 Pod 数量;自动给 Pod 推荐/设置 CPU/内存;生效需要重启 Pod(默认);有侵入性,比 HPA 用得少。

项目 HPA 横向扩缩容 VPA 纵向扩缩容
全称 Horizontal Vertical
扩缩方式 增加/减少 Pod 数量 调整 CPU/内存 request/limit
是否重启 Pod ❌ 不重启 ✅ 一般需要重启
适合负载 流量波动大 资源配置不合理
生效速度 快 慢
稳定性 高 中
生产使用 非常普遍 较少
能否一起开 不能同时开(会冲突) ---
支持资源 CPU、内存、自定义 CPU、内存
bash 复制代码
# 环境清理
root@master30:~# kubectl delete ns metric

九、ResourceQuota:命名空间级的总量硬限制

问题 :多用户/团队共享集群时,有人会用超过公平份额的资源。

解决:用资源配额限制每个 Namespace 能用的资源总量。

工作方式:

  • 不同团队在不同 namespace 下工作(可通过 RBAC 强制)
  • 集群管理员为每个 namespace 创建 ResourceQuota 对象
  • 创建资源时配额系统实时跟踪用量,确保不超过硬限制
  • 违反约束的请求会报 HTTP 403 FORBIDDEN,并明确告知违反了什么

若 namespace 启用了计算资源(cpu/memory)配额,用户必须 为 Pod 设定 request 和 limit,否则拒绝创建。可用 LimitRanger 准入控制器自动补默认值。

9.1 环境准备

bash 复制代码
root@master30:~# kubectl create ns quota
root@master30:~# kubectl config set-context --current --namespace quota

(需提前部署好 Metric Server)

9.2 两种配额类型

① 对象数量配额------提高稳定性,避免 etcd 无限增长、避免占用节点 IP 等资源

bash 复制代码
persistentvolumeclaims / services / secrets / configmaps / replicationcontrollers
deployments.apps / replicasets.apps / statefulsets.apps / jobs.batch / cronjobs.batch

② 计算资源配额------避免单个 namespace 吃光集群资源

资源名称 描述
limits.cpu 所有非终止状态 Pod 的 CPU 限制总和上限
limits.memory 所有非终止状态 Pod 的内存限制总和上限
requests.cpu 所有非终止状态 Pod 的 CPU 请求总和上限
requests.memory 所有非终止状态 Pod 的内存请求总和上限
requests.storage 所有 PVC 的存储请求总和上限
cpu 等同于 requests.cpu
memory 等同于 requests.memory

单位说明:

  • CPU :1 cpu = 1000m,默认单位是 CPU 核心数
  • memory :支持 1024 进制(Ki|Mi|Gi|Ti|Pi|Ei)和 1000 进制(k|M|G|T|P|E),默认单位是 G(如 1.5 = 1500M)

9.3 实验:对象数量配额如何拦违规行为

bash 复制代码
root@master30:~# kubectl create quota myquota --hard=pods=2,services=3,secrets=5,persistentvolumeclaims=10

root@master30:~# kubectl get resourcequotas 
NAME       AGE   REQUEST                                                                LIMIT
my-quota   10s   persistentvolumeclaims: 0/10, pods: 0/2, secrets: 1/5, services: 0/3

root@master30:~# kubectl describe quota myquota 
Name:                   myquota
Namespace:              quota
Resource                Used  Hard
--------                ----  ----
persistentvolumeclaims  0     10
pods                    0     2
secrets                 1     5
services                0     3

等价 YAML:

bash 复制代码
apiVersion: v1
kind: ResourceQuota
metadata:
  name: myquota
spec:
  hard:
    persistentvolumeclaims: "10"
    pods: "2"
    secrets: "5"
    services: "3"

创建 3 副本 Deployment 试试:

bash 复制代码
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/nginx --replicas=3 
root@master30:~# kubectl get all
NAME                      READY   STATUS    RESTARTS   AGE
pod/web-96d5df5c8-dl7qk   1/1     Running   0          40s
pod/web-96d5df5c8-j7fhh   1/1     Running   0          40s

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web   2/3     2            2           40s

NAME                            DESIRED   CURRENT   READY   AGE
replicaset.apps/web-96d5df5c8   3         2         2       40s

root@master30:~# kubectl describe rs web-96d5df5c8
LAST SEEN   TYPE      REASON              OBJECT                     MESSAGE
......
33s         Warning   FailedCreate        replicaset/web-96d5df5c8   Error creating: pods "web-96d5df5c8-xg6c9" is forbidden: exceeded quota: myquota, requested: pods=1, used: pods=2, limited: pods=2
2s          Warning   FailedCreate        replicaset/web-96d5df5c8   (combined from similar events): Error creating: pods "web-96d5df5c8-89p7j" is forbidden: exceeded quota: myquota, requested: pods=1, used: pods=2, limited: pods=2
25s         Normal    SuccessfulCreate    replicaset/web-96d5df5c8   Created pod: web-96d5df5c8-bkzmd
34s         Normal    ScalingReplicaSet   deployment/web             Scaled up replica set web-96d5df5c8 to 3

# 超过配额,创建失败

# 修改配额 pod数量为10
root@master30:~# kubectl patch resourcequotas myquota -p '{"spec":{"hard":{"pods":10}}}'

# 此时重新扩展rs
root@master30:~# kubectl scale rs web-96d5df5c8 --replicas 3

# 再次验证pod数量
root@master30:~# kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
pod/web-96d5df5c8-ajcz2   1/1     Running   0          2s
pod/web-96d5df5c8-dl7qk   1/1     Running   0          60s
pod/web-96d5df5c8-j7fhh   1/1     Running   0          60s

# 清理环境
root@master30:~# kubectl delete deployments.apps web
root@master30:~# kubectl delete resourcequotas myquota

思考题:如果一个用户可以管理多个 namespace,能否限定该用户的总配额?(提示:ResourceQuota 只作用于单个 namespace,跨 ns 需要自建计费/管控体系。)


十、Request 与 Limits:Pod 的资源契约

bash 复制代码
pod.containers.resources
├── requests:Pod 运行需要的**最少**计算资源,调度器据此查找资源充足的节点
└── limits  :Pod 能获得的**最多**计算资源,节点用 Linux cgroup 强制限制

10.1 实验:启用了计算资源配额却不写 resources

bash 复制代码
root@master30:~# vim resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: myquota
spec:
  hard:
    persistentvolumeclaims: "10"
    pods: "2"
    secrets: "5"
    services: "3"
    requests.cpu: 1000m
    requests.memory: "2048Mi"
    limits.cpu: 1000m
    limits.memory: "2048Mi"

root@master30:~# kubectl apply -f resourcequota.yaml

创建不带 resources 的 Pod:

bash 复制代码
root@master30:~# vim pod-without-quota.yaml
apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    app: web
spec:
  containers:
  - name: web
    image: httpd
    imagePullPolicy: IfNotPresent
    ports:
    - name: web
      containerPort: 80
      protocol: TCP

root@master30:~# kubectl apply -f pod-without-quota.yaml
Error from server (Forbidden): error when creating "pod-without-quota.yaml": pods "web" is forbidden: failed quota: myquota: must specify limits.cpu for: web; limits.memory for: web; requests.cpu for: web; requests.memory for: web

# 清理环境
root@master30:~# kubectl delete resourcequotas myquota

10.2 实验:requests 超上限 / 未超上限

bash 复制代码
root@master30:~# vim resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: myquota
spec:
  hard:
    requests.cpu: 1000m
    requests.memory: "2048Mi"

root@master30:~# kubectl apply -f resourcequota.yaml

示例 1:超上限 → 拒绝

bash 复制代码
root@master30:~# vim pod-request-1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    app: web
spec:
  containers:
  - name: web
    image: hub.laoma.cloud/library/httpd
    imagePullPolicy: IfNotPresent
    resources:
      requests:
        cpu: 2000m
        memory: 4096Mi
    ports:
    - name: web
      containerPort: 80
      protocol: TCP

root@master30:~# kubectl apply -f pod-request-1.yaml
Error from server (Forbidden): error when creating "pod-request-1.yaml": pods "web" is forbidden: exceeded quota: myquota, requested: requests.cpu=2,requests.memory=4Gi, used: requests.cpu=0,requests.memory=0, limited: requests.cpu=1,requests.memory=2Gi

示例 2:未超上限 → 成功,且能看到实时用量

bash 复制代码
root@master30:~# vim pod-request-2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    app: web
spec:
  containers:
  - name: web
    image: hub.laoma.cloud/library/httpd
    imagePullPolicy: IfNotPresent
    resources:
      requests:
        cpu: 200m
        memory: 1024Mi
    ports:
    - name: web
      containerPort: 80
      protocol: TCP

root@master30:~# kubectl apply -f pod-request-2.yaml
root@master30:~# kubectl get pod
NAME   READY   STATUS    RESTARTS   AGE
web    1/1     Running   0          43s

# 验证使用情况
root@master30:~# kubectl describe resourcequotas myquota 
Name:            myquota
Namespace:       quota
Resource         Used  Hard
--------         ----  ----
requests.cpu     200m  1
requests.memory  1Gi   2Gi

# 删除pod和quota
root@master30:~# kubectl delete pod web
root@master30:~# kubectl delete resourcequotas myquota

10.3 压测工具:progrium/stress

bash 复制代码
Usage: stress [OPTION [ARG]] ...
 -?, --help         show this help statement
     --version      show version statement
 -v, --verbose      be verbose
 -q, --quiet        be quiet
 -n, --dry-run      show what would have been done
 -t, --timeout N    timeout after N seconds
     --backoff N    wait factor of N microseconds before work starts
 -c, --cpu N        spawn N workers spinning on sqrt()
 -i, --io N         spawn N workers spinning on sync()
 -m, --vm N         spawn N workers spinning on malloc()/free()
     --vm-bytes B   malloc B bytes per vm worker (default is 256MB)
     --vm-stride B  touch a byte every B bytes (default is 4096)
     --vm-hang N    sleep N secs before free (default none, 0 is inf)
     --vm-keep      redirty memory instead of freeing and reallocating
 -d, --hdd N        spawn N workers spinning on write()/unlink()
     --hdd-bytes B  write B bytes per hdd worker (default is 1GB)

Example: stress --cpu 8 --io 4 --vm 2 --vm-bytes 128M --timeout 10s

Note: Numbers may be suffixed with s,m,h,d,y (time) or B,K,M,G (size).
bash 复制代码
# 压力测试内存
$ docker run --name stress hub.laoma.cloud/progrium/stress -m 1 --vm-bytes 512M

# 压力测试CPU
$ docker run --name stress hub.laoma.cloud/progrium/stress -c 1

# 压力测试IO
$ docker run --name stress hub.laoma.cloud/progrium/stress --d 1 --hdd-bytes 3G

Pod 中用法:

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: stress
spec:
  containers:
  - name: stress
    image: hub.laoma.cloud/progrium/stress
    imagePullPolicy: IfNotPresent
    command: ['sh','-c','sleep 3600']
    # 或者不用command,而是使用args作为参数传递给镜像的Entrypoint。
    #args: ['-m','1','--vm-bytes','512M']
    #args: ['-c','1']
    #args: ['-d','1','--hdd-bytes','3G']

10.4 实验:CPU limit → 限流不死

bash 复制代码
root@master30:~# vim resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: myquota
spec:
  hard:
    limits.cpu: 1000m
    limits.memory: "2048Mi"

root@master30:~# kubectl apply -f resourcequota.yaml

# pod 示例
root@master30:~# vim pod-limit-cpu.yaml
apiVersion: v1
kind: Pod
metadata:
  name: stress
spec:
  containers:
  - name: stress
    image: hub.laoma.cloud/progrium/stress
    imagePullPolicy: IfNotPresent
    args: ['-c','1']
    resources:
      limits:
        cpu: 200m
        memory: "256Mi"

root@master30:~# kubectl apply -f pod-limit-cpu.yaml

另一个终端监控(需提前部署 Metrics-Server):

bash 复制代码
root@master30:~# kubectl top pods
NAME     CPU(cores)   MEMORY(bytes)   
stress   201m         0Mi

结论 :CPU 使用率稳定维持在 200m 左右------被限住但没死。

bash 复制代码
# 删除 pod
root@master30:~# kubectl delete pod stress --force

10.5 实验:Memory limit → 直接 OOMKilled

bash 复制代码
root@master30:~# vim pod-limit-memory.yaml
apiVersion: v1
kind: Pod
metadata:
  name: stress
spec:
  containers:
  - name: stress
    image: hub.laoma.cloud/progrium/stress
    imagePullPolicy: IfNotPresent
    args: ['-m','1','--vm-bytes','512M']
    resources:
      limits:
        cpu: 200m
        memory: "256Mi"

root@master30:~# kubectl apply -f pod-limit-memory.yaml
bash 复制代码
root@master30:~# kubectl get pods -w
NAME     READY   STATUS      RESTARTS     AGE
stress   0/1     OOMKilled   1 (2s ago)   3s
stress   0/1     CrashLoopBackOff   1 (2s ago)   4s

结论 :申请 512M 但 limit 只有 256M → 状态 OOMKilled 并不断重启。

bash 复制代码
# 删除 pod
root@master30:~# kubectl delete pod stress --force
root@master30:~# kubectl delete resourcequotas myquota

10.6 经典面试题:limits 总和会超过节点资源吗?

答案:可能会。

假设节点可用内存 1G,每个 Pod requests=256M / limit=512M。节点按 requests 算大约能放 4 个 Pod,而此时 limits 总和已经是 2G。

那 K8s 怎么处理资源耗尽?

  • CPU :K8s 认为 CPU 是可压缩资源 ,容器达到 limit 时只减少其调度时间(变慢),不会杀死应用
  • Memory :K8s 认为内存是不可压缩资源 ,超限时会杀死占用超过 request 的应用(1.9 版本之后)。驱逐顺序:先杀没指定 request 的容器 → 再杀超出 request 更多的容器 → 同等条件下优先级更低的先死

十一、LimitRange:给"忘写 resources"的 Pod 兜底

K8s 创建 Pod 时默认不指定资源请求和限制;但 namespace 一旦设了配额,不带 resources 的 Pod 又创建不了。LimitRange (别名 limits)就是来解决这个矛盾的:为单个 Pod 定义资源请求/限制的 default、min、max 值。

LimitRange 作用于特定 namespace。

规则:

  • 请求的 Pod 未提供计算资源 → 用 default 值创建
  • 请求的资源小于 min → 无法创建
  • 请求的资源大于 max → 无法创建

11.1 定义 LimitRange

bash 复制代码
root@master30:~# vim limits.yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: mylimit
spec:
  limits:
    - type: Container
      max:
        memory: 1024Mi
        cpu: 1
      min:
        memory: 128Mi
        cpu: 100m
      default:
        memory: 512Mi
        cpu: 500m
      defaultRequest:
        memory: 256Mi
        cpu: 200m

字段说明:

  • name:只能用小写字母、数字、- 和 .,且必须以数字或字母开头结尾
  • default:配置 resourceQuota 时,创建 container 的默认 limit
  • defaultRequest:创建 container 的默认 request
  • max:该 namespace 下 container 的资源最大值
  • min:该 namespace 下 container 的资源最小值

约束关系 :min <= defaultRequest <= default <= max

bash 复制代码
root@master30:~# kubectl apply -f limits.yaml
root@master30:~# kubectl get limitranges 
NAME      CREATED AT
mylimit   2021-09-09T04:09:15Z

root@master30:~# kubectl describe limitranges mylimit 
Name:       mylimit
Namespace:  quota
Type        Resource  Min    Max  Default Request  Default Limit  Max Limit/Request Ratio
----        --------  ---    ---  ---------------  -------------  -----------------------
Container   cpu       100m   1    200m             500m           -
Container   memory    128Mi  1Gi  256Mi            512Mi          -

11.2 情况一:完全不写 resources → 自动补默认值

bash 复制代码
root@master30:~# vim pod-without-limits.yaml
apiVersion: v1
kind: Pod
metadata:
  name: stress
spec:
  containers:
  - name: stress
    image: hub.laoma.cloud/progrium/stress
    imagePullPolicy: IfNotPresent
    args: ['-c','1']

root@master30:~# kubectl apply -f pod-without-limits.yaml
root@master30:~# kubectl top pods
NAME     CPU(cores)   MEMORY(bytes)   
stress   501m         0Mi

root@master30:~# kubectl get pod stress -o yaml
......
spec:
  containers:
    image: hub.laoma.cloud/progrium/stress
    imagePullPolicy: IfNotPresent
    name: stress
    resources:
      limits:
        cpu: 500m
        memory: 512Mi
      requests:
        cpu: 200m
        memory: 256Mi
......

# 清理资源
root@master30:~# kubectl delete limitranges mylimit 
root@master30:~# kubectl delete pod web --force 

11.3 情况二:只指定 limit

示例 2-1:limit > max → 拒绝

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
  - name: web
    image: hub.laoma.cloud/library/nginx
    resources:
      limits:
        cpu: 1.1
        memory: 1100Mi

root@master30:~# kubectl apply -f limit.yml
Error from server (Forbidden): error when creating "limit.yml": pods "web" is forbidden: [maximum cpu usage per Container is 1, but limit is 1100m, maximum memory usage per Container is 1Gi, but limit is 1181116006400m]

示例 2-2:limit < min → 拒绝

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
  - name: web
    image: hub.laoma.cloud/library/nginx
    resources:
      limits:
        cpu: 60m
        memory: 60Mi

root@master30:~# kubectl apply -f limit.yml
Error from server (Forbidden): error when creating "limit.yml": pods "web" is forbidden: [minimum cpu usage per Container is 100m, but request is 60m, minimum memory usage per Container is 128Mi, but request is 60Mi]

示例 2-3:min < limit < max → 成功,但有个大坑

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
  - name: web
    image: hub.laoma.cloud/library/nginx
    resources:
      limits:
        cpu: 600m
        memory: 600Mi

root@master30:~# kubectl get pod web -o yaml
......
spec:
  containers:
  - image: hub.laoma.cloud/library/nginx
    imagePullPolicy: Always
    name: web
    resources:
      limits:
        cpu: 600m
        memory: 600Mi
      requests:
        cpu: 600m
        memory: 600Mi
......

✍️ 结论① :创建的容器 limits 必须满足 min < limit < max

✍️ 结论② :只指定 limits 时,requests 会自动等于 limits(而不是取 defaultRequest!)

11.4 情况三:只指定 requests

示例 3-1:requests > max → 校验直接失败

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
  - name: web
    image: hub.laoma.cloud/library/nginx
    resources:
      requests:
        cpu: 1600m
        memory: 600Mi

root@master30:~# kubectl apply -f limit4.yml 
The Pod "web" is invalid: 
* spec.containers[0].resources.requests: Invalid value: "1600m": must be less than or equal to cpu limit
* spec.containers[0].resources.requests: Invalid value: "1600Mi": must be less than or equal to memory limit

示例 3-2:requests < min → 拒绝

bash 复制代码
root@master30:~# kubectl apply -f limit.yml 
Error from server (Forbidden): error when creating "limit.yml": pods "web" is forbidden: [minimum cpu usage per Container is 100m, but request is 60m, minimum memory usage per Container is 128Mi, but request is 60Mi]

示例 3-3:min < requests < max → 成功

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
  - name: web
    image: hub.laoma.cloud/library/nginx
    resources:
      requests:
        cpu: 400m
        memory: 400Mi

root@master30:~# kubectl get pod web -o yaml
......
spec:
  containers:
  - image: hub.laoma.cloud/library/nginx
    imagePullPolicy: Always
    name: web
    resources:
      limits:
        cpu: 500m
        memory: 512Mi
      requests:
        cpu: 400m
        memory: 400Mi
......

✍️ 结论① :requests 必须满足 min < requests < limits

✍️ 结论② :只指定 requests 时,limits 取 default 值

11.5 一张表记住 LimitRange 的填充规则

你写的内容 系统自动补的部分
什么都不写 limits = default,requests = defaultRequest
只写 limits requests = limits(⚠️ 不是 defaultRequest)
只写 requests limits = default

11.6 LimitRange 能限定哪些资源

Type 资源 说明
container cpu、memory 限定单个容器
Pod cpu、memory 限定 Pod 中所有容器的总和
PVC storage 限定 PVC 申请的存储空间大小
bash 复制代码
apiVersion: v1
kind: LimitRange
metadata:
  name: storagelimits
spec:
  limits:
  - type: PersistentVolumeClaim
    max:
      storage: 2Gi
    min:
      storage: 1Gi
bash 复制代码
# 环境清理
root@master30:~# kubectl delete ns quota

十二、全文总结:一张图 + 六条经验

复制代码
┌──────────────── Pod 是怎么跑起来的 ────────────────┐
│ Client → apiserver(认证/授权/准入) → etcd           │
│        → controller-manager(控制器补齐 Pod 数量)    │
│        → scheduler(过滤 → 抢占 → 打分 → 绑定)       │
│        → kubelet(CNI 网络 / CSI 存储)               │
│        → containerd(拉镜像 / 起容器)                │
│        → 状态回写 Running                           │
└───────────────────────────────────────────────────┘

┌───────────── 调度约束(Pod 选节点 vs 节点选 Pod)─────────────┐
│  Pod 视角:nodeName / nodeSelector / nodeAffinity              │
│           podAffinity / podAntiAffinity(topologyKey 拓扑域) │
│  Node 视角:Taint 污点 + Toleration 容忍(专用节点/GPU 节点)  │
│  节点维护:cordon(关门)/ drain(关门+清人)/ uncordon        │
└───────────────────────────────────────────────────────────────┘

┌───────────── 资源管控(限总量 / 兜底)─────────────┐
│  ResourceQuota:namespace 级总量硬限制(对象数 + 计算资源) │
│  LimitRange:单容器默认值 + 上下限兜底                    │
│  requests:调度依据      limits:cgroup 硬限制            │
│  CPU 可压缩(限流)     内存不可压缩(OOMKilled)        │
└────────────────────────────────────────────────────┘

┌───────────── 弹性伸缩(动态容量)─────────────────┐
│  Metrics-Server:指标采集(kubectl top 的数据源)   │
│  HPA:横向扩缩副本数(CPU/内存/自定义指标)         │
│  behavior.scaleUp / scaleDown:扩缩节奏精细控制     │
│  VPA:纵向调 requests/limits(需重启,较少用)      │
└───────────────────────────────────────────────────┘

六条生产经验:

  1. Pod 一直 Pending → 先 kubectl describe pod 看 Events,对照「过滤 Predicate」表定位(资源不足 / 污点未容忍 / 亲和规则无解 / 节点被 cordon)
  2. 高可用服务标配 :podAntiAffinity + topologyKey: kubernetes.io/hostname(每节点一副本),且副本数别超过节点数
  3. 亲和 + 反亲和配置时要取交集,别让结果变成空集(实验 3 的 Pending 就是这么来的)
  4. "只跑在某类节点"必须污点 + 节点亲和性双管齐下,单靠 toleration 只拿到准入资格
  5. HPA 生效前提 :部署 Metrics-Server 且 Pod 必须设置 limits,否则 TARGETS 永远 <unknown>
  6. 多租户集群 :ResourceQuota(管总量) + LimitRange(给默认值)组合使用最省心
相关推荐
吴声子夜歌2 小时前
Kubernetes权威指南——Kubernetes安装配置指南(二进制文件安装)
容器·kubernetes
江华森3 小时前
Kubernetes集群部署实战:使用kubeadm在4台ECS服务器上搭建K8s v1.31集群
云原生·容器·kubernetes
闲云自留地3 小时前
K8s Ingress 全解:ingress-nginx 部署、域名路径路由、HTTPS 限流灰度一篇通
nginx·https·kubernetes
SelectDB技术团队3 小时前
ClickHouse 支持存算分离吗?Doris 社区版原生存算分离怎么落地
数据库·clickhouse·云原生·开源·apache doris·技术选型·存算分层
正经教主3 小时前
【FDE系列】阶段2:Day 43(UP详细版):Docker Compose — 多容器一键编排
运维·docker·容器
csdn_aspnet4 小时前
构建、运行与管理容器的 18 个常用 Docker 命令
docker·容器·eureka·command
木雷坞4 小时前
AnythingLLM Docker部署:中文资料上传后无引用的检查路径
运维·docker·容器
暖核4 小时前
K8s 深度拆解:健康检查、认证授权与可视化面板全梳理
云原生·容器·kubernetes
分布式存储与RustFS4 小时前
十分钟在自己机器上跑通一个 S3 兼容对象存储(RustFS + Docker)
云原生·开源·对象存储·分布式存储·s3·rustfs·性能基准