从零搭建 K8s 权限体系:开发组只读 + 运维组管理(中集)

上集拆解了 RBAC 四个核心概念(User / ServiceAccount / Role / Binding),搞清楚了 Subject → Binding → Role 这条链路。这集中手把手搭建,把概念变成能跑的命令。
建议读完上集再看这篇,概念定义和证书签发流程上集有详细说明。


一、场景设定

  • 开发组dev-team)→ devstaging 命名空间有只读权限
  • 运维组ops-team)→ 所有命名空间管理权限
  • 日志采集 ServiceAccountprod 命名空间 Pod 发现 + 节点信息采集

下面分三个案例,由浅入深:先从最简单的"单独给一个用户授权"讲起,再升级到"给组授权"的生产实战,最后讲"给程序(ServiceAccount)授权"------对应上面三个场景设定。


二、实战案例一:单独给用户授权

对应场景设定里的「新同事入职」------先从最简单的单个 User 讲起,理解 RBAC 三元组怎么串起来。

新同事 zhangsan 入职开发,需要在 devstaging 有只读权限。完整链路:签发证书(认证)→ 创建 Role(授权)→ RoleBinding(关联)→ 验证

1.1 签发证书------搞定"你是谁"

详细的证书签发流程参考上集「二.1.2 生产实战」,关键点:CSR 里 CN=zhangsan 就是用户名,RBAC 绑定时用 kind: Username: zhangsan 匹配。

bash 复制代码
cat > zhangsan-csr.json <<EOF
{
  "CN": "zhangsan",
  "key": { "algo": "rsa", "size": 2048 },
  "names": [{ "C": "CN", "ST": "BeiJing", "L": "BeiJing", "OU": "System" }]
}
EOF

# 找 CA → 签发 → 生成 kubeconfig(参考上集二.1.2 第 1-4 步)

拿到 zhangsan.kubeconfig 后,用户已经能连上集群------但 kubectl get pods 会报 403,因为还没授权。

1.2 创建 Role + RoleBinding------搞定"能干啥"

yaml 复制代码
# rbac-zhangsan.yaml
cat > rbac-zhangsan.yaml <<EOF
# 1. dev 命名空间的只读 Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dev-reader
  namespace: dev
rules:
- apiGroups: [""]
  resources: ["pods", "services", "configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets"]
  verbs: ["get", "list", "watch"]
---
# 2. RoleBinding:把 User zhangsan 绑到 dev-reader角色
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: zhangsan-dev-reader
  namespace: dev
subjects:
- kind: User                   # ← 注意:绑的是 User,不是 Group
  name: zhangsan               # 匹配证书 CN 字段
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: dev-reader
  apiGroup: rbac.authorization.k8s.io
---
# 3. staging 命名空间也要创建同名 Role(Role 是命名空间级资源,跨 ns 引用不到)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dev-reader
  namespace: staging
rules:
- apiGroups: [""]
  resources: ["pods", "services", "configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets"]
  verbs: ["get", "list", "watch"]
---
# 4. staging 的 RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: zhangsan-staging-reader
  namespace: staging
subjects:
- kind: User
  name: zhangsan
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: dev-reader
  apiGroup: rbac.authorization.k8s.io
EOF
bash 复制代码
kubectl apply -f rbac-zhangsan.yaml

1.3 验证

bash 复制代码
kubectl auth can-i get pods -n dev --as zhangsan        # → yes
kubectl auth can-i delete pods -n dev --as zhangsan     # → no------Role 里没有 delete
kubectl auth can-i get pods -n prod --as zhangsan       # → no------prod 没授权

💡 这种方式的缺点:每来一个新同事,都要写一套 RoleBinding。人多了之后 YAML 里全是一堆 User 绑定,改都改不过来。解决办法------案例二。


三、实战案例二:给组授权------开发组只读 + 运维组管理

3.1 核心原理:证书的 O 字段 = RBAC 的 Group name

