Kubernetes 网络策略与调度实战笔记(NetworkPolicy · 亲和/反亲和 · 污点容忍 · HPA)

本手册由 kaitou.txt(会话开头未录制部分,09:27–10:27)+ master30_2026-08-12_10_27_42.log(10:27–17:22)合并整理而成,在 kubeadm v1.30.2 集群(master30 + worker31/32,运行第 7 天)上完成。
三条主线:① NetworkPolicy 网络策略(podSelector / namespaceSelector / ipBlock / 端口 / 默认拒绝)→ ② 调度进阶(nodeName / nodeSelector / 亲和与反亲和 / 污点与容忍)→ ③ Metrics-Server 与 HPA 弹性伸缩。命令提示符保留真实时间戳,报错与修复全部来自实际操作;

📑 目录

# 章节 核心内容
整体框架与实验总览 实验流程图、节点环境、主题速览
实验准备:命名空间、Pod、NodePort 与集群 DNS web/ningcode 命名空间、web1/web2、跨命名空间解析
NetworkPolicy 基础:podSelector 入站控制 只放行指定标签 Pod、标签改写的坑
NetworkPolicy:namespaceSelector 与跨命名空间 命名空间标签、project=myproject、DNS 解析限制
NetworkPolicy:ipBlock 与端口控制 网段放行/排除、端口范围、ICMP 被拒
NetworkPolicy 默认策略:出入站四组合 默认允许/拒绝 Ingress、Egress、全拒绝
调度基础:nodeName 与 nodeSelector 指定节点、节点标签、删除 nodeSelector
节点亲和:nodeAffinity required + preferred、标签与 Pending 的关联
Pod 亲和与反亲和 podAffinity / podAntiAffinity / 多条件组合
污点与容忍:taint / tolerations NoSchedule / NoExecute、FailedScheduling 事件
十一 Metrics-Server 部署与资源监控 components.yaml、改镜像、kubectl top
十二 HPA 弹性伸缩实战 autoscale、资源限额、ab 压测、缩容回弹
十三 环境清理与恢复 删除命名空间、清理标签与污点
十四 常见错误与排查(重点) 本次实验全部报错:原因 + 修复
十五 命令速查表与小结 NetworkPolicy / 调度 / HPA 常用命令

一、整体框架与实验总览

1.1 实验流程图

集群运行第 7 天
master30 + worker31/32

实验准备
web/ningcode 命名空间
web1/web2 Pod + NodePort + DNS

NetworkPolicy
podSelector → namespaceSelector
→ ipBlock → 默认策略

调度进阶
nodeName → nodeSelector
→ 亲和/反亲和 → 污点容忍

资源与弹性
Metrics-Server → HPA + ab 压测

错误复盘 + 命令速查

1.2 节点环境

节点 主机名 IP 角色
master30 master30.ningcode.cn 10.1.8.30 控制平面
worker31 worker31.ningcode.cn 10.1.8.31 工作节点
worker32 worker32.ningcode.cn 10.1.8.32 工作节点
  • Pod 网段 10.224.0.0/16;Service 网段 10.96.0.0/12
  • 私有镜像仓库 hub.laoma.cloud(nginx 镜像 hub.laoma.cloud/library/nginx,节点上已缓存,用 --image-pull-policy=IfNotPresent 秒起)
  • 网络插件 Calico,支持 NetworkPolicy;本次 kubectl 上下文按实验分别在 webscheduler 命名空间间切换

1.3 主题速览

主题 核心知识点 时间范围
实验准备 NodePort、集群内/跨命名空间 DNS(web1.web) 09:27–10:27(kaitou.txt)
NetworkPolicy podSelector / namespaceSelector / ipBlock / 端口 / 默认策略 10:27–12:13
调度基础 nodeName 指定节点、nodeSelector 节点标签 12:13–13:45
亲和/反亲和 nodeAffinity、podAffinity、podAntiAffinity、多条件 13:45–15:03
污点与容忍 NoSchedule / NoExecute、FailedScheduling 15:03–15:59
弹性伸缩 Metrics-Server、kubectl top、HPA、ab 压测 17:03–17:22

↑ 回到目录


二、实验准备:命名空间、Pod、NodePort 与集群 DNS

本段命令来自 kaitou.txt(日志没录上的开头部分),时间 09:27–10:27。

2.1 建命名空间并切换上下文

root@master30 ~ 09:27:43# kubectl create ns web
namespace/web created

# 想用 kubens 插件,但没装
root@master30 ~ 10:21:31# kubens web
-bash: kubens: command not found

# 标准做法:直接改当前上下文的命名空间
root@master30 ~ 10:21:35# kubectl config set-context --current --namespace web
Context "kubernetes-admin@kubernetes" modified.

2.2 创建 web1 / web2 两个 Pod

root@master30 ~ 10:22:04# kubectl run web1 --image=hub.laoma.cloud/library/nginx --image-pull-policy=IfNotPresent
pod/web1 created

root@master30 ~ 10:22:41# kubectl exec -it web1 -- bash -c 'echo Hello web1 > /usr/share/nginx/html/index.html'

root@master30 ~ 10:22:52# kubectl run web2 --image=hub.laoma.cloud/library/nginx --image-pull-policy=IfNotPresent
pod/web2 created

root@master30 ~ 10:23:02# kubectl exec -it web2 -- bash -c 'echo Hello web2 > /usr/share/nginx/html/index.html'

2.3 用 NodePort 暴露服务

root@master30 ~ 10:23:33# kubectl expose pod web1 --port=80 --target-port=80 --type=NodePort
service/web1 exposed
root@master30 ~ 10:24:04# kubectl expose pod web2 --port=80 --target-port=80 --type=NodePort
service/web2 exposed

root@master30 ~ 10:24:13# kubectl get service
NAME   TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
web1   NodePort   10.104.31.85    <none>        80:30280/TCP   13s
web2   NodePort   10.110.240.75   <none>        80:30167/TCP   4s
  • 集群外可通过任意节点 IP:30280 访问 web1(后面 NetworkPolicy 实验会用到 30280)

2.4 集群内:用 Service 短名访问

# 建一个测试 Pod(同命名空间 web)
root@master30 ~ 10:24:17# kubectl run test --image=hub.laoma.cloud/library/nginx
pod/test created

root@master30 ~ 10:24:42# kubectl exec -it test -- curl -s web1
Hello web1
root@master30 ~ 10:24:55# kubectl exec -it test -- curl -s web2
Hello web2

2.5 跨命名空间:服务名.命名空间名

root@master30 ~ 10:24:58# kubectl create ns ningcode
namespace/ningcode created

# 第一次漏了 --image
root@master30 ~ 10:25:12# kubectl run test -n ningcode -- curl -s web1.web
error: required flag(s) "image" not set

root@master30 ~ 10:26:43# kubectl run test -n ningcode --image=hub.laoma.cloud/library/nginx
pod/test created

