AI Infra 训练营
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • CI/CD 实战(MySQL+Go+Vue)
  • HPA/Ingress/Hubble 实战
  • 面试速查 + 真实踩坑
  • Day 0 · 新手接管 Runbook
  • 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 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • CI/CD 实战(MySQL+Go+Vue)
  • HPA/Ingress/Hubble 实战
  • 面试速查 + 真实踩坑
  • Day 0 · 新手接管 Runbook
  • 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 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
  • Day 0 · 环境与硬件

    • Day 0 新手现场接管 Runbook:先看懂,再动手
    • 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:Longhorn 存储 + Cilium 二探(Hubble / NetworkPolicy / L7)
    • Day 5:PVC 在线扩容 + K8s 安全基线(RBAC / PSA / Secret 加密 / Kyverno)
    • Day 6:调度策略 + Prometheus / Loki 观测栈
    • Day 7:Harbor 私有镜像 + ArgoCD GitOps + Cilium WireGuard
  • Week 2:制品 + GitOps + AI Infra + 综合

    • Day 8:k3s 单节点 + NVIDIA Device Plugin + vLLM 跑 Qwen2.5-3B
    • Day 8(attempt 1):跨 WAN GPU 加入主集群(失败复盘)
    • Day 8:AlertManager 真接入 + PrometheusRule 实战
    • Day 8:集群内 CI 闭环 — Gitea + Jenkins + Kaniko
    • Day 9:Triton 多框架推理 + DCGM 跨集群可观测 + vLLM 实测
    • Day 10:MIG 硬切片 + AWQ 量化 + HPA Custom Metrics
    • Day 11:AI 业务端到端 —— chainlit + GitOps + 跨 WAN vLLM
    • Day 12:灾难恢复 + 生产事故注入
    • Day 13:LLM Operator + 多集群联邦 + Ambient Mesh + RAG
    • Day 14:CKA / CKS 真题演练 + 14 天技术栈横向汇总

Day 5:PVC 在线扩容 + K8s 安全基线(RBAC / PSA / Secret 加密 / Kyverno)

接上 Day 4 的 Longhorn 存储底座,今天一边把 PVC 在线扩到 10G、给每节点加二盘,一边把 K8s 安全主线一次过完:RBAC 最小权限、Pod Security Admission、Secret at-rest 加密、Kyverno 策略代码化。

整篇 6 个常见坑:

  • PVC patch 后 df 还是旧容量 → Longhorn block 扩了但 filesystem resize 等 Pod 重启
  • 并行改 5 节点 Node CR → Longhorn admission webhook 偶发 10s 超时
  • patch apiserver static manifest 用 --audit-policy-file 作锚点 → cp-2/cp-3 上根本没这条
  • Secret 默认只 base64,etcd 直查就是明文,rewrite 后才真加密
  • kubectl apply 装 Kyverno → CRD annotation > 256KB 报错,要 --server-side
  • Kyverno webhook Pod Running 后还要 30-60s warmup,第一个 apply 必 timeout

按 A → F 6 个阶段走。入场状态:5 节点 Longhorn 全 Ready,单节点单盘(~25G 可调度),集群总量 ~125G,storage-demo ns 跑 pg-0 StatefulSet(PVC 5Gi),audit 只配过 cp-1,所有 ns 没打 PSA label。


A. PVC 在线扩容:5Gi → 10Gi

PVC online expansion 的执行链:

patch PVC.spec.resources.requests.storage
  → external-resizer sidecar → CSI ControllerExpandVolume
  → Longhorn 把 block device 扩到新 size
  → CSI NodeExpandVolume 通知 kubelet
  → kubelet 在挂载点跑 resize2fs / xfs_growfs
  → df 看到新容量 / PVC.status.capacity 同步

3 个必要条件:

  1. SC 设了 allowVolumeExpansion: true(Longhorn 默认开)
  2. CSI driver 实现了 ControllerExpandVolume + NodeExpandVolume(Longhorn 有)
  3. Pod 在线挂载或卸载 —— Longhorn 1.5+ 支持 online,但 ext4 / xfs 的 resize 触发点在 mount 时,实际还是要重启 Pod

A.1 patch + 验证

kubectl exec -n storage-demo pg-0 -- df -h /var/lib/postgresql/data
# /dev/longhorn/pvc-...   4.8G   46M  4.8G   1%

