课程目录(第 12 章 / 共 33 章)
课程/走向生产

命名空间与 RBAC:多人共用一个集群

12 章 / 共 33·18 分钟·入门+命名空间RBAC安全

用一个集群服务多个团队:用 Namespace 做逻辑隔离,用 ServiceAccount 给 Pod 一个身份,用 RBAC 精确控制谁能对什么资源做什么。

学完这一章,你将能够

  • 说清 Namespace 能隔离什么、不能隔离什么
  • 会给 Pod 指定 ServiceAccount,并理解 Token 是怎么进到容器里的
  • 能写出 Role 与 RoleBinding,用 kubectl auth can-i 验证权限是否生效

Namespace 解决什么问题

到目前为止我们一直在 default 命名空间里折腾。一个人学习无所谓,但如果一个集群要给三个团队共用,问题马上就来了:名字会撞车,资源会被抢光,出了事谁都能删别人的东西。

Namespace(命名空间)是 Kubernetes 里的逻辑隔离单元,它主要解决四件事:

  • 分组:同一类东西放在一起,kubectl get pods -n demo 只看自己的。
  • 名字复用demostaging 里可以各有一个叫 web 的 Deployment,互不冲突。
  • 配额ResourceQuotaLimitRange 是按命名空间生效的,可以限制某个团队最多用多少 CPU、能创建多少个 Pod。
  • 权限边界:RoleBinding 授权只在某个命名空间内生效,这是 RBAC 的基础。

要特别注意它的边界:Namespace 不是网络隔离。默认情况下,不同命名空间的 Pod 可以直接互相访问,Service 的完整 DNS 名是 <service>.<namespace>.svc.cluster.local。真正的网络隔离要靠 NetworkPolicy,而且是否生效取决于你的 CNI 实现,细节见集群网络

集群创建后自带四个命名空间:default(不放东西时用)、kube-system(控制平面组件)、kube-public(公开可读)、kube-node-lease(节点心跳租约)。`kube-system` 里的东西不要随便动。

哪些资源不属于命名空间

有些资源天然是集群级的,它们没有 namespace 字段,写进 YAML 里也会被忽略:

类型例子
节点与集群Node、Namespace 自身、ComponentStatus
存储PersistentVolume、StorageClass、CSIDriver
权限ClusterRole、ClusterRoleBinding
扩展与策略CustomResourceDefinition、APIService、PriorityClass、IngressClass

想自己确认,可以用这条命令列出所有集群级资源:

bash
kubectl api-resources --namespaced=false
kubectl api-resources --namespaced=true | head -20

这也解释了一个新手常见困惑:为什么 PVC 是命名空间级的,而它绑定的 PV 却是集群级的——PVC 属于某个团队,PV 属于整个集群的存储池,这一点在第 10 章 存储卷与 PV/PVC里讲得更细。

动手:创建 demo 命名空间并切换默认上下文

bash
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl get namespaces
kubectl config set-context --current --namespace=demo
kubectl config view --minify --output 'jsonpath={..namespace}{"\n"}'

最后一条应该输出 demo。从这一刻起,你所有不带 -n 的命令都默认作用于 demo——这既是方便,也是风险,手一滑就可能在生产命名空间里执行了删除命令。建议在团队里养成显式写 -n demo 的习惯,或者给上下文起个一眼能认出来的名字。切回来和查看资源:

bash
kubectl config set-context --current --namespace=default
kubectl get all -n demo
kubectl get all,configmap,secret,pvc -n demo

kubectl get all 只包含 Pod、Deployment、ReplicaSet、StatefulSet、DaemonSet、Job、CronJob、Service 这些常见资源,不包括 ConfigMap、Secret、PVC、Ingress、ServiceAccount。排查时别被它的名字骗了。

ServiceAccount:Pod 在集群里的身份

RBAC 里被授权的主体(subject)有三种:UserGroupServiceAccount。前两种是「人」,由 kubeconfig 里的证书或 OIDC 提供;ServiceAccount(SA)是给 Pod 里的进程用的身份

每个命名空间都会自动创建一个叫 default 的 SA。如果你不指定,Pod 就用它。

bash
kubectl get serviceaccount -n demo
kubectl create serviceaccount app -n demo

从 Kubernetes 1.24 起,创建 SA 不再自动生成长期有效的 Secret Token。需要 Token 时,用下面的命令按需签发一个有时效的:

bash
kubectl create token app -n demo --duration=1h