# 跨命名空间 DNS 规则:web1.web = 服务 web1 + 命名空间 web
root@master30 ~ 10:26:54# kubectl exec -n ningcode test -- curl -s web1.web
Hello web1
root@master30 ~ 10:27:31# kubectl exec -n ningcode test -- curl -s web2.web
Hello web2

2.6 要点

  • 集群内 DNS:同命名空间用 服务名,跨命名空间用 服务名.命名空间名(web1.web 中 web 是命名空间名,容易看错)
  • 切命名空间标准做法是 kubectl config set-context --current --namespace xxx(kubens 需要额外安装)
  • kubectl run-- 后面是容器命令,镜像必须用 --image 单独指定

↑ 回到目录


三、NetworkPolicy 基础:podSelector 入站控制

3.1 概念

  • NetworkPolicy 是集群内部防火墙,由网络插件(Calico)执行,白名单模式:不写策略默认全放行;一旦有策略命中某个 Pod,未放行的流量全部被拒
  • podSelector:策略作用于哪些 Pod(目标);from / to:放行哪些来源 / 去向
  • policyTypes 声明只控制 Ingress(入站)还是 Egress(出站)

3.2 实验环境

日志主体从 10:27 开始,先用 NodePort 验证集群外访问,再写第一个策略:

root@master30 ~ 10:27:49# curl 10.104.31.85
Hello web1
root@master30 ~ 10:28:52# curl 10.110.240.75
Hello web2

3.3 第一个策略:只放行 run=test 的 Pod 访问 web1 的 80 端口

# netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-network-policy
spec:
  podSelector:
    matchLabels:
      run: web1
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          run: test
    ports:
    - protocol: TCP
      port: 80
root@master30 ~ 10:34:23# kubectl apply -f netpol.yaml
networkpolicy.networking.k8s.io/my-network-policy created

# 资源类型简写 netpol
root@master30 ~ 10:34:36# kubectl get netpo
error: the server doesn't have a resource type "netpo"
root@master30 ~ 10:34:42# kubectl get netpol
NAME                POD-SELECTOR   AGE
my-network-policy   run=web1       8s

3.4 验证:放行名单内的才能访问

# test(run=test,同命名空间)能访问 web1
root@master30 ~ 10:34:44# kubectl exec test -- curl -s web1
Hello web1
root@master30 ~ 10:35:25# kubectl exec test -- curl -s web2
Hello web2          # web2 没有策略,不受影响

# 自身访问不受影响;web2(run=web2,不在放行名单)被拦
root@master30 ~ 10:36:07# kubectl exec web1 -- curl -s web1
Hello web1
root@master30 ~ 10:44:32# kubectl exec web2 -- curl -s web1
^C                  # 挂起 → 被 NetworkPolicy 拦截

# 跨命名空间:ningcode 里的 test 标签虽是 run=test,但 podSelector 只匹配策略同命名空间的 Pod
root@master30 ~ 10:45:24# kubectl exec test -n ningcode -- curl -s --connect-timeout 3 web1.web
command terminated with exit code 28    # 超时
root@master30 ~ 10:46:18# #不同namespace中pod,标签不满足无法访问

3.5 把标签改掉:流量立刻被拦

# test 标签原来是 run=test(放行名单),改成 run=web2 后不再满足条件
root@master30 ~ 10:46:40# kubectl label pod test run=web2 --overwrite
pod/test labeled

root@master30 ~ 10:47:15# kubectl get pods --show-labels
NAME   READY   STATUS    RESTARTS   AGE   LABELS
test   1/1     Running   0          22m   run=web2
web1   1/1     Running   0          24m   run=web1
web2   1/1     Running   0          24m   run=web2

# 标签一改,立刻超时
root@master30 ~ 10:47:22# kubectl exec test -- curl -s --connect-timeout 3 web1
command terminated with exit code 28

印证:NetworkPolicy 按来源 Pod 的标签实时判断,标签变了策略立刻生效。

3.6 放行所有 Pod:podSelector: {}

# netpol.yaml(v2)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-network-policy
spec:
  podSelector:
    matchLabels:
      run: web1
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector: {}
    ports:
    - protocol: TCP
      port: 80
root@master30 ~ 10:48:48# kubectl apply -f netpol.yaml
networkpolicy.networking.k8s.io/my-network-policy configured

root@master30 ~ 10:48:59# kubectl exec test -- curl -s web1
Hello web1

# 注意 -- 与 curl 之间要有空格
root@master30 ~ 10:49:12# kubectl exec web2 --curl -s web1
error: unknown flag: --curl

root@master30 ~ 10:49:52# kubectl exec web2 -- curl -s web1
Hello web1          # 所有 Pod 都能访问 web1:80

3.7 要点

  • podSelector: {} 表示放行所有来源 Pod(注意作用目标是 web1,来源是空选择器)
  • 策略是"白名单":从 run=test 精确匹配换成 {} 全放行,验证时看的是当前生效的版本,改 YAML 忘了 apply 是排查时最常见的错觉
  • kubectl get netpol 可看策略列表;kubectl describe netpol 可看规则详情

↑ 回到目录


四、NetworkPolicy:namespaceSelector 与跨命名空间

4.1 按命名空间标签放行

podSelector 只能匹配同命名空间的 Pod;要放行其他命名空间的 Pod,用 namespaceSelector(按命名空间的标签匹配):

# netpol.yaml(v3)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-network-policy
  namespace: web
spec:
  podSelector:
    matchLabels:
      run: web1
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          project: myproject
    ports:
    - protocol: TCP
      port: 80
root@master30 ~ 11:06:29# kubectl apply -f netpol.yaml
networkpolicy.networking.k8s.io/my-network-policy configured

# 命名空间还没打标签 → 本命名空间的 test 也被拦(命名空间标签不满足)
root@master30 ~ 11:06:37# kubectl exec test -- curl -s --connect-timeout 3 web1
command terminated with exit code 28
root@master30 ~ 11:08:39# #此时无法访问,必须要有ns为myproject才能访问

给命名空间打标签:

root@master30 ~ 11:09:00# kubectl label namespaces ningcode project=myproject
namespace/ningcode labeled

# ningcode 命名空间满足 project=myproject → 放行
root@master30 ~ 11:09:24# kubectl exec -n ningcode test -- curl -s web1.web
Hello web1
root@master30 ~ 11:09:43# kubectl exec -n ningcode test -- curl -s web2.web
Hello web2

4.2 放行所有命名空间:namespaceSelector: {}

# netpol.yaml(v4)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-network-policy
  namespace: web
spec:
  podSelector:
    matchLabels:
      run: web1
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector: {}
    ports:
    - protocol: TCP
      port: 80
root@master30 ~ 11:10:01# kubectl apply -f netpol.yaml
networkpolicy.networking.k8s.io/my-network-policy configured

