在项目里被问得最多的K8s问题除了Pod起不来大概就是“为什么我明明有kubeconfig但kubectl get pods 却报错”。这话我听着太耳熟了。某次测试同事拿着配好的kubeconfig来求助说看Pod报错管理员说权限已经给了结果我一看授权对象写的是Group名但证书里的O字段根本没填用户组导致RBAC匹配不上。一个很小的配置问题却卡了好几个人一下午。其实K8s的访问控制到现在依然是很多团队的盲区个人环境里沾着管理员证书随便用没感觉一旦多人共用集群、要接入CI/CD、要给外部系统开放API就绕不开身份认证和权限授权。这篇文章就围绕API Server的认证机制和RBAC授权体系讲讲我实际配置权限、排查问题时的完整思路和踩坑记录。适合正在规划集群权限的人也适合那些被403和401反复折磨的排障选手。1. K8s访问控制全景一个请求进入API Server后要过哪几关1.1 三层关卡认证、授权、准入控制先说全景。任何客户端——不管是kubectl、Controller Manager、Scheduler还是Pod里的业务代码——只要想访问API Server请求都要依次通过三层检查认证Authentication、授权Authorization、准入控制Admission Control。名字听起来很接近但职责完全不同混在一起理解是排错最大的障碍。认证要回答“你是谁”它把请求里携带的证书、Token等凭证解析成一个身份。这个身份在K8s语境下通常包含username、uid、groups以及一些额外字段。认证层不负责判断这个身份能不能干活。授权要回答“你能不能做这件事”它拿认证得到的身份去匹配RBAC规则看这个身份对某个资源是否有指定操作的权限。这件事必须在认证通过之后才可能发生否则连身份都没有授权无从谈起。准入控制在很多文章里被一笔带过但它是第三层。请求通过了认证和授权已经被计划为一次资源变更此时会被一系列准入控制插件依次检查比如资源配额、Pod安全策略、命名空间是否存在等。它可以修改请求也可以拒绝请求。实际排错时如果只盯着授权很容易忽略准入层。比如有些请求返回的不是“forbidden”而是“exceeded quota”这其实是配额不足导致的拒绝不是权限问题。三层的关系也可以用一个生活中的例子来对照进小区大门先刷门禁卡认证门禁识别出你是业主还是访客进单元楼时再看你有没有这栋楼的钥匙授权最后进门后物业规定不能带大件物品上楼准入控制。每一步拦下的理由都不一样排查时得先分清到底被哪一步拦了。1.2 kubeconfig是如何串起认证过程的日常接触最多的认证入口就是kubeconfig文件。一个完整的kubeconfig由clusters、contexts、users三块组成clusters保存API Server地址和CA证书users保存客户端证书或Token凭证contexts把某个用户和某个集群组合在一起还可以指定默认命名空间。执行kubectl时它会读取current-context指向的那段配置拿到对应的cluster地址和user凭证再发起HTTPS请求。日常切换命名空间、切换集群改的都是context。很多人以为kubectl配置只需要一个文件就够了实际上同一台机器上可以同时放十几个kubeconfig只要每次通过--kubeconfig或KUBECONFIG环境变量指定就行。这里有个细节容易被忽略kubeconfig里的user字段并不仅仅是“账号名”而是整个客户端身份的载体。如果是客户端证书认证服务端会从证书里解析出CN字段作为用户名O字段作为用户所属的组。比如一个证书的CNdev-user、Odevelopers认证层就会认为这是用户名dev-user、属于developers组的用户。这一段理解和RBAC配置直接相关RBAC的subjects里写User时名字必须和证书里的CN一致写Group时必须和证书里的O一致。否则就是“证书都校验通过了但RBAC怎么都匹配不上”的诡异问题。我见过有人把CN写成了邮箱地址又把RBAC里的User写成工号两边怎么都对不上排查了很久才发现是身份标识不一致。2. API Server认证机制拆解客户端如何自证身份2.1 客户端证书认证与kubeconfig生成生产环境最常用的认证方式是X509客户端证书。API Server通过--client-ca-file参数指定CA证书所有请求的客户端证书都拿这把CA去验签验签通过后从证书里提取CN和O形成用户身份。给某个用户签发客户端证书的完整操作我一般都是这样做的。第一步用openssl生成私钥和CSRCN填用户名O填用户组名。组这个字段非常重要因为后面批量授权时按组授权比按用户授权方便得多。openssl genrsa -out dev-user.key 2048 openssl req -new -key dev-user.key -out dev-user.csr -subj /CNdev-user/Odevelopers然后用集群的CA证书给CSR签名。集群CA通常在各控制节点的/etc/kubernetes/pki/目录下openssl x509 -req -in dev-user.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out dev-user.crt -days 365这里要提醒一个坑某些环境下API Server用到的CA不一定是/etc/kubernetes/pki/ca.crt比如用了外部PKI或者云厂商托管集群必须从apiserver的启动参数里确认。搞错CA会导致客户端证书看起来有效但服务端验证永远失败而且报错信息很不直观只会说“certificate signed by unknown authority”。拿到证书和私钥之后把它们写进一套新的kubeconfigkubectl config set-cluster my-cluster \ --serverhttps://192.168.1.10:6443 \ --certificate-authorityca.crt \ --kubeconfigdev-kubeconfig kubectl config set-credentials dev-user \ --client-certificatedev-user.crt \ --client-keydev-user.key \ --kubeconfigdev-kubeconfig kubectl config set-context dev-context \ --clustermy-cluster \ --userdev-user \ --namespacedev \ --kubeconfigdev-kubeconfig kubectl config use-context dev-context --kubeconfigdev-kubeconfig这样就给dev-user准备了一套独立的kubeconfig。它能不能执行有效操作取决于后面的RBAC给了多少权限。很多刚上手的人在这里会犯一个错误觉得能通过认证就等于有权限于是拿着证书到处试发现还是各种403就开始怀疑集群坏了。其实认证和授权本来就是两件事。2.2 ServiceAccount与JWT Token的工作方式另一个高频认证场景是Pod内部访问API Server。比如一个服务要动态读取同命名空间下的ConfigMap或者一个Job要调用K8s API创建其他资源这种时候不会用客户端证书而是用ServiceAccount。ServiceAccount本质上就是K8s给“机器”用的身份账户可以理解成一台Pod专用的门禁卡。每个命名空间都会有一个默认的default ServiceAccountPod如果不显式指定serviceAccountName就会挂载这个默认账户。Pod启动时K8s会把对应的Token文件、CA证书以及命名空间信息通过投影卷挂载到容器里路径通常在/var/run/secrets/kubernetes.io/serviceaccount/。从K8s 1.24开始默认不再自动创建长期有效的ServiceAccount SecretToken通过TokenRequest API动态下发路径为/var/run/secrets/kubernetes.io/serviceaccount/token。这个Token是绑定了受众、过期时间等信息的JWT有有效期到期后由kubelet自动轮换。容器里的SDK比如client-go会自动处理Token的读取与刷新所以写业务代码时几乎感知不到这个过程。这里要特别提醒如果在K8s 1.24以上版本里手动创建ServiceAccount的Secret并把它挂载给Pod这种方式在长期运行环境里不是最优选择因为Token长期有效且不会被轮换一旦泄漏就是长期风险。应该依赖TokenRequest机制自动管理和轮换而不是手动维护Secret。2.3 匿名请求、静态Token与外部认证的边界除了证书和ServiceAccountAPI Server还支持静态Token文件、OpenID Connect、Webhook TokenReview等方式。实际项目里静态Token基本只有教学实验才会用因为它不好管理、泄漏后也难单独吊销。外部认证接入比如对接企业统一的身份平台是通过OpenID Connect或者Webhook方式把身份验证外包出去认证完成后API Server得到身份信息再走RBAC。这种方案适合用户量大、需要对接统一登录的组织但配置复杂且要求Kubernetes版本和外部平台都满足一定条件。匿名请求默认是关闭的但有个场景要特别注意如果API Server配置了--anonymous-authtrue那么任何未通过认证的请求都会被标记为system:anonymous用户。配合RBAC如果给这个匿名用户授予了权限等于给所有未认证请求开了口子。我在一个测试环境里见过有人把system:anonymous绑定了cluster-admin当时排查了一下午才明白为什么任何没有凭证的kubectl请求都能删掉所有Pod。这种配置在教学环境可能方便但生产环境一定要确认匿名开关是关闭的。3. RBAC授权配置实操从零创建只读与可写用户3.1 RBAC四个核心对象怎么配合RBAC模型的四个核心对象经常有人记混Role、ClusterRole、RoleBinding、ClusterRoleBinding。简单理解Role和ClusterRole是“权限定义”Binding是“授权关系”。Role只能在某个命名空间里定义能被绑定到该命名空间内的主体ClusterRole可以是集群级权限定义也可以复用到单个命名空间里。当ClusterRole被RoleBinding引用时它只在该RoleBinding所在的命名空间内生效被ClusterRoleBinding引用时则在整集群范围生效。反过来RoleBinding只能将Role或ClusterRole授权给某个用户、组或ServiceAccount作用范围限定在它所在的命名空间ClusterRoleBinding则把授权扩展到集群级别。日常里最容易混淆的就是一个ClusterRole被RoleBinding绑定之后它其实并不具备集群级权限只具备该命名空间内的权限。很多人以为“既然是ClusterRole加个RoleBinding就集群生效了”这完全是错误理解。参考这个对应关系会更直观对象作用范围能被谁引用引用后实际生效范围Role单一命名空间RoleBinding该命名空间ClusterRole集群范围或单一命名空间RoleBinding / ClusterRoleBindingRoleBinding所在命名空间 / 全集群RoleBinding单一命名空间绑定Role或ClusterRole到User/Group/SA该命名空间ClusterRoleBinding集群范围绑定ClusterRole到User/Group/SA全集群3.2 一个只读用户的完整配置流程回到开头那个场景给测试同事配一个只能看、不能改的账号且只在dev命名空间生效。分两步走。第一步创建Role对象允许对pods、pods/log、events、services、configmaps等资源执行get、list、watch。这里我特别提一句events日常排查Pod启动失败时事件信息是最有价值的如果漏掉events权限就会出现“明明能看Pod但排查不了问题”的尴尬情况。apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: dev name: dev-readonly rules: - apiGroups: [] resources: [pods, pods/log, events, services, configmaps, endpoints] verbs: [get, list, watch]第二步创建RoleBinding把dev-user这个用户和dev-readonly这个Role绑到dev命名空间apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: dev-user-readonly namespace: dev subjects: - kind: User name: dev-user apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: dev-readonly apiGroup: rbac.authorization.k8s.io保存为readonly.yaml后执行kubectl apply -f readonly.yaml。然后用dev-user的kubeconfig执行验证。如果能正常查看Pod说明配置生效再故意执行一次删除操作比如kubectl delete pod xxx能看到403说明权限边界正确。有人会问我“为什么不直接在Role里写*”其实“*”意味着pods的所有子资源都被授权有些子资源比如pods/exec、pods/attach是进入容器的入口并不一定适合给只读用户。所以我建议还是明确列出资源和操作权限尽可能小看起来配置长一点但安全边界清晰很多。3.3 给运维团队配集群级查看权限有时运维团队需要跨命名空间查看资源用单个Role就很麻烦。此时会用到ClusterRole加ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: ops-viewer rules: - apiGroups: [] resources: [pods, nodes, namespaces, services, events, persistentvolumes, persistentvolumeclaims, configmaps, secrets] verbs: [get, list, watch] - apiGroups: [apps] resources: [deployments, statefulsets, daemonsets, replicasets] verbs: [get, list, watch] - apiGroups: [batch] resources: [jobs, cronjobs] verbs: [get, list, watch]这里把secrets也放进了列表但要注意能查看secrets意味着能查看所有密钥内容这不是普通的“只读”权限。如果团队对安全要求高我建议把secrets拆出来单独控制或者明确记录谁能看。实际项目中运维查看secrets通常是合规需求但必须清楚这个权限的敏感程度。ClusterRoleBinding的写法里subjects用Group这样以后运维组加人只需要在证书里填对O组不用每次改RBACapiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: ops-viewer-binding subjects: - kind: Group name: ops-team apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: ops-viewer apiGroup: rbac.authorization.k8s.io使用Group的前提是用户证书里带有对应的O字段信息否则匹配不到人。这也是我一直强调生成证书时把O填好的原因。4. 权限粒度设计项目里如何划分角色而不失控4.1 按职责划分角色的落地模型权限不是越多越好。给所有用户都挂cluster-admin最简单但后果是灾难性的误删namespace、改动系统组件、密钥泄露全是这类环境的经典事故。我建议用一个简单的分层模型最底层是只读角色适用于大多数测试和只观察的人员中间层是按命名空间可写的角色适用于开发、发布人员顶层是集群配置管理角色只给少数运维管理员。每层之间的授权都通过Group或ServiceAccount去绑定而不是直接绑User。在设计角色时“最小够用”原则很重要一个角色只需要包含日常工作会用到的资源操作。比如普通开发人员需要创建Pod、调试应用就给他dev命名空间下pods、deployments、services、configmaps的get/list/watch/create/update/patch/delete不需要给nodes或persistentvolumes的权限。这不仅是安全考虑也是减少误操作范围的手段——权限越小手滑造成的损失越小。4.2 命名空间作为权限边界的实际效果命名空间天然就是权限边界。同一个ClusterRole被绑定到不同的RoleBinding就作用于不同的命名空间。这种模式非常适合多团队共用一个集群的场景A团队在ns-aB团队在ns-b两边互不可见。不过这里有一个很多人忽略的事情虽然RBAC层面隔离了但NetworkPolicy在默认情况下并不拦截Pod之间的网络通信。也就是说A团队的Pod可能仍然可以访问B团队的Pod IP和Service。如果不希望出现这种互通还要配合NetworkPolicy做网络层隔离。权限隔离不等于网络隔离这两者是相互独立的维度设计多租户集群时一定不要只做RBAC就以为万事大吉。4.3 聚合ClusterRole的合并规则K8s从1.9开始支持聚合ClusterRole。它本身不写具体规则而是通过标签选择器把其他ClusterRole的规则合并进来。这样插件类组件、扩展组件可以通过打标签的方式让自身权限自动挂靠到某个聚合角色上。比如先定义一个聚合ClusterRole名为aggregated-rbac-manager它只声明要选择带有特定标签的ClusterRoleapiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: aggregated-rbac-manager aggregationRule: clusterRoleSelectors: - matchLabels: rbac.example.com/aggregate-to: manager rules: []其他组件创建ClusterRole时只要给metadata加上这个标签它的规则就会自动合并到aggregated-rbac-manager里。最终再把aggregated-rbac-manager绑定给对应的用户或ServiceAccount。这种聚合机制适合中大型集群里大量自定义扩展的场景能显著减少手动维护ClusterRole规则的成本。但在中小型集群里用起来会比较绕我个人的建议是如果自定义ClusterRole不超过几十个用普通ClusterRole加清晰命名就够了别为了追求“优雅架构”把权限配置变得难读。5. 认证授权问题排查的完整方法论5.1 常见错误信息与根因对照表排错时第一步要区分401和403。401 Unauthorized指的是认证失败服务端根本不认识这个凭证403 Forbidden指的是认证已经通过但RBAC规则不允许这次操作。下面是我在实践中收集到的典型错误对照错误信息含义最常见根因Unable to connect to server: x509: certificate signed by unknown authority服务端证书不被信任kubeconfig里没配CA或配错了CAerror: You must be logged in to the server (Unauthorized)认证失败Token过期、kubeconfig里没有凭证信息pods is forbidden: User dev-user cannot list resource pods in API group in the namespace dev授权失败RBAC规则没有匹配到该用户或组error: the server does not allow access to the requested resource对象类型或子资源名称不匹配resource名写错比如把pods/log写成podsLogoperation cannot be fulfilled on ... the object has been modified更新冲突资源版本冲突和权限无关其中最值得记住的是第三种pods is forbidden。这条报错信息会非常精确地告诉我们谁在哪个命名空间对哪个资源的哪个操作被拒绝。此时直接对照三个维度即可定位RBAC的Subject是否匹配、Role规则是否覆盖了资源和操作、Binding作用范围是否包含该命名空间。5.2 kubectl auth can-i排查权限的第一工具不想靠猜直接问API Server“这个用户能不能干这件事”。kubectl auth can-i就是干这个的。# 查看当前用户能否在dev命名空间列出Pod kubectl auth can-i list pods -n dev # 以dev-user身份检查能否删除ConfigMap kubectl auth can-i delete configmaps -n dev --as dev-user # 查看某用户所有的权限列表 kubectl auth can-i --list -n dev --as dev-user用--as模拟用户身份时有个前提当前kubeconfig所在的账号得有查看这些权限的能力。如果当前账号本身就是cluster-admin才能用--as模拟任意用户如果当前登录账号权限不够命令会提示无权限。另外要记住can-i检查的是RBAC层不包含准入控制层。如果一个请求在准入阶段被拒绝can-i依然会显示allowed。所以看到can-i通过但实际请求被拒绝要把排查方向转到准入控制配置上。5.3 Token异常与ServiceAccount挂载问题Pod内访问API时最常见的报错是connection refused或者certificate signed by unknown authority。前者通常是ServiceAccount没有正确挂载或者集群DNS与API Server地址配置异常后者是Pod里的容器访问API Server时没有使用正确的CA证书。查这类问题时我一般会先进入Pod看挂载点是否存在kubectl exec -it pod-name -n dev -- ls /var/run/secrets/kubernetes.io/serviceaccount/正常情况下会看到ca.crt、namespace、token三个文件。如果文件缺失检查Pod的spec是否设置了automountServiceAccountToken: false或者ServiceAccount是否配置有误。如果文件存在但API访问异常再看容器内的环境变量是否指向了正确的API Server地址以及防火墙或网络策略是否放行了6443端口。6. 几个我反复踩的坑与最终建议6.1 权限变更不生效的真相RBAC的更新是即时生效的但有些人会遇到“改了RoleBinding还是没权限”的情况。原因通常是当前使用的Token里携带的User/Group与subjects不匹配或者机器上同时存在多个kubeconfig当前上下文指向了错误的集群。另外一个容易被忽略的场景是聚合ClusterRole。修改下层ClusterRole的标签后聚合结果的更新会有短暂延迟虽然通常几秒内就会同步但在自动化脚本里紧跟着检查权限时可能会偶发失败。遇到这种情况等一下再重试通常就好了。6.2 敏感绑定与安全习惯生产环境里我强烈建议关闭匿名访问同时不要把cluster-admin直接绑定给某个具体用户。即使要给管理员权限也建议用Group方式绑定并严格约束人数。每次大版本升级后应该复查一遍RBAC相关对象看看有没有意外产生的RoleBinding或ClusterRoleBindingkubectl get clusterrolebindings,rolebindings -A -o wide这个命令会列出所有绑定关系至少能让你对“哪些人有哪些权限”心里有数。权限是动态增长的东西时间久了不清理一定会出现权限蔓延。我见过一个集群里光ClusterRoleBinding就有上百条其中一多半是历史遗留的临时授权。最后再分享一个小习惯我给新用户配权限时会把要执行的YAML统一存到项目里的rbac/目录下走Git版本管理。这样不仅能看到谁能干什么还能追踪每次权限变更的记录。配合审计日志后续出问题要回溯是谁在什么时间改了什么东西会省很多事。权限管理本身不复杂复杂的是日积月累之后失去控制从一开始就养成规范化的习惯比后面花大力气清理要轻松得多。