Pod 里拿 Token 的方式也不同了:kubelet 会通过 projected volume 把一个短期 Token 挂到 /var/run/secrets/kubernetes.io/serviceaccount/token,同时挂上 ca.crtnamespace 两个文件。如果 Pod 根本不需要访问 API Server,在 spec 里加一行 automountServiceAccountToken: false 就能关掉挂载——进容器执行 ls /var/run/secrets/kubernetes.io/serviceaccount/ 会提示目录不存在,容器里拿不到任何凭据,也就无法访问 API Server。

RBAC 四要素

RBAC(Role-Based Access Control)回答一个问题:谁(subject)能对什么资源(resource)做什么操作(verb)。它由四个对象组合而成:

对象作用域作用
Role命名空间定义一组规则,比如「可以 get/list/watch pods」
ClusterRole集群同样定义规则,但可用于集群级资源(如 Node),也可被 RoleBinding 引用
RoleBinding命名空间把 Role 或 ClusterRole 绑到主体,授权只在这个命名空间内生效
ClusterRoleBinding集群把 ClusterRole 绑到主体,授权在所有命名空间生效

一次请求要走完四道关,RBAC 只负责其中的第二道:

text
kubectl 或 Pod 里的进程发起请求(带客户端证书或 Bearer Token)


① 认证 Authentication:你是谁?
      ├─ 人:kubeconfig 里的证书 / OIDC
      └─ Pod:ServiceAccount 的 Token(kubelet 挂到 /var/run/secrets/.../token)
      │ 通过 → 身份形如 system:serviceaccount:demo:app

② 鉴权 Authorization ← RBAC 在这一步生效
      Role / ClusterRole:定义「能对什么资源做什么」,写在 rules 里
      RoleBinding / ClusterRoleBinding:决定「把这份规则给谁」
      有规则匹配 → 放行;一条都不匹配 → 403 Forbidden
      │ 通过

③ 准入控制 Admission:请求合法,但要不要补默认值 / 拦下来?
      LimitRange、ResourceQuota、NetworkPolicy、各种 Webhook 都在这一步
      │ 通过

④ etcd:对象真正被写入(只有 kube-apiserver 能直接读写 etcd)

所以 ServiceAccount 决定「你是谁」(第①步),Role 决定「能干什么」,RoleBinding 决定「把这份权限给谁」——后两者一起在第②步起作用。

常用的 verb 有 getlistwatchcreateupdatepatchdeletedeletecollection;子资源要写成 pods/logpods/exec 这样的形式。注意 pods/exec 权限约等于「能进容器干任何事」,几乎等同于该节点上的 root,不要随手给。

一条经验法则:能用 RoleBinding 就不要用 ClusterRoleBinding。前者影响一个命名空间,后者影响整个集群,出错的代价差一个数量级。

动手:给 SA app 一份最小权限

目标:让 demo 里的 app 这个 SA 只能看 Pod 和 Pod 日志,不能删、不能改、不能看别的命名空间。

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app
  namespace: demo
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: demo
rules:
  - apiGroups:
      - ""
    resources:
      - pods
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - pods/log
    verbs:
      - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: app-pod-reader
  namespace: demo
subjects:
  - kind: ServiceAccount
    name: app
    namespace: demo
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

apiGroups 里的空字符串 "" 表示核心 API 组,Pod、Service、ConfigMap 都在这一组里;Deployment 属于 apps 组。

bash
kubectl apply -f sa-app.yaml
kubectl get serviceaccount,role,rolebinding -n demo

三个对象都出现就说明创建成功。接下来把这个 SA 挂到 Pod 上,让容器真的去调一次 API:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: api-client
  namespace: demo
spec:
  serviceAccountName: app
  restartPolicy: Never
  containers:
    - name: client
      image: curlimages/curl:8.10.1
      command:
        - sh
        - -c
        - |
          curl -s --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
            -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
            https://kubernetes.default.svc/api/v1/namespaces/demo/pods
bash
kubectl apply -f api-client.yaml
kubectl logs -n demo api-client

如果输出一段包含 "items" 的 JSON,说明 Pod 用 SA 身份成功读到了 Pod 列表;如果把 serviceAccountName 换成不存在的名字,Pod 会直接卡在 ContainerCreating,事件里写着 SA 找不到。

验证权限:kubectl auth can-i

kubectl auth can-i 是最直接的权限自检工具,它只回答「能不能」,不真的执行操作:

bash
kubectl auth can-i list pods -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i delete pods -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i get pods/log -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i list pods -n default --as=system:serviceaccount:demo:app

预期依次是 yesnoyesno。最后一条是关键:SA 的权限被 RoleBinding 限制在 demo 里,换个命名空间就不生效了。命令的退出码也跟着 yes/no 变化(yes 是 0,no 是 1),可以直接写进脚本做校验。

想看这个 SA 的完整权限清单:

bash
kubectl auth can-i --list -n demo --as=system:serviceaccount:demo:app

