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 4: Storage 主线 + Cilium 二探

目标 (双线并行):

  • 主线 (Storage): 装 Longhorn 分布式块存储,跑 StatefulSet (MySQL),做 Volume Snapshot + 节点宕机数据保留
  • 复线 (Cilium 二探): Day 1 我们只用 Cilium 当 CNI,今天启用 Hubble + NetworkPolicy + L7 Policy 耗时: 5-8 小时 风险: Longhorn 装错 disk 路径会写满 / iscsi 服务没启 PV 创建失败 / NetworkPolicy 一刀切会把自己的 Pod 全断掉

0. TL;DR (本 Day 7 节)

  1. A — Storage 现状 + 原理: 集群 0 SC 0 CSI 0 PV,从零搭。5 节点各 100G vdb 空闲盘,iscsi 全装。
  2. B — Longhorn 装: 准备 vdb → 挂 /var/lib/longhorn → 装 Longhorn → UI 暴露
  3. C — PVC + StatefulSet + Snapshot: MySQL StatefulSet,写数据,删 Pod 验持久,Volume Snapshot 备份/还原,drain 节点验副本迁移
  4. D — Cilium Hubble: 启用 hubble-relay + UI,看真实流量(L4 + L7)
  5. E — Cilium NetworkPolicy: default deny-all → 业务白名单逐步开,Hubble 反向验证哪些被拒
  6. F — Cilium L7 Policy (选做): 同一服务 GET 放 / POST 拦,Hubble 看 L7 协议解析

1. 学习目标 + 闭环输出

能秒答:

  • "PV / PVC / SC / CSI 是什么 chain?各自归属哪一层?"
  • "AccessMode RWO / ROX / RWX / RWOP 分别什么时候用?哪些 CSI 支持?"
  • "ReclaimPolicy Retain / Delete / Recycle 区别?"
  • "VolumeBindingMode Immediate vs WaitForFirstConsumer 区别?为什么 multi-zone 必须 wait?"
  • "Longhorn 跟 Ceph/Rook 选型差异?"
  • "为什么 StatefulSet 用 PVC 而 Deployment 一般不用?"
  • "Cilium Hubble 是怎么拿到流量数据的?跟 tcpdump 区别?"
  • "NetworkPolicy 默认是 allow-all 还是 deny-all?Cilium 的 Default Deny 怎么实现?"
  • "L7 Policy 跟 L4 Policy 差在哪里?Cilium 怎么解析 HTTP 而不破坏 TLS?"

能动手做:

  • 一键装 Longhorn,挂 PVC,跑 MySQL,做 backup + restore + 节点 drain
  • 默认所有 namespace 互相不通,逐步白名单开放,Hubble 看流量明细

产出物:

  • 一个生产可用的存储栈(SC + CSI + Snapshot + Backup)
  • 一份 default-deny 的网络策略基线 + Hubble UI
  • 简历可加: "搭建 Longhorn 分布式块存储 (5节点500G集群,3 副本),实现 PVC 动态分配 / Volume Snapshot / 跨节点容灾迁移"
  • 简历可加: "应用 Cilium NetworkPolicy 实现 default-deny,通过 Hubble 实时观测 + L7 规则下沉 HTTP 方法级访问控制"

2. Storage 原理速通(面试级 mini-book)

2.1 K8s 存储的 4 层栈

┌──────────────────────────────────────────────────────┐
│  Pod                                                 │
│    └─ volumes:                                       │
│         persistentVolumeClaim:                       │
│           claimName: my-pvc      ← 第 4 层: 业务声明 │
├──────────────────────────────────────────────────────┤
│  PVC (PersistentVolumeClaim) — 用户视角的"要"        │
│    spec: { accessModes, resources.requests.storage } │
│         ↑ 上层: 这是租户/业务的"声明"                │
│         ↓ 下层: 由 SC + provisioner 动态满足         │
├──────────────────────────────────────────────────────┤
│  SC (StorageClass) — 模板                            │
│    provisioner: driver.longhorn.io                   │
│    parameters: { numberOfReplicas: 3, ... }          │
│    reclaimPolicy / volumeBindingMode / allowExpand   │
│         ↑ admin 在系统层声明可用的"存储产品"         │
│         ↓ 触发 CSI 真创磁盘                          │
├──────────────────────────────────────────────────────┤
│  PV (PersistentVolume) — 集群级的"有"                │
│    capacity: 10Gi                                    │
│    csi: { driver: ..., volumeHandle: ... }           │
│         由 CSI provisioner 写入 etcd                 │
├──────────────────────────────────────────────────────┤
│  CSI Driver (DaemonSet + Deployment)                 │
│    - Controller plugin (Provision/Snapshot/Resize)   │
│    - Node plugin (Stage/Mount/Unmount/Unstage)       │
│         真正动磁盘的人                                │
├──────────────────────────────────────────────────────┤
│  存储后端 (本地盘 / SAN / 对象存储 / 分布式块)       │
└──────────────────────────────────────────────────────┘

链路记法: 「用户写 PVC → 找 SC → SC 调 CSI provisioner → 创 PV → 绑 PVC → 挂载到 Pod」一气呵成

2.2 PV 几个核心字段 — 全是面试题

字段选项含义
accessModesRWO (ReadWriteOnce)单节点读写(块存储天生这样)
ROX (ReadOnlyMany)多节点只读(快照 / 镜像分发)
RWX (ReadWriteMany)多节点读写(必须 NFS / CephFS / Longhorn-RWX)
RWOP (ReadWriteOncePod)单 Pod 读写(防止同节点多 Pod 抢)— K8s 1.22+
persistentVolumeReclaimPolicyRetainPVC 删了 PV 保留(数据安全)
DeletePVC 删了 PV 也删(动态分配默认)
Recycle已废弃(rm -rf 风险)
volumeBindingMode (SC 级)ImmediateSC 一旦看到 PVC 立刻 provision(可能选错 zone)
WaitForFirstConsumer等 Pod 调度时再 provision(本地盘 / multi-zone 必须)
allowVolumeExpansion (SC 级)true后续可以 kubectl edit pvc.spec.resources.requests.storage 扩容
false一旦创建大小不变

典型陷阱:

  • 本地盘 SC(local-path) 必须 WaitForFirstConsumer — 否则 PVC 在 node-A 创了,Pod 调度到 node-B,起不来
  • 生产 SC 必须 allowVolumeExpansion: true — 否则扩容只能"删了重建+恢复备份"
  • 默认 reclaimPolicy 是 Delete,数据安全场景应改 Retain 或 SC 配 reclaimPolicy: Retain

2.3 CSI 是什么 — 替代 in-tree volume driver

K8s 1.13 之前,所有存储 plugin (NFS / EBS / Ceph / GCE PD ...) 都在 K8s 代码里 (in-tree),每出一种新存储就要改 K8s 主分支 + 等版本发布 — 不可持续。