root@master30 ~ 11:10:07# kubectl exec test -- curl -s web1
Hello web1          # 本命名空间恢复访问
root@master30 ~ 11:11:48# kubectl exec web2 -- curl -s web1
Hello web1

4.3 DNS 解析的"假拦截"

# 跨命名空间用短名 web1 解析失败(DNS 不认短名,不是策略拦的)
root@master30 ~ 11:11:53# kubectl exec -n ningcode test -- curl -s web1
command terminated with exit code 6     # Could not resolve host
root@master30 ~ 11:12:09# #被dns限制了

# 用全限定名 web1.web.svc.cluster.local 就通了
root@master30 ~ 11:15:31# kubectl exec -n ningcode test -- curl -s web1.web.svc.cluster.local
Hello web1

4.4 要点

  • 放行外部命名空间必须用 namespaceSelectorpodSelector 只匹配策略所在命名空间
  • namespaceSelector: {} 放行所有命名空间;也可与 podSelector 组合(见第六章多条件)
  • 排查顺序:先确认能不能解析(exit 6 = DNS),再确认策略是否放行(exit 28 = 超时被拦)
  • 命名空间标签用 kubectl label ns <名称> key=value 打,kubectl get ns --show-labels 查看

↑ 回到目录


五、NetworkPolicy:ipBlock 与端口控制

5.1 按网段放行(ipBlock)

# netpol.yaml(v5)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-network-policy
  namespace: web
spec:
  podSelector:
    matchLabels:
      run: web1
  policyTypes:
  - Ingress
  ingress:
  - from:
    - ipBlock:
        # 放行 网段10.1.8.0/24
        cidr: 10.1.8.0/24
        # 不放行网段
        except:
        - 10.1.8.128/26
    ports:
    - protocol: TCP
      port: 80
root@master30 ~ 11:40:37# kubectl apply -f netpol.yaml
networkpolicy.networking.k8s.io/my-network-policy configured

# 集群内 Pod(10.224.x.x)不在 10.1.8.0/24 内 → 全部被拦
root@master30 ~ 11:40:44# kubectl exec test -- curl -s web1
^C
root@master30 ~ 11:40:53# #访问失败
root@master30 ~ 11:40:56# kubectl exec -n ningcode test -- curl -s web1.web
^C

5.2 宿主机走 NodePort 验证

root@master30 ~ 11:41:46# curl http://10.1.8.30:30280
^C
root@master30 ~ 11:42:06# curl http://10.1.8.31:30280
^C
root@master30 ~ 11:42:10# curl http://10.1.8.32:30280
Hello web1
root@master30 ~ 11:42:12# curl http://10.1.8.32:30280
Hello web1
root@master30 ~ 11:42:14# # 理论上:10.1.8.0/24网段中主机都可以通过集群任意节点访问web1

现象与理论有出入:master30 / worker31 上 curl 会挂起(^C),worker32 上正常返回。ipBlock 策略作用于 Pod 入站,NodePort 流量要经过 kube-proxy DNAT 后再被策略检查,来源判断和路径相关——实际以 worker32 通过为准,其它节点卡住的情况值得再深挖(本节记录现象,供后续研究)。

5.3 端口控制与 ICMP

把策略改成"只放行来源 Pod 的 32000–32768 端口"(port + endPort 表示端口范围):

# netpol.yaml(v6)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-network-policy
  namespace: web
spec:
  podSelector:
    matchLabels:
      run: web1
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          run: test
    ports:
    - protocol: TCP
      port: 32000
      endPort: 32768
root@master30 ~ 12:11:06# cat netpol.yaml     # 内容如上
root@master30 ~ 12:11:10# #限定端口范围
root@master30 ~ 12:11:21# vim netpol.yaml

用 ubuntu Pod 测 ping(标签 run=test,但 ICMP 不在策略放行的 TCP 32000–32768 内):

root@master30 ~ 11:43:22# kubectl run --rm -it ubuntu -l run=test --image ubuntu -- bash
# 容器内装 ping 工具
root@ubuntu:/# apt update
root@ubuntu:/# apt install -y iputils-ping
......
root@ubuntu:/# ping -c1 10.224.193.67
PING 10.224.193.67 (10.224.193.67) 56(84) bytes of data.
--- 10.224.193.67 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

# ping 服务名也一样不通(Destination Port Unreachable = 策略丢弃了 ICMP)
root@ubuntu:/# ping web1 -c1
PING web1.web.svc.cluster.local (10.104.31.85) 56(84) bytes of data.
From web1.web.svc.cluster.local (10.104.31.85) icmp_seq=1 Destination Port Unreachable
--- web1.web.svc.cluster.local ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms
root@ubuntu:/# exit
pod "ubuntu" deleted

5.4 要点

  • ipBlock 按来源/去向 IP 网段匹配,except 排除部分网段;适用于"只允许办公网段访问"
  • port: 起始端口 + endPort: 结束端口 可以限定端口范围;协议可以是 TCP / UDP / SCTP
  • NetworkPolicy 是四层策略:只放行声明的协议+端口,ICMP(ping)没有声明就会被拒——排查"ping 不通但 curl 通"先想到这一点
  • kubectl run --rm -it-l run=test 可以临时造一个带标签的测试 Pod,用完自动删除

↑ 回到目录


六、NetworkPolicy 默认策略:出入站四组合

白名单逻辑下,只要创建一条"空规则"策略就能实现默认拒绝/默认允许:

6.1 默认允许 / 拒绝组合

策略名 YAML 关键字段 效果
allow-all-ingress ingress: [- {}] 默认允许所有入站
default-deny-ingress 只有 policyTypes: [Ingress],无 ingress 规则 默认拒绝所有入站
allow-all-egress egress: [- {}] 默认允许所有出站
default-deny-egress 只有 policyTypes: [Egress],无 egress 规则 默认拒绝所有出站
default-deny-all policyTypes: [Ingress, Egress],无规则 入站出站全拒绝
# 默认允许所有入站流量
root@master30 ~ 12:12:25# cat netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-ingress
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - {}

# 默认拒绝所有入站流量(有 Ingress 就默认拒绝,不放任何规则)
root@master30 ~ 12:12:42# cat netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
  - Ingress

# 默认允许所有出站流量
root@master30 ~ 12:12:58# cat netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-egress
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - {}

# 默认拒绝所有出站流量
root@master30 ~ 12:13:13# cat netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
spec:
  podSelector: {}
  policyTypes:
  - Egress

# 默认拒绝所有入站和所有出站流量
root@master30 ~ 12:13:28# cat netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

6.2 多条件规则:from 里三选一

同一组 ports 下可以写多个 from 条目,满足任意一个即放行(逻辑 OR):

# netpol.yaml(多条件规则)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-network-policy
  namespace: web