生产环境通过 Group 管权限,不针对单个 User 。组名从哪里来?------证书签发时的 O 字段。

bash 复制代码
# zhangsan(开发)
{
  "CN": "zhangsan",
  "O": "dev-team"           # ← 这个 O 值就是 RBAC 的 Group name
}

# lisi(运维)
{
  "CN": "lisi",
  "O": "ops-team"
}

签发证书后,kubeconfig 里的客户端证书自动带上 O=dev-team,无需额外配置。

角色 CSR 里 O 字段 权限范围
开发 dev-team dev、staging 只读
运维 ops-team 全集群管理

3.2 新人入职:签发证书(以 lisi 为例)

原理清楚了,下面实际操作一把。假设运维组新来一个同事 lisi,给他签发证书------O=ops-team,签完 kubeconfig 里自动带上组信息。

bash 复制代码
#编写证书签名请求(CSR)
cat > lisi-csr.json <<EOF
{
  "CN": "lisi",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "ops-team",
      "OU": "System"
    }
  ]
}
EOF
#用集群 CA 签发客户端证书
cfssl gencert \
  -ca=/etc/kubernetes/ssl/ca.pem \
  -ca-key=/etc/kubernetes/ssl/ca-key.pem \
  -config=ca-config.json \
  -profile=kubernetes lisi-csr.json | cfssljson -bare lisi

#生成 kubeconfig 文件
# 4a. 写入集群信息
kubectl config set-cluster kubernetes \
  --certificate-authority=/etc/kubernetes/ssl/ca.pem \
  --embed-certs=true \
  --server=https://192.168.91.254:6443 \
  --kubeconfig=lisi.kubeconfig

# 4b. 写入用户凭证
kubectl config set-credentials lisi \
  --client-certificate=lisi.pem \
  --client-key=lisi-key.pem \
  --embed-certs=true \
  --kubeconfig=lisi.kubeconfig

# 4c. 创建上下文(关联用户 + 集群)
kubectl config set-context lisi@kubernetes \
  --cluster=kubernetes \
  --user=lisi \
  --kubeconfig=lisi.kubeconfig

# 4d. 设为默认上下文
kubectl config use-context lisi@kubernetes \
  --kubeconfig=lisi.kubeconfig

注意:这时 RBAC 一个字符都不用动。RoleBinding/ClusterRoleBinding 绑的是 Group ops-team,不是 lisi 这个人。lisi 证书签完,自动继承运维组全部权限。

3.3 开发组:dev 和 staging 只读