CSI (Container Storage Interface) 是一套 gRPC 接口规范,把"K8s 调用存储的接口"和"存储厂商实现"解耦:

K8s 内部组件                                CSI 厂商实现
├─ external-provisioner (sidecar) ─ gRPC ─→ Controller Service
│    (PVC 创建 → CreateVolume)              (向后端要 volume)
├─ external-attacher (sidecar) ────  ───→  Controller Service
│    (Pod 调度 → ControllerPublishVolume)   (attach to node)
├─ external-snapshotter (sidecar) ─  ───→  Controller Service
│    (VolumeSnapshot → CreateSnapshot)      
├─ external-resizer (sidecar) ─────  ───→  Controller Service
└─ Node Plugin (DaemonSet, kubelet) ─────→  Node Service
     (kubelet 启 Pod → NodeStageVolume     (mount filesystem)
      → NodeMountVolume)

每个 CSI driver 部署模式: 1 个 Controller Deployment + 1 个 Node DaemonSet (每节点 1 个)。

2.4 主流 CSI/存储方案对比

方案类型部署难度性能适合场景
local-path-provisioner (rancher)本地盘★极快dev / 学习 / 无状态 cache
NFS SubdirNFS★慢, 单点共享配置 / 小文件
Longhorn (rancher)分布式块★★中中小集群 K8s 原生存储
Rook-Ceph分布式块/文件/对象★★★★高大规模生产,运维成熟
OpenEBS (Jiva/cStor/Mayastor)多种★★★中-高灵活,Mayastor NVMe-oF 高性能
EBS/PD CSI (云厂)云盘★高公有云
JuiceFS对象存储+元数据★★中, RWX大文件 / 共享存储

今天选 Longhorn 的理由:

  • 真分布式 + Volume Snapshot + Backup to S3 全套
  • 装最简单 (helm/manifest 一行),自带 UI
  • 5 节点 × 100G vdb 完美匹配
  • 比 Rook-Ceph 学习曲线低 50%,但概念是一样的(replica / volume / snapshot)

2.5 Snapshot + Backup 的工业意义

  • Snapshot = 卷的 COW 副本,同集群同存储后端内秒级
    • 用途: 升级前回滚点 / 误删恢复 / dev 环境克隆
    • 实现: 存储后端的 redirect-on-write (Longhorn) / copy-on-write (Ceph)
  • Backup = 卷数据导出到对象存储 (S3/MinIO),跨集群可恢复
    • 用途: DR (disaster recovery) / 异地容灾 / 长期归档
    • 工具: Velero (Pod+config 一起) / Longhorn 内置 (只卷)

K8s 提供 VolumeSnapshot CRD(by external-snapshotter,Longhorn 实现),把 snapshot 也变成"声明式资源" — 这是今天 Day 4.C 要演的。


3. Cilium 二探: 我们 Day 1 没用上的功能

Day 1 装 Cilium 只用了它的 CNI 功能(Pod 网络互通)。但 Cilium 真正的卖点是:

模块Day 1 用了?Day 4 启用
CNI (Pod 网络)✅—
kube-proxy 替代❌ (kube-proxy 还在跑)不动
Hubble (流量可观测)❌✅ D 节
NetworkPolicy (L3/L4 防火墙)❌✅ E 节
CiliumNetworkPolicy (L7 HTTP/gRPC)❌✅ F 节
Cilium Service Mesh (sidecar-less)❌后续 Day
Ingress Controller❌后续 Day
ClusterMesh (多集群)❌Day 13

Hubble 怎么拿到流量:

  • Cilium agent 用 eBPF 程序挂到内核 socket/TC hook 上(零 sidecar)
  • 每个 packet 经过 eBPF,字段 (src/dst pod / namespace / verdict / L7 method/path) 抽出来发到 ring buffer
  • hubble-relay 聚合所有节点的 events
  • Hubble UI / CLI 消费

比 tcpdump 强在哪:

  • 不是抓包,是已知是哪个 Pod(Cilium 知道 IP→endpoint→Pod 映射)
  • 支持 L7 (HTTP method/path/status, gRPC method, DNS query) 解析,前提是 policy 启用 L7
  • 性能影响小(eBPF 不复制 packet)

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

Day 4.A — Storage 现状 survey (2026-05-26)

A1. 集群存储现状

What: 探查 SC / CSI / PV / PVC 现状

kubectl get sc
kubectl get csidrivers
kubectl get pv,pvc -A
kubectl get crd | grep -iE "snapshot|volume"

Expected: 应全空(Day 0-3 没装存储) Actual:

sc           : No resources found
csidrivers   : No resources found
pv,pvc       : No resources found
snapshot CRD : 无

✅ 完全空白,从零开干

A2. 节点能力盘点(5 节点)

What: 各节点 iscsi + 数据盘

for h in m1 m2 m3 m4 m5; do
  ssh $h 'which iscsiadm; lsmod | grep iscsi; lsblk -d'
done

Actual:

节点iscsiadm系统盘 vda数据盘 vdb
m1 (cp-1)✅ /usr/sbin/iscsiadm30G100G
m2 (cp-2)✅30G100G
m3 (cp-3)✅30G100G
m4 (w-1)✅30G100G
m5 (w-2)✅30G100G

总容量: 5 × 100G = 500G;Longhorn 默认 3 副本 → 可用 ~166G

⚠️ 注意 iscsi_tcp 内核模块没加载(Longhorn 运行时会 modprobe,但 Ubuntu 22.04 默认未持久化 → 重启会丢)— B 段要 fix

Outcome: 硬件就绪,可装 Longhorn Lesson: 集群上 storage,先做 3 件事 survey — 系统盘剩余 / 数据盘存在 / iscsi 装没装(Longhorn/OpenEBS/Rook 都强依赖 open-iscsi)


Day 4.B — 装 Longhorn

B1. ⚠️ 真坑预警 #1 — vdb 不是空盘!别 mkfs!

What (差点犯的错): 原计划: 5 节点都有 100G vdb 空闲,直接 mkfs.xfs /dev/vdb 当 Longhorn 数据盘 Why 是大坑: lsblk -f /dev/vdb 显示

NAME   FSTYPE FSVER LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
vdb                                                                           
└─vdb1 ext4   1.0         8f306892-b7bd-489a-9955-387c4afeaf8e   90.1G     3% /var/lib/containerd

vdb1 已经被挂载到 /var/lib/containerd — 厂家预装,放 container 镜像存储。如果直接 mkfs.xfs /dev/vdb,会摧毁所有 container 镜像存储,集群所有 Pod 立刻炸。