spec:
  podSelector:
    matchLabels:
      run: web1
  policyTypes:
  - Ingress
  ingress:
  - from:
    - ipBlock:
        cidr: 10.1.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          run: test
    ports:
    - protocol: TCP
      port: 80
  • 含义:web1 的 80 端口,允许来自 10.1.1.0/24 命名空间标签 project=myproject 同命名空间标签 run=test 的流量
  • 注意:如果想把 namespaceSelectorpodSelector 变成""关系,要写进同一个 from 条目里(缩进层级不同,逻辑不同)

6.3 要点

  • 默认拒绝套路:podSelector: {} + policyTypes: [Ingress](或 Egress),规则留空
  • 默认允许套路:在对应方向写一条空规则 - {}
  • 删除策略用 kubectl delete netpol <name>;实验做完 kubectl delete ns web ningcode 直接整体清理(见第十三章)

↑ 回到目录


七、调度基础:nodeName 与 nodeSelector

7.1 概念

方式 作用 特点
nodeName 直接指定节点名 硬绑定,绕过调度器,节点不存在时 Pod 一直 Pending
nodeSelector 按节点标签选择 匹配所有带该标签的节点,可多节点
nodeAffinity 节点亲和表达式 支持 In/NotIn/Exists 等操作符,有 required / preferred 两种

实验用 scheduler 命名空间:

root@master30 ~ 12:13:43# kubectl create ns scheduler
namespace/scheduler created
root@master30 ~ 12:14:03# kubectl config set-context --current --namespace scheduler
Context "kubernetes-admin@kubernetes" modified.

7.2 nodeName 指定节点:写错主机名 → Pending

root@master30 ~ 12:14:07# kubectl create deployment webapp --image nginx --replicas 2 -o yaml --dry-run=client > deploy-with-nodeName.yaml

在生成的 YAML 里加 nodeName。第一次写成了 worker32.laoma.cloud(节点实际叫 worker32.ningcode.cn):

root@master30 ~ 12:15:28# kubectl apply -f deploy-with-nodeName.yaml
deployment.apps/webapp created

root@master30 ~ 12:15:36# kubectl get pods -o wide |awk '{print $1,$3,$7}'
NAME STATUS NODE
webapp-55c6b896bc-gp6mj Pending worker32.laoma.cloud
webapp-55c6b896bc-ptf9r Pending worker32.laoma.cloud

修正主机名后(并把镜像换成私有仓库,官方 nginx 拉取太慢):

# deploy-with-nodeName.yaml(关键段)
spec:
  template:
    spec:
      # 添加nodeName配置
      nodeName: worker32.ningcode.cn
      containers:
      - image: hub.laoma.cloud/library/nginx
        name: nginx
root@master30 ~ 12:20:50# kubectl apply -f deploy-with-nodeName.yaml
deployment.apps/webapp created

root@master30 ~ 12:21:20# kubectl get pods -o wide | awk '{print $1,$3,$7}'
NAME STATUS NODE
webapp-8667d9c9c-7spk6 Running worker32.ningcode.cn
webapp-8667d9c9c-p6x2p Running worker32.ningcode.cn

7.3 nodeSelector:按节点标签调度

# 给 worker32 打标签 disktype=ssd
root@master30 ~ 13:40:49# kubectl label node worker32.ningcode.cn disktype=ssd
node/worker32.ningcode.cn labeled

root@master30 ~ 13:41:00# kubectl get node -L disktype
NAME                   STATUS   ROLES           AGE     VERSION   DISKTYPE
master30.ningcode.cn   Ready    control-plane   6d23h   v1.30.2
worker31.ningcode.cn   Ready    <none>          6d22h   v1.30.2
worker32.ningcode.cn   Ready    <none>          6d22h   v1.30.2   ssd

把 deploy-with-nodeName.yaml 里的 nodeName 换成 nodeSelectornodeSelector: {disktype: ssd}),apply 后 Pod 仍落在 worker32(唯一带 ssd 标签的节点):

root@master30 ~ 13:41:45# kubectl apply -f deploy-with-nodeName.yaml
deployment.apps/webapp configured

root@master30 ~ 13:41:57# kubectl get pods -o wide | awk '{print $1,$3,$7}'
NAME STATUS NODE
webapp-6d6d67484b-pcttl Running worker32.ningcode.cn
webapp-6d6d67484b-sfvmg Running worker32.ningcode.cn

删除节点标签后,已运行的 Pod 不受影响(调度是一次性的):

root@master30 ~ 13:42:59# kubectl label node worker32.ningcode.cn disktype-
node/worker32.ningcode.cn unlabeled

7.4 删除 Deployment 里的 nodeSelector

root@master30 ~ 13:44:24# # 删除deployment的nodeSelector
root@master30 ~ 13:44:52# kubectl patch deployment webapp --type json \
-p '[{"op":"remove","path":"/spec/template/spec/nodeSelector"}]'
deployment.apps/webapp patched

# 滚动更新后两个副本分散到不同节点
root@master30 ~ 13:45:02# kubectl get pods -o wide |awk '{print $1,$3,$7}'
NAME STATUS NODE
webapp-686868b8c6-58l2w Running worker32.ningcode.cn
webapp-686868b8c6-n6s5c Running worker31.ningcode.cn

7.5 要点

  • nodeName 是硬指定:主机名写错 / 节点不存在时 Pod 永久 Pending,describe pod 看不到调度事件(绕过了调度器)
  • nodeSelector 匹配"带标签的节点集合",更适合多节点按属性(磁盘、GPU、区域)分流
  • 删除 nodeSelector 用 kubectl patch deployment ... --type json -p '[{"op":"remove",...}]',模板一变就会触发滚动更新
  • 判断节点主机名用 kubectl get nodes(本集群是 worker31/32.ningcode.cn,不是 laoma.cloud)

↑ 回到目录


八、节点亲和:nodeAffinity

8.1 YAML:required + preferred

# deploy-with-nodeAffinity.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:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: CPU
                operator: In
                values:
                - L1
                - L2
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            preference:
              matchExpressions:
              - key: MEM
                operator: In
                values:
                - L1
                - L2
      containers:
      - image: hub.laoma.cloud/library/nginx
        name: nginx
  • requiredDuringScheduling...硬要求,必须满足 CPU In (L1, L2),否则 Pending
  • preferredDuringScheduling...软偏好,尽量满足 MEM In (L1, L2),满足不了也能调度(weight 越大优先级越高)

8.2 先 apply 后打标签:从 Pending 到 Running

节点还没有 CPU 标签,直接 apply → 两个副本都 Pending:

root@master30 ~ 14:00:16# kubectl apply -f deploy-with-nodeAffinity.yaml
deployment.apps/web created

root@master30 ~ 14:00:43# kubectl get pods
NAME                  READY   STATUS    RESTARTS   AGE
web-c74fd5fbd-2lb9l   0/1     Pending   0          3s
web-c74fd5fbd-55m9w   0/1     Pending   0          3s

给节点打上标签后,调度器自动把 Pod 调度上去(required 条件被满足):