kubectl patch pvc pgdata-pg-0 -n storage-demo --type=merge \
  -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'

立刻看 PVC:

spec.resources.requests.storage: 10Gi   <- 改了
status.capacity:                  5Gi   <- 还没改
Longhorn Volume.spec.size: 10737418240  <- 后端已接到
PVC condition: FileSystemResizePending
  message: "Waiting for user to (re-)start a pod to finish file system resize"

Longhorn 的 block 层已经扩了,但 filesystem 没 resize。重启 Pod 让 kubelet 在 mount 时跑 resize2fs:

kubectl delete pod pg-0 -n storage-demo
kubectl wait --for=condition=ready pod pg-0 -n storage-demo --timeout=120s
kubectl exec pg-0 -n storage-demo -- df -h /var/lib/postgresql/data
# /dev/longhorn/pvc-...   9.8G  128M  9.7G   1%

kubectl exec pg-0 -n storage-demo -- psql -U postgres -d lab -c 'SELECT count(*) FROM bootcamp;'
# 7  ← 数据无损

A.2 真坑:PVC 缩容会丢数据

PVC 只能扩,不能缩。几乎所有 CSI 都不实现 ControllerExpand 的负方向 —— filesystem 在 block 缩容前必须 resize2fs -M 先收缩,但内核没有安全可靠的 online shrink 实现,强行做会丢尾部块。

要"缩"的真姿势:建新小 PVC → 应用层 dump 数据 → restore 到新 PVC → 删老 PVC。没捷径。


B. Longhorn 加二盘:节点存储扩容

Day 4 装 Longhorn 时每节点只用 /var/lib/longhorn(vda 系统盘下),可调度 ~25G。vdb 那块 100G 数据盘已经挂在 /var/lib/containerd,用同一个 fs 的子目录给 Longhorn 加二盘,节点容量直接翻倍:

路径用途文件系统
/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/...containerd image / overlayfsvdb1 (ext4, 100G)
/var/lib/containerd/longhorn-extra/Longhorn 第二个 disk同一个 vdb1

两者目录分离不冲突,共享 fs 配额。

不能直接 kubectl edit(会丢已有 default disk),用 Python 拼 spec:

import json, sys
node = json.load(sys.stdin)
node["spec"]["disks"]["extra-disk-vdb"] = {
    "path": "/var/lib/containerd/longhorn-extra",
    "allowScheduling": True,
    "storageReserved": 1073741824,
    "diskDriver": "",
    "tags": ["extra"],
}
print(json.dumps(node))
for n in m1 m2 m3 m4 m5; do
  ssh $n 'mkdir -p /var/lib/containerd/longhorn-extra'
  kubectl get node.longhorn.io $n -n longhorn-system -o json \
    | python3 /tmp/add-disk.py \
    | kubectl replace -f -
done

B.1 真坑:admission webhook 10s 超时

5 节点并行 patch 时:

Error: failed calling webhook "mutator.longhorn.io":
  Post "https://longhorn-admission-webhook.longhorn-system.svc:9502/v1/webhook/mutation?timeout=10s":
  context deadline exceeded

Longhorn 的 mutator / validator Service 后面有 5 个 manager Pod,但 webhook 的 timeoutSeconds 默认 10s,5 个并发 patch 偶发超时。修法 3 选 1:节点串行、重试一次、调大 timeoutSeconds。

普适经验:admission webhook 是 apiserver 同步阻塞调用,webhook 慢 = apiserver 慢。生产 webhook 应做到「轻量 + 短超时 + 多副本 + failurePolicy 选好」。

B.2 验证

BeforeAfter
集群总量125 GB634 GB
可调度~125 GB585 GB
3 副本可用~42 GB~195 GB

老 Volume 不会自动均衡到新 disk —— Longhorn replica 创建时就定死 disk,迁移要走 replicaAutoBalance 或手动 evict。新 Volume 会在两块 disk 间按 anti-affinity 调度,要强制进新 disk 给 SC 加 parameters.diskSelector: "extra" 走 tag 匹配。


C. 安全速通:5 大支柱定位

① AuthN      谁?         cert / OIDC / ServiceAccount
② AuthZ      能做啥?     RBAC / ABAC                  ← D
③ Admission  准入控制    PSA / Kyverno / Gatekeeper   ← E G
④ Encryption in-transit  TLS
              at-rest    etcd 加密                    ← F
⑤ Audit      日志(Day 2 已配)