yaml 复制代码
# rbac-dev-group.yaml
cat > rbac-dev-group.yaml <<EOF
# 1. 创建只读 ClusterRole(可被多个 命名空间 复用)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: read-only
rules:
- apiGroups: [""]
  resources: ["pods", "services", "configmaps", "secrets", "endpoints"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets", "statefulsets", "daemonsets"]
  verbs: ["get", "list", "watch"]
---
# 2和3 RoleBinding:把 dev-team 组绑定到 dev 命名空间和staging
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-team-readonly
  namespace: dev
subjects:
- kind: Group
  name: dev-team                  # ← 组名,匹配证书 O=dev-team
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: read-only
  apiGroup: rbac.authorization.k8s.io
---
# 3. staging 也绑一次
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-team-readonly
  namespace: staging
subjects:
- kind: Group
  name: dev-team
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: read-only
  apiGroup: rbac.authorization.k8s.io
EOF
bash 复制代码
kubectl apply -f rbac-dev-group.yaml

3.4 运维组:全集群管理

yaml 复制代码
# rbac-ops-group.yaml
cat > rbac-ops-group.yaml <<EOF
# 利用 K8s 内置的 cluster-admin,直接 ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: ops-team-admin
subjects:
- kind: Group
  name: ops-team                  # ← 证书里 O=ops-team 的人自动获得
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin             # K8s 内置超级管理员
  apiGroup: rbac.authorization.k8s.io
EOF
bash 复制代码
kubectl apply -f rbac-ops-group.yaml

如果运维只需特定权限(如管理 Node、PV,但不能删 Namespace),可自定义 ClusterRole 替代 cluster-admin,按最小权限原则来。

3.5 验证

bash 复制代码
# 开发 zhangsan:dev + staging 可读,prod 进不去
kubectl auth can-i get pods -n dev --as zhangsan        # → yes
kubectl auth can-i get pods -n staging --as zhangsan    # → yes
kubectl auth can-i get pods -n prod --as zhangsan       # → no
kubectl auth can-i delete pods -n dev --as zhangsan     # → no

# 运维 lisi------注意要用 KUBECONFIG 或 --as-group,不能只用 --as
# ⚠️ --as lisi 只模拟 User "lisi",不会带上证书里的 Group "ops-team"
# 运维的权限是通过 Group 绑定的,直接用 lisi 的 kubeconfig 最准确:
KUBECONFIG=lisi.kubeconfig kubectl auth can-i get pods -n prod    # → yes
KUBECONFIG=lisi.kubeconfig kubectl auth can-i delete pods -n dev  # → yes
KUBECONFIG=lisi.kubeconfig kubectl auth can-i get nodes           # → yes

# 或者用 --as + --as-group 显式指定组
kubectl auth can-i get pods -n prod --as lisi --as-group ops-team # → yes

🔑 关键区别 :案例一绑的是 kind: User--as zhangsan 就够了。案例二绑的是 kind: Group--as lisi 只模拟 User 身份、不带上 Group,必须加 --as-group ops-team 或直接用 kubeconfig 文件验证。

3.6 核心价值:人动权限不动

bash 复制代码
王五入职开发组:
  ① 写 CSR,O=dev-team
  ② cfssl 签发 → 生成 kubeconfig
  ③ 结束。RBAC 一个字符都不用改
     → 因为 RoleBinding 绑的是 Group dev-team,不是 User 王五

王五转岗到运维:
  ① 重新签发证书,O 改成 ops-team
  ② 权限自动从只读变管理
  ③ 还是不用改 RBAC

生产铁律 :永远通过 Group 分配权限。证书里的 O 字段就是天然的 RBAC Group。


四、实战案例三:给程序授权(ServiceAccount)

前面两个案例讲的都是"人"的身份(User + Group),日常 kubectl 操作靠它们。但集群里跑的日志/监控 Agent(Filebeat、Prometheus)是程序自动跑的------没有真人敲命令,用的是 ServiceAccount

4.1 创建 SA + 授权

以 Filebeat 为例:它的 DaemonSet 跑在每个节点上,需要发现新 Pod → 拿到 Pod 元数据(标签、ns)→ 给日志打标才能送到 Elasticsearch。这些操作全是调 K8s API,default SA 零权限,直接 403。按最小权限原则,只给刚好够的:

yaml 复制代码
# log-collector-sa.yaml ------ 一个文件三段,SA → Role → RoleBinding 一步到位
cat > log-collector-sa.yaml << EOF
# ===== 第 1 段:创建 ServiceAccount =====
apiVersion: v1
kind: ServiceAccount
metadata:
  name: log-collector
  namespace: prod              # SA 是命名空间级,日志 Agent 部署在哪个 ns 就建哪个 ns
---
# ===== 第 2 段:定义权限 =====
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: log-reader
  namespace: prod
rules:
- apiGroups: [""]              # 核心 API 组(Pod/Node 在这)
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
  # get/list:拉取当前 Pod 列表,拿到 Pod 名、命名空间、标签等元数据
  # watch:监听 Pod 增删,新 Pod 一创建 Filebeat 马上开始采日志
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list"]
  # 拿到节点名、IP 等信息,日志记录里能标注"这条日志来自哪个节点"
---
# ===== 第 3 段:绑定 SA 到 Role =====
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: log-collector
  namespace: prod
subjects:
- kind: ServiceAccount
  name: log-collector
  namespace: prod
roleRef:
  kind: Role
  name: log-reader
  apiGroup: rbac.authorization.k8s.io
EOF
bash 复制代码
kubectl apply -f log-collector-sa.yaml

到这里 SA 和权限就位了------log-collector 这个ServiceAccount能看 Pod 和 Node 信息,但不能改任何东西。但 Pod 怎么用这个 SA 呢?往下看。

4.2 Pod 如何使用 SA

DaemonSet 里 serviceAccountName 一行搞定,K8s 自动把凭证挂进每个节点的日志采集 Pod

yaml 复制代码
# filebeat-daemonset.yaml
cat > filebeat-daemonset.yaml << EOF
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: filebeat
  namespace: prod
spec:
  selector:
    matchLabels:
      app: filebeat
  template:
    metadata:
      labels:
        app: filebeat
    spec:
      serviceAccountName: log-collector   # ← 一行指定身份,不写默认用 default(零权限)
      containers:
      - name: filebeat
        image: docker.elastic.co/beats/filebeat:8.15.0
EOF

kubectl apply -f filebeat-daemonset.yaml

K8s 调度这个 Pod 时自动做三件事------把三个关键文件挂进容器:

文件 路径 用途
Token /var/run/secrets/.../token 认证凭证,Filebeat 拿它调 API 证明"我是 log-collector"
CA 证书 /var/run/secrets/.../ca.crt 验证 API Server 身份,防中间人
命名空间 /var/run/secrets/.../namespace 知道自己在哪个 ns

Filebeat 进程启动后读这三个文件,就能调 K8s API 拉 Pod 列表、监听 Pod 变化。用 default SA 的话------刚启动就 403,日志采集直接废掉。

那什么时候需要自定义 SA、什么时候用 default 就行?

Pod 类型 用哪个 SA 原因
纯业务 Pod(Nginx、MySQL、Redis、Java 服务) default(甚至关掉 Token 挂载) 不调 K8s API,不需要任何权限
CI/CD 流水线(GitLab Runner、Jenkins Agent) 自定义 SA(如 cicd-deployer 要 update Deployment、create ConfigMap
日志/监控采集(Filebeat、Prometheus) 自定义 SA 需要 get/watch pods、nodes 做服务发现
Operator / Controller(cert-manager、etcd-operator) 自定义 SA 需要 CRUD 自定义资源
Ingress Controller(nginx-ingress) 自定义 SA 需要 get/watch services、endpoints、secrets
外部密钥同步(External Secrets Operator) 自定义 SA 需要 create/update secrets
备份工具(Velero) 自定义 SA 需要 get/list 全集群资源
自己写的调 API 的应用 自定义 SA 按需给,如查 Pod 列表、动态创建 Job

一句话:Pod 里程序只要 curl https://kubernetes.default.svc 调 K8s API,就不能用 default(零权限),必须自定义 SA + 绑 Role。 反过来,跑纯业务的 Pod 用 default 完全够,更建议设 automountServiceAccountToken: false 关掉 Token 挂载------安全,又少一个攻击面。

4.3 获取 SA 的 Token(给集群外工具用)

Pod 内程序自动有 Token,但 有些场景需要在集群外用这个 SA------比如调试时从本机 curl API 拉 Pod 列表:

bash 复制代码
# 一条命令生成 Token,直接打印到终端
kubectl create token log-collector -n prod --duration=720h

输出就是一长串 JWT 字符串,拿来当 Bearer Token 用。

版本说明kubectl create token 需要 kubectl ≥ 1.24。如果 kubectl 是 1.23(create token 还不存在),但服务器是 1.24+(不再自动生成 Secret),就手动建一个 Secret 兜底。

那什么时候需要手动拿 Token?

场景 需要手动拿? 原因
Pod 里用这个 SA(serviceAccountName: log-collector K8s 自动把 Token 注入容器,程序直接读文件即可
集群外工具用这个 SA(本机脚本、调试) 没有 Pod、没有自动挂载,得先 create token 拿到凭证

一句话:SA 是身份注册,Token 是出门证。在集群内(Pod)自动发证,在集群外(笔记本)得自己领。

4.4 验证 SA 权限------从两个角度测一遍

角度一:集群内测试(kubectl auth can-i)

SA 在 K8s 内部的完整身份是 system:serviceaccount:<ns>:<sa-name>

bash 复制代码
# Filebeat 能不能看 Pod 列表?(预期 yes)
kubectl auth can-i get pods -n prod \
  --as system:serviceaccount:prod:log-collector      # → yes

# Filebeat 能不能删 Pod?(预期 no------只读 SA,不该有删除权限)
kubectl auth can-i delete pods -n prod \
  --as system:serviceaccount:prod:log-collector      # → no

# Filebeat 能不能操作 Deployment?(预期 no------日志采集碰不到 Deployment)
kubectl auth can-i get deployments -n prod \
  --as system:serviceaccount:prod:log-collector      # → no

# 列出 log-collector 的全部权限
kubectl auth can-i --list -n prod \
  --as system:serviceaccount:prod:log-collector
# Resources           Verbs
# pods                [get list watch]
# nodes               [get list]
# ↑ 就两行,干净利落。没有 delete、没有 create、没有 update。
#   日志 Agent 只需要"看",不需要改任何东西。

角度二:集群外测试(curl 模拟本机脚本)

从其他服务器模拟:不带 Token 和带上 Token 各调一次 API:

sh 复制代码
# ① 不带 Token → 401,API Server 不知道你是谁
curl -k https://192.168.91.254:6443/api/v1/namespaces/prod/pods
# {"status": "Failure", "message": "Unauthorized", "code": 401}

# ② 带上 Token → 200,log-reader Role 允许 get pods
TOKEN=$(kubectl create token log-collector -n prod --duration=720h)
curl -k -H "Authorization: Bearer $TOKEN" \
  https://192.168.91.254:6443/api/v1/namespaces/prod/pods
# 返回 prod 下 Pod 列表 JSON ← 认证 + 授权都通过

# ③ 有 Token 但越权 → 403,认证过了,权限不够
curl -k -H "Authorization: Bearer $TOKEN" \
  -X DELETE \
  https://192.168.91.254:6443/api/v1/namespaces/prod/pods/some-pod
# {"status": "Failure", "message": "Forbidden", "code": 403}

三条命令串起完整认知:没 Token → 401(不知道你是谁)、有 Token → 200(认证+授权通过)、越权操作 → 403(认证通过但权限不够)。Token 管认证,Role 管授权,各干各的。

SA identity 格式速记system:serviceaccount:<ns>:<sa-name>。所有 SA 自动属于 system:serviceaccounts 这个 Group,可按此做统一授权。例如给所有命名空间的 SA 批量开只读:

sh 复制代码
subjects:
- kind: Group
  name: system:serviceaccounts
  apiGroup: rbac.authorization.k8s.io

五、总结

  • RBAC 三条链路缺一不可:身份(User/SA)→ 绑定(RoleBinding)→ 权限(Role),断任何一条都是 403。
  • 生产永远用 Group 授权:新人入职只改证书 O 字段,RBAC 一个字不用动;百人团队 RoleBinding 还是那几行。
  • SA 是程序的身份,不是人的替代品:人用 kubectl 走证书,程序调 API 走 SA Token,两回事。
  • 配完权限必须验证kubectl auth can-i 查集群内,curl + Token 查集群外,别猜。

中集小结:三个案例走完了从零搭建权限体系的完整流程。案例一理解三元组,案例二是生产真玩法------Group 授权新人入职只签发证书、RBAC 不用动,案例三讲程序身份 ServiceAccount------Pod 里的日志/监控采集器怎么安全调 K8s API。

📺 下集预告:「K8s RBAC 进阶避坑指南」------最小权限原则、聚合 ClusterRole、SA Token 1.24 变化、安全审计巡检、apiGroups 排坑、6 条踩坑经验 + 落地清单。