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 节)
- A — Storage 现状 + 原理: 集群 0 SC 0 CSI 0 PV,从零搭。5 节点各 100G vdb 空闲盘,iscsi 全装。
- B — Longhorn 装: 准备 vdb → 挂
/var/lib/longhorn→ 装 Longhorn → UI 暴露 - C — PVC + StatefulSet + Snapshot: MySQL StatefulSet,写数据,删 Pod 验持久,Volume Snapshot 备份/还原,drain 节点验副本迁移
- D — Cilium Hubble: 启用 hubble-relay + UI,看真实流量(L4 + L7)
- E — Cilium NetworkPolicy: default deny-all → 业务白名单逐步开,Hubble 反向验证哪些被拒
- 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 几个核心字段 — 全是面试题
| 字段 | 选项 | 含义 |
|---|---|---|
accessModes | RWO (ReadWriteOnce) | 单节点读写(块存储天生这样) |
| ROX (ReadOnlyMany) | 多节点只读(快照 / 镜像分发) | |
| RWX (ReadWriteMany) | 多节点读写(必须 NFS / CephFS / Longhorn-RWX) | |
| RWOP (ReadWriteOncePod) | 单 Pod 读写(防止同节点多 Pod 抢)— K8s 1.22+ | |
persistentVolumeReclaimPolicy | Retain | PVC 删了 PV 保留(数据安全) |
| Delete | PVC 删了 PV 也删(动态分配默认) | |
| Recycle | 已废弃(rm -rf 风险) | |
volumeBindingMode (SC 级) | Immediate | SC 一旦看到 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 Subdir | NFS | ★ | 慢, 单点 | 共享配置 / 小文件 |
| 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/iscsiadm | 30G | 100G |
| m2 (cp-2) | ✅ | 30G | 100G |
| m3 (cp-3) | ✅ | 30G | 100G |
| m4 (w-1) | ✅ | 30G | 100G |
| m5 (w-2) | ✅ | 30G | 100G |
总容量: 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 (调整方案):
- 不动 vdb
- Longhorn 数据用默认
/var/lib/longhorn(在 vda 系统盘上, 每节点 ~25G 可用) - 总集群可用 ~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 分两件事:
- CRDs: 定义
VolumeSnapshot/VolumeSnapshotContent/VolumeSnapshotClass类型 — 集群级,跨 CSI driver 通用 - snapshot-controller: 协调 VolumeSnapshot → VolumeSnapshotContent 的 controller — 上游单独实现,所有 CSI driver 共享一个,不是各 driver 自己实现
- 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:NoScheduletaint - 默认 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):
- 数据持久化: 删 Pod 重建,数据还在
- Snapshot/Restore: 创快照 → 改数据 → 从快照恢复 → 数据是快照时刻的
- 节点故障容忍: drain replica 节点,Pod 不动,数据无损
资源清单 (/root/storage-demo/):
| 文件 | 内容 |
|---|---|
00-snapshot-class.yaml | VolumeSnapshotClass (driver=longhorn, type=snap) |
10-postgres.yaml | namespace + headless Service + StatefulSet (Postgres 16-alpine, PVC template 5Gi) |
20-snapshot.yaml | VolumeSnapshot 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 两步:
- 临时: patch DaemonSet
engine-image-ei-51cc7b9c加 toleration - 持久:
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:
| Expected | Actual | |
|---|---|---|
| Pod pg-0 状态 | Running, 不重启 | ✅ Running, 不重启 |
| cp-3 上 replica | stopped (而非 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-3 | HA 基础 |
| 数据持久化 (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-peer | 443 | cilium-agent → hubble peer 互发现 (mTLS) |
| hubble-relay | 80 | gRPC aggregator,CLI/UI 连这里 |
| hubble-ui | 80 | Web 前端 |
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 件秒答得清的事:
- Pod 身份明示:
storage-demo/pg-0:59440 (ID:32617)— 不是裸 IP,是 ns/pod-name + 端口 + Cilium endpoint identity - 完整 TCP 状态机: SYN → SYN+ACK → ACK → PSH → FIN — 比 tcpdump 还易读,因为带方向 (-> / <-) 和 verdict (FORWARDED / DROPPED)
- 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 区别"
| 维度 | tcpdump | Cilium Hubble |
|---|---|---|
| 数据源 | libpcap / AF_PACKET | eBPF tracepoint at TC hooks |
| 性能 | 高 (内核到用户复制全包) | 低 (只导出 metadata, 不复制 payload) |
| 身份 | 只 IP | ns/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 条 NetworkPolicy 在每 namespace 设 default-deny (ingress + egress 都拒)
- 后续 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 内所有 podpolicyTypes: [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 podfrom.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+端口 allowpolicy-verdict:none: 没命中, default deny 生效
E5. 面试必答 — NetworkPolicy 的 4 个细节
- Cilium 兼容 K8s 原生
networking.k8s.io/v1— 没用 CiliumNetworkPolicy 也能 work (我们这次用的就是原生) - Cilium 比 calico/flannel 多 L7 / FQDN / DNS-aware 能力 (Day 4.F)
- namespaceSelector + podSelector 是 AND 关系(同一
from项里),不是 OR - 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生效
两个杀手锏:
- Storage 链路完整: Longhorn + VolumeSnapshot + drain 容忍 — 生产 ready
- 网络可观测 + 可控: 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