输出会分成 Resources、Non-Resource URLs、Resource Names 几列,* 表示通配符。除此之外还有几行是 RBAC 默认给所有已认证用户的权限(比如读 /healthz),不要误以为是你的 Role 给的。

动手练习:从建 SA 到收紧权限

  1. 用命令创建 SA、Role、RoleBinding(如果上面已经 apply 过 sa-app.yaml,这三条会提示 AlreadyExists,跳过即可):
bash
kubectl create serviceaccount app -n demo
kubectl create role pod-reader -n demo --verb=get,list,watch --resource=pods
kubectl create rolebinding app-pod-reader -n demo --role=pod-reader --serviceaccount=demo:app
  1. 看这个 SA 的完整权限清单,注意区分「自己的 Role 给的」和「RBAC 默认给所有已认证用户的」:
bash
kubectl auth can-i --list -n demo --as=system:serviceaccount:demo:app
  1. 逐条验证边界——list podsyesdelete podsno,换个命名空间也是 no
bash
kubectl auth can-i list pods -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i delete pods -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i list pods -n default --as=system:serviceaccount:demo:app
  1. 再收紧一步:把 Role 改成只允许读指定的那个 Pod,用 resourceNames 限定,然后确认读别的 Pod 会被拒绝:
bash
kubectl create role pod-reader -n demo --verb=get --resource=pods \
  --resource-name=api-client --dry-run=client -o yaml | kubectl apply -f -
kubectl auth can-i get pods/api-client -n demo --as=system:serviceaccount:demo:app
kubectl auth can-i get pods/web -n demo --as=system:serviceaccount:demo:app

预期第一条是 yes、第二条是 no。最后删掉绑定,确认权限立刻消失——RBAC 是逐请求判定的,改完不用重新签发 Token:

bash
kubectl delete rolebinding app-pod-reader -n demo
kubectl auth can-i get pods/api-client -n demo --as=system:serviceaccount:demo:app

最小权限原则的检查清单

权限一旦给多了很难收回来,所以每次写 Role 前对着这份清单过一遍:

  1. 每个应用用自己的 SA,不要复用 default,也不要把多个应用塞进一个 SA。
  2. 作用域优先 Role + RoleBinding;确实需要集群级权限(Node、PV、CRD)时才用 ClusterRoleBinding
  3. verb 只给需要的:只读就给 getlistwatch,不要图省事写 *
  4. 资源写具体:pods 而不是 *pods/exec 约等于节点上的 root,能不给就不给。
  5. 需要精确到单个对象时用 resourceNames,例如只允许读某个 ConfigMap(配置本身见第 9 章 ConfigMap 与 Secret)。
  6. 定期用 kubectl auth can-i --list --as=system:serviceaccount:<ns>:<sa> 回看,删掉没人用的 RoleBinding。
  7. 临时提权用短时效 Token(kubectl create token --duration=1h),不要造长期凭据。

常见坑与排错

现象原因怎么确认怎么办
应用调 API 返回 403 ForbiddenRole 里没有对应的 verb/resource,或 RoleBinding 的 subject 名字写错kubectl auth can-i <verb> <resource> -n <ns> --as=system:serviceaccount:<ns>:<sa>kubectl describe rolebinding <名字> -n <ns>补规则,或逐字核对 subject 的 namenamespace
Pod 卡在 ContainerCreating,事件说 SA 找不到serviceAccountName 拼错,或 SA 不在同一个命名空间kubectl get sa -n <ns>kubectl describe pod改对名字;SA 必须与 Pod 同命名空间
kubectl auth can-icannot impersonate你自己的账号没有 impersonate 权限报错信息里直接写着换有权限的账号验证,或直接读 RoleBinding
改完 Role 后权限似乎没变改错了命名空间,或看的是另一个 SAkubectl get role -n <ns> -o yaml 对照;kubectl auth can-i --list --as=...RBAC 是逐请求判定的,改完立即生效,不需要重新签发 Token
命名空间删不掉里面还有资源或对象上挂着 finalizerkubectl describe namespace <名字>Conditions先清空资源,或按提示处理 finalizer