Day 5 打 ②③④,OIDC 和 audit 留后续 / 已有。

4 个高频内置 ClusterRole:cluster-admin(集群 root,admin.conf 用,生产严控)/ admin(ns 内全权限,不能改 Roles)/ edit(改资源不能改 Roles)/ view(只读,默认不含 Secret)。

最危险的 Group system:masters:任何认证用户绑了它就是 cluster-admin(绕过 RBAC 决策),kubeadm admin.conf 证书 O=system:masters,所以 admin.conf = root 凭证。

PSA、Secret 加密 provider、Kyverno vs Gatekeeper 三个对比表挪到本篇末尾「面试常见题」里,避免重复。下面直接开打实战。


D. RBAC 实战:只读 viewer SA

场景:新成员入职,只允许看 storage-demo 的 Pod / ConfigMap / 工作负载,不允许看 Secret,不允许跨 ns,不允许写。

D.1 4 个 manifest

apiVersion: v1
kind: ServiceAccount
metadata: {name: demo-viewer, namespace: storage-demo}
---
# K8s 1.24+ SA 不再自动建 Secret token,要手动建
apiVersion: v1
kind: Secret
metadata:
  name: demo-viewer-token
  namespace: storage-demo
  annotations: {kubernetes.io/service-account.name: demo-viewer}
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: {name: pod-cm-reader, namespace: storage-demo}
rules:
- apiGroups: ['']
  resources: ['pods', 'pods/log', 'configmaps']
  verbs: ['get', 'list', 'watch']
- apiGroups: ['apps']
  resources: ['statefulsets', 'deployments']
  verbs: ['get', 'list', 'watch']
# 故意不给 secrets / 不给 create/update/delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: {name: demo-viewer-can-read, namespace: storage-demo}
subjects:
- {kind: ServiceAccount, name: demo-viewer, namespace: storage-demo}
roleRef: {apiGroup: rbac.authorization.k8s.io, kind: Role, name: pod-cm-reader}

为什么不直接用内置 view ClusterRole:view 包含很多其他资源(networkpolicies / ingresses / hpa / ...),范围太大;自己写 Role 更精确,体现最小权限。

D.2 生成 kubeconfig

TOKEN=$(kubectl get secret demo-viewer-token -n storage-demo -o jsonpath='{.data.token}' | base64 -d)
CA=$(kubectl get secret demo-viewer-token -n storage-demo -o jsonpath='{.data.ca\.crt}')
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')

cat > viewer.kubeconfig <<EOF
apiVersion: v1
kind: Config
clusters:
- {name: bootcamp, cluster: {server: $APISERVER, certificate-authority-data: $CA}}
contexts:
- {name: viewer@bootcamp, context: {cluster: bootcamp, namespace: storage-demo, user: demo-viewer}}
current-context: viewer@bootcamp
users:
- {name: demo-viewer, user: {token: $TOKEN}}
EOF

发给新员工,export KUBECONFIG=viewer.kubeconfig 即可。

D.3 7 场景验证

alias kv='kubectl --kubeconfig=viewer.kubeconfig',跑 7 个命令:

#命令结果
1kv get pod✓ pg-0 列出
2kv get configmap✓ kube-root-ca.crt 列出
3kv get secret✗ Forbidden: cannot list "secrets"
4kv get pod -n default✗ Forbidden(跨 ns)
5kv get nodes✗ Forbidden ... "at the cluster scope"
6kv run nginx --image=nginx✗ Forbidden: cannot create "pods"
7kv auth can-i get secretsno

Forbidden 消息提示出 resource / API group / namespace 三要素:User "system:serviceaccount:storage-demo:demo-viewer" cannot create resource "pods" in API group "" in the namespace "storage-demo",直接告诉你少了哪条 Role rule。

D.4 kubectl auth can-i 是 RBAC 调试金锤子

kubectl auth can-i create deployments --as=system:serviceaccount:storage-demo:demo-viewer -n storage-demo
# no

走 K8s SubjectAccessReview API,不真发请求,只问"我能不能"。CI/CD pipeline 也能用它做前置检查 fail-fast。

调试套路:

  1. kubectl auth can-i <verb> <resource> -n <ns> --as=<user> 看能不能
  2. kubectl get rolebinding,clusterrolebinding -A -o wide | grep <sa-name> 看 SA 绑了哪些 Role
  3. kubectl get role <name> -n <ns> -o yaml 看 rules 缺哪条
  4. 仍不通 → audit log 找 authorization.k8s.io/decision: forbid

