1. 访问控制架构拆解一次API请求的完整生命周期说真的用过Kubernetes的人都知道k8s一切皆对象所有操作——不管是kubectl get pods、kubectl apply还是Pod内部访问API Server本质上都是向API Server发起的HTTP请求。但API Server不可能来者不拒它得先搞清楚三件事你是谁认证、你被允许干什么授权、你干的事有没有违反集群规范准入控制。这三件事就是k8s访问控制体系的三个关卡这篇文章我们重点拆解前两个关卡认证和RBAC授权。很多刚接触容器编排的朋友一上来就奔着部署应用去了Service、Deployment、Ingress玩得飞起却忽略了访问控制这一层。等到了生产环境要多人协作、要给不同角色分配不同权限的时候才发现集群权限管理一塌糊涂所有人共用一把管理员钥匙谁都能删namespace谁都能看别人的业务日志。这篇文章就是来解决这个问题的我会带你从API Server的视角完整走一遍认证和授权的链路然后手把手完成一个从创建用户到分配只读权限的完整实操案例。适合刚入门k8s的开发者也适合在团队里负责k8s集群运维但权限管控一直没理清的同学。1.1 请求抵达API Server后的三道关卡我们先想象一个场景你执行了kubectl get pods这背后到底发生了什么如果把API Server比作一栋写字楼的大门你按下的每一个kubectl命令都是一次进入大门的尝试。管理员不会让你随便进门禁系统会依次查验你的工牌认证、核对你的门禁权限范围授权、确认你要做的事情是否在允许范围内准入控制。第一道关卡是Authentication认证解决的是你是谁的问题。kubectl会从kubeconfig配置里读出你的身份凭证不管是客户端证书、Token还是用户名密码把它放到HTTP请求的头里发给API Server。API Server验明正身后会在请求上下文中标记好你的用户名、用户组、UID等信息比如system:admin、system:serviceaccount:default:my-sa这种格式。第二道关卡是Authorization授权解决的是你能不能干这件事的问题。API Server不会自己拍脑袋做决定它会拿着你的身份信息去问一个叫作Authorizer的组件。目前最主流的授权模式就是RBAC基于角色的访问控制它把权限抽象成角色再把角色绑定到用户或组身上API Server只要查一下这张角色-用户映射表就知道你有没有权限执行这个操作了。第三道关卡是Admission Controller准入控制解决的是你干的事合不合规矩的问题。即使你有权限API Server还会把请求交给一系列准入控制器去检查比如Pod是否设置了资源限制、镜像仓库地址是否被白名单限制、是否违反Pod安全策略等。这一层不是这篇的重点但你得知道它在链路中的位置不然排查问题的时候容易漏掉方向。1.2 为什么认证和授权必须分两层设计我在刚开始接触k8s访问控制的时候也有过困惑既然最终都要靠RBAC控制权限为什么不能直接拿身份信息做判断非要分一个认证一个授权其实这两个环节的逻辑完全不同而且必须解耦。认证解决的是身份可信的问题它只关心这个请求到底是谁发起的而不关心这个人能干什么。就像楼下保安先确认你拿的是不是我们这栋楼的工牌但不问你进去之后是去会议室还是去机房。授权解决的是操作合规的问题它只关心你这个已经被确认了身份的人能不能在这个namespace里执行get pod这种操作。把这两层分开的最大好处是灵活。你可以选择多种认证手段——证书、Token、外部OIDC但底下的RBAC规则可以完全不受影响。反过来你调整权限模型——比如把某个用户从只读角色换成管理员角色——也不需要重新签发证书。我在实际项目里就遇到过这种情况团队走了一个人要清理他的权限如果当初是用证书直接映射权限那就得重新处理证书吊销。而用RBAC只需要删掉相应的RoleBinding就行干净利落。实际上生产环境的k8s集群管理员经常会同时配置多种认证方式用ServiceAccount给Pod用用X509证书给人用用OIDC对接公司统一登录。这些不同来源的身份最终汇入同一个RBAC授权体系在API Server看来都是一个个的user字符串授权逻辑完全不用关心你当初是从哪个门进来的。1.3 访问控制组件全家福与协作关系k8s的访问控制不是单一组件而是一组模块的协同工作。我把它们整理成一张协作关系图来理解API Server所有请求的入口负责统一调度认证、授权、准入三个环节Authenticator认证器支持多种认证策略比如Client CA、Bearer Token、ServiceAccount、OpenID ConnectAuthorizer授权器支持多种授权模式RBAC只是其中之一还有ABAC、Webhook、Node等Admission Controller准入控制器可插拔式设计在请求落库前做最后的校验和变更认证器和授权器是串联关系认证不通过请求直接返回401 Unauthorized根本走不到授权环节。认证通过后授权器开始工作如果不通过返回403 Forbidden。这两个错误码是平时排查问题最先要看的信息——401说明身份识别失败403说明权限不足定位方向完全不一样。说到授权模式k8s支持多种授权模块同时启用比如--authorization-modeAlwaysAllow、--authorization-modeRBAC生产环境一般只用RBAC。API Server收到请求后会依次询问每一种授权器是否允许任何一个授权器说允许请求就通过了。这里有个细节如果集群启用了多个授权模式请求者只要满足任意一个授权器的规则就会被放行所以配置授权模式时要注意这个叠加逻辑别以为只开了RBAC就只有一套规则在起作用。2. 认证机制深入API Server如何确认你的身份2.1 认证的本质与常见方式对比不管认证的方式花样再多本质上都是回答一个问题你有什么证据证明你是你说的那个人就像过安检你可以出示身份证X509证书也可以出示员工卡静态Token还可以刷脸OIDC外部认证都可以证明身份但每种方式的信任链条和管理成本不一样。k8s的认证方式主要有以下几种客户端CA证书X509、静态Token文件、ServiceAccount Token、OpenID ConnectOIDC、Webhook Token认证以及HTTP Basic Auth基本已被淘汰。我把它们放在一张表里对比认证方式信任基础适用场景管理成本X509客户端证书集群CA签发集群管理员、长期稳定的运维人员中需要处理证书签发与轮换ServiceAccountAPI Server内置Pod访问API Server、CI/CD系统低k8s自动管理Token静态Token静态文件配发简单场景、自动化脚本低但轮换麻烦安全性一般OIDC外部身份提供商企业统一账号体系、SSO登录高需对接外部系统Webhook Token外部认证服务深度定制认证逻辑高需要自建认证服务在实际项目中我把X509证书作为人和集群之间认证的主要方式因为k8s本身就把证书签发的链路做得比较完善管理员可以通过CertificateSigningRequestCSR对象来给用户签发证书整个过程有审计日志可控可查。2.2 X509证书认证人进集群的身份证X509证书认证的原理不复杂你持有由集群CA签发的客户端证书请求API Server时带上它API Server拿配置的CA证书去验签。验签通过再从证书里提取CNCommon Name作为用户名提取OOrganization作为用户组。这一步特别关键因为RBAC授权规则里匹配的user字段就是在认证阶段从证书里提取出来的CN和O。我在项目里给新同学签发证书的时候通常会把用户名规范成姓名的英文缩写这种结构组信息按团队来分。比如给维护订单服务的小张签证书CN填zhang.sanO填order-team。这样后续RBAC规则可以按组来授权比如给order-team这个组统一分配order命名空间下的只读权限。这个规范最好在团队初建的时候就定下来不然后面人员一多CN和O五花八门RBAC规则会写得痛不欲生。关于证书签发比较标准的做法是通过k8s的CSR机制。管理员创建一个CertificateSigningRequest对象把用户自己的私钥生成的证书请求内容放进去然后kubectl certificate approve批准API Server会用集群CA给它签名最终拿到一个clinet证书。整套流程不需要SSH到master节点操作也不需要手动碰CA私钥走的是k8s自己的控制面接口审计日志还能看到是谁批准的。这个流程后面实操部分我会手把手带一遍。2.3 ServiceAccount给Pod发一张机打工牌X509证书是发给人的但k8s集群里其实人员不只会发请求——每个Pod里的应用也可能需要访问API Server比如一个自定义控制器要实时监听某个资源的变化或者一个Job启动后要把运行结果写回集群。这时候不能给每个Pod手动生成X509证书太麻烦了而且Pod重建以后身份又变了根本管理不过来。于是k8s提供了ServiceAccount机制。ServiceAccount是k8s的一等公民资源创建完一个ServiceAccount后k8s会为它生成对应的Token。在Pod的spec里指定serviceAccountName这个Token就会被自动挂载到Pod的文件系统里默认路径/var/run/secrets/kubernetes.io/serviceaccount/tokenPod里的应用读取这个Token放进请求头的Bearer字段API Server识别出这个身份就是system:serviceaccount:namespace:sa-name。这个机制最巧妙的地方在于ServiceAccount天然和namespace绑定属于哪个namespace一目了然。给ServiceAccount授权的时候最小的权限授予原则非常好落地——某个Pod只需要读取自己所属namespace下的ConfigMap那就创建一个Role限定在那个namespace的get权限再创建一个RoleBinding把它绑定到这个ServiceAccount上其他的一概不给。我在自己的项目里习惯每个服务单独建一个ServiceAccount坚决不共用default这样审计日志里每一笔API请求都能追溯到是哪个服务发出的出了问题也方便回滚和追责。关于Token这块有一个细节需要特别提醒早期k8s使用的是长期有效的静态ServiceAccount Token存在Secret里也是挂载到Pod里。但从1.21版本开始k8s引入了TokenRequest API可以为ServiceAccount签发带有过期时间的短时Token默认有效期1小时Pod侧通过投射卷自动刷新。生产环境强烈建议使用这种短时Token机制降低Token泄露的风险。如果监控到集群里还有老的Secret型ServiceAccount Token在被使用建议逐步迁移。2.4 认证链路的优先级与匿名请求API Server处理认证请求时有一个优先级顺序配置了多种认证方式的情况下会按照顺序依次尝试。如果某个环节返回了错误API Server不会立即拒绝请求而是继续尝试下一种认证方式直到有一种方式明确认可了这个请求。这里有一个很多人容易忽略的坑如果有多种认证方式都配置了其中一种方式静默返回无法识别这个用户API Server会继续用下一种方式尝试最终只要有一种认证成功请求就带着这种认证方式解析出的用户名和组进入授权环节。这个特性意味着你配置的认证方式是或的关系不是你想象的只能用一个。另外还有一个比较有意思的话题是匿名请求。k8s是允许匿名访问的默认情况下一个没有携带任何身份凭证的请求会被标记为system:anonymous用户属于system:unauthenticated组。默认策略下匿名用户什么权限都没有但如果你手动绑定了RoleBinding给这个用户或组就真的可以以匿名身份访问了。我在一些测试环境里确实这么干过给匿名用户绑定了一个只能访问特定namespace下status子资源的角色用来暴露一些只读的集群状态信息。不过生产环境务必关掉所有匿名绑定的权限毕竟匿名用户不受任何审计追踪的约束。3. RBAC授权体系四个核心对象如何织起权限网3.1 Role、ClusterRole、RoleBinding、ClusterRoleBindingRBAC之所以比传统的ABAC基于属性的访问控制好用就在于它把权限管理抽象成了角色这个概念。角色是权限的集合用户通过绑定角色来获得权限。这就像公司里的岗位说明书——你不需要给每个人单独写一套工作职责只要定义好运维工程师、开发工程师、访客这几个岗位角色再把人塞进对应的岗位绑定就行了。k8s RBAC有四个核心对象Role定义一组权限规则限定在某个命名空间内生效ClusterRole定义一组权限规则集群范围内生效也可以跨namespace复用RoleBinding在某个命名空间内将用户/组/ServiceAccount绑定到某个RoleClusterRoleBinding在整个集群范围内将用户/组/ServiceAccount绑定到某个ClusterRole具体到YAML里Role看起来是这样的apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: default name: pod-reader rules: - apiGroups: [] resources: [pods] verbs: [get, list, watch]规则里包含四要素apiGroupsAPI组、resources资源类型、verbs操作动词、resourceNames可选的资源名单。apiGroups为表示核心API组也就是pods、services这些最基础的资源。如果在apiGroups里写apps那就是针对Deployment、ReplicaSet这些资源。verbs支持get、list、watch、create、update、patch、delete、deletecollection等操作还可以使用通配符*表示所有操作。ClusterRole和Role的区别在于范围但实际使用中有一个很常见的模式先在ClusterRole里定义好一套通用的、跨namespace有用的权限集合比如对所有namespace里的Pod做get操作然后用RoleBinding在不同namespace下把这个ClusterRole绑定给不同的人。为什么这么做因为Role本身是绑定namespace的如果你要在10个namespace里给10个人相同权限直接建Role会累死人。但创建一个ClusterRole再在10个namespace里各建一个RoleBinding引用它就清爽很多。要注意的是RoleBinding引用ClusterRole时ClusterRole的权限只在当前RoleBinding所在的namespace范围内生效。3.2 绑定关系详解主体与作用范围RoleBinding和ClusterRoleBinding都是把谁和哪个角色拴在一起的桥梁。区别在于RoleBinding只能绑定本namespace里的Role或集群里的ClusterRole作用范围是本namespaceClusterRoleBinding只能绑定ClusterRole作用范围是整个集群。YAML示例如下apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: namespace: default name: read-pods-binding subjects: - kind: User name: zhang.san apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.ioRoleBinding里的subjects支持三种kindUser来自认证的用户、Group来自认证的用户组、ServiceAccount。这正好和我们前面讲的认证环节对应上了——认证环节从请求里提取出用户和组的信息授权环节就拿着这些信息去找对应的绑定关系。如果认证阶段用的是X509证书那User的name就是证书的CNGroup就是证书的O。如果认证阶段用的是ServiceAccount那User的name就是system:serviceaccount:namespace:sa-name只不过你不需要手动写这个全名直接设置subjects的kind为ServiceAccountname为SA名字namespace为SA所在命名空间即可。这里有一个容易踩坑的地方roleRef一旦在RoleBinding里定义后面如果通过kubectl edit修改roleRef指向的其他角色k8s会直接拒绝因为RBAC的绑定关系是声明式的不允许在binding里直接篡改角色引用。想改绑定关系正确做法是删掉旧的RoleBinding建一个新的。我第一次碰到这个问题时还以为是集群哪里出问题了后来看文档才发现这是有意设计防止绑定关系被意外破坏导致权限集会混乱。3.3 权限判定逻辑请求放行的最小充分条件API Server在执行授权检查时规则是这样的如果请求中的用户或者所属的任意一个用户组所绑定的任意一个Role或者ClusterRole包含了针对该资源的操作权限则放行。这也就是说权限是或的关系——你绑了10个角色只要其中一个角色给了你权限你就能操作。这也意味着越权这件事在RBAC里其实很容易发生因为你可能在不知不觉中通过多个角色叠加出了一大堆你并不需要的权限。为了把这个说清楚我们看一个例子。你有一个用户绑定了两个角色角色Adefault命名空间下的pods get权限角色B集群级的pods get权限ClusterRole那么即使你本意只是想让这个用户在default命名空间读取Pod信息因为角色B的存在这个用户实际上可以在集群的所有命名空间读取Pod信息。这不是你想看到的结果但k8s不会阻止这样的绑定。所以运维人员在创建绑定时必须要有意识地做最小授权审查——每一次绑定都要问自己这个用户真的需要这个角色的全部权限吗能不能把角色拆细一点我在团队里推动的权限管理规范是每个角色尽量只做一类事情比如只读角色、发布角色、命名空间管理员角色绑定的时候单独评估不留叠加空间。另外还要特别提醒的是Pod创建过程中的关联权限。在一个命名空间里你要创建Pod不光要pods的create权限还需要能够读取和修改ServiceAccount、ConfigMap、Secret等关联资源因为Pod可以引用这些对象。如果你只给了pods的create权限实际创建时经常会遇到奇怪的403错误。我记得有一次排查自己搭的发布系统一直报Secret读取失败看权限规则明明有pods的create权限后来才发现给发布角色漏了secrets的get权限。这种关联权限问题最容易在刚开始用RBAC自定义权限的时候踩到。3.4 k8s内置ClusterRole清单k8s安装完成后系统自带了一批ClusterRole同时集群本身也依赖这些ClusterRole来运作。认识它们对你理解整个权限体系很有帮助kubectl get clusterrole输出里你会看到这些名称cluster-admin、admin、edit、view、system:node、system:controller-manager、system:kube-scheduler、system:discovery等。其中前四个是最常用的它们的权限层级是这样的ClusterRole名称作用范围权限内容适用场景view命名空间内大多数资源的只读权限get/list/watch只读访问场景edit命名空间内可以修改大部分资源但不能修改RBAC规则和配额日常开发调试admin命名空间内命名空间内的全部权限包括RBAC命名空间管理员cluster-admin集群级整个集群的全部权限集群管理员我以前一直以为给开发人员绑定view就够了但实际用过之后发现view角色其实漏了不少资源比如它不包含对ResourceQuota和LimitRange的查看权限也不包含对StorageClass的查看权限。开发人员执行kubectl describe node时也会发现看不到节点上的压力信息。后来我们的做法是在view基础上自己补了一个扩展只读角色把StorageClass、ResourceQuota、LimitRange这些也放进去减少开发人员的疑惑。顺便说一句system:discovery这个角色可能是大家最忽视的它允许用户访问API Server的发现接口也就是/api和/apis这些路径没有这个绑定的人kubectl执行起来会直接报the server has asked for the client to provide credentials或者无法完成资源列表补全。大多数情况下在RBAC里创建自定义角色的时候我会顺带把system:discovery也绑定进去不然会出现很诡异的看起来有权限但命令就是跑不通的问题。4. 实操案例创建一个只读用户并完成授权理论讲得再多不如亲手跑一遍。这一节我会带着你完整走一遍实战流程创建一个叫某开发者的人给他分配一个命名空间下的只读权限让他可以使用kubectl查看Pod、Service、Deployment等资源但没有修改和删除权限也没有查看Secret的权限。整个过程分为五步生成私钥和CSR、提交CSR并批准、创建Role和RoleBinding、生成kubeconfig、验证效果。先说环境我用的是k8s v1.28版本单master节点其他的环境大同小异。实操之前确认一下你的kubectl能正常访问集群并且当前用户是管理员。4.1 生成私钥和证书签名请求创建一个存放用户材料的目录进入目录后执行mkdir -p certs cd certs # 生成私钥 openssl genrsa -out zhangsan.key 2048 # 根据私钥生成CSR证书签名请求 openssl req -new -key zhangsan.key -out zhangsan.csr \ -subj /CNzhang.san/Oorder-team这个命令生成一个2048位的RSA私钥然后用它生成证书请求。-subj里CN必须替换成你要分配的用户名这里就是zhang.sanO是用户组order-team。注意CN和O的内容决定了后续认证出来的身份信息一定要和你的权限规划对得上。如果不确定怎么写建议先用openssl req -text -noout -in zhangsan.csr查看CSR里的内容是不是你想要的。接着把CSR内容编码成Base64去掉换行准备提交给k8sCSR_CONTENT$(cat zhangsan.csr | base64 -w 0) echo $CSR_CONTENT这里的Base64文件会有点长可以临时存在shell变量里后面创建YAML的时候直接复制进去。4.2 通过CSR对象申请证书创建一个CSR的YAML文件特别注意把上一步的输出填到request字段apiVersion: certificates.k8s.io/v1 kind: CertificateSigningRequest metadata: name: zhangsan-csr spec: request: 上面生成的Base64内容 signerName: kubernetes.io/kube-apiserver-client expirationSeconds: 31536000 usages: - client authsignerName填kubernetes.io/kube-apiserver-client表示这是一个客户端认证证书usages必须是client auth。我把expirationSeconds设置为一年这意味着这个证书一年后过期。生产环境可以根据团队的安全策略调整证书有效期一般建议不超过一年配套走证书轮换机制免得时间一长证书泄露了还有人拿着它进来。提交后审批kubectl apply -f csr.yaml kubectl get csr kubectl certificate approve zhangsan-csr审批通过后用下面的命令把签好的证书取出来kubectl get csr zhangsan-csr -o jsonpath{.status.certificate} | base64 -d zhangsan.crt刚才生成的zhangsan.key和zhangsan.crt就是用户的证书凭证一定保存好。CA证书直接从集群的kubeconfig里提取就行——一般情况是你自己管理集群时CA证书就存在/etc/kubernetes/pki/ca.crt如果是托管集群从kubeconfig里解析就行。下面是我生成kubeconfig时会用到的完整流程# 提取集群CA证书 kubectl config view --raw -o jsonpath{.clusters[0].cluster.certificate-authority-data} | base64 -d ca.crt # 建立kubeconfig kubectl config set-cluster my-cluster \ --serverhttps://API_SERVER_ADDRESS:6443 \ --certificate-authorityca.crt \ --kubeconfigzhangsan.kubeconfig kubectl config set-credentials zhang.san \ --client-certificatezhangsan.crt \ --client-keyzhangsan.key \ --kubeconfigzhangsan.kubeconfig kubectl config set-context default \ --clustermy-cluster \ --userzhang.san \ --kubeconfigzhangsan.kubeconfig kubectl config use-context default --kubeconfigzhangsan.kubeconfig这段操作背后做了什么set-cluster写入集群的API Server地址和CA证书set-credentials写入用户证书和私钥set-context把集群和用户关联起来。使用这个kubeconfig的客户端请求API Server时就会携带客户端证书API Server验签通过后识别CN为zhang.san、组为order-team。4.3 为只读场景定义角色接下来创建权限。只读取业务命名空间信息的角色我用的是自定义Role而不是直接复用系统内置的view角色。因为内置view角色里不含ResourceQuota和StorageClass的查看权限范围也不完全符合需求。下面是一个量身定制的只读角色apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: order-ns name: order-readonly rules: - apiGroups: [] resources: [pods, pods/log, services, configmaps, endpoints, events, replicationcontrollers, persistentvolumeclaims, resourcequotas, limitranges] verbs: [get, list, watch] - apiGroups: [apps] resources: [deployments, replicasets, statefulsets, daemonsets] verbs: [get, list, watch] - apiGroups: [batch] resources: [jobs, cronjobs] verbs: [get, list, watch]注意几个关键点我刻意没把secrets加进去因为只读用户不应该能查看Secret里的敏感内容。如果你需要给某人Secret的查看权限要做单独的审批。pods/log是一个子资源它需要单独声明在resources里。很多新手把pods加进去就觉得能看日志了其实只有加上pods/log才能通过kubectl logs查看Pod日志。我加上了resourcequotas和limitranges这样只读用户执行kubectl describe ns order-ns时也能看到资源配额信息避免因为view角色权限缺失而出现输出不全的现象。这里没有给pods/exec、pods/attach这些进入Pod内部的操作权限因为只读角色不应该有这些高危操作。4.4 把用户绑定到角色上apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: namespace: order-ns name: order-readonly-binding subjects: - kind: User name: zhang.san apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: order-readonly apiGroup: rbac.authorization.k8s.io将上面两个YAML提交kubectl apply -f order-readonly-role.yaml kubectl apply -f order-readonly-binding.yaml这个时候我们还需要考虑一个细节如果zhang.san需要切换namespace或列出集群里的资源那么他至少还要有system:discovery角色的绑定。我前面说到过这个问题——没有绑定这个角色即使你在某个namespace下有权限kubectl get namespace这类命令也会失败。所以再补一个ClusterRoleBinding是很有必要的kubectl create clusterrolebinding zhang-san-discovery \ --clusterrolesystem:discovery \ --userzhang.san为什么Discovery权限要用ClusterRoleBinding而不是RoleBinding因为system:discovery本身是一个ClusterRole而且发现接口作用于集群级资源。对普通用户来说给这个权限不会泄露业务敏感信息只是让他们能正常使用kubectl的自动补全和资源发现功能。我在所有自定义用户上都会补这个绑定算是一个标准做法。4.5 用新用户的kubeconfig验证权限现在来验证效果。用之前生成的zhangsan.kubeconfig# 在order-ns里查看Pod应该成功 kubectl --kubeconfigzhangsan.kubeconfig -n order-ns get pods # 查看default命名空间的Pod应该403 kubectl --kubeconfigzhangsan.kubeconfig -n default get pods # 尝试删除一个Pod应该403 kubectl --kubeconfigzhangsan.kubeconfig -n order-ns delete pod pod-name # 尝试查看Secret应该403 kubectl --kubeconfigzhangsan.kubeconfig -n order-ns get secrets预期结果梳理一下操作结果原因get podsorder-ns成功order-readonly角色授权get podsdefault403角色限定在order-ns内delete pod403没有delete verbsget secrets403secrets不在角色资源列表内get nodes403集群级资源需要ClusterRole权限我在实际项目中通常还会加一步让这个只读用户用kubectl get deploy查看Deployment确认apps组也生效。因为角色规则里包含了apps组的资源这一步应该也没问题。如果哪一步返回了403就按照返回的user信息去排查角色绑定这一步正好衔接下面要讲的排查技巧。4.6 ServiceAccount授权实战补充刚才实践的是人的身份现在补充一个Pod场景的授权。假设有个服务需要在order-ns里读取ConfigMap以便实现配置热更新。这样操作kubectl create serviceaccount config-refresher -n order-ns然后创建一个角色只允许get和list configmapsapiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: order-ns name: config-reader rules: - apiGroups: [] resources: [configmaps] verbs: [get, list, watch]绑定ServiceAccountapiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: namespace: order-ns name: config-refresher-binding subjects: - kind: ServiceAccount name: config-refresher namespace: order-ns roleRef: kind: Role name: config-reader apiGroup: rbac.authorization.k8s.io在Deployment的Pod模板里指定serviceAccountName即可spec: serviceAccountName: config-refresher containers: - name: app image: nginx这样Pod启动后k8s会自动挂载该SA的TokenPod里的代码读取Token请求API Server时就被解析为SA身份自动获得config-reader角色的权限。整个链路不需要手动维护任何证书非常干净。这也是我在项目里为服务间API调用授权的首选方式——不是直接给ServiceAccount绑定一个宽泛的ClusterRole而是精确到namespace资源操作的最小授权。5. 常见问题与排查技巧实录5.1 场景一一直401 Unauthorized认证没过如果你在使用kubeconfig访问集群时返回401说明API Server没有认出来你的身份。优先排查这几个点证书是否过期。X509客户端证书在过期后API Server会直接拒绝。用openssl x509 -in zhangsan.crt -text -noout查看Not After字段。kubeconfig里的certificate-authority是否正确。如果CA证书不对API Server验签失败同样过不了。证书和私钥是否配对。可以用openssl x509 -in zhangsan.crt -pubkey -noout和openssl pkey -in zhangsan.key -pubout -noout对比输出的公钥是否一致如果不一样就是配对了错误文件。API Server的Client CA配置。检查/etc/kubernetes/manifests/kube-apiserver.yaml里--client-ca-file是否指向了正确的CA证书文件以及这个CA是否是签发用户证书的那个CA。有一个细节我在排查时踩过坑如果CA证书没问题、证书配对也正确但还是401那就要看认证链路的顺序。比如你同时配置了Webhook认证和Client CA认证Webhook返回的error可能会让API Server最终选择拒绝而不是继续尝试Client CA。虽然API Server官方说会fallback但某些实现并不总是按预期走。如果是在托管集群里可以直接查看API Server的日志里面会明确记录认证失败阶段的报错信息。5.2 场景二403 Forbidden认证过了但授权不够认证通过后返回403说明授权环节没有匹配到规则。最常见的几种原因角色限定的namespace和请求的namespace不一致。这是最典型的用户困惑点。比如绑定了order-ns的Role却访问了default下的资源k8s返回403而不是直接告诉你namespace错了。解决方式是用kubectl auth can-i命令确认在特定namespace下的权限。RoleBinding里的subjects名字拼写错误。用户名和CN必须完全一致大小写也敏感。比如证书CN是Zhang.Sanbinding里写的是zhang.san授权就匹配不上。用户组没匹配上。如果你用角色绑定的是Group而不是User那用户证书里的O字段必须和组名完全一致。O里有多个值的情况下一个组能匹配上就行。集群级资源和命名空间级资源混淆。比如用户想查看节点信息但节点是集群级资源只能通过ClusterRole来授权。你绑定的Role再怎么加资源也管不到集群级资源。推荐使用下面的方式快速定位# 模拟某个用户是否有权限执行某个操作 kubectl auth can-i get pods -n order-ns --as zhang.san kubectl auth can-i delete pod -n order-ns --as zhang.san--as参数可以模拟指定用户的权限查询API Server会返回yes或no非常适合在写RBAC规则时快速验证。5.3 场景三权限明明绑定了但命令还是跑不通这类问题最磨人因为RBAC规则本身看着没问题但实际执行时总是报错。我总结了几种看起来不该出问题但实际上经常出问题的情况缺少system:discovery绑定。新用户拿到了kubeconfig一跑kubectl get pods就报the server has asked for the client to provide credentials。这种情况就是用户没有discovery权限导致kubectl没法获取API资源列表。解法就是我前面提过的补一个ClusterRoleBinding绑定system:discovery。Pod里用的SA没绑对。很多Deployment在清单里没有显式指定serviceAccountNamePod会使用default SA。如果default SA没有权限Pod里的应用自然访问不了API Server。检查Pod的yaml确认SA名字。子资源权限漏配。比如pods/exec、pods/log、pods/portforward这些子资源单独在resources里声明才能赋权。你给了pods的get权限不代表能看日志、进容器。Token过期。如果你的Pod内应用使用TokenRequest API生成的短时Token每隔一段时间会轮换一次应用需要每次请求时重新读取Token文件。如果应用启动时只读了一次Token并缓存在内存里到期后访问API Server就会一直403。解决办法是在应用侧周期性重新读取Token文件或者使用带有投射卷的SA Token自动轮换机制。5.4 最佳实践小步验证、最小授权、定期巡检最后聊聊我的访问控制实践心得。K8s的RBAC权限管理是我在所有基础设施管控里感觉性价比最高的抓手因为模型简单、文档完整、用起来顺手。但再简单的模型也扛不住混乱的配置。我总结了几条落地经验非常实用。第一所有权限变更都要走审批流程。团队里可以定下一条规矩任何人要申请k8s权限需要提一个申请单说明目的由集群管理员审核后创建角色和绑定。同时所有角色的YAML都放在一个统一的Git仓库里管理用GitOps方式提交变更。这样任何权限变更都能被记录、审查和追溯出现问题可以快速回滚。我在团队里推动这个方式之后权限相关的事故率下降得非常明显。第二角色要尽量细分避免出现一个超级角色绑定到所有人身上的情况。比如只读角色、日志查看角色、命名空间运维角色、集群管理员角色要分开。绑定的时候确认这个人真的需要整个角色那么多权限如果不是拆分角色或者创建新角色。第三定期做权限巡检。每个季度导出一次所有RoleBinding和ClusterRoleBinding清单逐条核对是否还有人在使用。因为人员流动、项目切换总会留下一堆废弃的绑定关系。我试过用脚本定期扫描所有RoleBinding列出超过90天未使用的主体再人工确认清理效果相当不错。第四掌握好kubectl auth can-i这个命令。有时候不想真的执行一个操作只想知道到底有没有权限用它是最直接的方式。也支持--list参数查看当前用户拥有的全部权限列表排查我怎么有这个权限的问题时特别好用。6. 补充知识点伪代码梳理访问控制决策流程整篇文章讲的认证和授权在代码层面其实是API Server里一个个判断分支的组合。为了让理解更牢固我在这里补一段伪代码来模拟API Server的访问控制判断流程。不是真实源码但逻辑就是这样的。看懂了这段逻辑你后面排查任何权限问题都会很清醒。func HandleRequest(req) { # 第一步认证 user, authenticated : Authenticate(req) if !authenticated { return 401 } # 第二步获取用户所属的所有组 groups : GetUserGroups(user) # 第三步授权检查 authorized : false for _, authorizer : range Authorizers { decision : authorizer.Authorize(user, groups, req) if decision Allow { authorized true break } if decision NoOpinion { continue } if decision Deny { return 403 } } if !authorized { return 403 } # 第四步准入控制 for _, admission : range AdmissionControllers { ok : admission.Validate(req) if !ok { return 403 } } # 第五步持久化或读取 return HandleRequestAndStore(req) }对照这段逻辑再看一次之前遇到的排查痛点401必然是第一步认证的问题403不是授权失败就是准入拒绝。而RBAC授权检查的核心就是遍历用户和组绑定的每一个Role/ClusterRole看是否有哪条规则覆盖了当前请求的apiGroup、resource、verb、namespace。rules里antecedent和后续条件全部满足请求就通过。记住这一点你就不再需要猜权限了。另外值得留意的是授权模式是可以通过API Server参数开关的比如用--authorization-modeRBAC开启RBAC授权用--authorization-modeRBAC,Node同时开启Node授权模式Node模式专门为kubelet设计。如果某个请求匹配到了RBAC但没有匹配到NodeNode授权器返回NoOpinionRBAC再判断是否放行。如果RBAC也没匹配到请求就被拒绝。所以你在托管集群里看着某些奇怪但允许的操作往往是多个授权器协同的结果而不是RBAC单独放行的。到这里k8s访问控制的认证和RBAC授权部分就讲完了。纸上得来终觉浅建议你找一个小集群亲手创建一个用户、绑一个只读角色、再故意操作几步看看报错效果。真跑一遍权限这层才算真正长在你身上了。我这几年管集群的经验总结成一句话别图省事给所有人发管理员证书花半小时把RBAC配好后面能给你省下无数个加班的夜晚。