AI Infra 训练营
总览
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
HiHuo 主站
GitHub
总览
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
HiHuo 主站
GitHub
  • Day 0 · 环境与硬件

    • Day 0:5 节点裸 Ubuntu → K8s 装机基线
  • Week 1:K8s 内核 + 周边基础设施

    • Day 1:3 CP HA 集群 + CNI 选型 + DNS 调优
    • Day 2: 控制面 deep dive + etcd 内核 + chaos drill
    • Day 3: CRD + Operator (kubebuilder 从 0 写)
    • Day 4: Storage 主线 + Cilium 二探
    • Day 5: Volume Expansion + 安全主线
    • Day 6: 调度 + 观测主线 + Day 2 遗留修复
    • Day 7: Harbor + ArgoCD + Cilium Service Mesh
  • Week 2:制品 + GitOps + AI Infra + 综合

    • Day 8 主线 — AI Infra: GPU + k3s + vLLM + Qwen2.5
    • Day 8 主线 — AI Infra 尝试 1 (跨 WAN GPU 加入主集群)
    • Day 8 (alt) — AlertManager 真接入 + PrometheusRule 实战
    • Day 8: CI Infrastructure — Gitea + Jenkins + Kaniko
    • Day 9: Triton + GPU Metrics + 推理性能对比
    • Day 10: MIG + 量化 + HPA Custom Metrics
    • Day 11: AI Agent 业务端到端 — 把 Day 1-10 全部串起来
    • Day 12: 灾难恢复 + 生产事故注入
    • Day 13: LLM Operator + 联邦 + Mesh + RAG
    • Day 14: CKA/CKS 真题演练 + 14 天 Bootcamp 终极总结

Day 6: 调度 + 观测主线 + Day 2 遗留修复

目标 (双主线 + 一修复):

  • 修复: 给 cp-2/cp-3 补 audit-policy,完成 control plane 对称
  • 主线 1 (调度): 亲和性 / 反亲和 / 拓扑分散 / 污点容忍 / HPA 一锅端
  • 主线 2 (观测): kube-prometheus-stack + Loki + 接入 Hubble/Longhorn metrics 耗时: 6-8 小时 风险: 调度策略写错可能让 Pod 全卡 Pending / Prometheus 装错可能压垮节点 / 监控 cardinality 爆炸

0. TL;DR (7 节 + 1 修复)

  1. A — 修 Day 2 遗留: cp-2/cp-3 补 audit-policy,3 cp 对称
  2. B — 调度速通: 调度器 5 阶段 / 4 种亲和 / 拓扑 / taint / HPA / VPA / CA mini-book
  3. C — 节点亲和 + Pod 反亲和: label 节点为 zone,nodeAffinity + podAntiAffinity 协同
  4. D — Topology Spread + 污点容忍: 均匀分散 + taint 软隔离
  5. E — HPA + metrics-server: 装 metrics-server,跑压力测试触发自动扩缩
  6. F — kube-prometheus-stack: Prometheus + Grafana + AlertManager + node-exporter + ksm
  7. G — Loki + Promtail: 日志栈,Grafana 集成查询

1. 学习目标 + 闭环输出

能秒答:

  • "kube-scheduler 的 5 个阶段是?"
  • "nodeAffinity vs nodeSelector 区别?required vs preferred?"
  • "podAntiAffinity 用 hostname vs zone 拓扑域的区别?"
  • "TopologySpreadConstraints 跟 podAntiAffinity 啥关系?为啥 K8s 1.27+ 推荐前者?"
  • "HPA 的 metrics 从哪来?Resource 跟 Custom Metrics 区别?"
  • "Prometheus 的 Operator 模式跟 helm-chart 部署的区别?ServiceMonitor 是干啥的?"
  • "Loki 的存储是 fluentd-as-blob,跟 ES 全文索引区别在哪?"

简历可写:

  • "K8s 调度策略调优 (亲和/拓扑/污点),保证关键服务跨可用区分布;HPA + metrics-server 实现 CPU 触发自动扩缩"
  • "搭建 kube-prometheus-stack 全栈观测;Loki + Promtail 日志聚合;集成 Cilium Hubble metrics + Longhorn metrics"

10. 实时执行日志(6 维度)

Day 6.A — 修 Day 2 遗留: 3 cp audit-policy 对称

A1. 发现 — Day 2 只配了 cp-1!

What (Day 6 开头 survey 才发现):

for h in m1 m2 m3; do
  ssh $h 'ls /etc/kubernetes/audit-policy.yaml; grep audit /etc/kubernetes/manifests/kube-apiserver.yaml; ls /var/log/kubernetes/audit.log'
done

Actual:

audit-policy.yamlapiserver argsaudit.log
cp-1✅✅ 完整✅ 44MB (累积 17h)
cp-2✅ (Day 2 复制)❌ 缺❌ 不存在
cp-3✅ (Day 2 复制)❌ 缺❌ 不存在

Why 这是大问题:

  • 客户端 kubectl 走 LB → 每次请求随机命中 cp-1/cp-2/cp-3
  • 命中 cp-2 / cp-3 的请求 没有 audit log → 集群有 2/3 流量没记录!
  • 安全审计假象: 看 cp-1 的 audit.log 自以为完整,实际只 1/3

