Kubernetes 认证与权限管理:从 kubeconfig 到 RBAC
开头
写 [[《Kubernetes 核心网络枢纽:Service 与 Ingress 完全指南》|Service 那篇]] 和 [[k8s中IPVS 三种负载均衡模式详解|IPVS 那篇]] 的时候,我一直在摆弄"流量怎么进集群"。这周方向反过来了:我在搭那个 AI-SRE 项目的 Agent,它得能自己看 Pod、看事件,异常了还能删掉重启。代码在本地 go run 跑得好好的——因为连的是我 Windows 家目录下的 ~/.kube/config。一旦打包成镜像扔进集群,Pod 日志第一行就是:
pods is forbidden: User "system:serviceaccount:default:default"
cannot list resource "pods" in API group "" in the namespace "default"
403 Forbidden。
我当时的第一反应特别朴素:“我本机 kubectl get pods 明明查得到啊,凭什么程序不行?”
然后就卡了一整晚。查资料时满屏的 Authentication、Authorization、Subject、RoleBinding、ClusterRole,每个词都认识,拼在一起就完全不知道谁先谁后、谁管谁。最要命的是我压根没意识到:我平时敲的 kubectl,和 Pod 里的程序,走的是两套完全不同的"身份通道"。kubectl 用的是我本机的证书,Agent 用的是它自己的 ServiceAccount——前者是我,后者是那个 Pod。
这篇就把 K8s 这套"谁能干什么"的机制,从 kubectl 一路捋到 RBAC,最后落到怎么给 Agent 配一套不越权的权限。
1. 为什么 Kubernetes 需要认证与授权
1.1 API Server 是唯一的大门
K8s 里所有操作,本质都是"跟 kube-apiserver 说话"。
kubectl、控制器进程、Pod 里的程序、外面的 CI 流水线,全都是 HTTP 客户端,请求打到同一个 API Server。它不是一个"可以绕开"的服务,而是整个集群的唯一入口——etcd 不对外,调度器不对外,kubelet 也不对外,只有它开着门。你所有的"操作集群",翻译到底层,都是一次对它的 HTTP 请求:
GET https://<apiserver>:6443/api/v1/namespaces/default/pods
搞清楚这一点,后面所有概念就有了落点:认证、授权、准入,全都发生在这一层。
1.2 为什么不能让任何人直接访问
因为 API Server 后面就是整个集群的命根子。
- 能
list pods,就看得到所有业务拓扑; - 能
get secrets,就拿得到数据库密码、镜像仓库凭据、各种 Token; - 能
delete deployments,一条命令就能把生产打挂。
kubectl 能通,不是因为它"在集群内部所以免检",而是因为你本机早就带着一份凭证了(下一章讲)。很多人以为 API Server 是"裸奔"的,其实它一直在验人——只是你从没注意过自己是怎么被验的。
还有一个更反直觉的点必须说清楚:Pod 里的程序也需要身份。 我们直觉上会觉得"都在集群内部了,应该算自己人吧",但在 K8s 看来,任何进程只要敲 API Server 的门,就得先自报家门。我的 Agent 想查 Pod,也得先证明它是谁——这正是我开头那个 403 的根。
1.3 认证、授权、准入:三道关卡
一个请求要过三道关,顺序不能乱:
请求
↓
Authentication 你是谁? → 认证失败:401 Unauthorized
↓
Authorization 你能干什么? → 授权失败:403 Forbidden
↓
Admission 这个操作合不合规矩?(配额、命名空间是否存在……)
↓
写入 etcd / 执行
分工是这样的:
- Authentication(认证):只负责确认身份,完全不判断权限。它只回答"你是不是你说的那个人"。
- Authorization(授权):拿认证出来的身份,去查"这个身份能不能做这件事"。我开头那个 403,就卡在这一关。
- Admission(准入控制):认证授权都过了,还要过一圈准入插件,比如校验 Namespace 是否存在、资源配额够不够、有没有违反 LimitRange。
请把 401 和 403 的区别背下来:
401= 我没认出你是谁(认证没过,凭证有问题);403= 我认出你了,但你没这个权限(认证过了,授权没过)。
这一条能省你半天时间。我那天一开始看到 403 还以为是自己 Token 没挂上,折腾半天才反应过来:认证早就成功了,日志里都写着"User system:serviceaccount:default:default"了——问题根本不在认证,在授权。方向对了,剩下就顺了。
2. K8s 访问控制的整体流程
这是整篇博客最重要的一张图,后面每一章都往这张图上挂:在这里插入图片描述](https://i-blog.csdnimg.cn/direct/29e3d98dc25c44eba2362003e0cda0f0.png)

记住一句话:认证在前,授权在后,准入垫底。 认证不认人,授权不放行,准入再补一刀。三层各管各的,互不代替。
3. Kubernetes 里的"身份"到底是什么
这一章回答一个问题:
Kubernetes 到底在给"谁"做认证?
K8s 里能被认证的身份有两类:User 和 ServiceAccount。它俩看着像,用起来天差地别,是整个授权模型的起点。
3.1 User
- 面向人,或者人背后的外部系统;
- 典型的比如:你用
kubectl时的 IT 管理员身份、CI 里的发布账号。
这里有个特别容易搞混的点:Kubernetes 里根本没有"User"这个 API 对象。 你 kubectl get users 会直接报错——没有这个资源。
为什么?因为 K8s 把"用户从哪来"这件事交给了外部:
- 用客户端证书认证时,证书里的
CN(Common Name)就是用户名; - 用 OIDC 时,用户名来自身份提供方(比如公司 SSO)的 token;
- K8s 自己不存用户、不管密码、不给你创建账号。
所以你可以理解为:User 是"外面的人",K8s 只是在请求里认出他是谁,然后当成一个字符串用。 这个字符串后面在 RBAC 里会作为"Subject"出现。
3.2 ServiceAccount
- 面向 Pod / 程序,是给机器用的身份;
- 和 User 不一样,ServiceAccount 是实打实的 Kubernetes API 对象,归 K8s 自己管。
apiVersion: v1
kind: ServiceAccount
metadata:
name: sre-agent-sa
namespace: default
它有 Namespace(属于某个命名空间)、能 kubectl get sa 查到、能像别的对象一样被创建删除。Pod 只要在 spec.serviceAccountName 里写上名字,就以这个身份运行了。
3.3 User 和 ServiceAccount 的区别
把这张表贴在心里,后面看 RBAC 的 Subject 就不会晕:
| User | ServiceAccount | |
|---|---|---|
| 面向 | 人 / 外部身份 | Pod / 程序 |
| 是不是 K8s 对象 | 不是(外部管理) | 是(API 对象) |
| 有没有 Namespace | 没有 | 有 |
| 常见认证方式 | 客户端证书 / OIDC | Token |
| 典型使用场景 | kubectl、人工操作 | client-go、Controller、Agent |
| K8s 内如何表示 | 一个字符串(如证书 CN) | system:serviceaccount:<ns>:<name> |
注意最后一行——ServiceAccount 在 RBAC 里的"用户名"是一长串 system:serviceaccount:default:sre-agent-sa。我排错时就是被这一长串救了命:日志里那句 cannot list ... User "system:serviceaccount:default:default",直接告诉我"哦,用的是 default 命名空间下的 default 这个 SA"。身份没配对,就是它。
4. kubectl 是怎么完成认证的
这一章解释我开头那个疑惑:凭什么本机 kubectl 能查,Pod 里的程序不能?答案就在"你本机带了什么"里面。
4.1 kubectl get pods 背后发生了什么
你敲下:
kubectl get pods
实际上发生的是:
kubectl
│
│ 读取 ~/.kube/config(或 $KUBECONFIG 指定的文件)
▼
kubeconfig
│
├── API Server 地址
├── CA 证书(用来验服务端是不是真的)
├── client certificate(我的身份)
└── client key(我的私钥)
│
▼
kube-apiserver
│
验证证书 → 认出"你是谁"
kubectl 自己没有身份。它每次都在翻一个配置文件,把里面的证书附在 HTTPS 请求上,发给 API Server。API Server 验完证书,就知道"哦,是集群管理员来了"。
这个文件,就是 kubeconfig。
4.2 kubeconfig 是什么
它长这样(结构,不是完整内容):
apiVersion: v1
kind: Config
clusters: # 去哪里
- name: minikube
cluster:
server: https://127.0.0.1:6443
certificate-authority-data: <CA 的 base64>
users: # 我是谁
- name: minikube
user:
client-certificate-data: <我的证书 base64>
client-key-data: <我的私钥 base64>
contexts: # 把"去哪里"和"我是谁"绑一起
- name: minikube
context:
cluster: minikube
user: minikube
namespace: default
current-context: minikube # 当前用哪一套
三个核心块,我用自己的话记:
cluster = 去哪里(API Server 地址 + 服务端 CA)
user = 我是谁(客户端证书 + 私钥,或 Token)
context = 把 cluster 和 user 配对,顺便带一个默认 namespace
为什么要分三层?因为现实里你会有多个集群、多个身份:测试集群用只读账号,生产集群用管理员账号。context 就是"预设组合",kubectl config use-context prod-admin 一敲,等于切换整套"去哪 + 是谁"。
顺手记两个命令:
kubectl config view # 看结构,敏感字段默认打码
kubectl config view --raw # 连证书内容一起看(小心,会暴露私钥)
kubectl config current-context # 我现在用的是哪个 context
4.3 客户端证书认证:机制在这里
kubeconfig 里那对 client-certificate / client-key,就是客户端证书认证。它的机制说起来很简单:
client certificate ┐
client private key ├─→ 附在请求上 ─→ kube-apiserver
CA ┘ │
用 CA 验证这张证书是不是自己签的
│
看懂证书里的 CN / O
关键在于,证书里有两个字段会被 API Server 直接"翻译"成身份:
CN(Common Name)→ 用户名(User)O(Organization)→ 用户所属的组(Group)
也就是说,认证这一关根本不需要查数据库——证书是 CA 签的,只要签名有效,K8s 就相信里面的 CN/O 是真的。这也是为什么证书认证这么快、这么"无状态"。
比如一个证书如果 CN=admin、O=system:masters,那 API Server 就认为"来了个叫 admin 的用户,属于 system:masters 这个组"——而 system:masters 组在 K8s 里是超级管理员(它绑定到了 cluster-admin 这个内置 ClusterRole,后面会见到)。
想看自己 minikube 的证书里到底写了啥:
# --raw 把 base64 解出来,再用 openssl 看证书主体
kubectl config view --raw -o jsonpath='{.users[0].user.client-certificate-data}' \
| base64 -d | openssl x509 -noout -subject
# subject=O=system:masters, CN=minikube-user
看到 O=system:masters 了吗?这就是为什么我本机 kubectl 有无限权限——不是集群没设防,是我揣着一张"超级管理员工作证"。而 Pod 里的 Agent 用的是另一个身份(ServiceAccount),权限要单独给。两套通道,各论各的。
5. Pod 是怎么完成认证的
这一章直接结合我的 SRE Agent 来讲。
5.1 为什么 Pod 也需要身份
Agent 里有一句典型的 client-go 代码:
pods, err := clientset.CoreV1().Pods("default").List(ctx, metav1.ListOptions{})
翻译成 HTTP,就是:
SRE Agent Pod
│
│ GET /api/v1/namespaces/default/pods
▼
kube-apiserver
API Server 收到这个请求,第一件事就是问:
这个请求是谁发出来的?
它不会因为"请求来自集群内部"就放行。集群内部一样是威胁面——一个被攻破的 Pod,如果默认就有权限,那整个集群就完了。所以每个 Pod 想调 API,都得先绑定一个身份。
5.2 ServiceAccount:Pod 的身份
在 Deployment 里指定:
spec:
template:
spec:
serviceAccountName: sre-agent-sa # ← 这个 Pod 以谁的身份运行
containers:
- name: sre-agent
image: sre-agent:v1
于是这个 Pod 的身份链路就成立了:
SRE Agent Pod
│
│ 绑定 serviceAccountName
▼
sre-agent-sa
│
│ 自动获得 Token
▼
kube-apiserver
这里有个坑我踩过:如果你不写 serviceAccountName,Pod 会用所在 Namespace 里一个叫 default 的 ServiceAccount。 而 default 这个 SA 默认几乎没有任何权限(就是开头日志里那个 system:serviceaccount:default:default)。所以"忘记指定 SA"和"指定了但没绑权限",报错长得一模一样,都是 403。
5.3 Pod 里的 Token 到底放在哪
K8s 会把 ServiceAccount 的凭证挂成文件塞进容器里,固定路径:
kubectl exec -it <sre-agent-pod> -- ls /var/run/secrets/kubernetes.io/serviceaccount/
# ca.crt namespace token
三个文件,各有用途:
| 文件 | 作用 |
|---|---|
token | 证明"我是这个 SA"(Bearer Token) |
ca.crt | 验证 API Server 的证书是不是真的 |
namespace | 当前 Pod 所在的命名空间 |
(顺带一提,从 K8s 1.24 开始,这个 token 不再是以前那种"永久有效、存在 Secret 里"的了,而是短期、会自动轮换的投影 token(projected token),有过期时间、绑定到具体 Pod/audience。老教程里那些"去 Secret 里找 token"的写法,现在要么找不到,要么不安全,别照抄。)
5.4 InClusterConfig():一行代码搞定
那为什么我的 Go 代码里,Pod 里不用手动配 kubeconfig 就能连上?因为 client-go 有个专门的入口:
config, err := rest.InClusterConfig()
if err != nil {
panic(err.Error())
}
clientset, err := kubernetes.NewForConfig(config)
InClusterConfig() 干的活,就是帮你把上面那套东西自动拼成一份 kubeconfig 等价物:
容器环境变量 KUBERNETES_SERVICE_HOST / KUBERNETES_SERVICE_PORT
+
/var/run/secrets/kubernetes.io/serviceaccount/token
+
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
│
▼
InClusterConfig()
│
▼
一份"内存里的 kubeconfig"→ clientset
对比一下就很清楚了:
- 本机
kubectl:读~/.kube/config文件 → 用你个人的证书; - Pod 里的程序:
InClusterConfig()读环境变量 + 挂载文件 → 用 Pod 的 ServiceAccount Token。
两套通道,两套身份。这也是我开头那个疑问的完整答案:本机能查是"我这个人有权限",Pod 不能查是"这个 SA 没权限"——压根不是一回事。
6. Authorization:认证成功之后怎么办
先用一句话建立概念:
认证解决"你是谁",授权解决"你能干什么"。
认证通过后,K8s 拿到一个身份(比如 system:serviceaccount:default:sre-agent-sa),然后开始逐条问:
sre-agent-sa
│
▼
能不能:
get pods ? ← 看单个 Pod 详情
list pods ? ← 列出 Pod 列表(watch / informer 也要它)
delete pods ? ← 删 Pod(Agent 自愈要靠它)
get secrets ? ← 拿密钥(危险,通常不给)
每一项"能做 / 不能做",都是一次授权判断。
K8s 支持好几种授权模式(Node、RBAC、Webhook、ABAC、AlwaysAllow……),但绝大多数集群默认、且推荐用的是 RBAC——基于角色的访问控制。后面的章节全是围绕它。你会在 kube-apiserver 的启动参数里看到 --authorization-mode=Node,RBAC 这样的配置。
7. RBAC 权限模型
这是整篇的核心章节。RBAC 全称 Role-Based Access Control,翻译过来就是"基于角色的访问控制",四个概念拼起来:
Subject(主体:谁)
│
│ Binding(绑定:把他和权限连起来)
▼
Role / ClusterRole(角色:一份权限清单)
│
▼
Rules(规则:具体到 对什么资源 做 什么动作)
拆开一个一个看。
7.1 Rule:权限的最小单位
规则就是"对某类资源,允许做某些动作"。最典型的一条:
rules:
- apiGroups: [""] # 哪个 API 组
resources: ["pods"] # 哪种资源
verbs: ["get", "list", "watch"] # 允许哪些动作
三个字段,是 RBAC 里最容易晕的地方,我一个个拆:
apiGroups(API 组):K8s 的资源按"组"分类。核心资源(pods、services、configmaps)在核心组里,写法是空字符串 ""——这个特别反直觉,我第一次看到 apiGroups: [""] 以为是写漏了。
- 核心组:
""(pods、services、configmaps、secrets、events、nodes…) - 批处理:
batch(jobs、cronjobs) - 应用:
apps(deployments、statefulsets、daemonsets) - RBAC 自己:
rbac.authorization.k8s.io(roles、rolebindings…)
怎么查某个资源的 apiGroup?kubectl api-resources 一列就清楚了:
kubectl api-resources | grep -E "^(pods|deployments|jobs)"
# pods v1 true Pod [""]
# deployments apps true Deployment
resources(资源):就是对什么东西生效,如 pods、deployments、secrets。注意还有子资源,写法是 资源/子资源,比如 pods/log(看日志)、pods/exec(进容器)。我的 Agent 要看日志排障,就得单独给 pods/log 的权限——给 pods 权限并不自动包含 pods/log,这一点坑过我。
verbs(动作):能干什么。常见的有:
| verb | 含义 |
|---|---|
get | 查单个对象 |
list | 列出对象集合 |
watch | 持续监听变化(informer 必需) |
create | 创建 |
update | 修改 |
patch | 局部修改 |
delete | 删除 |
deletecollection | 批量删除 |
重点:
list和watch是分开的!client-go的 informer 机制靠watch长连接监听变化,所以只要你的程序用了 informer,就必须同时给list+watch,光给list会一直报错。我一开始只给了get,结果 Agent 里 list 直接 403,因为"看一个"和"看一堆"是两个不同的动词。
7.2 Role:一份"限定命名空间"的权限清单
Role 就是把上面的 rules 打包,起个名字:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: sre-agent-role
namespace: default # ← 关键:Role 属于某个命名空间
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
记住这条,这是 Role 和 ClusterRole 的分水岭:
Role 是命名空间级的。 上面这个 Role 即使绑给某个身份,那个身份也只能在
default命名空间里看 Pod。换个命名空间,权限就没有了。
想让它跨命名空间?要么在每个命名空间都建一份,要么用 ClusterRole。
8. RoleBinding:把身份和权限连起来
这是初学者最容易混的地方,我必须强调一遍:光创建 Role 是没有任何效果的。
Role
↓
只是一份"权限定义"(一张写好的菜单,还没给任何人)
你还得再写一个 RoleBinding,把"身份"和"Role"连起来:
ServiceAccount + Role
│
▼
RoleBinding("把这份菜单给这个人")
完整写法:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: sre-agent-rolebinding
namespace: default
subjects: # 给谁(可以多个)
- kind: ServiceAccount # 主体类型:ServiceAccount / User / Group
name: sre-agent-sa
namespace: default # ServiceAccount 必须带上它的命名空间
roleRef: # 给什么权限
kind: Role # 引用 Role 还是 ClusterRole
name: sre-agent-role
apiGroup: rbac.authorization.k8s.io
两个 block,一个"给谁",一个"给什么":
subjects:主体。kind可以是ServiceAccount、User、Group。roleRef:引用哪个 Role/ClusterRole。
这里坑最多,我列三条:
roleRef里的apiGroup必须是rbac.authorization.k8s.io,不是空字符串。写错了报错很模糊。roleRef是不可变的。 RoleBinding 建好之后,你不能改它引用的 Role——想换角色只能删了重建。subjects里 ServiceAccount 的namespace不能省。 同名 SA 在不同命名空间是不同的身份,K8s 需要这个字段才能唯一定位。
最终的连接关系:
ServiceAccount(sre-agent-sa)
│
│ RoleBinding
▼
Role(sre-agent-role)
│
▼
get / list / watch pods
一句话记忆:Role 是"权限清单",RoleBinding 是"发放动作"。两者缺一不可。
9. ClusterRole 与 ClusterRoleBinding
那为什么 K8s 不干脆只用 Role?因为有两类东西压根不属于任何命名空间。
9.1 Role 的边界
上一节说了,Role 是命名空间级的,当它绑到一个身份上时,也只在那个命名空间里生效。
9.2 ClusterRole:集群范围的权限定义
ClusterRole 和 Role 结构几乎一样,但它没有命名空间,可以用来描述两种东西:
- 集群级资源(本来就没有命名空间的):
nodes、namespaces、persistentvolumes…… - 所有命名空间里的资源:比如"允许在全部命名空间里 list pods"。
我的 Agent 要读节点状态(判断是不是节点压力导致的故障),Node 是集群级资源,只能靠 ClusterRole:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sre-agent-node-reader # 注意:没有 namespace 字段
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
9.3 ClusterRoleBinding:把身份绑定到 ClusterRole
对应地,绑定也得用集群级的 ClusterRoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sre-agent-node-reader-binding
subjects:
- kind: ServiceAccount
name: sre-agent-sa
namespace: default
roleRef:
kind: ClusterRole
name: sre-agent-node-reader
apiGroup: rbac.authorization.k8s.io
最终形成这张全景图,看到它就不会混了:
User / ServiceAccount
│
▼
Binding ←── RoleBinding(命名空间内) / ClusterRoleBinding(整个集群)
│
▼
Role / ClusterRole ←── Role 只能在命名空间内;ClusterRole 可以集群级
│
▼
Rules(apiGroups / resources / verbs)
一个高频面试/实战点:RoleBinding 也能引用 ClusterRole。效果是——把 ClusterRole 里定义的权限,只授予到 RoleBinding 所在的命名空间内。这在实践里非常有用:定义一份通用的 ClusterRole(比如"只读 pods"),然后在每个命名空间用 RoleBinding 引它,权限就被"局部化"了,不会一给就给全集群。反过来,ClusterRoleBinding 一用就是全集群生效,慎用。
10. 实战:给 SRE Agent 配置 RBAC
这一章是整篇的实战部分。目标很明确——我的 Agent 只需要:
list pods 列出 Pod
get pods 查看单个 Pod 详情
watch pods 实时感知 Pod 变化(informer)
get pods/log 拉异常 Pod 的日志
list events 看事件(排查 CrashLoop 的关键)
get/list nodes 看节点状态(判断是不是节点问题)
注意我故意没给 delete pods。Agent 目前只做"观察 + 建议",删除动作走人工确认,这是下一章"最小权限"的伏笔。
10.1 第一步:创建 ServiceAccount
# sa.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: sre-agent-sa
namespace: default
kubectl apply -f sa.yaml
kubectl get sa sre-agent-sa
10.2 第二步:定义 Role(命名空间级权限)
# role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: sre-agent-role
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"] # 子资源:看日志要单独授权
verbs: ["get"]
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list"]
kubectl apply -f role.yaml
10.3 第三步:定义 ClusterRole(节点状态)
# clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sre-agent-node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
kubectl apply -f clusterrole.yaml
10.4 第四步:两个 Binding,把权限发出去
# rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: sre-agent-rolebinding
namespace: default
subjects:
- kind: ServiceAccount
name: sre-agent-sa
namespace: default
roleRef:
kind: Role
name: sre-agent-role
apiGroup: rbac.authorization.k8s.io
---
# clusterrolebinding.yaml(可以放同一个文件,用 --- 分隔)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sre-agent-node-reader-binding
subjects:
- kind: ServiceAccount
name: sre-agent-sa
namespace: default
roleRef:
kind: ClusterRole
name: sre-agent-node-reader
apiGroup: rbac.authorization.k8s.io
kubectl apply -f rolebinding.yaml
10.5 第五步:让 Deployment 用这个 SA
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: sre-agent
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: sre-agent
template:
metadata:
labels:
app: sre-agent
spec:
serviceAccountName: sre-agent-sa # ← 关键:绑定身份
automountServiceAccountToken: true # 默认就是 true,写出来更清楚
containers:
- name: sre-agent
image: sre-agent:v1
kubectl apply -f deployment.yaml
kubectl get pods -l app=sre-agent
(如果某个 ServiceAccount 根本不需要访问 API,把它设成 automountServiceAccountToken: false,能少一个被利用的凭证——这是安全实践。)
10.6 第六步:验证权限(这一步最关键)
kubectl auth can-i 是授权的"照妖镜",它直接模拟某个身份的授权判断,不用真的跑程序:
# 语法:kubectl auth can-i <动词> <资源> --as=<身份>
kubectl auth can-i get pods \
--as=system:serviceaccount:default:sre-agent-sa
# yes
kubectl auth can-i list pods \
--as=system:serviceaccount:default:sre-agent-sa
# yes
kubectl auth can-i get pods/log \
--as=system:serviceaccount:default:sre-agent-sa
# yes
kubectl auth can-i list nodes \
--as=system:serviceaccount:default:sre-agent-sa
# yes
kubectl auth can-i delete pods \
--as=system:serviceaccount:default:sre-agent-sa
# no ← 正是我们想要的:Agent 不能删 Pod
kubectl auth can-i get secrets \
--as=system:serviceaccount:default:sre-agent-sa
# no ← 更不能拿密钥
看到 yes 和 no 交替出现,这套 RBAC 就活了:允许的给 yes,不该给的给 no。 那个 no 比 yes 还重要——它是"最小权限"落地的证据。
再补一个更爽的,一次性列出这个身份的所有权限:
kubectl auth can-i --list \
--as=system:serviceaccount:default:sre-agent-sa
它会列出一张表,包括从 system:authenticated、system:serviceaccounts 这些默认组继承来的权限。看这张表能发现很多"意料之外的权限",我在排错时靠它就揪出过一个问题。
10.7 真机验证:看 Pod 里到底挂没挂上
前面都是"模拟"。最后进 Pod 里确认真实环境:
kubectl exec -it <sre-agent-pod> -- ls /var/run/secrets/kubernetes.io/serviceaccount/
# ca.crt namespace token
# 直接用这个 token 打一次 API,看看返回码
kubectl exec -it <sre-agent-pod> -- sh -c '
curl -s -o /dev/null -w "%{http_code}\n" \
--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/default/pods'
# 200 ← 通了
看到 200,说明整条链路(SA → Token → RBAC → 授权通过)都通了。
(补一个 1.27+ 才有的小工具,能直接问"我是谁":)
kubectl auth whoami \
--as=system:serviceaccount:default:sre-agent-sa
# ATTRIBUTE VALUE
# Username system:serviceaccount:default:sre-agent-sa
# Groups [system:serviceaccounts system:serviceaccounts:default system:authenticated]
注意那个 Groups——ServiceAccount 自动属于 system:serviceaccounts 和 system:serviceaccounts:default 两个组。这意味着你可以给整个"命名空间里所有 SA"或"全集群所有 SA"批量授权(通过给这些组绑 Role),但这也是个风险点,具体给权限时得想清楚。
11. Kubernetes 权限排错
这一章对 SRE 方向特别有用,因为线上最常见的报错,就是程序抛一个 403 Forbidden。
11.1 先区分 401 和 403
拿到报错,第一步不是乱试,是先看是 401 还是 403:
- 401 Unauthorized:认证没过。凭证本身有问题——Token 没挂上、证书过期、kubeconfig 写错。
- 403 Forbidden:认证过了,授权没过。身份是对的,就是没权限。
我的那个坑是 403,日志里明确写着 User "system:serviceaccount:default:default"——这行字就是答案:身份是 default SA,不是我以为的 sre-agent-sa。 如果你看到 403 里出现的身份跟你预期的 SA 不一样,那问题在"绑定"环节,不在"授权"环节。
11.2 403 的排查链路
我总结成一条从上到下的检查链,出 403 就顺着往下走:
403 Forbidden
↓ 认证成功了吗?(401 才是认证问题,403 说明已经认出你了)
↓ 身份是谁?(看报错里的 User "...")← 先看这一行!
↓ ServiceAccount 对吗?(Pod 的 serviceAccountName 写了吗?拼对了吗?)
↓ RoleBinding 存在吗?(是不是只建了 Role 没建 Binding?)
↓ Role 引用对吗?(roleRef 指向的 Role 名字、apiGroup 对不对?)
↓ resource 对吗?(是 pods 还是 pods/log?子资源要单独授权!)
↓ verb 对吗?(要 list/watch 只给了 get?)
↓ namespace 对吗?(Subject 的 namespace 和 Role 的 namespace 对不对得上?)
这条链子基本能覆盖 90% 的 403。剩下的 10% 才需要翻 kube-apiserver 的审计日志。
11.3 三个救命命令
第一个:kubectl auth can-i——模拟判断,不用真跑程序。
kubectl auth can-i get pods \
--as=system:serviceaccount:default:sre-agent-sa
第二个:看 Pod 实际用的是哪个 SA——这是我最常用的排查第一步,因为它能直接回答"身份对不对"。
kubectl get pod <pod-name> -o jsonpath='{.spec.serviceAccountName}'
# 如果输出 default,那基本就是没指定 SA 了
第三个:看 RoleBinding 到底绑了谁——确认"发放"环节有没有问题。
kubectl describe rolebinding sre-agent-rolebinding
# Role: Role/sre-agent-role
# Subjects:
# Kind Name Namespace
# ServiceAccount sre-agent-sa default
Subjects 那几行是不是你想要的绑定,一眼就能看出来。
11.4 进阶:翻 API Server 的日志
如果 can-i 说 yes,但程序还是 403,那说明有一层"你以为的权限"和"实际生效的权限"不一致。这时候去看 kube-apiserver 的日志和审计记录:
# 找到 apiserver 的 Pod
kubectl get pods -n kube-system | grep apiserver
# 翻日志里的 Forbidden(minikube 环境)
kubectl logs -n kube-system <apiserver-pod> | grep -i forbidden
审计日志(如果开了 --audit-policy-file)里,会精确记录每次请求的身份、资源、动词、以及被哪条规则拒绝。这是终极武器,一般用不上,但知道它在哪。
12. 最小权限原则
最后一章讲安全实践。这是整篇的落点,也是为什么我前面死活不给 Agent delete pods。
反面教材长这样:
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
这三个星号一写,等于把整个集群交出去了——能看所有密钥、能删所有东西。搜索"K8s 被挖矿"的案例,一堆都是因为某个工作负载的 SA 绑了 cluster-admin 或者通配符权限,被打了之后横向提权,直接把整个集群端掉。
正确姿势是:
SRE Agent
↓
只需要
↓
get / list / watch pods
get pods/log
get / list events
get / list nodes
即:
程序需要什么权限,就只授予什么权限。多一个都不给。
几条具体的原则,我记在笔记里:
-
不用通配符。
verbs: ["*"]、resources: ["*"]一律拆细。 -
secrets是最危险的资源。 能get secrets基本等于能拿到一切凭证,非必要绝对不给。有些安全策略会专门禁止授权 secrets。 -
优先
get,慎给list。list能一次拿到全部对象,信息暴露面远大于get单个。 -
能用 Role 就别用 ClusterRole,能用 RoleBinding 就别用 ClusterRoleBinding。 范围越小越安全。需要 ClusterRole 时,优先用 RoleBinding 去引用它,把权限限制在单个命名空间。
-
给机器身份建独立的 SA,别共用
default。 一个 SA 一个用途,出了问题好定位、好回收。 -
能用
resourceNames就限定到具体对象。 比如只允许操作某一个特定的 ConfigMap:rules: - apiGroups: [""] resources: ["configmaps"] resourceNames: ["my-agent-config"] verbs: ["get", "update"]这样即使身份泄露,能碰的也只有那一个对象。
-
Agent 的"写操作"要人审。 我的设计里 Agent 只做观察和建议,真正的修复动作(删 Pod、改副本数)走人工确认或单独的、权限更大的 SA,跟只读的 Agent 隔开。
13. 总结:把认证授权串起来
最后用一张图收尾,这张图能背下来,K8s 的认证授权就通了:
Kubernetes API 请求
│
▼
kube-apiserver
│
▼
┌─────────────────┐
│ Authentication │ ← 你是谁?
│ 认证 │ 失败 = 401
└────────┬────────┘
│
┌───────────┴───────────┐
│ │
User ServiceAccount
│ │
kubeconfig / 证书 Token
(CN=用户名 O=组) (挂载在 Pod 里)
│ │
└───────────┬───────────┘
▼
┌─────────────────┐
│ Authorization │ ← 你能干什么?
│ RBAC │ 失败 = 403
└────────┬────────┘
│
▼
Role / ClusterRole (权限清单)
│
▼
RoleBinding / (发放动作)
ClusterRoleBinding
│
▼
允许 / 拒绝
│
▼
Kubernetes API
一句话浓缩:
认证管"你是谁":kubectl 靠证书,Pod 靠 ServiceAccount Token
授权管"你能干什么":RBAC 说了算
RBAC 四件套:Subject --Binding--> Role/ClusterRole --Rules--> 具体权限
Role 管一个命名空间,ClusterRole 管集群级
Role 和 RoleBinding 必须成对出现,只建 Role 等于没建
排错先看 401 还是 403,再看报错里的 User 是谁
最后一条:只给需要的那点权限
我踩过的坑合集
| 坑 | 我的翻车经历 | 正确做法 |
|---|---|---|
Pod 里 403,但本机 kubectl 正常 | 以为程序有 bug,其实两套身份通道 | Pod 有独立 SA,权限要单独配,别拿本机权限类比 |
| 看到 403 却去查 Token | 折腾半天,其实认证早过了 | 先看报错里的 User "...",403 是授权问题不是认证问题 |
没指定 serviceAccountName | 报错里出现 system:serviceaccount:default:default | 显式声明 SA;看到 default:default 就知道漏配了 |
| 只建了 Role 没建 RoleBinding | can-i 一直 no,百思不解 | Role 和 Binding 必须成对,Role 单建不生效 |
只给 get 却报 list 403 | informer 要 list + watch | "看一个"和"看一堆"是不同动词,按需给全 |
给 pods 权限但读不了日志 | 以为 pods 权限含日志 | pods/log 是子资源,要单独授权 |
| 去老教程里翻 Secret 找长 Token | 1.24 后不再自动创建 | 新版本用投影 Token,kubectl create token <sa> 也能临时签 |
| 想给"整个集群只读"却写了 Role | Role 只在一个命名空间生效 | 集群级用 ClusterRole + ClusterRoleBinding |
roleRef.apiGroup 写成空字符串 | apply 报错信息很模糊 | 必须写 rbac.authorization.k8s.io |
最后说两句
写这篇的过程,我反复在 minikube 里 kubectl apply、kubectl auth can-i、kubectl describe rolebinding,中间有好几次 can-i 明明该 yes 却返回 no,全靠 describe 一项一项对才找出来。那天晚上我最大的收获其实不是记住了几个字段,而是终于建立了一个清晰的心智模型:任何一次 API 请求,先在脑子里过一遍"它是以谁的身份发的?这个身份有什么权限?",90% 的权限问题当场就能定位。
还没完全搞懂的地方:Admission(准入控制) 我只知道有这一层,但具体的内置准入插件、以及怎么用 webhook 写自定义准入,完全没碰过——这块其实是 K8s 安全的另一大半。还有 OIDC 认证,我只知道它是"接公司 SSO",真实怎么配、怎么和 RBAC 的 User/Group 对起来,也只是概念。审计日志 我翻过几次,但怎么根据审计日志做权限收敛(找出长期没人用的权限并回收),还没实践。
下一步打算:把 Agent 的 SA 权限真正做到最小化——先用 kubectl auth can-i --list 把当前权限全列出来,再对着 Agent 的实际调用(client-go 里到底调了哪些 API)逐条裁剪;然后抽时间搭一个 OIDC,用一张自签证书把"User 证书认证"这条链路自己手搓一遍,彻底把 CN/O → 用户/组的映射跑通。
对了,回看 [[有关docker Namespace原理]]:当时我理解"Namespace 是隔离资源视图",而这篇里 Namespace 又成了 RBAC 权限的边界——同一个词,在隔离和权限两个层面各出现了一次,K8s 真是把"边界"这个概念用到了极致。从 [[《Kubernetes 核心网络枢纽:Service 与 Ingress 完全指南》|Service 那篇]] 的"流量怎么进",到这周的"身份怎么过",集群内部那点事,好像慢慢连成一张网了。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/Zoeww_0319/article/details/166735417




