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 节点
NodeCR → 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 个必要条件:
- SC 设了
allowVolumeExpansion: true(Longhorn 默认开) - CSI driver 实现了 ControllerExpandVolume + NodeExpandVolume(Longhorn 有)
- 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 / overlayfs | vdb1 (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 验证
| Before | After | |
|---|---|---|
| 集群总量 | 125 GB | 634 GB |
| 可调度 | ~125 GB | 585 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 个命令:
| # | 命令 | 结果 |
|---|---|---|
| 1 | kv get pod | ✓ pg-0 列出 |
| 2 | kv get configmap | ✓ kube-root-ca.crt 列出 |
| 3 | kv get secret | ✗ Forbidden: cannot list "secrets" |
| 4 | kv get pod -n default | ✗ Forbidden(跨 ns) |
| 5 | kv get nodes | ✗ Forbidden ... "at the cluster scope" |
| 6 | kv run nginx --image=nginx | ✗ Forbidden: cannot create "pods" |
| 7 | kv auth can-i get secrets | no |
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。
调试套路:
kubectl auth can-i <verb> <resource> -n <ns> --as=<user>看能不能kubectl get rolebinding,clusterrolebinding -A -o wide | grep <sa-name>看 SA 绑了哪些 Rolekubectl get role <name> -n <ns> -o yaml看 rules 缺哪条- 仍不通 → audit log 找
authorization.k8s.io/decision: forbid
D.5 真坑:RBAC 错配能锁死自己
最常见两种翻车:
- 删了 cluster-admin ClusterRoleBinding —— 想做 cleanup,把
kubeadm:cluster-admins删了,自己的 kubeconfig 立刻 Forbidden。修法:用system:mastersGroup 的客户端证书(admin.conf)救场,因为这个 Group 绕过 RBAC。 - 改
system:nodesGroup 的 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 处:
command加--encryption-provider-config=/etc/kubernetes/encryption-config.yamlvolumeMounts把这个文件挂进容器volumes加 hostPath
kubelet 检测到 manifest 改了会自动重启 apiserver。
滚动策略(关键):
- 先 patch cp-1 → 等
kube-apiserver-k8s-cp-1Running 1/1(约 80s) - 验证集群 API 仍可访问(per-node HAProxy 把流量路由到任意可用 apiserver)
- 再 patch cp-2,等 Ready
- 最后 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 留了个雷今天才炸。
修法两步:
- 立刻:patch 脚本换用
--tls-private-key-file这种任何 apiserver 必有的 arg 作锚点 - 后续:把 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 密钥 |
| 性能影响 | 0 | Secret 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 | 命中 | 实际 |
|---|---|---|---|
| 1 | Deployment 无 app.kubernetes.io/name | require-app-label | 拒:Resource must set label 'app.kubernetes.io/name' |
| 2 | Pod image: nginx:latest | disallow-latest-tag | 拒:Image tag must not be 'latest' or empty |
| 3 | Pod 没 resources.requests | require-resources | 拒:Container must set resources.requests.cpu / memory |
| 4 | Pod 全合规 | (无) | 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 流量观测。