Lesson (Working Method #19 — Verify):

  • Day 2 之后没去 cp-2/cp-3 验证文件后续配置 — 这是验证不完整
  • HA 集群的 control plane 必须完全对称, 任何 manifest 变更要逐节点验
  • 后续 round 都应该把"3 cp 对称"作为收尾检查项

A2. Patch 脚本 — 把 audit 注入 manifest

策略: 用 --encryption-provider-config (Day 5.E 已加,3 cp 都有) 作锚点

args 插入:

arg_old = "    - --encryption-provider-config=/etc/kubernetes/encryption-config.yaml"
arg_new = (
  "    - --audit-policy-file=/etc/kubernetes/audit-policy.yaml\n"
  + arg_old
  + "\n    - --audit-log-path=/var/log/kubernetes/audit.log"
  + "\n    - --audit-log-maxage=7"
  + "\n    - --audit-log-maxbackup=3"
  + "\n    - --audit-log-maxsize=100"
)

volumeMount + hostPath: 在 encryption-config 前后插 audit-policy.yaml + /var/log/kubernetes

新增的 mkdir: os.makedirs("/var/log/kubernetes", exist_ok=True) — cp-2/cp-3 上没这目录,Pod hostPath DirectoryOrCreate 也能补,但提前建更稳

A3. 滚动 patch + 验证

What:

ssh m2 'python3 /tmp/patch-audit.py'  # cp-2
# 等 45s apiserver restart
ssh m3 'python3 /tmp/patch-audit.py'  # cp-3
# 等 45s

ssh m1 'kubectl get pod -n kube-system -l component=kube-apiserver'
for h in m1 m2 m3; do
  ssh $h 'ls -la /var/log/kubernetes/audit.log; wc -l /var/log/kubernetes/audit.log'
done

Actual:

NAME                      READY   STATUS    RESTARTS   AGE
kube-apiserver-k8s-cp-1   1/1     Running   0          43m
kube-apiserver-k8s-cp-2   1/1     Running   0          60s
kube-apiserver-k8s-cp-3   1/1     Running   0          7s

cp-1: 46MB / 42717 行 (Day 2 起累积)
cp-2: 417KB / 412 行 (刚启)
cp-3: 187KB / 226 行 (刚启)

✅ 3 cp 现在 audit + encryption + audit-log 完全对称

Outcome: 集群真正"零盲点审计",所有请求无论命中哪个 cp 都有完整记录 Lesson:

  • 任何"修复 Day N 遗留"的 round, 也要按 6 维度记录, 作为后续审计依据
  • HA 集群配置变更应当广播到所有副本,变更前后跑对称性校验脚本

Day 6.B — 调度速通 mini-book

B1. kube-scheduler 的 5 阶段流程

新 Pod 创建 (status: Pending)
    ↓
kube-scheduler watch 到 unscheduled Pod
    ↓
┌─────────────────────────────────────────────────────────┐
│  1. Queue        (Pod 入 priority queue)                │
│                                                          │
│  2. Filter (硬条件,扣除不符合的 node)                    │
│     ├─ NodeUnschedulable                                 │
│     ├─ NodeName / NodeAffinity                          │
│     ├─ NodeResourcesFit (CPU/Memory/GPU 够吗)           │
│     ├─ PodTopologySpread                                │
│     ├─ NodePorts (端口冲突?)                            │
│     ├─ TaintToleration                                  │
│     └─ VolumeBinding (PV 在该节点可挂吗)                │
│                                                          │
│  3. Score (软条件,给每节点打分)                         │
│     ├─ ImageLocality (镜像已在节点 +分)                 │
│     ├─ NodeResourcesBalancedAllocation (CPU/Memory 均)  │
│     ├─ InterPodAffinity (亲和/反亲和)                   │
│     ├─ NodeAffinity (preferred 项)                      │
│     └─ TaintToleration (preferred 项)                   │
│                                                          │
│  4. Reserve      (锁定资源)                              │
│                                                          │
│  5. Bind         (写 Pod.spec.nodeName)                 │
└─────────────────────────────────────────────────────────┘

关键认知:

  • Filter = 硬过滤,不通过 = 节点直接 out
  • Score = 软评分,通过 Filter 的节点之间排名,最高分 win
  • Filter 全 0 = 0/N nodes are available 错误
  • 优先级 Pod (preemption) 可以驱逐低优先级 Pod 腾位置

B2. 4 种亲和性 — 高频面试题

类型yaml 字段拓扑域 (topologyKey)适用场景
nodeSelectorspec.nodeSelector: {disktype: ssd}隐式 node 级简单粗暴,精确匹配 label
nodeAffinityspec.affinity.nodeAffinity.*隐式 node 级复杂表达式 (In/NotIn/Exists/Lt/Gt)
podAffinityspec.affinity.podAffinity.*任意 (hostname / zone / region)"我要跟某 Pod 同节点 / 同 zone" (cache 协同)
podAntiAffinityspec.affinity.podAntiAffinity.*任意"我要分散" (HA,避免单点故障)

required vs preferred:

  • requiredDuringSchedulingIgnoredDuringExecution: 硬 (放 Filter 阶段) — 不满足直接不调度
  • preferredDuringSchedulingIgnoredDuringExecution: 软 (放 Score 阶段) — 不满足也行,加权打分
  • 没有 *DuringExecution 选项 (Pod 运行中节点 label 变了不会迁,只影响新调度)

B3. TopologySpreadConstraints — 比 podAntiAffinity 更现代

K8s 1.27+ 推荐用 TopologySpreadConstraints 替代复杂的 podAntiAffinity:

topologySpreadConstraints:
- maxSkew: 1                                # 任意两拓扑域 Pod 数差不超过 1
  topologyKey: topology.kubernetes.io/zone  # 拓扑域 = zone
  whenUnsatisfiable: DoNotSchedule          # 不满足拒绝调度(类似 required)
  labelSelector:
    matchLabels: {app: my-app}

vs podAntiAffinity:

podAntiAffinityTopologySpreadConstraints
语义"不能在一起" (binary)"尽量均匀" (smooth)
拓扑域每个拓扑域内 0 个或 1 个拓扑域之间数量差 ≤ maxSkew
性能O(n²) pairs 比较O(n) 计数
推荐场景强排斥 (主备数据库)通用均衡 (无状态服务)

典型 yaml:

  • nginx 6 副本, 3 zone → maxSkew: 1 → 每 zone 2 副本
  • 关键服务 + zone label + maxSkew: 1 + DoNotSchedule = 强制跨可用区分布

B4. Taint + Toleration — 节点级"拒绝默认"

kubectl taint node k8s-cp-1 dedicated=db:NoSchedule
# 现在 cp-1 默认拒绝任何 Pod, 除非 Pod 显式带 toleration

3 个 effect:

effect行为
NoSchedule不允许新 Pod 调度过来 (已有 Pod 不动)
PreferNoSchedule软拒绝, 尽量别调过来
NoExecute已有不带 tolerations 的 Pod 也被驱逐

经典套路 (生产):

  • 节点 1: dedicated=gpu:NoSchedule → 只允许 GPU workload
  • 节点 2: dedicated=db:NoExecute → 数据库专用,普通 Pod 都不能跑
  • 控制平面: node-role.kubernetes.io/control-plane:NoSchedule (kubeadm 默认)

B5. HPA / VPA / CA 三件套

HPAVPACA
全称HorizontalPodAutoscalerVerticalPodAutoscalerClusterAutoscaler
调整Pod 副本数Pod CPU/Memory request节点数
数据源metrics-server / Prometheusmetrics-server + 历史unscheduled Pod, 闲置节点
推荐✅ 必装用 recommendation 模式公有云 + 容量需求波动大

HPA 工作循环 (15s):

  1. 从 metrics-server 拉所有 Pod 的 CPU/Memory 当前使用
  2. 算平均利用率
  3. 跟 targetCPUUtilizationPercentage 比 → 决定扩缩副本数
  4. patch Deployment.spec.replicas

HPA 升级版: 用 metric.external (Custom Metric API + Prometheus Adapter) 接 Prometheus 指标(如 QPS, 队列长度)做扩缩 — 比 CPU 准确得多

B6. 节点 label 准备 (本 Day demo 用)

# 给 5 节点 label, 模拟多可用区 + 异构存储
kubectl label node k8s-cp-1 topology.kubernetes.io/zone=zone-a
kubectl label node k8s-w-1  topology.kubernetes.io/zone=zone-a
kubectl label node k8s-cp-2 topology.kubernetes.io/zone=zone-b
kubectl label node k8s-w-2  topology.kubernetes.io/zone=zone-b
kubectl label node k8s-cp-3 topology.kubernetes.io/zone=zone-c

kubectl label node k8s-cp-1 disktype=ssd
kubectl label node k8s-cp-2 disktype=ssd
kubectl label node k8s-w-1  disktype=hdd
kubectl label node k8s-w-2  disktype=hdd
kubectl label node k8s-cp-3 disktype=hdd

布局表:

NodeZoneDisktype
k8s-cp-1zone-assd
k8s-cp-2zone-bssd
k8s-cp-3zone-chdd
k8s-w-1zone-ahdd
k8s-w-2zone-bhdd

后续 C / D 实战都基于这个布局


Day 6.C — 节点亲和 + Pod 反亲和实战

C1. 4 个 Deployment Demo 设计

Demo调度策略副本预期分布
1 demo1-ssd-onlynodeSelector: {disktype: ssd}3只在 cp-1/cp-2 (zone-a ssd / zone-b ssd)
2 demo2-zonec-preferrednodeAffinity required In [ssd,hdd]3任意 Linux 节点(几乎不限)
3 demo3-spread-zonepodAntiAffinity topologyKey=zone33 zone 各 1 个
4 demo4-one-per-nodepodAntiAffinity topologyKey=hostname55 节点各 1 个

每个 Deployment 都加:

  • tolerations: [{operator: Exists}] (允许 control-plane)
  • app.kubernetes.io/name label (满足 Kyverno require-app-label)
  • resources.requests (满足 require-resources)
  • image 必须带 tag (满足 disallow-latest-tag) — 用 pause:3.10 玩具 image

C2. Demo 1 — nodeSelector

spec:
  template:
    spec:
      nodeSelector:
        disktype: ssd

最简单的语法 — 单 key value 精确匹配

Actual:

demo1-ssd-only:
  k8s-cp-1: 1
  k8s-cp-2: 2

✅ 全在 ssd 节点(cp-1 + cp-2),hdd 节点(cp-3/w-1/w-2)全跳过

C3. Demo 3 — podAntiAffinity 在 zone

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels: {app: demo3-spread-zone}
            topologyKey: topology.kubernetes.io/zone

语义: "我的同类 Pod 不能跟我在同一个 zone"

Actual:

demo3-spread-zone:
  k8s-cp-2: 1   ← zone-b
  k8s-cp-3: 1   ← zone-c
  k8s-w-1: 1    ← zone-a

✅ 完美跨 3 zone (即使 zone-a 有 2 节点,只放 1 个)

Lesson:

  • 3 副本 + 3 zone + topologyKey=zone → 每 zone 必 1 个
  • 如果改 4 副本会怎样? required → 第 4 个调度失败 (没第 4 zone),Pod Pending

C4. Demo 4 — podAntiAffinity 在 hostname

topologyKey: kubernetes.io/hostname

5 副本 + topologyKey=hostname → 每节点 1 个

Actual:

demo4-one-per-node:
  k8s-cp-1: 1
  k8s-cp-2: 1
  k8s-cp-3: 1
  k8s-w-1: 1
  k8s-w-2: 1

✅ 5 节点 5 副本,完美 (前提是 5 个节点都 schedulable, 我们 tolerated control plane)

C5. 面试要点 — required vs preferred 的取舍

选择优点缺点
required严格,行为可预测不满足 = Pending,可能 0 副本
preferred灵活,fallback OK不能保证,可能"反亲和失效"

生产推荐组合:

  • 关键无状态服务: required hostname (不允许同节点) + preferred zone (尽量跨 zone)
  • 普通服务: preferred 双 + required 不约束
  • 数据库 / 中间件: required + 严格 (HA 必须)

C6. ⚠️ 真坑预警 — required 太严会让 Pod Pending

# 8 副本 + topologyKey=hostname + 只 5 节点 → 3 个 Pod Pending

Filter 阶段筛掉所有节点 → 调度失败 → 0/5 nodes are available: 5 didn't match Pod's affinity/anti-affinity rules

Fix:

  • 用 preferredDuringScheduling 而非 required
  • 或加节点
  • 或减副本

Day 6.D — Topology Spread + 污点容忍

D1. Demo 5 — TopologySpread maxSkew=1 (6 副本, 3 zone)

spec:
  template:
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels: {app: demo5-topology}

Actual (Pod 6 副本):

zone-a (w-1):  2 副本
zone-b (cp-2): 2 副本
zone-c (cp-3): 2 副本

✅ 完美均分 (zone-a 有 cp-1+w-1 两节点,但 TopologySpread 只看 zone 间均匀,不强制 zone 内均匀)

D2. vs podAntiAffinity 的区别 — 6 副本 + 3 zone 跑两次

podAntiAffinity requiredTopologySpread maxSkew=1
4 副本/3 zone❌ 第 4 Pod Pending (zone 满)✅ skew=1 时第 4 个进入任意 zone
6 副本/3 zone❌ 不可能 (每 zone 1 个上限)✅ 2+2+2 完美
7 副本/3 zone❌✅ 3+2+2 (skew=1 满足)

结论: TopologySpread 是 podAntiAffinity 的 "soft" 版,K8s 1.27+ 强烈推荐它

D3. Demo 6 — Taint NoSchedule

What:

kubectl taint node k8s-cp-1 dedicated=db:NoSchedule

Pod 不带 dedicated toleration 时,cp-1 直接 Filter out

Actual (demo6-no-toler 4 副本):

k8s-cp-2: 1
k8s-cp-3: 1
k8s-w-1: 1
k8s-w-2: 1

✅ 4 副本散到非 cp-1 的 4 节点,cp-1 全被排除

D4. Demo 7 — toleration 后可进 cp-1

spec:
  template:
    spec:
      nodeSelector: {kubernetes.io/hostname: k8s-cp-1}    # 强制 cp-1
      tolerations:
      - {key: node-role.kubernetes.io/control-plane, operator: Exists, effect: NoSchedule}
      - {key: dedicated, value: db, effect: NoSchedule}    # ← 关键: tolerate dedicated taint

Actual (demo7-toler-db 4 副本):

demo7-toler-db-...-9fkrs    k8s-cp-1
demo7-toler-db-...-bkpsj    k8s-cp-1
demo7-toler-db-...-cgtmc    k8s-cp-1
demo7-toler-db-...-f5kdp    k8s-cp-1

✅ 4 副本全在 cp-1 — tolerations 让 dedicated=db taint 失效

D5. Taint 的 3 个 effect 实战对比

effect含义demo
NoSchedule新 Pod 不允许调度过来,已有 Pod 不动Demo 6 验证
PreferNoSchedule软拒, 尽量别来(本 Day 跳过)
NoExecute已有 Pod 也驱逐(除非 tolerate)用于节点维护(kubectl drain 内部就是打 NoExecute taint)

生产典型 taint 配置:

# GPU 节点专用
kubectl taint node gpu-node-1 nvidia.com/gpu=present:NoSchedule
# Pod 显式 tolerate + nodeSelector 才能用 GPU

# 数据库节点
kubectl taint node db-node-1 dedicated=db:NoExecute
# 已经误调度的 Pod 立即驱逐

# 维护
kubectl drain k8s-w-1 --ignore-daemonsets   # 内部 = taint + NoExecute

D6. 4 个调度机制 cheat sheet

机制行使在表达性当前推荐
nodeSelectorlabel key=value弱 (单 key)简单场景, dev
nodeAffinitylabel expression强 (In/NotIn/Exists/Gt/Lt)生产
podAntiAffinity requiredPod label强 (binary, 0/1)主备 / 关键服务
TopologySpreadPod label + topologyKey强 (skew 量化)K8s 1.27+ 默认推荐
Taint+Toleration节点级强 (3 effects)节点专用 / 维护

搭配模式:

  • 数据库主备: podAntiAffinity required hostname + nodeSelector ssd
  • 无状态业务: topologySpreadConstraints zone (maxSkew=1) + tolerations 让 cp 可调度
  • GPU 工作流: taint gpu节点 + Pod 显式 nodeAffinity + toleration

Day 6.E — HPA + metrics-server

E1. 装 metrics-server (HPA Resource metric 前置)

What:

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# kubelet 是 self-signed cert,要 disable TLS verify
kubectl patch deployment metrics-server -n kube-system --type=json -p='[
  {"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}
]'

Why --kubelet-insecure-tls:

  • metrics-server 通过 kubelet 的 :10250 端口拉 metrics
  • kubelet cert 是 kubeadm 自签的,不在 K8s 系统的 trust chain 里
  • 不加这个参数 → metrics-server 反复 cert error,kubectl top 永远 "Metrics API not available"

生产更好做法:

  • 配 --kubelet-extra-args=--rotate-server-certificates=true
  • kube-controller-manager 自动签 kubelet server cert
  • metrics-server 用这些 cert,不需要 insecure

Actual (~90s 拉镜像 + 启动):

metrics-server   1/1   Running

kubectl top nodes:
k8s-cp-1   4932m   61%   4093Mi   52%
k8s-cp-2   3254m   40%   2892Mi   36%
k8s-cp-3   3171m   39%   2947Mi   37%
k8s-w-1    2455m   30%   2534Mi   32%
k8s-w-2    2684m   33%   2558Mi   32%

✅ metrics 全节点拉到

E2. HPA Demo Deployment + HPA 资源

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hpa-web
spec:
  replicas: 1
  selector: {matchLabels: {app: hpa-web}}
  template:
    spec:
      containers:
      - name: c
        image: nginx:1.27-alpine
        resources:
          requests: {cpu: 100m, memory: 32Mi}   # ← baseline 100m
          limits:   {cpu: 500m, memory: 64Mi}    # ← max 500m
        ports: [{containerPort: 80}]
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: {name: hpa-web}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hpa-web
  minReplicas: 1
  maxReplicas: 8
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50    # 目标 = 50% of requests

关键设计:

  • Utilization 50% 意思: avg(Pod CPU) / Pod CPU request = 50%
  • 即每 Pod 用 50m (= 50% of 100m request) 时, HPA "刚刚好",超过会扩容
  • target 还可以是 AverageValue(绝对量)或 Value(单 Pod)

E3. 压测触发扩容

What (起一个 loadgen Pod 死循环 curl):

kubectl run loadgen --image=busybox:1.36 \
  --restart=Never --labels='app=loadgen' \
  --overrides='{"spec":{...,"containers":[{"command":["sh","-c","while true; do wget -qO- --timeout=2 http://hpa-web; done"]}]}}'

E4. 实测扩容曲线(60s 内)

时刻CPU%Replicas行为
T+0s0% / 50%1压测刚启,metrics 没拉到
T+15s0% / 50%1还在 metrics 拉取窗口
T+30s65% / 50%1超阈值,触发扩
T+45s81% / 50%2HPA 扩到 2 副本 ✅
T+60s26% / 50%22 副本分担, 回归阈值下
T+120s24% / 50%2持续平稳 2 副本

E5. 停压测 → 等待缩容

What:

kubectl delete pod loadgen

Actual:

时刻CPU%Replicas行为
停压 +20s16% / 50%2CPU 立即降
停压 +40s19% / 50%2仍 2 副本
停压 +60s13% / 50%2没缩
停压 +80s0% / 50%2没缩
5 min 后0% / 50%1 (预期)HPA stabilization window 过期

Why scaleDown 慢:

  • HPA 有 behavior.scaleDown.stabilizationWindowSeconds 默认 300s (5 min)
  • 设计目的: 避免抖动 (CPU 短暂回落就缩容,马上又要扩)
  • 可配置: behavior.scaleDown.policies: [{type: Percent, value: 50, periodSeconds: 60}]

E6. HPA 进阶 — 不只 CPU

metrics:
- type: Resource                         # Resource 指 CPU/Memory
  resource: {name: cpu, target: ...}
- type: Pods                             # Pods 指标: 跨 Pod avg
  pods: {metric: {name: http_requests}, target: {averageValue: 1k}}
- type: Object                           # Object 指标: 单个对象的 metric
  object: {metric: {name: queue_messages_ready}, describedObject: ...}
- type: External                         # 集群外 metric (Prometheus / CloudWatch)
  external: {metric: {name: sqs_queue_size}, target: {averageValue: 30}}

生产典型:

  • Resource CPU/Memory 作 baseline
  • External (Prometheus QPS / 队列长度) 作主要触发器
  • 这要装 Prometheus Adapter (Day 6.F 后续可扩展)

简历可写:

落地 HPA 基于 metrics-server 的 CPU 自动扩缩,压测 30s 内 1→2 副本响应;扩展到 External Metrics(Prometheus QPS),业务指标精确驱动


Day 6.F — kube-prometheus-stack

F1. 全栈架构图

kube-prometheus-stack(Helm chart)= 一锅装好 8+ 组件:

┌─────────────────────────────────────────────────────────┐
│  Prometheus Operator  (controller, 管 Prometheus CR)    │
│  + ServiceMonitor CRD (定义如何 scrape 一个 Service)    │
│  + PodMonitor CRD     (定义如何 scrape 一个 Pod)        │
│  + PrometheusRule CRD (定义告警规则)                    │
│  ↓ 创建/调谐                                            │
│                                                          │
│  Prometheus (StatefulSet, 10Gi PVC, 拉 metrics + TSDB)  │
│  AlertManager (StatefulSet, 收 alert, 路由 Email/PD/IM) │
│  Grafana (Deployment, 2Gi PVC, dashboard UI)            │
│                                                          │
│  node-exporter (DaemonSet x 5, 节点 metrics)            │
│  kube-state-metrics (Deployment, K8s 资源 metrics)      │
└─────────────────────────────────────────────────────────┘

F2. 装 Helm + kube-prometheus-stack

What (helm binary + chart install):

curl -sL https://get.helm.sh/helm-v3.16.3-linux-amd64.tar.gz | tar xz -C /tmp
mv /tmp/linux-amd64/helm /usr/local/bin/

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install kps prometheus-community/kube-prometheus-stack \
  --namespace monitoring --create-namespace \
  --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.storageClassName=longhorn \
  --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=10Gi \
  --set prometheus.service.type=NodePort \
  --set grafana.service.type=NodePort \
  --set grafana.persistence.enabled=true \
  --set grafana.persistence.storageClassName=longhorn \
  --set grafana.persistence.size=2Gi \
  --set grafana.adminPassword=bootcamp \
  --set 'prometheus.prometheusSpec.tolerations[0].operator=Exists' \
  --set 'grafana.tolerations[0].operator=Exists' \
  ... (其他组件 tolerations)

关键配置选择:

  • Prometheus PVC 10Gi (Longhorn) — TSDB 数据持久 + 重启不丢
  • Grafana PVC 2Gi (Longhorn) — dashboard + datasource 配置持久
  • 全组件 tolerations:Exists — 让 control plane 也可调
  • adminPassword=bootcamp(生产用 Secret 引用)

F3. PolicyException(Kyverno 兼容)

apiVersion: kyverno.io/v2
kind: PolicyException
metadata: {name: monitoring-stack-exception, namespace: kyverno}
spec:
  exceptions:
  - {policyName: require-app-label, ruleNames: [check-label]}
  - {policyName: disallow-latest-tag, ruleNames: [require-image-tag]}
  - {policyName: require-resources, ruleNames: [require-cpu-mem]}
  match:
    any:
    - resources:
        namespaces: [monitoring]

⚠️ Kyverno PolicyException 是 K8s 1.13.2 默认 disabled 的 alpha feature,要在 Kyverno ConfigMap 里 enable。 Actual: 不开 exception 也跑通了 — kube-prometheus-stack chart 默认 Pod 都带 resources/label/tag,符合 Kyverno 3 条 policy ✅(chart 质量高)

F4. Pod 启动观察

启动 ~3min, 11 个 Pod:

NAME                                                    READY   STATUS
alertmanager-...-alertmanager-0                         2/2     Running   (alertmanager + config-reloader)
kps-grafana-...                                         3/3     Running   (grafana + dashboard-sidecar + datasource-sidecar)
kps-kube-prometheus-stack-operator-...                  1/1     Running   (Prometheus Operator controller)
kps-kube-state-metrics-...                              1/1     Running   (resource metrics)
kps-prometheus-node-exporter (DaemonSet x5)             1/1     Running   (节点 metrics)
prometheus-...-prometheus-0                             2/2     Running   (prometheus + config-reloader)

StatefulSet 选择:

  • Prometheus / AlertManager 用 StatefulSet (PVC stable, 顺序启动)
  • Grafana 用 Deployment (无状态, PVC 走 PV 同样持久)

F5. ServiceMonitor 自动注入 — 不用手写 scrape config

kubectl get servicemonitor -A

Actual (13 个 ServiceMonitor):

kps-grafana
kps-kube-prometheus-stack-alertmanager
kps-kube-prometheus-stack-apiserver
kps-kube-prometheus-stack-coredns
kps-kube-prometheus-stack-kube-controller-manager
kps-kube-prometheus-stack-kube-etcd
kps-kube-prometheus-stack-kube-proxy
kps-kube-prometheus-stack-kube-scheduler
kps-kube-prometheus-stack-kubelet
kps-kube-prometheus-stack-operator
kps-kube-prometheus-stack-prometheus
kps-kube-state-metrics
kps-prometheus-node-exporter

ServiceMonitor 工作机制:

  1. 用户/Chart 创建 ServiceMonitor CR (说"我想 scrape 这些 Service 的这些端口")
  2. Prometheus Operator watch ServiceMonitor
  3. Operator 把 ServiceMonitor 转成 Prometheus 的 scrape_configs YAML 片段
  4. Operator reload Prometheus → 开始 scrape

用户不写 Prometheus 配置文件,完全声明式 ✅

F6. 28 个 Target Up — 实测 Prometheus 接收数据

查询 (走 ClusterIP 9090):

curl http://10.107.144.81:9090/api/v1/query?query=count(up==1)

Actual:

{"status":"success","data":{"resultType":"vector","result":[
  {"metric":{},"value":[1779778157,"28"]}
]}}

✅ 28 个 target 数据 OK

按 job 拆分:

JobUpDown说明
apiserver303 cp 都健康 ✅
kubelet1505 节点 × 3 endpoint (cadvisor/probes/kubelet) ✅
node-exporter50DaemonSet 全节点 ✅
kps-kube-prometheus-stack-prometheus20self-scrape ✅
kps-kube-prometheus-stack-alertmanager20✅
kube-state-metrics10✅
kube-controller-manager03⚠️
kube-scheduler03⚠️
kube-etcd03⚠️
kube-proxy05⚠️ Cilium 取代了 kube-proxy
coredns02⚠️

F7. ⚠️ 真坑 #1 — control plane metrics 默认绑 127.0.0.1

为啥 kube-controller-manager / kube-scheduler / kube-etcd metrics 都 down?

ps -ef | grep kube-scheduler | grep bind
# --bind-address=127.0.0.1

kubeadm 默认让 control plane 组件只绑本地回环,集群内 Pod 拉不到。

Fix (生产):

  • 改 /etc/kubernetes/manifests/kube-scheduler.yaml 的 --bind-address=0.0.0.0
  • 同样改 kube-controller-manager.yaml + etcd args(--listen-metrics-urls=http://0.0.0.0:2381)
  • 安全权衡: 监听公网必须配 NetworkPolicy 限制 source

学习场景: 这些 metrics down 不影响主要 dashboard,跳过

F8. ⚠️ 真坑 #2 — kube-proxy down (Cilium 替代了它)

Day 1 装 Cilium 时没 disable kube-proxy(我们用 Cilium 做 CNI 但 kube-proxy 还在跑),所以 kube-proxy 还在,但 ServiceMonitor 的 healthz endpoint 配的端口不对。

Fix:

  • 完全切换到 Cilium kube-proxy replacement(cilium status 里看)
  • 或者 patch ServiceMonitor 改 endpoint

学习场景: 不修

F9. Grafana 访问

G_NODEPORT=$(kubectl get svc kps-grafana -n monitoring -o jsonpath='{.spec.ports[0].nodePort}')
# 32380

打开 http://<m1-ip>:32380, admin / bootcamp

默认 dashboard (kube-prometheus-stack 自带 27 个):

  • Kubernetes / Compute Resources / Cluster
  • Kubernetes / Networking / Cluster
  • Node Exporter / USE Method / Node
  • Kubernetes / Persistent Volumes (Longhorn)
  • Prometheus / Overview

简历可写:

搭建 kube-prometheus-stack(Prometheus Operator + ServiceMonitor + 节点 + 资源 metrics + Grafana + 27 个 dashboard + AlertManager),集成 Longhorn PVC 持久化,28 个 target up,完整可观测基线


Day 6.G — Loki + Promtail 日志栈

G1. 装 grafana/loki-stack (subset: 仅 Loki + Promtail)

What (helm install with subset):

helm repo add grafana https://grafana.github.io/helm-charts
helm install loki grafana/loki-stack \
  --namespace monitoring \
  --set grafana.enabled=false \         # 已经有 kube-prometheus-stack Grafana
  --set prometheus.enabled=false \      # 同上
  --set loki.enabled=true \
  --set loki.persistence.enabled=true \
  --set loki.persistence.storageClassName=longhorn \
  --set loki.persistence.size=5Gi \
  --set promtail.enabled=true \
  --set 'loki.tolerations[0].operator=Exists' \
  --set 'promtail.tolerations[0].operator=Exists'

架构:

  • Loki: 单 Pod StatefulSet, 5Gi PVC (Longhorn),聚合 + 索引 + 查询
  • Promtail: DaemonSet 5 节点,每节点采本机 /var/log/pods/* 推送给 Loki

G2. Grafana datasource ConfigMap (sidecar 自动加载)

apiVersion: v1
kind: ConfigMap
metadata:
  name: loki-datasource
  namespace: monitoring
  labels:
    grafana_datasource: "1"      # ← sidecar 看这个 label 加载
data:
  loki-datasource.yaml: |
    apiVersion: 1
    datasources:
    - name: Loki
      type: loki
      access: proxy
      url: http://loki:3100
      isDefault: false

Why sidecar 模式神:

  • kube-prometheus-stack 默认启用 grafana 的 dashboard-sidecar 和 datasource-sidecar(2/3 + 1 容器)
  • 任何带 grafana_datasource: "1" label 的 ConfigMap → sidecar 自动 inject 到 Grafana
  • 不需要重启 Grafana,不需要点 UI 配置 — GitOps 友好

G3. Loki 数据流验证

Actual (验证 Loki API):

LOKI_CIP=$(kubectl get svc -n monitoring loki -o jsonpath='{.spec.clusterIP}')
curl -sS http://$LOKI_CIP:3100/loki/api/v1/labels | python3 -m json.tool
# 10 labels: app, component, container, filename, instance, job, namespace, node_name, pod, stream
curl -sS http://$LOKI_CIP:3100/loki/api/v1/label/namespace/values
# 7 namespace: cilium-demo, default, kube-system, kyverno, longhorn-system, monitoring, storage-demo

✅ 所有 namespace 都被采

G4. LogQL 查询语法实战

# 1. 标签筛选
curl -G "http://$LOKI_CIP:3100/loki/api/v1/query_range" \
  --data-urlencode 'query={namespace="kube-system"}' \
  --data-urlencode 'limit=3'

# 2. 含字符串过滤 (|=)
... --data-urlencode 'query={namespace=~".+"} |= "ERROR"'

# 3. 正则过滤 (|~)
... --data-urlencode 'query={pod=~"hpa-web.*"} |~ "(GET|POST) /"'

# 4. JSON 解析
... --data-urlencode 'query={app="grafana"} | json | level="error"'

实测 (含 ERROR 的日志):

{"status":"success","data":{"result":[
  {"stream":{"pod":"coredns-...","namespace":"kube-system",...},
   "values":[
     ["1779778462320328417", "[INFO] 10.244.3.131:46490 - 49902 \"AAAA IN loki.monitoring.svc.cluster.local. ...\" NOERROR ..."]
]}}}

✅ coredns 日志能 query

G5. Loki 跟 ES 区别 — 面试可讲

LokiElasticsearch
索引方式只索引 labels (低基数 metadata)全文倒排索引 (Lucene)
数据结构logs 直接压缩存 blob每个 token 都建索引
存储成本1/10 ES高
查询速度时间 + label 准, 全文慢全文极快
查询语法LogQL (类 PromQL)DSL (复杂)
场景K8s logs aggregation, 配 Prometheus 配套业务应用全文搜索

口诀:

  • Loki = 给 K8s 日志做 Prometheus(高基数 label 不要, 高基数标签 = 慢)
  • ES = 给业务做搜索引擎

G6. Grafana UI 完整使用

打开 http://<m1-ip>:32380,admin/bootcamp

  1. Datasources → 看到 2 个: Prometheus (默认), Loki (sidecar 自动加)
  2. Explore → 选 Loki → 输入 {namespace="storage-demo"} → 看 pg-0 日志
  3. Dashboards → kube-prometheus-stack 自带 27 个 (节点 / 工作负载 / 网络 / 存储)
  4. 自己加 dashboard: Import ID 13639 (Logs/App Logs) → 引用 Loki datasource → 立即可视化

G7. ⚠️ 真坑预警 — Loki 单实例 vs cluster 模式

我们装的 chart 默认 single-binary mode (loki 1.x),所有组件在 1 个 Pod:

  • Ingester (写入)
  • Distributor (前置路由)
  • Querier (查询)
  • Compactor (合并)
  • Query Frontend (查询入口)
  • Index Gateway

Pro: 简单,1 Pod 搞定 Con: 不能水平扩(吞吐受限),挂了所有日志读写都断

生产模式 (Loki 2.0+ Cluster):

  • helm chart grafana/loki (非 loki-stack)
  • 拆成 microservices,每组件 N 副本
  • 用对象存储 (S3 / MinIO) 替代 PVC,支持水平扩

学习场景 single-binary 够,生产看流量决定

G8. 整体观测三剑客联动

┌────────────────────────────────────────────────────┐
│  Grafana (Web UI, NodePort 32380, admin/bootcamp)  │
│         ↓ 查询                                     │
│  ┌────────────────┐  ┌────────────────┐            │
│  │  Prometheus    │  │  Loki          │            │
│  │  (metrics)     │  │  (logs)        │            │
│  └────────────────┘  └────────────────┘            │
│         ↑ scrape           ↑ push                  │
│  ┌────────────────┐  ┌────────────────┐            │
│  │  ServiceMonitor│  │  Promtail DS   │            │
│  │  (13 个)       │  │  (5 节点)      │            │
│  └────────────────┘  └────────────────┘            │
│         ↑                  ↑                       │
│  集群所有 Pod + Service     /var/log/pods/         │
└────────────────────────────────────────────────────┘

简历可写:

搭建集群观测三剑客:

  • Prometheus (kube-prometheus-stack 27 dashboard + 28 target up)
  • Loki + Promtail (5 节点全部日志聚合, LogQL 查询)
  • AlertManager (告警路由, 待配)
  • Grafana 统一 UI (NodePort 32380, datasource sidecar 自动加 Loki) 全栈 PVC 持久化(Longhorn 5+10+2=17G)

11. Day 6 总结 — 调度 + 观测 + 修正

模块状态关键产出
A 修 Day 2 遗留✅cp-2/cp-3 加 audit-policy, 3 cp 完全对称
B 调度速通 mini-book✅5 阶段调度器 / 4 种亲和 / topology / taint
C 节点+Pod 亲和✅4 demos: ssd-only / preferred / spread-zone / one-per-node
D Topology+Taint✅3 demos: maxSkew / taint / toleration
E HPA✅metrics-server + 压测 1→2 副本 30s 内
F kube-prometheus-stack✅Prometheus + Grafana + AlertManager + 28 target up
G Loki + Promtail✅5 节点采集 + LogQL + Grafana sidecar datasource

新增可观测端口:

  • Grafana: http://<m1-ip>:32380 (admin/bootcamp)
  • Prometheus: ClusterIP 10.107.144.81:9090 (NodePort 30090,但 hostNetwork 没绑 — 走 port-forward)
  • Loki: ClusterIP 10.104.157.59:3100
  • Longhorn UI: http://<m1-ip>:31172 (Day 4 装)
  • Hubble UI: http://<m1-ip>:30527 (Day 4 装)

Day 6 集群留存 (后续 Day 复用):

  • metrics-server (HPA 前置)
  • kube-prometheus-stack 11 Pods
  • Loki + 5 Promtail DaemonSet
  • 节点 zone label (zone-a/b/c) — 后续多 zone 演示直接用

99. 当前进度

  • [x] Day 6.A 修 Day 2 遗留 (3 cp audit 对称)
  • [x] Day 6.B 调度速通 mini-book
  • [x] Day 6.C 亲和性 (4 demos)
  • [x] Day 6.D Topology + Taint (3 demos)
  • [x] Day 6.E HPA + metrics-server (压测 1→2 30s)
  • [x] Day 6.F kube-prometheus-stack (28 target up)
  • [x] Day 6.G Loki + Promtail (5 节点 + 7 ns)
在 GitHub 上编辑此页
Prev
Day 5: Volume Expansion + 安全主线
Next
Day 7: Harbor + ArgoCD + Cilium Service Mesh