root@master30 ~ 14:00:46# kubectl label nodes worker31.ningcode.cn CPU=L1
node/worker31.ningcode.cn labeled
root@master30 ~ 14:01:19# kubectl label nodes worker32.ningcode.cn CPU=L2
node/worker32.ningcode.cn labeled

root@master30 ~ 14:01:25# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-c74fd5fbd-2lb9l Running worker31.ningcode.cn
web-c74fd5fbd-55m9w Running worker31.ningcode.cn

8.3 观察 preferred 偏好的作用

再次重启(新 Pod 会重新调度),此时 worker32 补上 MEM=L2 标签:

root@master30 ~ 14:01:51# kubectl rollout restart deployment web
deployment.apps/web restarted

root@master30 ~ 14:02:09# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-758688877c-nk5q7 Running worker31.ningcode.cn
web-758688877c-rzwcn Running worker32.ningcode.cn

# worker32 再补一个 MEM=L2(preferred 的匹配项)
root@master30 ~ 14:02:21# kubectl label nodes worker32.ningcode.cn MEM=L2
node/worker32.ningcode.cn labeled

root@master30 ~ 14:03:14# kubectl rollout restart deployment web
deployment.apps/web restarted

root@master30 ~ 14:03:17# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-5c5dd94447-djkzl Running worker32.ningcode.cn
web-5c5dd94447-qp2bp Running worker32.ningcode.cn

两个节点都满足 required(CPU L1/L2),MEM=L2 的 worker32 成为 preferred 优先项,重启后两个副本都倾向它。

8.4 清理

root@master30 ~ 14:03:23# kubectl delete deployments.apps web
deployment.apps "web" deleted
root@master30 ~ 14:03:33# kubectl label nodes worker31.ningcode.cn CPU-
root@master30 ~ 14:03:48# kubectl label nodes worker32.ningcode.cn CPU-
root@master30 ~ 14:03:52# kubectl label nodes worker32.ningcode.cn MEM-

8.5 要点

  • 亲和表达式支持 In / NotIn / Exists / DoesNotExist / Gt / Lt 操作符,比 nodeSelector 的等值匹配灵活得多
  • required 不满足 → Pending(和 nodeSelector 一样);preferred 只是"尽量",不影响最终可用性
  • 节点标签随时可加可删(kubectl label node xxx key- 删除),但只影响之后的调度

↑ 回到目录


九、Pod 亲和与反亲和

9.1 概念

  • podAffinity:新 Pod 倾向于和某些 Pod 同节点/同拓扑域(如:缓存和业务同机)
  • podAntiAffinity:新 Pod 避免和某些 Pod 同节点(如:高可用副本分散到不同节点)
  • 必须指定 topologyKey:在哪个维度上"同"或"不同"(kubernetes.io/hostname = 节点,topology.kubernetes.io/zone = 可用区)

9.2 podAffinity:web2 跟着 web1 走

先给 worker31 打 zone 标签,再让 web2 亲和 web1:

# 注意:节点名写错会报 NotFound
root@master30 ~ 14:27:04# kubectl label node worker31.laoma.cloud topology.kubernetes.io/zone=v
Error from server (NotFound): nodes "worker31.laoma.cloud" not found

root@master30 ~ 14:28:54# kubectl label node worker31.ningcode.cn topology.kubernetes.io/zone=v
node/worker31.ningcode.cn labeled
# deploy-with-podaffinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web1
spec:
  replicas: 1
  selector: {matchLabels: {app: web1}}
  template:
    metadata: {labels: {app: web1}}
    spec:
      nodeName: worker31.ningcode.cn      # web1 固定到 worker31
      containers:
      - name: web-server
        image: hub.laoma.cloud/library/nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web2
spec:
  replicas: 1
  selector: {matchLabels: {app: web2}}
  template:
    metadata: {labels: {app: web2}}
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: [web1]
            topologyKey: topology.kubernetes.io/zone
      containers:
      - name: web-server
        image: hub.laoma.cloud/library/nginx

验证:web2 因为亲和 web1(且 zone=v 只有 worker31 有),被调度到 worker31:

root@master30 ~ 14:32:06# kubectl apply -f deploy-with-podaffinity.yaml

root@master30 ~ 14:32:24# kubectl get pods -o wide
NAME                    READY   STATUS    RESTARTS   AGE   IP               NODE
web1-79958b69cb-5ggcq   1/1     Running   0          20s   10.224.195.139   worker31.ningcode.cn
web2-767459494f-rdjxt   1/1     Running   0          35s   10.224.195.140   worker31.ningcode.cn

(第一次实验时 web2 一直 Pending,因为 web1 的 nodeName 写成了不存在的 worker31.laoma.cloud,两个条件互相冲突,删掉旧 Pod 重来后正常——见第十四章)

9.3 podAntiAffinity:store 副本分散到不同节点

# deploy-with-podAntiAffinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: store
spec:
  replicas: 3
  selector: {matchLabels: {app: store}}
  template:
    metadata: {labels: {app: store}}
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: [store]
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-server
        image: hub.laoma.cloud/library/nginx
root@master30 ~ 14:35:16# kubectl apply -f deploy-with-podAntiAffinity.yaml
deployment.apps/store created

# 3 个副本、3 个节点:前两个 Running,第 3 个 Pending(反亲和:不能和 store 同节点)
root@master30 ~ 14:36:15# kubectl get all
pod/store-bff9b9ffd-954l4   1/1     Running   0          40s
pod/store-bff9b9ffd-lr6jv   1/1     Running   0          40s
pod/store-bff9b9ffd-wnp2b   0/1     Pending   0          40s

集群只有 3 个节点,required 反亲和"每个节点最多一个 store"最多只能放 3 个副本,多出来的 Pending——这就是反亲和 required 的硬约束。

9.4 多条件组合:web-store = 反亲和自己 + 亲和 store

# deploy-with-multi-Affinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-store
spec:
  replicas: 1
  selector: {matchLabels: {app: web-store}}
  template:
    metadata: {labels: {app: web-store}}
    spec:
      affinity:
        podAntiAffinity:          # 自己不能和其他 web-store 同节点
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: [web-store]
            topologyKey: "kubernetes.io/hostname"
        podAffinity:              # 但必须和 store 同节点
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: [store]
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: hub.laoma.cloud/library/nginx
root@master30 ~ 14:39:05# kubectl apply -f deploy-with-multi-Affinity.yaml
deployment.apps/web-store created

# 扩容:store 只有 1 个副本(在 worker31),web-store 必须跟它同节点、又不能重复
root@master30 ~ 14:41:20# kubectl scale deployment store --replicas 1
root@master30 ~ 14:41:26# kubectl scale deployment web-store --replicas 2
root@master30 ~ 14:41:45# ## 在亲和性作用下,只有worker32节点具备满足条件的pod
root@master30 ~ 14:42:03# ## 在反亲和性作用下,worker32节点上已经运行了一个副本