D.5 真坑:RBAC 错配能锁死自己

最常见两种翻车:

  • 删了 cluster-admin ClusterRoleBinding —— 想做 cleanup,把 kubeadm:cluster-admins 删了,自己的 kubeconfig 立刻 Forbidden。修法:用 system:masters Group 的客户端证书(admin.conf)救场,因为这个 Group 绕过 RBAC。
  • 改 system:nodes Group 的 ClusterRole —— kubelet 用这个身份连 apiserver,一改可能整集群 NotReady。

预防:所有 RBAC 改动先在测试集群验证;生产改前 kubectl get clusterrole,clusterrolebinding -o yaml > backup.yaml 留底;确保至少一条 admin.conf 在手。


E. PSA restricted:3 场景看拦谁

kubectl create ns psa-test
kubectl label ns psa-test \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=v1.30 \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/audit=restricted

enforce-version: v1.30 是 pin 规则版本,避免 K8s 升级时 PSA 规则变严把现有 Pod 全炸。

E.1 场景 1:privileged: true 直接拒

spec:
  containers:
  - name: c
    image: nginx:1.27-alpine
    securityContext: {privileged: true}
Error from server (Forbidden): pods "bad-priv" is forbidden:
  violates PodSecurity "restricted:v1.30":
  - privileged (must not set securityContext.privileged=true)
  - allowPrivilegeEscalation != false
  - unrestricted capabilities (must set securityContext.capabilities.drop=["ALL"])
  - runAsNonRoot != true
  - seccompProfile (must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")

一次性列 5 项违规,错误消息非常友好。

E.2 场景 2:默认 nginx(无 securityContext)也拒

spec:
  containers:
  - {name: c, image: nginx:1.27-alpine}
violates PodSecurity "restricted:v1.30":
  - allowPrivilegeEscalation != false
  - unrestricted capabilities
  - runAsNonRoot != true
  - seccompProfile

99% 的现成 docker 镜像(nginx / redis / mysql / postgres)默认 root + 不显式 drop capabilities + 没 seccomp,直接 apply 到 restricted ns 全炸。PSA 落地的最大阻力不是配置,是业务镜像要先改造。

E.3 场景 3:合规 Pod —— 5 项全设

spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 65534
    seccompProfile: {type: RuntimeDefault}
  containers:
  - name: c
    image: nginx:1.27-alpine
    command: ['sleep', '300']         # nginx 默认要写 /var/log/nginx,跟 readOnly 冲突
    securityContext:
      allowPrivilegeEscalation: false
      runAsNonRoot: true
      runAsUser: 65534
      readOnlyRootFilesystem: true
      capabilities: {drop: [ALL]}

good-compliant created,1/1 Running。

E.4 渐进落地策略

直接上 enforce: restricted 把存量业务全炸,推荐路径:

第 1 周:  warn=restricted + audit=restricted          (观察)
第 2-4 周:根据 audit log 改业务镜像 + Helm chart
第 5 周:  enforce=baseline + warn=restricted          (baseline 兜底)
第 N 周:  enforce=restricted

warn + audit 两个 label 是 PSA 最好的工具 —— 给业务团队 N 周缓冲期,不要 surprise enforce。


F. Secret aescbc 加密:3 CP 滚动 + etcd 验证密文

F.1 加密前:etcd 直查就是明文

kubectl create secret generic super-password -n storage-demo \
  --from-literal=password=MyP@ssw0rd_BeforeEncryption \
  --from-literal=api_key=sk_live_1234567890abcdef

# 通过 etcd static pod 绕过 apiserver 直读 etcd
kubectl exec -n kube-system etcd-k8s-cp-1 -- sh -c "ETCDCTL_API=3 etcdctl \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  get /registry/secrets/storage-demo/super-password"

输出(节选):

api_key
sk_live_1234567890abcdef
password
MyP@ssw0rd_BeforeEncryption
Opaque

password 和 api_key 完整明文。任何能拿到 etcd 数据库 / snapshot backup 的人都能 grep 出来。

F.2 写 EncryptionConfiguration

KEY=$(head -c 32 /dev/urandom | base64)   # 256-bit AES key

cat > /etc/kubernetes/encryption-config.yaml <<EOF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: [secrets]
  providers:
  - aescbc:
      keys:
      - {name: key1, secret: ${KEY}}
  - identity: {}    # 兜底,允许读旧 unencrypted Secret
EOF
chmod 600 /etc/kubernetes/encryption-config.yaml

# 3 个 cp 都要同一份
scp /etc/kubernetes/encryption-config.yaml m2:/etc/kubernetes/
scp /etc/kubernetes/encryption-config.yaml m3:/etc/kubernetes/
ssh m2 'chmod 600 /etc/kubernetes/encryption-config.yaml'
ssh m3 'chmod 600 /etc/kubernetes/encryption-config.yaml'

providers 顺序很关键:

  • 第一个用于 write —— 新 Secret 用 aescbc 加密写
  • 所有按顺序尝试 read(先 aescbc,解不出试 identity 兜底明文)
  • 密钥轮转:新 key 放第一(write 用新)+ 老 key 第二(read 兼容)+ rewrite Secret + 删老 key

F.3 patch apiserver static pod(3 CP 滚动)

apiserver 是 kubeadm 跑的 static pod,manifest 在 /etc/kubernetes/manifests/kube-apiserver.yaml。要加 3 处:

  1. command 加 --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
  2. volumeMounts 把这个文件挂进容器
  3. volumes 加 hostPath

kubelet 检测到 manifest 改了会自动重启 apiserver。

滚动策略(关键):

  1. 先 patch cp-1 → 等 kube-apiserver-k8s-cp-1 Running 1/1(约 80s)
  2. 验证集群 API 仍可访问(per-node HAProxy 把流量路由到任意可用 apiserver)
  3. 再 patch cp-2,等 Ready
  4. 最后 patch cp-3

绝对不能 3 cp 同时改 —— manifest 写错,3 个 apiserver 同时炸 = 集群整个不可达。

F.4 真坑:cp-2/cp-3 上 audit 锚点不存在

第一版 patch 脚本用 --audit-policy-file 这行作 sed 插入锚点,cp-1 上跑通了。换到 cp-2:

AssertionError: audit-policy-file arg not found

回查 Day 2 笔记 —— audit log 只配了 cp-1,cp-2/cp-3 没同步。HA 控制面节点应该完全对称,Day 2 留了个雷今天才炸。

修法两步:

  1. 立刻:patch 脚本换用 --tls-private-key-file 这种任何 apiserver 必有的 arg 作锚点
  2. 后续:把 audit 配置同步到 cp-2/cp-3(本 Day 不展开,记 TODO)

教训:改 HA 集群前先 verify 实际状态。kubectl get pod -n kube-system kube-apiserver-* -o yaml | grep <arg> 三个 apiserver 都看一遍再写脚本,不要假设对称。

F.5 Rewrite 所有 Secret 让加密生效

EncryptionConfiguration 启用后,只有新写入的 Secret 被加密,已有 Secret 还是明文。要让加密对存量生效:

kubectl get secrets -A -o json | kubectl replace -f -

每条 replace 经过 apiserver → 触发 write 路径 → aescbc 加密 → 写入 etcd。

F.6 加密后:etcd 直查全乱码

kubectl exec -n kube-system etcd-k8s-cp-1 -- sh -c "ETCDCTL_API=3 etcdctl ... \
  get /registry/secrets/storage-demo/super-password"
k8s:enc:aescbc:v1:key1:%OVP
UR=.
a!*)
;Ofi-&g
4G}H+