Fix (调整方案):

  1. 不动 vdb
  2. Longhorn 数据用默认 /var/lib/longhorn (在 vda 系统盘上, 每节点 ~25G 可用)
  3. 总集群可用 ~125G, 3 副本下 ~42G — 学习足够

Lesson (Working Method #1 — Survey Before Coding 的活生生案例):

  • 任何动磁盘的操作之前,必须 lsblk -f 看是否已有文件系统/挂载
  • mkfs / wipefs / parted 都是不可逆操作,出错就丢数据
  • 厂家给的"空闲盘"不一定真空,可能挂在某个不显眼的目录

B2. 节点准备 — iscsi + nfs + 数据目录

What (5 节点):

# 1. 加载并持久化 iscsi_tcp 内核模块
modprobe iscsi_tcp
echo "iscsi_tcp" > /etc/modules-load.d/iscsi_tcp.conf
# 2. 启动 + enable iscsid (Longhorn 依赖 open-iscsi)
systemctl enable --now iscsid
# 3. nfs-common (Longhorn RWX 模式要)
apt-get install -y nfs-common
# 4. 数据目录
mkdir -p /var/lib/longhorn
mkdir -p /var/lib/containerd/longhorn-extra  # 后续扩盘演示用

Why 每步关键:

  • iscsi_tcp: Longhorn engine 用 iSCSI 把 volume export 给本机 kubelet (从 Longhorn 视角,挂载是"iSCSI 挂载",即使数据是本地的)
  • /etc/modules-load.d/iscsi_tcp.conf: 重启后自动加载(Ubuntu 22.04 不持久化默认 modprobe)
  • iscsid (open-iscsi 服务):iSCSI initiator daemon,Longhorn 的 mount 流程必经
  • nfs-common: Longhorn RWX (多读多写) PV 用 NFSv4,客户端要装 nfs-common

Expected: 5 节点 lsmod | grep iscsi_tcp 有, systemctl is-active iscsid = activeActual: ✅ 5 节点全过 Outcome: 前置就绪,可装 Longhorn Lesson: Longhorn 装失败 99% 是因为 iscsi/nfs 没装 — official docs 第一行就强调,但好多人跳过

B3. 装 Volume Snapshot CRDs(必须先装,不然 Longhorn 装上后无法做快照)

What:

# CRDs (upstream kube-csi-snapshotter v8.2.0)
for f in client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml \
         client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml \
         client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml; do
  kubectl apply -f "https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.2.0/${f}"
done
# 集群级 snapshot-controller (官方 reference)
for f in deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml \
         deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml; do
  kubectl apply -f "https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/v8.2.0/${f}"
done

Why 分两件事:

  1. CRDs: 定义 VolumeSnapshot/VolumeSnapshotContent/VolumeSnapshotClass 类型 — 集群级,跨 CSI driver 通用
  2. snapshot-controller: 协调 VolumeSnapshot → VolumeSnapshotContent 的 controller — 上游单独实现,所有 CSI driver 共享一个,不是各 driver 自己实现
  3. driver 内的 external-snapshotter sidecar: 由 CSI driver (Longhorn) 自己起,实际调 CSI gRPC CreateSnapshot

Expected: 3 个 CRD + snapshot-controller Deployment 起 Actual:

volumesnapshotclasses.snapshot.storage.k8s.io created
volumesnapshotcontents.snapshot.storage.k8s.io created
volumesnapshots.snapshot.storage.k8s.io created
deployment.apps/snapshot-controller created

Outcome: snapshot 链路前置就绪 Lesson (面试可讲):

  • CRD vs controller 分离设计 — CRD 是契约(K8s 集群级共识),controller 是实现(可有多个 vendor)
  • 这是 K8s 早期"in-tree volume plugin"的反面教材的成功转化 — 在线扩展存储能力

B4. 装 Longhorn v1.7.2 manifest

What:

curl -sS https://raw.githubusercontent.com/longhorn/longhorn/v1.7.2/deploy/longhorn.yaml -o /tmp/longhorn.yaml
kubectl apply -f /tmp/longhorn.yaml
  • manifest 5047 行, 40 个 kind (CRDs + ServiceAccount + Role + Service + DaemonSet + Deployment + ...)
  • 镜像源 longhornio/*:v1.7.2 (Docker Hub)

B5. ⚠️ 真坑 #2 — Longhorn 默认不容忍 control-plane taint

What:

kubectl get pods -n longhorn-system -o wide
# 只在 k8s-w-1, k8s-w-2 有 Pod, cp-1/2/3 完全没 longhorn-manager

Why:

  • Longhorn manifest 没有 tolerations
  • 我们集群 cp-1/2/3 有 node-role.kubernetes.io/control-plane:NoSchedule taint
  • 默认 Longhorn 只跑在 worker 节点 — 这是官方推荐(生产隔离)
  • 但学习/小集群应该 5 节点全跑,才能演示 3 副本

Fix:

TOL='[{"operator":"Exists"}]'   # 万能 toleration
kubectl patch ds longhorn-manager -n longhorn-system \
  -p "{\"spec\":{\"template\":{\"spec\":{\"tolerations\":$TOL}}}}"
kubectl patch deploy longhorn-driver-deployer -n longhorn-system -p "..."
kubectl patch deploy longhorn-ui -n longhorn-system -p "..."
# csi-plugin DS 也要,因为 csi-plugin Deployment 是 driver-deployer 动态创建的
kubectl patch ds longhorn-csi-plugin -n longhorn-system -p "..."

Expected: 等几分钟,5 节点都有 longhorn-manager + longhorn-csi-plugin Actual:

NAME                READY   ALLOWSCHEDULING   SCHEDULABLE   AGE
k8s-cp-1   True    true              True          4m
k8s-cp-2   True    true              True          3m
k8s-cp-3   True    true              True          4m
k8s-w-1    True    true              True          3m
k8s-w-2    True    true              True          4m

✅ 5 节点全 Schedulable

Lesson:

  • 装第三方 manifest 前先 grep tolerations 确认是否覆盖 control plane
  • HA 集群 (3 master) 应该把 storage 分散到所有节点,提高 replica 分布;但生产单纯只算容量 worker 节点,master 可不参与
  • {"operator":"Exists"} 是万能容忍,生产不建议,应该明确写每条 taint

B6. ⚠️ 真坑 #3 — csi-plugin race condition

What: patch toleration 后, cp-1 / cp-3 的 longhorn-csi-plugin CrashLoopBackOff,而 cp-2 OK

longhorn-csi-plugin-sxd87   1/3   CrashLoopBackOff   3 (19s ago)   81s   k8s-cp-3
longhorn-csi-plugin-vvq66   1/3   CrashLoopBackOff   3 (17s ago)   81s   k8s-cp-1

日志:

time="..." level=fatal msg="Error starting CSI manager:
  Failed to initialize Longhorn API client:
  Get \"http://longhorn-backend:9500/v1\": context deadline exceeded"

Why: cp-1/cp-3 上的 csi-plugin Pod 在 longhorn-backend (Manager) 还没就绪时启动 — 启动时连接 Service 超时,fatal 退出,进入 CrashLoopBackOff exponential backoff (40s+)

是个典型 startup ordering race:

  • DaemonSet 一旦 Pod schedulable 立即起 Pod
  • 但 longhorn-backend Service 后端 Pod (longhorn-manager) 在那台节点还没 Ready
  • 加上 cp-1/cp-3 刚加 toleration → 启动慢半拍

Fix (delete 让 DS 重建 — backend 已 ready):

kubectl delete pod -n longhorn-system longhorn-csi-plugin-vvq66 longhorn-csi-plugin-sxd87

Expected: 重建后能连 backend,3/3 Running Actual: ✅

longhorn-csi-plugin-nxjpt   3/3   Running   1 (19s ago)   31s   k8s-cp-3
longhorn-csi-plugin-qfcbl   3/3   Running   1 (19s ago)   31s   k8s-cp-1

验证网络通(从 cp-1 上拉 backend API):

kubectl run test-net --rm -i --restart=Never --image=curlimages/curl \
  --overrides='{"spec":{"nodeSelector":{"kubernetes.io/hostname":"k8s-cp-1"}, ...}}' \
  -- curl http://longhorn-backend.longhorn-system:9500/v1
# 返回完整 API 索引 JSON ✅

Lesson (面试可讲):

  • CrashLoopBackOff 不一定是代码 bug,常常是 startup ordering — 生产 controller 应该 graceful retry + healthz 不通过表达"未就绪"
  • 自愈机制: K8s DS/Deployment 会自动 restart,exponential backoff 30s/60s/120s — 短时网络抖动一般 5 分钟内自愈
  • 如果业务 Pod 也有这种问题,加 initContainers 等依赖就绪,或者用 dependsOn 服务网格(Linkerd / Istio)

B7. Longhorn 默认 SC + UI 暴露

What:

kubectl get sc
# longhorn (default)   driver.longhorn.io   Delete   Immediate   true   (auto-created)

# UI 暴露 NodePort
kubectl patch svc longhorn-frontend -n longhorn-system -p '{"spec":{"type":"NodePort"}}'

Actual:

  • SC longhorn 自动建好,已是 default-class
  • UI: http://<node-ip>:31172(NodePort)
  • 32 个 Pod 全 Running

Outcome 总览:

项状态
longhorn-manager (DaemonSet 5节点)✅ Running 5/5
longhorn-csi-plugin (DaemonSet 5节点)✅ Running 5/5
csi-{attacher,provisioner,resizer,snapshotter}✅ Running 各 3 副本
longhorn-ui (Deployment 2副本)✅ Running, NodePort 31172
StorageClass longhorn✅ default
5 nodes.longhorn.io Schedulable✅ 总 ~125G
VolumeSnapshot CRDs✅ 上游 v8.2.0
snapshot-controller (kube-system)✅ 2 副本

Lesson (本节核心收获):

  • Longhorn 装好不等于可用,要校验:SC 默认 + 节点全 Schedulable + CSI Plugin DS 覆盖
  • SC reclaimPolicy: Delete 是默认 — 生产数据敏感场景应 Retain,删 PVC 数据保留,人工再 release

Day 4.C — PVC + StatefulSet + Volume Snapshot 真演

C1. 设计 — Postgres StatefulSet,触及 Day 4 所有要点

3 个 Litmus Test(Working Method #2):

  1. 数据持久化: 删 Pod 重建,数据还在
  2. Snapshot/Restore: 创快照 → 改数据 → 从快照恢复 → 数据是快照时刻的
  3. 节点故障容忍: drain replica 节点,Pod 不动,数据无损

资源清单 (/root/storage-demo/):

文件内容
00-snapshot-class.yamlVolumeSnapshotClass (driver=longhorn, type=snap)
10-postgres.yamlnamespace + headless Service + StatefulSet (Postgres 16-alpine, PVC template 5Gi)
20-snapshot.yamlVolumeSnapshot pg-snap-1 引用 PVC
30-restore.yaml新 PVC pg-restored, dataSource=pg-snap-1
40-restore-pod.yaml临时 Pod 挂 pg-restored 验数据

关键设计选择:

  • 用 StatefulSet 而非 Deployment + PVC: StatefulSet 给每个 replica 独立 PVC (volumeClaimTemplates),Pod 重建时 PVC 不变
  • AccessMode RWO: Postgres 是单写,RWO 完美匹配
  • 加 tolerations: [{operator: Exists}]: 允许调度到 control plane (5 节点都能跑)
  • PVC 5Gi: 小起步,可以后续 expand 演示

C2. ⚠️ 真坑 #4 — 第一次 Pod attach 失败: engine-image 没全节点覆盖

What (失败现场): 首次 apply,PVC bound,但 Pod stuck ContainerCreating:

FailedAttachVolume: unable to attach volume pvc-... to k8s-cp-2:
  cannot attach volume because the data engine image longhornio/longhorn-engine:v1.7.2
  is not deployed on at least one of the replicas' nodes or the node that the volume
  is going to attach to

Why:

  • Pod 被调度到 cp-2,但 cp-2 上没有 engine-image-ei-* Pod(DaemonSet 也是没 toleration)
  • Longhorn 要求 attach 节点必须有 engine-image,否则无法启动 volume engine 进程

Fix 两步:

  1. 临时: patch DaemonSet engine-image-ei-51cc7b9c 加 toleration
  2. 持久: kubectl patch setting taint-toleration -n longhorn-system --type=merge -p '{"value":":NoSchedule"}' — Longhorn 系统组件全局生效,reconcile 时不会被覆盖

Expected after fix: engine-image Pod 在 5 节点全 Running Actual: ✅

engine-image-ei-...-2w2x9   1/1   Running   0   26s   k8s-cp-2
engine-image-ei-...-c8j58   1/1   Running   0   31s   k8s-cp-3
engine-image-ei-...-cpffg   1/1   Running   0   57s   k8s-w-1
engine-image-ei-...-tdnhd   1/1   Running   0   24s   k8s-cp-1
engine-image-ei-...-w5b7n   1/1   Running   0   57s   k8s-w-2

Lesson (面试可讲):

  • Longhorn 有 3 个 DaemonSet 必须每节点 (longhorn-manager / longhorn-csi-plugin / engine-image-ei-*)
  • 它们是分批 patch 的 — engine-image 是 Longhorn 自管 (CRD engineimages.longhorn.io),Longhorn controller 创建对应 DS
  • Setting 优于 直接 patch: Longhorn 的 controller 会自己 reconcile DS 配置,如果不通过 setting 改 toleration,Longhorn 下次会"修正"DS 配置覆盖你的 patch

C3. ⚠️ 真坑 #5 — Volume 调度选址是"创建时"决定的

What: 即使 engine-image 后来 5 节点都有,已经创建的 Volume 的 replicas 还是只在 w-1/w-2 — 因为 Volume 创建那一刻 cp-1/2/3 没 engine-image

只删 Pod 不行,因为 PVC 一旦 bind PV,replica 分布固定。必须删 PVC + Volume重建:

kubectl delete sts pg -n storage-demo
kubectl delete pvc pgdata-pg-0 -n storage-demo

Expected: 重新 apply 后,Volume 在 3 个节点 (anti-affinity) Actual:

Volume   state=attached   robustness=healthy   node=k8s-w-2
Replicas: 3 个,分布在 w-2, w-1, cp-3

✅ 跨 3 节点完美

Lesson (面试可讲):

  • 存储 plugin 的调度是 一次性的 —— PVC 一旦 Bound, 物理位置不变;但 K8s Pod 调度是持续的
  • 升级存储能力(新加节点 / 新加 disk)对新 Volume 生效;老 Volume 需要手动 migrate (Longhorn UI 里可以)
  • 这是为什么大集群必须前期统一 plan storage layout — 后期再补节点,老数据不会自动均衡

C4. 第一个 Litmus Test: 数据持久化(删 Pod 不丢)

What:

# 创 table + 写 2 行 (注意: SQL 字符串字面量必须用单引号, 双引号是 column identifier)
kubectl exec pg-0 -- psql -U postgres -d lab -c \
  'CREATE TABLE bootcamp(id serial primary key, msg text, ts timestamp default now());'
kubectl exec pg-0 -- psql -U postgres -d lab -c \
  "INSERT INTO bootcamp(msg) VALUES ('msg1'), ('msg2');"
kubectl exec pg-0 -- psql -U postgres -d lab -c 'SELECT * FROM bootcamp;'

# 删 Pod 让 StatefulSet 重建
kubectl delete pod pg-0 -n storage-demo
kubectl wait --for=condition=ready pod -n storage-demo pg-0 --timeout=120s

# 验数据
kubectl exec pg-0 -- psql -U postgres -d lab -c 'SELECT * FROM bootcamp;'

Expected: 重建后 2 行数据 + timestamp 不变 Actual:

 id |  msg  |             ts
----+-------+----------------------------
  1 | msg1  | 2026-05-26 05:00:36.521755
  2 | msg2  | 2026-05-26 05:00:36.521755
(2 rows)

✅ id + msg + ts 完全一致

Lesson (面试可讲):

  • StatefulSet 用 volumeClaimTemplates,每 replica 有独立 PVC,Pod 重建,PVC 不变
  • 反观 Deployment 用 PVC: Deployment 没有 stable PVC 绑定,重建可能换 PVC, 数据可能丢
  • 单 replica Postgres + PVC 是经典反模式生产里要 Patroni / pg-operator 做 HA,但PVC + StatefulSet 是 storage 持久性的最小验证

C5. 第二个 Litmus Test: Snapshot + Restore

What (4 步):

# 1. 创快照
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata: {name: pg-snap-1, namespace: storage-demo}
spec:
  volumeSnapshotClassName: longhorn-snapshot-class
  source: {persistentVolumeClaimName: pgdata-pg-0}
# 2. 等 READYTOUSE
kubectl get volumesnapshot -n storage-demo
# 8s 内 READYTOUSE=true

# 3. 再插 3 行 (snapshot 之后的数据)
kubectl exec pg-0 -- psql ... "INSERT ... 'AFTER snapshot row 1' ..."
# 现在 table 共 5 行

# 4. 从 snapshot 创新 PVC + 挂临时 Pod
apiVersion: v1
kind: PersistentVolumeClaim
metadata: {name: pg-restored, namespace: storage-demo}
spec:
  storageClassName: longhorn
  accessModes: [ReadWriteOnce]
  resources: {requests: {storage: 5Gi}}
  dataSource:
    name: pg-snap-1
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io

Expected:

  • 原 pg-0 : 5 行
  • 恢复 pg-restored: 2 行(snapshot 时刻)
  • 时间戳: 行 1/2 的 timestamp 一致(同一行,同一时刻写的)

Actual:

原 pg-0:                            行数: 5
恢复的 pg-verify-restored:          行数: 2
2 行的 timestamp:  2026-05-26 05:00:36.521755 (一模一样)

✅ Snapshot/Restore 完美

Lesson (面试可讲):

  • VolumeSnapshot 是声明式 — 用户写 YAML,K8s + CSI driver 接管所有底层逻辑
  • 创建 snapshot 不阻塞 IO — Longhorn 用 redirect-on-write,瞬时
  • 从 snapshot 恢复需要新建 PVC,不是 in-place restore — 这是为了让用户对比新旧数据
  • 应用层 backup 的位置 — 业务层 backup (mysqldump / pg_dump) 是 logic 一致性 (可能比 storage snapshot 更快/更慢但更小);存储层 snapshot 是 block 一致性 (适合大数据 / 不可中断业务)

C6. 第三个 Litmus Test: 节点 drain 不丢数据

What:

# 看 replica 分布
kubectl get replicas.longhorn.io -n longhorn-system -o jsonpath='...'
# pvc-6559...-r-2898289e  node=k8s-w-2   state=running
# pvc-6559...-r-3642e3b3  node=k8s-w-1   state=running
# pvc-6559...-r-3c97ecdf  node=k8s-cp-3  state=running

# drain cp-3 (一个 replica 在那, Pod 不在)
kubectl drain k8s-cp-3 --ignore-daemonsets --delete-emptydir-data --force --timeout=60s
sleep 20

# 看 Pod, 应仍 Running
kubectl get pod pg-0 -n storage-demo
# pg-0   1/1   Running   0   3m

# 看 replica 状态变化
kubectl get replicas.longhorn.io ... | grep pvc-6559
# r-2898289e  node=w-2   state=running
# r-3642e3b3  node=w-1   state=running
# r-3c97ecdf  node=cp-3  state=stopped  ← 关键!

Expected vs Actual:

ExpectedActual
Pod pg-0 状态Running, 不重启✅ Running, 不重启
cp-3 上 replicastopped (而非 deleted)✅ stopped
数据查询5 行✅ 5 行
写入新数据成功✅ INSERT 0 1

uncordon 后:

kubectl uncordon k8s-cp-3
sleep 30
# replica 自动 running

Lesson (面试核心):

  • Longhorn 区分"临时不可调度"和"永久故障":
    • drain (cordon): 节点还在 cluster, replica = stopped, 等节点回来 (默认 30 min replica-replenishment-wait-interval)
    • delete node 或长时间不通: replica 会被 evict, 在其他节点 rebuild
  • Pod 在 w-2,replica 在 cp-3, w-2 → cp-3 是正常 Longhorn 跨节点同步,但 attach 用的是 w-2 本地 replica(localhost engine)
  • HA 关键: 多 replica + 跨节点反亲和 + replica 自愈

C7. 关键发现总结

验证项结果代表的能力
PVC 动态分配✅ Longhorn driver 接管provision 自动化
3-node anti-affinity✅ w-1/w-2/cp-3HA 基础
数据持久化 (Pod 删)✅ ts 不变StatefulSet + PVC 语义
Snapshot 创建 (8s)✅ READYTOUSE=true备份点
Restore (snapshot 时刻)✅ 2 行 vs 5 行恢复能力
节点 drain Pod 不动✅ Running 不重启单节点故障容忍
Replica 自愈 (uncordon 后)✅ 30s 内 running自愈机制

简历可写:

搭建 Longhorn 分布式块存储 (5 节点, 500G 集群容量),实现 PVC 动态分配 / 3-副本跨节点反亲和 / VolumeSnapshot 秒级 COW 备份 / 节点 drain 数据无损迁移,与 K8s VolumeSnapshot CRD (上游 v8.2.0) + snapshot-controller 集成,生产形态完整.


Day 4.D — Cilium Hubble

D1. 装 cilium CLI + 当前状态盘点

What:

CILIUM_CLI_VERSION=v0.16.20
curl -L https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-amd64.tar.gz | tar -xz -C /usr/local/bin
cilium status

Actual (Day 1 装的 Cilium 1.16.5):

Cilium:             OK     (DS 5/5)
Operator:           OK     (1/1)
Envoy DaemonSet:    OK     (5/5)
Hubble Relay:       disabled    ← 待启用
ClusterMesh:        disabled
Cluster Pods: 43/45 managed by Cilium
Helm chart version: 1.16.5

注意:cilium-envoy DaemonSet 是 1.16+ 引入的 — 把 Envoy proxy 拆成独立 DS(以前嵌在 cilium-agent 里),为 L7 policy / Service Mesh 准备

D2. 启用 Hubble + Relay + UI(一行)

What:

cilium hubble enable --ui

Why 一句话原理:

  • Hubble = 节点级流量观察组件 (eBPF events 经 ring buffer → cilium-agent → gRPC)
  • Hubble Relay = 集群级聚合,把 5 节点的 hubble socket 合并成单一 stream(Hubble UI / CLI 连这一个)
  • Hubble UI = Web 图形化(看 service map + flow detail)

Expected:

Hubble Relay:   OK
hubble-relay    Deployment   1/1
hubble-ui       Deployment   1/1

Actual: ✅ 全 Running, 启动耗时 ~40s

3 个新 service:

Service端口用途
hubble-peer443cilium-agent → hubble peer 互发现 (mTLS)
hubble-relay80gRPC aggregator,CLI/UI 连这里
hubble-ui80Web 前端

D3. 暴露 UI + 装 hubble CLI

What:

# UI 暴露 NodePort
kubectl patch svc hubble-ui -n kube-system -p '{"spec":{"type":"NodePort"}}'
# → http://<node-ip>:30527

# 装 hubble CLI
HUBBLE_VERSION=v1.16.5
curl -L https://github.com/cilium/hubble/releases/download/${HUBBLE_VERSION}/hubble-linux-amd64.tar.gz | tar -xz -C /usr/local/bin

# port-forward relay
kubectl port-forward -n kube-system svc/hubble-relay 4245:80 &

# 检查
hubble status --server localhost:4245

Actual:

Healthcheck (via localhost:4245): Ok
Current/Max Flows: 20,475/20,475 (100.00%)     ← ring buffer 已满,新事件覆盖老的
Flows/s: 102.53
Connected Nodes: 5/5                            ← Relay 已连接所有 5 节点的 cilium-agent

✅ 每秒 102 个 flow 事件 — 这是集群默认基线流量(健康检查 / DNS / Calico-Cilium 控制面)

D4. 实测: 跨命名空间 HTTP 流量观察

What (制造测试流量):

# 在 cilium-demo ns 起 nginx
kubectl create ns cilium-demo
kubectl run nginx -n cilium-demo --image=nginx:1.27-alpine --port=80 --labels=app=nginx
kubectl expose pod nginx -n cilium-demo --port=80 --name=nginx-svc

# 从 pg-0 (storage-demo ns) 发 5 次 wget
for i in {1..5}; do
  kubectl exec -n storage-demo pg-0 -- wget -qO- --timeout=3 http://nginx-svc.cilium-demo
done

# 看 Hubble trace
hubble observe --server localhost:4245 --pod storage-demo/pg-0 --last 25

Actual (节选关键 flow):

storage-demo/pg-0:36099 (ID:32617) -> 169.254.20.10:53 (world) to-stack FORWARDED (UDP)
storage-demo/pg-0:36099 (ID:32617) <- 169.254.20.10:53 (world) to-endpoint FORWARDED (UDP)
                  ↑ DNS 查询: nginx-svc.cilium-demo, 走 node-local-dns (Day 1 配的!)

storage-demo/pg-0:59440 (ID:32617) -> cilium-demo/nginx:80 (ID:14519) to-overlay FORWARDED (TCP Flags: SYN)
storage-demo/pg-0:59440 (ID:32617) <- cilium-demo/nginx:80 (ID:14519) to-endpoint FORWARDED (TCP Flags: SYN, ACK)
storage-demo/pg-0:59440 (ID:32617) -> cilium-demo/nginx:80 (ID:14519) to-overlay FORWARDED (TCP Flags: ACK)
storage-demo/pg-0:59440 (ID:32617) -> cilium-demo/nginx:80 (ID:14519) to-overlay FORWARDED (TCP Flags: ACK, PSH)  ← HTTP request
storage-demo/pg-0:59440 (ID:32617) <- cilium-demo/nginx:80 (ID:14519) to-endpoint FORWARDED (TCP Flags: ACK, PSH)  ← HTTP response
storage-demo/pg-0:59440 (ID:32617) -> cilium-demo/nginx:80 (ID:14519) to-overlay FORWARDED (TCP Flags: ACK, FIN)   ← close

3 件秒答得清的事:

  1. Pod 身份明示: storage-demo/pg-0:59440 (ID:32617) — 不是裸 IP,是 ns/pod-name + 端口 + Cilium endpoint identity
  2. 完整 TCP 状态机: SYN → SYN+ACK → ACK → PSH → FIN — 比 tcpdump 还易读,因为带方向 (-> / <-) 和 verdict (FORWARDED / DROPPED)
  3. DNS / TCP 都抓: Pod 一次 wget 触发: 2 次 DNS UDP (A + AAAA) + 一次 TCP 完整连接 → Hubble 都记下

D5. drop 事件观察

What:

hubble observe --server localhost:4245 --type drop --last 5

Actual:

fe80::6c83:feff:fe2c:76 (ID:23732) <> ff02::2 (unknown) Unsupported L3 protocol DROPPED (ICMPv6 RouterSolicitation)

Lesson: Cilium 默认不开 IPv6,IPv6 router solicit 被 drop —正常但 noisy。生产关 IPv6 stack 或加 policy 静默

D6. UI 体验(NodePort 30527)

打开 http://<m1-ip>:30527:

  • 左侧 namespace 列表
  • 选 cilium-demo → 中间出现 Service Map (圆点 = endpoint,线 = flow)
  • 点 nginx pod → 右侧 detail 出 IP / labels / identity / 流量历史
  • 实时刷新(每秒接收 flows 流式更新)

Hubble UI 是面试演示的杀手锏 — 视觉化 service-to-service 调用图

D7. Hubble 的设计精髓 — 面试问"跟 tcpdump 区别"

维度tcpdumpCilium Hubble
数据源libpcap / AF_PACKETeBPF tracepoint at TC hooks
性能高 (内核到用户复制全包)低 (只导出 metadata, 不复制 payload)
身份只 IPns/pod/labels/identity (查 Cilium endpoint cache)
L7没有有 (HTTP method/path/status, gRPC, DNS, Kafka...)
集群级每节点单独抓Relay 聚合所有节点
实时 UI没有Hubble UI
历史tcpdump 写 pcap 文件ring buffer (默认 4096 events/cilium-agent)
TLS看不见加密内容同样看不见 (除非用 Cilium 拦截 TLS)

面试要点: Hubble 的 "性能 + 身份 + L7" 是 tcpdump 完全做不到的。但 tcpdump 仍然有用 — 抓完整 payload 做应用层 debug 时

D8. ⚠️ 真坑 #6 — ring buffer 溢出

观察到:

EVENTS LOST: HUBBLE_RING_BUFFER CPU(0) 1

连续 4 次出现。说明 cilium-agent 的 ring buffer 小,流量 102 flow/s 在淹没默认容量

Fix (生产):

helm upgrade cilium cilium/cilium -n kube-system --reuse-values \
  --set hubble.eventBufferCapacity=16384

学习场景: 不修也行,事件丢点不影响功能

Lesson:

  • Hubble 是 sampling observability,不保证 100% 抓全;真要审计必须配合外部 export (Hubble→OTEL→Loki/Tempo)
  • 跟 Prometheus 的 scrape 一样,是 eventually-complete 模型

Day 4.E — Cilium NetworkPolicy (default-deny + 白名单)

E1. 设计 — Zero Trust 网络的两步法

K8s NetworkPolicy 默认是 opt-in: 没 policy 时,所有 Pod 互通(网络层面是 trust-all)。

生产 Zero Trust 模式:

  1. 第 1 条 NetworkPolicy 在每 namespace 设 default-deny (ingress + egress 都拒)
  2. 后续 NetworkPolicy 逐个白名单 (来源 ns + 来源 pod label + 端口)

任何新部署的服务默认进不去 — 必须明确写一条 allow 才通

E2. Baseline — 当前 cilium-demo 无 policy,任何 Pod 都能访问

What: kubectl exec pg-0 -- wget nginx-svc.cilium-demoActual: 返回 nginx HTML ✅ 全通 (因为没 NetworkPolicy)

E3. Apply default-deny-all

What:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: cilium-demo
spec:
  podSelector: {}    # 空选择器 = 选中 ns 所有 pod
  policyTypes:
  - Ingress
  - Egress           # 既禁入又禁出

Why 这条 yaml 长得简单但语义强:

  • podSelector: {} = ns 内所有 pod
  • policyTypes: [Ingress, Egress] = 同时锁双向
  • 没有 ingress: / egress: 字段 = 默认 deny (空白名单)
  • 这一条 = "本 namespace 进入 Zero Trust 状态"

Expected: pg-0 → nginx 立即 timeout Actual:

wget: download timed out

✅

Hubble 看到的:

storage-demo/pg-0:59454 (ID:32617) <> cilium-demo/nginx:80 (ID:14519) policy-verdict:none INGRESS DENIED (TCP Flags: SYN)
storage-demo/pg-0:59454 (ID:32617) <> cilium-demo/nginx:80 (ID:14519) Policy denied DROPPED (TCP Flags: SYN)
  • policy-verdict:none INGRESS DENIED: Cilium 检查 ingress policy, 没匹配项 → deny
  • TCP SYN 被 drop, 客户端 wget timeout (3 次重试都没 ACK)

E4. 白名单: allow pg → nginx

What:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-pg-to-nginx
  namespace: cilium-demo
spec:
  podSelector:
    matchLabels: {app: nginx}
  policyTypes: [Ingress]
  ingress:
  - from:
    - namespaceSelector:
        matchLabels: {kubernetes.io/metadata.name: storage-demo}
      podSelector:
        matchLabels: {app: pg}
    ports:
    - {protocol: TCP, port: 80}

Why 3 个 selector 嵌套:

  • podSelector.matchLabels.app=nginx: 这条 policy 作用于 nginx pod
  • from.namespaceSelector + podSelector: 允许的来源是 (ns=storage-demo) ∩ (pod label app=pg)
  • kubernetes.io/metadata.name: K8s 1.21+ 自动给 ns 加的 built-in label,直接 select

Expected:

  • pg-0 → nginx 通
  • 其他 pod (app=other) → nginx 拦

Actual:

# pg-0 (app=pg)
kubectl exec -n storage-demo pg-0 -- wget -qO- http://nginx-svc.cilium-demo
→ <!DOCTYPE html><html>...  ✅

# other-tester (app=other,同 ns)
kubectl run other-tester -n storage-demo --image=alpine --labels='app=other' \
  --rm -i --restart=Never -- wget -qO- http://nginx-svc.cilium-demo
→ BLOCKED + wget: download timed out  ✅

Hubble 完整 verdict:

storage-demo/pg-0          → cilium-demo/nginx:80  policy-verdict:L3-L4 INGRESS ALLOWED
storage-demo/other-tester  → cilium-demo/nginx:80  policy-verdict:none  INGRESS DENIED
  • policy-verdict:L3-L4 INGRESS ALLOWED: 命中白名单, L3-L4 表示按 IP+端口 allow
  • policy-verdict:none: 没命中, default deny 生效

E5. 面试必答 — NetworkPolicy 的 4 个细节

  1. Cilium 兼容 K8s 原生 networking.k8s.io/v1 — 没用 CiliumNetworkPolicy 也能 work (我们这次用的就是原生)
  2. Cilium 比 calico/flannel 多 L7 / FQDN / DNS-aware 能力 (Day 4.F)
  3. namespaceSelector + podSelector 是 AND 关系(同一 from 项里),不是 OR
  4. egress 比 ingress 难写 — Pod 出去要 DNS / metadata service / kube-apiserver,常忘记开导致 Pod 莫名 hang;调试时 Hubble 看 egress DENIED 就一目了然

Day 4.F — Cilium L7 Policy (HTTP method)

F1. 设计 — 同一服务,GET 通 / POST 拦

继续上面的 nginx,要演示L4 policy 做不到 的事:

  • L4 policy 只能说"允许 80 端口",但80 端口里 GET/POST/DELETE 全平等
  • L7 policy 能说"只允许 HTTP GET",拦截写操作 — 真正的应用层防火墙

Why 这是 Cilium 的杀手锏:

  • 传统方案要 sidecar (Istio/Linkerd) 每 Pod 多一个 Envoy 进程
  • Cilium 用节点级 cilium-envoy DaemonSet (Day 1 装 1.16 已默认包含),每节点 1 个 Envoy → HTTP 流量被 Cilium TC hook 截流到这个 Envoy → 解析 + apply policy → 转发
  • 零 sidecar, 资源/运维成本远低于 Istio

F2. 删 K8s NetworkPolicy + Apply CiliumNetworkPolicy

What:

kubectl delete networkpolicy default-deny-all allow-pg-to-nginx -n cilium-demo
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-pg-get-only
  namespace: cilium-demo
spec:
  endpointSelector:
    matchLabels: {app: nginx}
  ingress:
  - fromEndpoints:
    - matchLabels:
        app: pg
        k8s:io.kubernetes.pod.namespace: storage-demo
    toPorts:
    - ports:
      - {port: '80', protocol: TCP}
      rules:
        http:
        - method: GET    # ← L7! 只允许 GET

Why 不能再用 K8s 原生 NetworkPolicy:

  • 原生 networking.k8s.io/v1 只支持 L3-L4 (ns/pod/IP/port)
  • 要 L7 必须用 vendor CRD (cilium.io/v2 或 security.istio.io/v1beta1 等)
  • 这是 Day 1 装 Cilium 不是 Calico 的理由之一

F3. 测 GET (应通) + POST (应拦)

Actual:

# GET
kubectl exec -n storage-demo pg-0 -- wget -qO- http://nginx-svc.cilium-demo/
→ <!DOCTYPE html><html><head>...  ✅ 通

# POST
kubectl exec -n storage-demo pg-0 -- sh -c 'apk add curl; curl -X POST http://nginx-svc.cilium-demo/ -d test'
→ Access denied  ✅ 拦

Hubble L7 verdict (hubble observe --type l7):

storage-demo/pg-0:59458 → cilium-demo/nginx:80  http-request FORWARDED (HTTP/1.1 GET http://nginx-svc.cilium-demo:80/)
storage-demo/pg-0:59460 → cilium-demo/nginx:80  http-request DROPPED   (HTTP/1.1 POST http://nginx-svc.cilium-demo/)

✅ Hubble 直接打印出 HTTP method + URL — 这是 L4 policy / tcpdump 永远做不到的

F4. 这套 demo 的工业意义

简历可写:

用 Cilium CiliumNetworkPolicy 实现 HTTP 方法级访问控制 (GET 放 / POST 拦),依托 cilium-envoy DaemonSet (sidecar-less proxy),Hubble UI 实时观察 L7 verdict — 比 Istio sidecar 方案节省 N 倍 Pod 数量,实现微服务粒度的应用层防火墙

生产用法:

  • 内部 admin API 路径 (/admin/*) 限制只允许来自 ops namespace
  • 数据库 metric 接口只允许 Prometheus pod 访问
  • 第三方回调 endpoint 只接收特定 IP + 特定 method
  • DNS-aware policy: 允许 Pod 出 egress 到 *.googleapis.com,拒其他公网 IP (Cilium FQDN policy)

F5. ⚠️ 真坑 #7 — L7 policy 需要 Envoy 介入,L4 routing 会变化

Why 注意: 启用 L7 policy 后,流量被 redirected 到 cilium-envoy:

  • Service IP → Envoy → 真实 backend Pod
  • TCP 状态机看起来不一样 (多了 envoy 这跳)
  • 性能 vs 纯 L4 大约 +10-20% latency
  • 启用 L7 后,TCP-level metric 也走 Envoy, Pod 视角的 src IP 可能变 envoy 的

对策:

  • 只在需要 L7 控制的 namespace 用 L7 policy
  • L4 流量(大多数情况)走原生 NetworkPolicy 或 Cilium L4 policy,不触发 Envoy redirect
  • 性能敏感场景做 benchmark,对比 L4-only vs L7 启用的差距

11. Day 4 总结 — 完成度

模块状态关键能力
A Storage survey✅探查 5 节点容量
B Longhorn 装 + 3 坑✅5节点 ~125G 存储池,SC=longhorn
C PVC + StatefulSet✅Postgres 持久化 + Snapshot 恢复 + drain 测试
D Hubble✅5 节点 102 flow/s 实时观察,UI NodePort 30527
E NetworkPolicy✅default-deny + 白名单,Hubble 见 verdict
F L7 Policy✅GET/POST 区分拦截,HTTP-aware visibility

集群留存:

  • StorageClass longhorn (default)
  • VolumeSnapshotClass longhorn-snapshot-class
  • Hubble Relay + UI 在跑
  • cilium-demo + storage-demo namespace
  • pg-0 PostgreSQL (有 6 行测试数据)
  • pg-snap-1 VolumeSnapshot + pg-restored PVC
  • CiliumNetworkPolicy allow-pg-get-only 生效

两个杀手锏:

  1. Storage 链路完整: Longhorn + VolumeSnapshot + drain 容忍 — 生产 ready
  2. 网络可观测 + 可控: Hubble 看流量 + NetworkPolicy 控访问 + L7 HTTP 方法级 — 接近 Service Mesh 但无 sidecar 开销

99. 当前进度

  • [x] Day 4.A Storage survey 完成
  • [x] Day 4.B Longhorn 安装(踩 vdb 复用 + taint-toleration + race condition 三坑)
  • [x] Day 4.C PVC + StatefulSet + Snapshot/Restore + drain 全套通过
  • [x] Day 4.D Hubble 启用 + flow 端到端追踪
  • [x] Day 4.E NetworkPolicy default-deny + 白名单 + Hubble verdict
  • [x] Day 4.F L7 Policy GET 通/POST 拦 + Hubble HTTP visibility
在 GitHub 上编辑此页
Prev
Day 3: CRD + Operator (kubebuilder 从 0 写)
Next
Day 5: Volume Expansion + 安全主线