结果:第 2 个 web-store 副本 Pending——它必须和 store 同节点,但那个节点已经有 web-store 副本(反亲和冲突),没有节点能同时满足两个 required 条件。

9.5 要点

  • podAffinity / podAntiAffinitytopologyKey 决定"同/不同"的粒度:hostname 是节点级,zone 是可用区级
  • 两个 required 条件叠加可能无解 → Pod 一直 Pending,kubectl describe pod 看事件最直接
  • 高可用部署用反亲和 required + 节点数 ≥ 副本数;业务就近放一起用亲和
  • 副本多、节点少时,反亲和 required 会拒绝多余副本,这是预期行为

↑ 回到目录


十、污点与容忍:taint / tolerations

10.1 概念

  • **污点(taint)**打在节点上,让普通 Pod"不敢来";**容忍(toleration)**打在 Pod 上,“我受得了这个污点”
  • 三种效果:
    • NoSchedule:不调度新 Pod(已在运行的不管)
    • PreferNoSchedule:尽量不调度(软)
    • NoExecute:不调度 + 驱逐已在运行的 Pod

10.2 打污点

# worker31:CPU=L1:NoSchedule;worker32:CPU=L2:NoSchedule
root@master30 ~ 15:05:54# kubectl taint nodes worker31.ningcode.cn CPU=L1:NoSchedule
node/worker31.ningcode.cn tainted
root@master30 ~ 15:50:09# kubectl taint nodes worker32.ningcode.cn CPU=L2:NoSchedule
node/worker32.ningcode.cn tainted

10.3 容忍度不匹配 → Pending

# deploy-with-tolerations.yaml(v1:容忍 CPU=L3,但污点是 L1/L2 → 不匹配)
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: "L3"
        effect: "NoSchedule"
      containers:
      - name: nginx
        image: hub.laoma.cloud/library/nginx
        imagePullPolicy: IfNotPresent
root@master30 ~ 15:50:35# kubectl apply -f deploy-with-tolerations.yaml
deployment.apps/web created

root@master30 ~ 15:50:44# kubectl get pods
web-7d9d5f896c-k9nsc         0/1     Pending   0          4s
web-7d9d5f896c-r6wrt         0/1     Pending   0          4s

看事件,三个节点都有未匹配的污点:

