我还记得那个周二的凌晨三点,手机震得像要散架一样。那是某家电商公司刚上云没多久,他们的DBA老张在测试环境顺手输了条 kubectl delete pod --all,手抖了一下,把回车键敲在了生产环境的 context 上。
那一瞬间,所有线上 MySQL 容器同时掉线,订单系统直接瘫痪。虽然五分钟后自动拉起,但那半小时的宕机损失,足够让 CTO 当场辞职。
这件事之后,公司技术委员会达成了一个共识:Kubernetes 多租户不能靠自觉,必须靠铁腕策略。 我们今天就来聊聊,怎么用最硬核的 RBAC(基于角色的访问控制)和 LimitRange(资源限制)把生产环境护得严严实实,顺便把“熊孩子”和“崩溃的 Pod”都关进笼子里。
第一层防线:Namespace 是最基本的隔离墙
很多初学者觉得 Namespace 只是给资源“分个类”,是个心理安慰。大错特错。在 Kubernetes 里,Namespace 就是安全边界。跨 Namespace 的资源,默认是无法直接访问的,除非你显式地开了特权通道。
我们先来看一个典型的多租户环境规划。假设我们有三个团队:frontend(前端开发)、backend(后端开发)、ops(运维)。他们的生产资源必须物理隔离,逻辑隔离。
apiVersion: v1
kind: Namespace
metadata:
name: prod-backend
labels:
env: production
team: backend
---
apiVersion: v1
kind: Namespace
metadata:
name: prod-frontend
labels:
env: production
team: frontend
---
apiVersion: v1
kind: Namespace
metadata:
name: ops-monitoring
labels:
env: production
team: ops
注意看,我们在 Namespace 上加了 labels。这不仅仅是为了好看,而是为了后续 RBAC 规则编写时,可以用 labelSelector 进行精确匹配。比如,运维团队只能监控带有 team: ops 或者 env: production 标签的 Namespace 里的资源,而不是整个集群。
在这里我要插入一个真实的教训。之前有家公司,所有团队共用一个 default Namespace 里的 prod 前缀资源。结果后端团队为了调试,写了一个 ClusterRole 允许查看所有 Secret。这个 Role 被绑定了 prod-* 的 Namespace。后来前端团队往自己的 Namespace 里塞了一个 AWS Access Key 的 Secret,结果后端的一个实习生用 kubectl get secret -n prod-frontend 直接拿到了 Key,转手卖给了竞争对手……虽然现在听着像段子,但这在早期云原生时代是真实发生过的。
所以,严格隔离 Namespace,是第一步,也是最重要的一步。
第二层防线:RBAC——给每个人戴上紧箍咒
RBAC 的核心逻辑其实很简单:谁(Subject) 在 什么资源(Resource) 上,做了 什么操作(Verb)。
针对刚才那个误删生产库的悲剧,我们需要建立一套细粒度的 RBAC 体系。
1. 拒绝“管理员”心态
首先,除非是 SRE 负责人,否则任何人都不应该拥有 ClusterAdmin 权限。我们来看看如何创建一个仅允许操作 prod-backend Namespace 的 Role。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: backend-dev-role
namespace: prod-backend
rules:
- apiGroups: ["", "apps", "extensions"]
resources: ["pods", "deployments", "services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# 注意:这里没有 delete 和 deletecollection
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"] # 只能读,不能改
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: backend-dev-binding
namespace: prod-backend
subjects:
- kind: Group
name: backend-team # 这对应你们身份管理系统里的 group
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: backend-dev-role
apiGroup: rbac.authorization.k8s.io
你看,这个 Role 里我们故意漏掉了 delete。这意味着,后端开发人员无论怎么手抖,都删不掉任何资源。如果他们真的需要扩容(比如双11),必须提 Ticket,由运维用 update 或者 patch 来修改副本数,或者审批后执行 kubectl scale。
2. 更狠的一招:禁止所有 Namespace 级别的删除操作
为了彻底杜绝“老张式”悲剧,我们可以创建一个 ClusterRole,禁止任何人在生产 Namespace 里执行 delete 操作,无论他们有什么其他权限。
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: no-delete-in-prod
rules:
- apiGroups: ["", "apps", "extensions", "batch"]
resources: ["pods", "deployments", "jobs", "cronjobs", "services", "configmaps", "secrets"]
verbs: ["delete", "deletecollection"]
resourceNames: [] # 空表示不限制具体资源名,针对所有资源
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: deny-delete-prod-namespaces
subjects:
- kind: Group
name: system:authenticated # 所有认证用户
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: no-delete-in-prod
apiGroup: rbac.authorization.k8s.io
# 但是,这个 Binding 需要配合 Admission Controller 或者使用 OPA/Gatekeeper 才能精确限制到 prod- 前缀的 Namespace
# 在标准 RBAC 中,ClusterRoleBinding 对所有 Namespace 生效
# 所以,更安全的做法是分别在各 prod Namespace 下创建 RoleBinding,拒绝该 Group
等等,这里有个坑。标准的 Kubernetes RBAC 是 Allow 优先 的。如果你在一个 Namespace 下给 backend-team 绑定了 backend-dev-role(包含 create/update),然后又绑定了 no-delete,RBAC 引擎会检查所有规则。如果没有任何规则允许 delete,那就默认拒绝。
但是,如果有一个更高层级的 ClusterRole 赋予了 *(星号)权限,那这个限制就失效了。所以,一定要确保没有任何 Group 在 prod-* Namespace 下拥有 ClusterAdmin 或 * 角色的绑定。
你可以用这个命令审计一下:
kubectl auth can-i delete pods --namespace prod-backend --as=backend-dev-group
# 应该返回: no
3. 运维团队的“只读+紧急恢复”权限
运维团队需要能重启 Pod,但不能随便删。我们可以给他们一个专门的 Role,允许 restart(通过 patch)和 logs,但禁止 delete。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ops-emergency-role
namespace: prod-backend
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "log", "attach"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "patch"] # patch 可以用来滚动重启
- apiGroups: [""]
resources: ["pods"]
verbs: ["delete"]
resourceNames: ["emergency-restart-helper"] # 这里可以用 LimitRange 或 InitContainer 配合实现安全重启,或者干脆禁止 delete,只用 patch deployment
实际上,更推荐的做法是:禁止 delete pod,强制使用 kubectl rollout restart。因为 delete pod 会导致 Pod 被删除后由 ReplicaSet 重建,这个过程不可控,可能引发短暂的服务抖动。而 rollout restart 是 Deployment 级别的平滑操作。
第三层防线:LimitRange——防止“邻居”偷你的资源
RBAC 解决了“谁能干什么”的问题,但没解决“干坏事能造成多大损失”的问题。
假设后端团队的一个开发者,写了一个有内存泄漏的 Go 程序,或者更糟,一个挖矿病毒。如果没有 LimitRange,这个 Pod 可以无限申请内存,最终把整个 Node 上的其他 Pod 全部 OOM Kill 掉。
LimitRange 的作用就是给 Namespace 里的每个资源对象设定默认的限制和上限。
1. 设置默认的 Container 资源限制
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: prod-backend
spec:
limits:
- default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "200m"
memory: "256Mi"
max:
cpu: "2000m"
memory: "4Gi"
min:
cpu: "50m"
memory: "64Mi"
type: Container
- max:
pods: "20"
type: Pod
这段配置非常关键,我们来拆解一下:
default和defaultRequest:如果开发者的 YAML 里没写资源限制,Kubernetes 会自动套用这些默认值。这意味着,哪怕是最粗心的开发者,提交的 Pod 也最多只能占用 512Mi 内存和 500m CPU。max:这是硬限制。即使开发者在 YAML 里写了memory: 10Gi,Kubernetes 也会拒绝创建,并报出类似Forbidden: maximum memory usage per Container is 4Gi的错误。type: Pod:限制每个 Pod 内最多只能有 20 个容器。防止有人搞“大杂烩”式部署,把整个业务栈塞进一个 Pod。
2. 资源配额:ResourceQuota 与 LimitRange 的配合
光有 LimitRange 还不够,你还得控制整个 Namespace 的总资源消耗,防止一个团队把所有集群资源吃光。这时候需要 ResourceQuota。
apiVersion: v1
kind: ResourceQuota
metadata:
name: backend-quota
namespace: prod-backend
spec:
hard:
requests.cpu: "10"
requests.memory: "20Gi"
limits.cpu: "20"
limits.memory: "40Gi"
pods: "50"
services: "10"
secrets: "20"
configmaps: "20"
这样,prod-backend 这个 Namespace 总共只能使用 10 核 CPU 的请求量,40 核的计算量上限,以及 50 个 Pod。一旦超过,新的资源创建请求会被直接拒绝。
这就形成了一个完整的闭环:RBAC 控制谁能操作,LimitRange 控制单个资源的大小,ResourceQuota 控制 Namespace 的总规模。
第四层防线:隔离崩溃 Pod——防止级联故障
回到标题里提到的“隔离崩溃 Pod”。在微服务架构里,一个 Pod 崩溃(CrashLoopBackOff)如果不加干预,可能会频繁重启,消耗大量 CPU 和 I/O,甚至触发 Kubernetes 的 Eviction 机制,把整个 Node 上的其他 Pod 都挤走。
1. 使用 PodDisruptionBudget (PDB) 保护关键服务
虽然 PDB 主要用在维护场景,但它也能防止在节点故障时,关键服务被过度驱逐。
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: payment-service-pdb
namespace: prod-backend
spec:
minAvailable: 2
selector:
matchLabels:
app: payment-service
这意味着,无论发生什么(包括 Pod 崩溃重启导致的短暂不可用),集群至少要保持 2 个 payment-service 的副本在线。如果只有一个副本崩溃了,Kubernetes 会优先重启它,而不是去动其他重要的系统组件。
2. 设置重启策略和退避限制
在 Deployment 里,我们可以调整 backoffLimit 和 restartPolicy,来避免崩溃的 Pod 无限重启拖垮 Node。
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
namespace: prod-backend
spec:
replicas: 3
selector:
matchLabels:
app: payment-service
template:
metadata:
labels:
app: payment-service
spec:
restartPolicy: Always
# 关键配置:如果 Pod 连续失败,退避时间会指数增长,给 Node 喘息机会
backoffLimit: 6
# 对于 Job 类型,可以设置 activeDeadlineSeconds,防止僵尸任务占用资源
3. 使用 taints 和 tolerations 隔离故障节点
如果某个 Node 上的 Pod 频繁崩溃,我们可以手动给这个 Node 打 taint,阻止新 Pod 调度上去,直到运维排查完毕。
# 运维人员执行
kubectl taint node node-03 crash-loop=true:NoSchedule
这样,即使 prod-backend Namespace 里有未定义的 tolerations,新创建的 Pod 也不会被调度到这个有问题的节点上。而已有的 Pod 会继续运行,直到自然结束。
第五层防线:审计与监控——让一切有迹可循
做了这么多限制,万一有人绕过了呢?或者系统出错了呢?我们需要 Audit Log。
Kubernetes 的审计功能可以记录所有对 API Server 的请求,包括谁、什么时候、从哪个 IP、做了什么操作。
1. 配置 Audit Policy
创建一个 audit-policy.yaml:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# 记录所有敏感操作
- level: Metadata
resources:
- group: ""
resources: ["pods", "secrets", "configmaps"]
verbs: ["delete", "update", "patch"]
# 记录所有对 production Namespace 的操作
- level: RequestResponse
namespaces: ["prod-backend", "prod-frontend"]
verbs: ["*"]
# 忽略健康检查
- level: None
users: ["kubelet"]
verbs: ["get"]
resources:
- group: ""
resources: ["nodes"]
然后把这个配置挂载到 API Server 的启动参数里:
--audit-policy-file=/etc/kubernetes/audit-policy.yaml
--audit-log-path=/var/log/kubernetes/audit.log
--audit-log-maxage=30
--audit-log-maxbackup=10
--audit-log-maxsize=100
这样,所有对生产环境的删除、修改操作都会被详细记录。一旦再次发生“误删”事件,我们可以迅速回溯日志,找到是哪个用户、在什么时间、通过什么工具发起的请求。
2. 结合 Prometheus 监控 LimitRange 使用情况
我们可以部署一个 Exporter,实时监控每个 Namespace 的资源使用率,并与 ResourceQuota 对比。一旦使用率超过 80%,就发送告警给对应的团队负责人。
# Prometheus Rule 示例
groups:
- name: namespace-quota
rules:
- alert: NamespaceQuotaHighUsage
expr: |
sum by (namespace) (
kube_pod_resource_request{namespace=~"prod-.*"}
) /
sum by (namespace) (
kube_resourcequota{namespace=~"prod-.*", resource=~"requests.cpu|requests.memory"}
) * 100 > 80
for: 5m
labels:
severity: warning
annotations:
summary: "Namespace {{ $labels.namespace }} 资源使用率超过 80%"
实战总结:如何教小朋友理解这套机制
如果你要给团队里新来的实习生,或者甚至是一个不懂技术的产品经理讲清楚这套东西,可以打个比方:
想象我们的 Kubernetes 集群是一家高档写字楼。
- Namespace 就是不同的公司租户。前台(API Server)只允许持有该公司工牌的人进入该公司楼层。
- RBAC 就是公司的门禁权限和岗位职责。后端开发组的门禁只能刷到开发部区域,不能刷到财务部(Secrets)。而且,门禁系统明确规定,开发部没有“销毁文件”的权限,只有“新增”和“修改”的权限。
- LimitRange 就是办公室的工位大小。不管你多么有才华,你的工位最多只能放下一台笔记本电脑和一台显示器(512Mi 内存)。你不能把家里的沙发搬进来占地方。
- ResourceQuota 是整层楼的总用电量。这层楼总共只能消耗 100 度电,不管里面有多少家公司,用超了就跳闸。
- PodDisruptionBudget 是消防演习预案。规定即使发生火灾(节点故障),这家公司的核心业务(payment-service)必须保证至少有 2 个人在岗,不能全跑光了。
通过这一套组合拳,我们不仅防止了“手滑”导致的灾难,也限制了恶意行为或意外故障的扩散范围。
最后的建议
安全不是一劳永逸的。建议每季度进行一次 RBAC 审计,移除那些不再需要的权限绑定。同时,启用 Pod Security Standards (PSS),禁止 Privileged 容器在生产 Namespace 中运行。
记住,最好的安全策略,是让“犯错”变得不可能,或者让“犯错”的后果被严格控制在最小范围内。希望这份指南能帮你的 Kubernetes 集群,再也不经历那个周二的凌晨三点。