前缀 k8s:enc:aescbc:v1:key1: 标识加密算法和 key name,后面全是 binary 密文,grep 不到 MyP@ssw0rd 任何字样。

apiserver 解密路径正常:kubectl get secret super-password -o jsonpath='{.data.password}' | base64 -d 仍能拿到 MyP@ssw0rd_BeforeEncryption。

F.7 生产关键点

加密前加密后
kubectl get secret明文明文(apiserver 解密)
etcdctl 直查明文k8s:enc:aescbc:v1:key1: + 密文
谁能解任何 etcd 读权限者必须有 encryption-config.yaml 密钥
性能影响0Secret CRUD +5-10% latency

密钥管理:别提交 Git,别误进对象存储 backup,公有云走 KMS provider。加密只防 etcd 泄漏,要防应用越权访问 Secret 必须配 audit 监控谁 get secrets。


G. Kyverno:3 条 ClusterPolicy + PolicyReport

G.1 装 Kyverno v1.13

kubectl apply --server-side --force-conflicts \
  -f https://github.com/kyverno/kyverno/releases/download/v1.13.2/install.yaml

必须用 --server-side。普通 kubectl apply 会把整份 yaml 塞到 last-applied-configuration annotation,Kyverno CRD 太大触发 metadata.annotations: Too long: must have at most 262144 bytes。SSA(K8s 1.16+)字段所有权由 controller name 管理,不写 last-applied annotation,绕过 256KB 限制。cert-manager / Istio / Linkerd 大 CRD 都吃过这个亏。