root@master30 ~ 15:51:24# kubectl describe pod web-7d9d5f896c-qqjl2
...
Tolerations:                 CPU=L3:NoSchedule
                             node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Warning  FailedScheduling  19s  default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {CPU: L1}, 1 node(s) had untolerated taint {CPU: ...
root@master30 ~ 15:51:42# # 事件表明3个节点上的污点与pod不匹配,所以无法创建pod

10.4 容忍度匹配 → 正常调度

把容忍值改成 L1(匹配 worker31 的 CPU=L1 污点):

# deploy-with-tolerations.yaml(v2:容忍 CPU=L1 的 NoSchedule 和 NoExecute)
spec:
  template:
    spec:
      tolerations:
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoSchedule"
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoExecute"
root@master30 ~ 15:52:33# kubectl apply -f deploy-with-tolerations.yaml
deployment.apps/web created

root@master30 ~ 15:52:39# kubectl get pods -o wide
NAME                  READY   STATUS    RESTARTS   AGE   IP               NODE
web-78945b5cc-87j8w   1/1     Running   0          5s    10.224.195.144   worker31.ningcode.cn
web-78945b5cc-tmb97   1/1     Running   0          5s    10.224.195.145   worker31.ningcode.cn

两个副本都落在 worker31(唯一能容忍 CPU=L1 污点的节点)。

10.5 多个污点 → 需要多个容忍

再给 worker31 加 MEM=L2 污点后,只有 CPU=L1 容忍就不够了(还要容忍 MEM=L2):

root@master30 ~ 15:55:09# kubectl taint nodes worker31.ningcode.cn MEM=L2:NoSchedule
# worker31 现在有 CPU=L1 和 MEM=L2 两个 NoSchedule 污点

root@master30 ~ 15:57:25# kubectl describe pod web-745f6fc44d-bhjt9
Events:
  Warning  FailedScheduling  27s  default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {CPU: }, 1 node(s) had untolerated taint {MEM: L2}, 1 node(...

补上 MEM=L2 的容忍后恢复:

# deploy-with-tolerations.yaml(v3:CPU=L1 NoSchedule/NoExecute + MEM=L2 NoSchedule)
spec:
  template:
    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"
root@master30 ~ 15:58:24# kubectl apply -f deploy-with-tolerations.yaml

root@master30 ~ 15:58:30# kubectl get pods -o wide
web-65bfcb4fc-d6pw5   1/1     Running   0          8s    10.224.195.146   worker31.ningcode.cn
web-65bfcb4fc-qt448   1/1     Running   0          8s    10.224.195.147   worker31.ningcode.cn

10.6 清理污点

# 删除污点:key:Effect- 结尾
root@master30 ~ 15:58:38# kubectl taint nodes worker31.ningcode.cn CPU=L1:NoSchedule-
root@master30 ~ 15:59:05# kubectl taint nodes worker31.ningcode.cn CPU=L2:NoSchedule-
root@master30 ~ 15:59:11# kubectl taint nodes worker31.ningcode.cn MEM=L2:NoSchedule-
root@master30 ~ 15:59:20# kubectl taint nodes worker32.ningcode.cn CPU=L2:NoSchedule-
root@master30 ~ 15:59:27# kubectl taint nodes worker31.ningcode.cn CPU=L1:NoExecute-

10.7 要点

  • 容忍度 = 污点 key + value + effect 全部匹配(Equal);只想"不管值"用 operator: Exists
  • NoExecute 不仅阻止调度,还会驱逐节点上不匹配的现有 Pod(本次只观察了 NoSchedule 场景)
  • Pod Pending 排查必看 kubectl describe pod 的 Events:had untolerated taint {key: value} 直接点名问题
  • 删除污点是 kubectl taint node <节点> <key>:<effect>-,注意 key 不带 value 也能按 key 删

↑ 回到目录


十一、Metrics-Server 部署与资源监控

11.1 下载清单并替换镜像

root@master30 ~ 15:59:49# wget -O components.yaml http://192.168.46.200/class/course-materials/softwares/stage03/metrics-server-components-v0.7.1.yaml
--2026-08-12 17:03:07--  http://192.168.46.200/class/course-materials/softwares/stage03/metrics-server-components-v0.7.1.yaml
components.yaml                   100%[=========================================>]   4.24K  --.-KB/s    in 0s

root@master30 ~ 17:03:07# # 修改 Metrics-Server,不校验tls
root@master30 ~ 17:03:17# grep image: components.yaml
        image: registry.k8s.io/metrics-server/metrics-server:v0.7.1

# 换成私有仓库
root@master30 ~ 17:04:47# sed -i 's/registry.k8s.io/hub.laoma.cloud/g' components.yaml

日志中还在 vim 里取消了 metrics-server 对 TLS 的校验(--kubelet-insecure-tls),离线环境 kubelet 自签证书必须关校验,否则 metrics 采集失败。

11.2 部署并验证

root@master30 ~ 17:05:17# kubectl apply -f 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
......
deployment.apps/metrics-server created
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created

# 等 Pod Running(0/1 → 1/1)
root@master30 ~ 17:05:45# kubectl get pods -n kube-system |grep metric
metrics-server-5b98f887f4-w8f22                0/1     Running   0               30s
# 节点资源用量
root@master30 ~ 17:05:59# kubectl top node
NAME                   CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
master30.ningcode.cn   87m          2%     1757Mi          46%
worker31.ningcode.cn   37m          0%     967Mi           25%
worker32.ningcode.cn   44m          1%     999Mi           26%

# 命名空间内 Pod 资源用量
root@master30 ~ 17:06:10# kubectl top pods -n kube-system
NAME                                           CPU(cores)   MEMORY(bytes)
calico-kube-controllers-5b778fd8ff-cdsb9       2m           19Mi
coredns-5485cd7479-p6h4r                       2m           11Mi
......
metrics-server-5b98f887f4-w8f22                4m           15Mi

11.3 调快 HPA 同步周期(可选优化)

# 给 kube-controller-manager 增加启动参数(静态 Pod 清单)
root@master30 ~ 17:06:22# vim /etc/kubernetes/manifests/kube-controller-manager.yaml

containers[0].command 列表里追加三个参数(日志注释"添加三行"):

    - --horizontal-pod-autoscaler-sync-period=10s
    - --horizontal-pod-autoscaler-initial-readiness-delay=20s
    - --horizontal-pod-autoscaler-downscale-stabilization=60s
  • sync-period=10s:HPA 每 10 秒检查一次指标(默认 15s)
  • initial-readiness-delay=20s:Pod 启动后 20 秒内不算未就绪
  • downscale-stabilization=60s:缩容冷却 60 秒,防止抖动

改完 kube-controller-manager 会自动重启(静态 Pod),kubectl get pods -n kube-system |grep controller 确认新 Pod 起来即可。

11.4 要点

  • kubectl top 的数据来自 metrics-server(Metrics API),没有它 kubectl top 会报错
  • 离线环境两个必改:镜像换私有仓库、--kubelet-insecure-tls 关校验
  • HPA 依赖"每个 Pod 的 CPU 使用率",Pod 不写 resources.requests 时 HPA 无法计算利用率

↑ 回到目录


十二、HPA 弹性伸缩实战

12.1 创建 Deployment 并配置 HPA

root@master30 ~ 17:07:52# kubectl create deployment web --image=hub.laoma.cloud/library/nginx
deployment.apps/web created

# 2~5 副本,CPU 利用率目标 80%
root@master30 ~ 17:09:24# kubectl autoscale deployment web --max=5 --min=2 --cpu-percent=80
horizontalpodautoscaler.autoscaling/web autoscaled

# 导出 HPA 定义(tee 同时存文件)
root@master30 ~ 17:09:41# kubectl get hpa web -o yaml |tee hpa-cpu.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web
  namespace: scheduler
spec:
  maxReplicas: 5
  metrics:
  - resource:
      name: cpu
      target:
        averageUtilization: 80
        type: Utilization
    type: Resource
  minReplicas: 2
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
# 刚创建时指标还没采集到,TARGETS 显示 <unknown>
root@master30 ~ 17:10:10# kubectl get hpa
NAME   REFERENCE        TARGETS              MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   cpu: <unknown>/80%   2         5         2          36s

12.2 关键:给 Pod 设置资源限额

HPA 计算的是"平均利用率 = 实际使用 / requests",Pod 没写 requests 时无法计算,所以用 edit 打开 Deployment 把 resources 取消注释并保存:

root@master30 ~ 17:10:17# kubectl edit deployments.apps web
root@master30 ~ 17:12:14# kubectl edit deployments.apps web
deployment.apps/web edited
root@master30 ~ 17:13:34# 修改pod设定资源限额
# 在容器里配置(取消注释后保存)
resources:
  limits:
    cpu: 500m
  requests:
    cpu: 100m

12.3 暴露 Service 并压测

root@master30 ~ 17:14:03# kubectl expose deployment web --port=80 --target-port=80
service/web exposed

root@master30 ~ 17:14:08# kubectl get svc
NAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   10.97.246.181   <none>        80/TCP    7s

# 安装压测工具 ab(apache2-utils)
root@master30 ~ 17:14:30# apt install -y apache2-utils
# 监控窗口看 HPA;压测窗口:100 并发循环压
root@master30 ~ 17:15:13# while true ;do ab -n 300000 -c 100 http://10.97.246.181/;sleep 1;done
This is ApacheBench, Version 2.3
Benchmarking 10.97.246.181 (be patient)
Completed 30000 requests
......
Concurrency Level:      100
Time taken for tests:   25.650 seconds
Complete requests:      96966
Failed requests:        9
Requests per second:    3780.38 [#/sec] (mean)

12.4 结果:自动扩容到 5,停压后缩回 2

压测中 HPA 把副本从 2 扩到 5(达到 maxReplicas),CPU 使用率回落到 17%:

# 监控窗口抓到的状态(压测中)
NAME   REFERENCE        TARGETS        MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   cpu: 17%/80%   2         5         5          9m7s

# 5 个副本全部 Running
NAME                   READY   STATUS    RESTARTS   AGE
web-7cf9fd6bcb-2fpvf   1/1     Running   0          2m17s
web-7cf9fd6bcb-6mvgj   1/1     Running   0          5m18s
web-7cf9fd6bcb-mgk5t   1/1     Running   0          96s
web-7cf9fd6bcb-wbchx   1/1     Running   0          5m17s
web-7cf9fd6bcb-xkwvt   1/1     Running   0          46s

停止压测后(^C),CPU 掉到 0%,等待缩容冷却(60s)后自动缩回 minReplicas=2:

root@master30 ~ 17:19:28# #最大副本为5个,停止压力测试,会清除pod
# 停压后
NAME   REFERENCE        TARGETS       MINPODS   MAXPODS   REPLICAS   AGE
web    Deployment/web   cpu: 0%/80%   2         5         2          12m

NAME                   READY   STATUS    RESTARTS   AGE
web-7cf9fd6bcb-mgk5t   1/1     Running   0          4m35s
web-7cf9fd6bcb-wbchx   1/1     Running   0          8m16s

12.5 要点

  • HPA 工作流:metrics-server 采集 → 每 10s(默认 15s)算平均利用率 → 超出 target 扩容、低于 target 且过了冷却期缩容
  • Pod 必须声明 resources.requests,否则 TARGETS 一直是 <unknown>,HPA 不干活
  • 扩缩是渐进的:CPU 超 80% 会按比例计算目标副本数,上限 maxReplicas=5
  • ab -n 300000 -c 100:总数 30 万、并发 100;注意先确认 Service IP 再压(压错 IP 会白等)

↑ 回到目录


十三、环境清理与恢复

13.1 清理 NetworkPolicy 实验环境

# 一次性删除三个命名空间(web 有策略、ningcode 有跨 ns 测试 Pod)
root@master30 ~ 12:13:30# kubectl delete ns network web ningcode
namespace "web" deleted
namespace "ningcode" deleted
Error from server (NotFound): namespaces "network" not found   # 打错了,忽略

13.2 清理调度实验环境

# 删除节点标签
kubectl label node worker31.ningcode.cn CPU-
kubectl label node worker32.ningcode.cn CPU-
kubectl label node worker32.ningcode.cn MEM-
kubectl label node worker31.ningcode.cn topology.kubernetes.io/zone-

# 删除污点
kubectl taint nodes worker31.ningcode.cn CPU=L1:NoSchedule-
kubectl taint nodes worker31.ningcode.cn CPU=L2:NoSchedule-
kubectl taint nodes worker31.ningcode.cn MEM=L2:NoSchedule-
kubectl taint nodes worker32.ningcode.cn CPU=L2:NoSchedule-
kubectl taint nodes worker31.ningcode.cn CPU=L1:NoExecute-

# 删除 Deployment / Service
kubectl delete deployment --all -n scheduler
kubectl delete svc --all -n scheduler

实验产生的 YAML 文件(netpol.yaml、deploy-with-*.yaml、hpa-cpu.yaml 等)保留在 master 上,可留作模板。

13.3 检查集群状态

kubectl get nodes              # 3 节点全部 Ready
kubectl get ns                 # 只剩系统命名空间
kubectl get netpol -A          # 网络策略已随 ns 删除
kubectl get hpa -A             # HPA 随 deployment 清理

13.4 要点

  • 命名空间是天然的资源隔离边界,删 ns = 里面所有资源(含 NetworkPolicy)一起删,实验收尾最省事
  • 节点标签 / 污点是"全局"的,不随命名空间删除,必须手动清理,否则影响后续实验调度
  • 误删资源前先 kubectl get <类型> 确认名称,避免出现 namespaces "network" not found 这类拼写错误

↑ 回到目录


十四、常见错误与排查(重点)

14.1 NetworkPolicy 专项

现象 原因 修复
curlexit code 6(Could not resolve host) DNS 解析失败,不是策略拦的 跨命名空间用 服务名.命名空间名 或全限定名 .svc.cluster.local
curl 挂起 / exit code 28(超时) NetworkPolicy 未放行该来源 检查 from 条件(podSelector 同 ns 才匹配、namespaceSelector 要打标签)
ping 不通但 curl 通 策略只放行了声明的 TCP 端口 需要 ICMP 时在 ports 里声明协议,或接受"四层策略"行为
改完 YAML 忘了 apply,验证结果不变 生效的是当前已 apply 的版本 kubectl apply -f netpol.yaml 后重新验证
podSelector 放行不了别的命名空间 Pod podSelector 只匹配同命名空间 改用 namespaceSelector

14.2 调度 / 污点专项

现象 原因 修复
required 亲和/反亲和叠加后 Pending 约束无解(没有节点同时满足) 检查亲和条件与节点/副本分布,放宽 required 或加节点
副本数 > 节点数 + 反亲和 required 每节点最多 1 个副本 少于节点数部署,或用 preferred
打标签后 Pending 的 Pod 变 Running nodeSelector/affinity 条件被满足 正常;节点标签可后补,调度器会自动调度
kubectl top 报错 metrics-server 没装 / 没就绪 部署 metrics-server 并等 Pod Ready

14.3 概念要点对照

对比项 说明
nodeName vs nodeSelector vs nodeAffinity 硬指定节点 / 标签等值匹配 / 表达式匹配(支持操作符 + 软硬两档)
podAffinity vs podAntiAffinity 跟谁"同" vs 跟谁"不同",都要 topologyKey
NoSchedule vs NoExecute 只挡新调度 / 还驱逐存量 Pod
NetworkPolicy 的 from 多条件 多个条目 = OR;同一条目内嵌套 podSelector+namespaceSelector = AND
HPA 计算前提 Pod 必须有 resources.requests,metrics-server 必须可用

↑ 回到目录


十五、命令速查表与小结

15.1 命令速查表

场景 命令
切换命名空间 kubectl config set-context --current --namespace <ns>
跨命名空间解析 curl <服务名>.<命名空间名><服务名>.<ns>.svc.cluster.local
打节点标签 kubectl label node <节点> key=value;删除 kubectl label node <节点> key-
打命名空间标签 kubectl label ns <ns> key=value
打污点 kubectl taint nodes <节点> key=value:NoSchedule;删除 ... key:NoSchedule-
看调度事件 kubectl describe pod <pod>(Events 里的 FailedScheduling)
查 NetworkPolicy kubectl get netpol / kubectl describe netpol <名>
临时测试 Pod kubectl run --rm -it ubuntu -l run=test --image ubuntu -- bash
装压测工具 apt install -y apache2-utilsab -n 300000 -c 100 http://<IP>/
看资源用量 kubectl top node / kubectl top pods
创建 HPA kubectl autoscale deployment <名> --max=5 --min=2 --cpu-percent=80
看 HPA kubectl get hpa / kubectl get hpa <名> -o yaml
删除 HPA kubectl delete hpa <名>

15.2 小结

  1. NetworkPolicy 是白名单防火墙:不写策略全放行,写了策略没放行的全拒绝;podSelector(同 ns)、namespaceSelector(跨 ns)、ipBlock(按网段)三种来源匹配要分清
  2. DNS 与策略是两回事exit code 6 是解析失败(短名问题),exit code 28 才是被策略拦截(超时)——排障先分清
  3. 调度三条路径:nodeName 硬指定(写错就 Pending)、nodeSelector 标签等值、亲和表达式(支持操作符 + required/preferred),优先级从高到低灵活度从低到高
  4. 反亲和是"高可用"的开关:副本分散要 required + 节点数 ≥ 副本数;多个 required 亲和叠加可能无解,describe pod 看事件最直接
  5. 污点与容忍必须严格配对:key/value/effect 全匹配才生效;NoExecute 会驱逐存量 Pod;事件里 untolerated taint {key: value} 直接点名
  6. HPA 三件套:metrics-server 就绪 + Pod 有 resources.requests + target 合理;压测会扩到 max,停压会缩回 min(有 60s 冷却),全程 kubectl get hpa -w 可观察

完。本手册基于 kaitou.txt + master30_2026-08-12_10_27_42.log 整理,命令时间戳均为原始记录;省略了重复命令与长输出,报错、修复、验证结果与日志一致。

更多推荐