四个容易踩的坑

  • ClusterRoleBinding 给过头:把 cluster-admin 绑到一个 SA 上,等于给这个 SA 所在的所有 Pod 发了万能通行证,而且授权是全集群的,将来想收回来得先找出谁在依赖它。生产环境优先用 RoleBinding,确实需要集群级权限时才用 ClusterRoleBinding,并写清用途。
  • `--as` 验证时的误判:如果报 cannot impersonate resource ... 之类的 Forbidden,那通常是你自己的账号没有 impersonate 权限,而不是目标 SA 没权限。另外 can-i 只检查规则,SA 不存在时它照样可能返回 no,别把它当成「SA 存在性检查」。
  • SA 名字拼错导致 ForbiddenRoleBindingsubjects[].name 写错(比如 app 写成 apps)不会报错,只是权限静默不生效,表现就是应用调 API 时收到 403。用 kubectl describe rolebinding app-pod-reader -n demo 逐字核对。
  • 默认 SA 被赋予高权限:很多教程会让你 kubectl create clusterrolebinding default-admin --clusterrole=cluster-admin --serviceaccount=default:default,这等于让这个命名空间里每一个没写 serviceAccountName 的 Pod 都拥有集群管理员权限。一旦某个 Pod 被利用,整个集群就失守了。

还有两个细节:RoleRoleBindingnamespace 必须和被授权对象在同一个命名空间,跨命名空间授权要靠 ClusterRoleBinding 或者给每个命名空间各建一份;删除命名空间会把它里面的所有资源一起删掉,这是不可撤销的,执行前一定用 kubectl get all -n <ns> 看一眼。

自测题

自测:为什么说 Namespace 不是网络隔离?(点击展开答案)

因为 Namespace 只是 API 对象上的一个分组标签,它影响的是「名字、配额、权限」,不改变网络平面。Pod 的 IP 由 CNI 分配,跨命名空间的路由默认是通的,所以 demo 里的 Pod 可以直接连 staging 里的 Service,甚至能用 <service>.<namespace>.svc.cluster.local 精确寻址。要拦网络流量必须用 NetworkPolicy,而且它是否真正生效取决于 CNI 插件是否实现了它。把 Namespace 当成「网络隔离」会导致一个危险假设:以为放了敏感服务在单独命名空间就安全了。

自测:为什么能用 RoleBinding 就不要用 ClusterRoleBinding?(点击展开答案)

因为两者的影响范围差一个数量级。RoleBinding 只在它所在的命名空间内授权,出错了影响面可控;ClusterRoleBinding 的授权覆盖所有命名空间,一次误绑就等于给这个主体开了全集群的门,而且事后要收回来还得先找出谁在依赖它。所以判断标准很简单:只在一个命名空间里干活就用 RoleBinding;只有真正操作集群级资源(Node、PV、CRD)或需要跨所有命名空间时才用 ClusterRoleBinding,并写清用途。

自测:Pod 默认挂载的 SA Token 为什么是安全风险?(点击展开答案)

因为每个命名空间都有一个 default SA,而如果不指定 serviceAccountName,Pod 就会自动用它并挂载 Token。一旦有人给这个 default SA 绑了高权限(很多教程会这么干),那么这个命名空间里所有没写 SA 的 Pod 都自动拥有那些权限——哪怕它们根本不需要访问 API Server。降低风险的做法是:给每个应用单独建 SA、只授最小权限,对不需要调 API 的 Pod 显式设置 automountServiceAccountToken: false

小结

  • Namespace 提供逻辑隔离、配额和权限边界,但不提供网络隔离;PV、Node、StorageClass 等资源是集群级的。
  • ServiceAccount 是 Pod 的身份;1.24 之后不再自动生成长期 Secret,按需用 kubectl create token 签发短期 Token。
  • RBAC 四要素:Role/ClusterRole 定义规则,RoleBinding/ClusterRoleBinding 把规则绑到主体;能用 RoleBinding 就别用 ClusterRoleBinding。
  • kubectl auth can-ikubectl auth can-i --list 是验证权限的第一手段,注意区分「自己没权限验证」和「目标真的没权限」。
  • 最小权限原则落到操作上:只给需要的 verb、只给需要的资源、优先限定命名空间。

练习

  1. demo 里的 SA app 追加一条规则:允许 get 名为 app-config 的 ConfigMap(用 resourceNames 限定),然后用 kubectl auth can-i get configmap/app-config -n demo --as=system:serviceaccount:demo:app 验证。
  2. 创建一个 staging 命名空间,把 webkubectl get deployment web -n demo -o yaml 导出、改掉命名空间后 apply 进去,确认两个命名空间的同名资源互不影响。
  3. kubectl get role,rolebinding -A -o wide 看看你的集群里已经有哪些授权,找出其中有没有不该存在的 ClusterRoleBinding。

到目前为止我们的工作负载都是「一直跑着」的:Deployment 保证副本数,Service 负责流量。但现实里还有另一类任务——跑一次就结束,或者每天凌晨跑一次。下一章第 13 章 Job 与 CronJob讲这类任务。

相关章节:第 15 章 排障手册ForbiddenContainerCreating 的排查流程)、集群网络(NetworkPolicy 与 CNI 实现)。