装完起 4 个 controller:admission / background / cleanup / reports。

G.2 3 条 ClusterPolicy

---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: {name: require-app-label}
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-label
    match: {any: [{resources: {kinds: [Deployment, StatefulSet]}}]}
    validate:
      message: "Resource must set label 'app.kubernetes.io/name'"
      pattern:
        metadata:
          labels: {app.kubernetes.io/name: '?*'}
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: {name: disallow-latest-tag}
spec:
  validationFailureAction: Enforce
  rules:
  - name: require-image-tag
    match: {any: [{resources: {kinds: [Pod]}}]}
    validate:
      message: "Image tag must not be 'latest' or empty"
      pattern:
        spec:
          containers:
          - image: "!*:latest & *:*"
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: {name: require-resources}
spec:
  validationFailureAction: Enforce
  rules:
  - name: require-cpu-mem
    match: {any: [{resources: {kinds: [Pod]}}]}
    validate:
      message: "Container must set resources.requests.cpu / memory"
      pattern:
        spec:
          containers:
          - resources:
              requests: {cpu: '?*', memory: '?*'}

Kyverno pattern 语法:'?*' 任意非空字符串、*:* 含冒号、!*:latest 不以 latest 结尾、& AND、| OR。

G.3 4 场景测试

#Manifest命中实际
1Deployment 无 app.kubernetes.io/namerequire-app-label拒:Resource must set label 'app.kubernetes.io/name'
2Pod image: nginx:latestdisallow-latest-tag拒:Image tag must not be 'latest' or empty
3Pod 没 resources.requestsrequire-resources拒:Container must set resources.requests.cpu / memory
4Pod 全合规(无)created → Running

错误消息会标出哪条 policy 的哪个 rule 在哪个字段路径失败:

require-app-label:
  check-label: 'validation error: ...
    rule check-label failed at path /metadata/labels/'

G.4 PolicyReport:全集群合规快照

Kyverno 后台 controller 持续扫描存量资源(包括 policy 上线之前就存在的),kubectl get policyreport -A 输出每个资源的 Pass / Fail 计数。Fail 是违规但因为是 policy 上线前存量、admission 拦不到,只在 report 里标红。

适合「先 audit 后 enforce」的渐进策略:validationFailureAction: Audit 跑两周 → 看 PolicyReport 改完存量 → 切 Enforce。

G.5 真坑:Kyverno webhook startup race

Kyverno Pod Running 后立刻 apply policy / 测试 Pod,第一次大概率 context deadline exceeded —— webhook handler 起来后还要 30-60s 加载 policy + 初始化 cache。修法:等一会重试,生产调 Deployment 到 3 副本。

failurePolicy 二选一:Fail(安全,webhook 不可达 = admission 拒一切,但 Kyverno 挂 = K8s API 半死)或 Ignore(可用,webhook 不可达 = 放行,但 Kyverno 挂 = policy 失效)。折中:默认 Fail + 给 kube-system 写 PolicyException 豁免,避免控制面被自家 policy 锁死。


面试常见题

Q1:PVC 在线扩容的完整流程?为什么 patch 完 df 还是旧容量?

4 段:patch PVC requests.storage → external-resizer 调 CSI ControllerExpandVolume 扩 block → CSI NodeExpandVolume 通知 kubelet → kubelet 在挂载点跑 resize2fs / xfs_growfs。

df 没变是第 4 步没触发:block 层扩完后 PVC 状态变成 FileSystemResizePending。多数 CSI 的 NodeExpand 在 mount 时执行,要 kubectl delete pod 让控制器重建,kubelet 重新 mount 时 resize2fs 才跑。

深问:能不能缩? —— 几乎所有 CSI 都不实现缩容,内核没有安全可靠的 online shrink 实现,强行做丢尾部数据。真姿势是建新小 PVC + 应用层迁移。

Q2:RBAC 4 元组是什么?怎么调试 Forbidden?

  • Subject:User / Group / ServiceAccount
  • Verb:get / list / watch / create / update / patch / delete / deletecollection
  • Resource:pods / deployments / secrets / ...(subresource 单列 pods/log)
  • APIGroup:core 用 "",apps / rbac / networking 等单独标

加 Scope(Namespaced vs Cluster)就是完整决策维度。Forbidden 消息会把 resource / API group / namespace 三要素都告诉你,对照少哪个加哪个 Role rule。

调试金锤子:kubectl auth can-i <verb> <resource> -n <ns> --as=<user> —— 走 SubjectAccessReview API,不真发请求只问能不能。

最危险的 Group 是 system:masters,绑了它就是 cluster-admin(绕过 RBAC)。kubeadm admin.conf 证书 O=system:masters,admin.conf = 集群最后救命稻草。

Q3:PSA 三档分别拦什么?跟 PSP 区别?怎么落地?

  • privileged:啥都不拦
  • baseline:拦明显逃逸(hostNetwork / hostPID / hostIPC / hostPath / privileged: true / capabilities.add)
  • restricted:baseline + runAsNonRoot + allowPrivilegeEscalation: false + seccompProfile + capabilities.drop: [ALL] + readOnlyRootFilesystem

3 mode:enforce 拒 / warn kubectl 警告 / audit 写 audit log,可同时打。

跟 PSP 区别:PSP 集群级 CRD + 绑 SA 配置复杂、支持 mutate;PSA 是 ns label 三两字搞定,纯 validation 不 mutate(要 mutate 用 Kyverno)。K8s 1.25 PSP 被删 PSA 接班。

落地必须渐进:warn + audit 观察 → 改业务镜像 → enforce: baseline 兜底 → enforce: restricted。直接上 restricted 把 99% 现成 docker 镜像(默认 root + 无 seccomp)全炸。

Q4:K8s Secret at-rest 加密原理?密钥怎么轮转?

默认 Secret 在 etcd 就是 base64(不是加密)。修法:apiserver 配 EncryptionConfiguration + --encryption-provider-config。providers 数组有序,第一个用于 write,所有按顺序 read,最后 identity 兜底明文(允许读旧未加密数据)。

主流 provider:aescbc(AES-256-CBC + HMAC-SHA-256)/ aesgcm(AES-256-GCM)/ kms v2(云 KMS)。

启用后必须 kubectl get secrets -A -o json | kubectl replace -f - rewrite 所有 Secret —— 只对新写入生效,存量不会自动加密。

密钥轮转套路:新 key 放第一(write 用新)+ 老 key 第二(read 兼容)+ rewrite Secret + 删老 key。

验证密文:etcdctl 直读 /registry/secrets/<ns>/<name>,前缀 k8s:enc:aescbc:v1:key1: 加密成功。

实战坑:HA 集群 3 个 apiserver static manifest 必须对称配置,patch 前 verify 实际 args,别假设 cp-1 上有的 arg cp-2/cp-3 都有。

Q5:Kyverno vs OPA Gatekeeper 怎么选?

Kyverno YAML 语法 + 支持 mutate/generate/verifyImages + 学习 1 天,适合中小团队快速落地;Gatekeeper Rego DSL + 只 validate + 学习 1 周,适合已用 OPA 做 API gateway / Terraform / CI 想策略语法统一的大型企业。

部署关键点:

  • 装 Kyverno 用 kubectl apply --server-side --force-conflicts,大 CRD 会撞 256KB annotation 限制
  • failurePolicy:Fail 安全但 Kyverno 挂 = K8s API 半死,给关键 ns 写 PolicyException 豁免;Ignore 可用但 policy 失效
  • 落地走 validationFailureAction: Audit → PolicyReport 改存量 → 切 Enforce

下一步

Day 5 结束后集群具备:可扩 PVC + 节点存储、最小权限 RBAC、PSA restricted enforce、Secret aescbc 加密、Kyverno 3 条 ClusterPolicy。Day 6 进入网络策略和服务治理:CiliumNetworkPolicy L7 规则、Ingress + cert-manager 自动签证书、Hubble 流量观测。

在 GitHub 上编辑此页
Prev
Day 4:Longhorn 存储 + Cilium 二探(Hubble / NetworkPolicy / L7)
Next
Day 6:调度策略 + Prometheus / Loki 观测栈