做K8s运维的朋友应该都遇到过这类需求:CI/CD流水线要调用集群API做发布,监控系统要采集集群指标,或者外部应用需要读集群资源状态。最直接的办法就是给外部系统准备一个ServiceAccount(后面统一叫SA)的token,让它拿着这个token通过API Server认证。如果你的集群还是1.24之前的旧版本,事情很简单——创建SA之后系统会自动生成一个对应Secret,token就在里面躺着,复制出来就能用。但从1.24开始这个默认行为被砍掉了,到了K8s 1.32及之后的版本,你创建完SA之后等半天也看不到那个带token的Secret。
我最初也在这个地方卡了很久,查文档、翻源码才搞明白。这篇文章就用我实际踩坑的经历,把K8s 1.32以上集群手动生成SA token的方法一次讲清楚,包括短期token和长期token两条路线怎么选、怎么组装外部kubeconfig、以及那些文档里不会写的坑。无论你是刚接触K8s的新手,还是正在帮外部系统对接集群的老人,这篇都值得收藏。
1. 为什么新版集群不再"自动生成"SA token
1.1 旧机制里到底发生了什么
在1.24之前的版本里,只要执行kubectl create sa xxx,控制平面里的一个控制器就会自动创建一个同名的Secret,这个Secret的type是kubernetes.io/service-account-token,里面data.token字段就是SA的长期token。这个token没有过期时间,任何持有它的人都能以这个SA的身份调用API。
更关键的是,Pod在创建时如果指定了SA且没有关闭自动挂载,kubelet会把对应Secret挂载到Pod里的/var/run/secrets/kubernetes.io/serviceaccount/token,容器内应用读这个文件就能访问集群API。
这套机制用了很多年,但它有两个越来越让安全团队头疼的问题:一是token长期有效,一旦泄漏很难控制损失,只能靠删SA或者改权限来兜底;二是token跟Secret绑定,没法按需设置有效期和用途边界,审计和轮换都麻烦。
1.2 TokenRequest API是替代方案
为了解决上面这些问题,K8s引入了TokenRequest API,核心思路是从"给一个无限期的静态token"变成"按需签发一个短期token"。在一个大版本周期内,这个API迭代成熟后,官方就决定从1.24开始默认不再自动创建SA对应的Secret。
现在Pod里的serviceaccount挂载方式也变了,默认走的是投影卷(Projected Volume),kubelet会周期性地通过TokenRequest向API Server请求一个短时token,写进只读文件里。Pod内的应用拿到的token通常过期时间是一小时左右,kubelet会在过期前自动续期,旧token直接失效。这样即使某个Pod的token被拿走了,风险窗口也被压缩得很小。
1.3 1.32里手动创建Secret还灵不灵
我一开始以为1.32彻底禁用了老方式,后来实测发现并没有。虽然系统不再自动创建一个全新Secret,但如果你手动声明一个Secret,设置好type字段和对应annotation,控制平面里的旧控制器仍然会识别它,帮你在Secret里填充token。
这意味着现在有两条并行的路可以走:一条是走TokenRequest API,拿短期token,适合单个任务、短生命周期场景;另一条是手动建Secret,继续拿长期token,适合那些没法频繁续期的外部系统。两条路我都验证过,1.32版本下都能正常使用。
2. 动手前先理清权限和服务身份
2.1 SA、Secret、RBAC这三层关系
拿到一个token之后,它在K8s里的身份映射是这样的:token本身只是凭证,API Server解出token里的信息后,会确认它对应的是哪个SA,然后用这个SA绑定的RBAC规则来决定能做什么操作。
所以整个链路是:token证明你是哪个SA → 该SA在哪个命名空间 → 该命名空间下RBAC绑定了哪些权限。
我在实操中见过不少"token生成对了但还是调不通"的案例,最后查下来基本都是RBAC这一层没配置好。比如token对应的SA在prod命名空间,但只给这个SA绑定了dev命名空间的Role,那调prod的API自然就是403。
2.2 给外部系统一个清晰的最小权限
不管外部系统是只读巡检还是需要触发Job,都建议先画清楚它到底要调哪些API,然后按API分组去设计RBAC。最省事的做法当然是直接绑cluster-admin,但这是运维事故的高发源头,千万别图省事。
下面是一个只读权限的示例,适合监控巡检类外部系统:
kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: external-readonly rules: - apiGroups: [""] resources: ["pods", "services", "endpoints", "nodes", "namespaces"] verbs: ["get", "list", "watch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: external-readonly-binding subjects: - kind: ServiceAccount name: ci-reader namespace: ci # 改成你真实SA所在namespace roleRef: kind: ClusterRole name: external-readonly apiGroup: rbac.authorization.k8s.io如果外部系统只需要操作某一个命名空间的资源,那就用Role和RoleBinding,别用Cluster级别的,权限范围越小越安全。
2.3 短期token还是长期token:按场景选
这是我最常被问到的问题。我的建议其实很简单,看外部系统能不能自己续期:
- 能自己续期的系统(比如SDK支持读取kubelet投影token、或者定时任务可以每次调用TokenRequest再重新加载kubeconfig),优先用短期token。
- 没法改代码、只能塞一个固定token跑半年的老系统,就只能用Secret方式拿长期token。
- CI/CD单次构建、一次性的数据迁移任务,用kubectl create token指定一个像8小时或24小时的有效期就非常合适。
下面这张表是我日常选型的参考:
| 对比维度 | kubectl create token | Secret绑定SA |
|---|---|---|
| token有效期 | 默认1小时,可通过--duration指定 | 长期有效,不自动过期 |
| 轮换方式 | 到期前重新生成 | 删除重建Secret,换取新token |
| 风险敞口 | 小 | 大,泄漏后影响时间长 |
| 适用场景 | CI/CD单次任务、短时操作 | 无法改代码的外部系统 |
| 官方推荐度 | 推荐 | 兼容保留,不推荐新接入 |
3. 手动生成SA token的完整实操
3.1 准备阶段:创建SA并绑定权限
先建一个SA。我这里以外部监控系统要读取ci命名空间资源为例:
kubectl create ns ci kubectl create sa ci-reader -n ci创建完验证一下:
kubectl get sa ci-reader -n ci这时候你去kubectl get secret -n ci看,会发现没有任何跟ci-reader相关的Secret,这就是新版集群和旧版集群最直观的区别。
然后把上面那套RBAC配置保存成rbac.yaml,执行:
kubectl apply -f rbac.yaml这里有个细节容易忽略:RBAC里的subjects用的是"kind: ServiceAccount",不是"kind: User"。我之前见过有同事把SA写成了User,结果怎么弄都是权限不足,因为这个API对象的subjects类型是固定的,SA必须用ServiceAccount类型。
3.2 方式A:kubectl create token生成短期token
新版kubectl自带了create token子命令,这是最推荐的方式。命令如下:
kubectl create token ci-reader -n ci --duration=8h --audience=https://kubernetes.default.svc参数解释一下:
- --duration指定过期时间,支持Go的时长格式,比如6h、24h、720h。默认是1小时。
- --audience填API Server认可的受众标识。如果集群没做特殊配置,填https://kubernetes.default.svc一般都能通过。
执行完会直接输出一串JWT格式的token,形如eyJhbGciOiJSUzI1NiIsImtpZCI6...很长一串。这个token可以直接用在kubeconfig里,也可以放在HTTP请求的Authorization: Bearer头里。
我在实际使用中一般是把token写进环境变量,然后在脚本里用:
SA_TOKEN=$(kubectl create token ci-reader -n ci --duration=24h)这样后续脚本里直接用$SA_TOKEN就行,不用复制粘贴,也避免token出现在shell历史里。
3.3 方式B:Secret绑定SA生成长期token
如果外部系统确实需要一个不过期的token,就手动创建Secret,并加上下面这个关键annotation。
apiVersion: v1 kind: Secret metadata: name: ci-reader-token namespace: ci annotations: kubernetes.io/service-account.name: ci-reader type: kubernetes.io/service-account-token保存为secret.yaml后执行:
kubectl apply -f secret.yaml等几秒钟,控制器会往这个Secret里写入token和ca.crt。验证一下:
kubectl get secret ci-reader-token -n ci -o yaml看到的data字段里应该有三项:ca.crt、namespace、token。token字段的值是base64编码后的JWT字符串。
提取token的命令:
kubectl get secret ci-reader-token -n ci -o jsonpath='{.data.token}' | base64 -d如果你在macOS上执行,有些老版本的base64命令不认-d参数,用--decode或者-D都可以。
这里有两个坑要重点说。第一个,Secret的type必须是kubernetes.io/service-account-token,写错成Opaque的话控制器不会往里面填token。第二个,annotation里的key是kubernetes.io/service-account.name,不是service-account-name,少一个点或者多了个横杠都不行。我有一次手滑把name拼成了names,结果token字段空了十分钟,排查的时候差点把集群翻了个底朝天。
3.4 组装外部系统的kubeconfig
外部系统拿到token之后,通常会配一个独立的kubeconfig文件来访问集群。我一般这样组装:
先拿到API Server地址和CA数据:
kubectl cluster-info拿到类似https://192.168.1.100:6443这样的地址。然后:
kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}'这一步拿到的也是一段base64编码的CA证书数据。到这里,token、API Server地址、CA数据三样东西都齐了,把它们拼成一个kubeconfig文件:
apiVersion: v1 kind: Config clusters: - name: external-cluster cluster: server: https://192.168.1.100:6443 certificate-authority-data: <上面拿到的base64数据> contexts: - name: external-context context: cluster: external-cluster namespace: ci user: external-user current-context: external-context users: - name: external-user user: token: <SA token字符串>这个文件可以放到外部系统对应的目录里,比如/opt/monitor/kubeconfig,或者挂在外部容器的某个安全目录。
3.5 验证token能否正常访问集群
配置文件写好后,第一步先做本地验证:
kubectl --kubeconfig=external-kubeconfig auth whoami输出里会显示USERNAME类似system:serviceaccount:ci:ci-reader,说明token认证是靠SA的身份,这个输出没问题的话再试一次资源操作:
kubectl --kubeconfig=external-kubeconfig get pods -n ci能正常返回Pod列表,说明整个链路已经通了。如果这一步报错,大部分情况不是token有问题,而是CA数据或者RBAC的问题,下面第4部分会有排查清单。
4. 常见问题与排查实录
4.1 401 Unauthorized:audience不匹配
报错表现是HTTP 401,token拿去curl API Server完全认不了。这个问题多见于用TokenRequest生成的短期token,因为TokenRequest里会带audience字段,而API Server校验时要求audience匹配。
排查方式很简单,看看你生成token时是不是指定了奇怪的audience。大多数集群用默认的https://kubernetes.default.svc就好。如果你实在不确定集群接受什么audience,就用kubectl create token时不带--audience参数,让它用默认值。
4.2 token怎么7天就过期了
有同事反馈,用Secret绑定SA生成的token,一周后就访问不了了。这里要区分一下:如果你用的是kubectl create token方式,默认1小时过期非常正常;如果你用的是Secret方式但token还是过期了,那就要检查是不是集群里开了额外的token老化策略,比如某些配置或安全组件强制TTL。
另外一个常见原因:外部系统的kubeconfig里同时配置了token和client-certificate,而证书过期时间更短,表现上就像token失效了。建议外部kubeconfig里只保留token认证字段,别混着配客户端证书。
4.3 403 Forbidden:RBAC没生效
401是身份不被认可,403是身份认可但权限不足。这两个区别一定要分清。403的排查思路就是看RBAC:SA在哪个命名空间、绑定了哪个Role、Role里有没有对应资源的操作权限。
我遇到过一种很隐蔽的情况:用ClusterRoleBinding绑定了SA之后,ClusterRole里的apiGroups没写对。比如要读Deployment,apiGroups得写apps,即使同名的资源在多个apiGroup里,写错组就是没权限。这类问题光看报错信息往往看不出来,建议一条一条资源去试,缩小范围。
4.4 删除与吊销:让一个token立刻失效
长期token一旦泄漏,第一反应就是赶紧让它失效。对Secret方式来说,直接删掉Secret:
kubectl delete secret ci-reader-token -n ci删掉之后所有持有这个token的请求都会变成401。对短期token来说,还没到过期时间就等它自然过期,或者如果是因为SA被删导致,最直接的办法是删掉绑定的SA,同命名空间下所有相关token都会一并失效。
4.5 base64解码在不同平台上的坑
提取Secret里的token要base64解码,大多数Linux发行版自带base64 -d,macOS自带的base64命令老版本只认-D,有些用GNU coreutils的macOS环境则两个参数都认,依具体环境而异。
我在文章里统一用base64 -d,如果你在macOS上报错,改成base64 --decode或者base64 -D即可。别小看这一点,我见过有同事直接复制走了base64编码后的原文当token用,结果请求一直失败,查了半天还怀疑是集群配置问题。
5. 安全使用建议
5.1 永远别绑cluster-admin
给外部系统配权限时,最容易犯的错误就是图省事绑个cluster-admin。我见过不止一次,外部监控系统拿着cluster-admin token,结果某天误操作把集群里重要的资源全删了。回头看,这个系统的正常功能只需要读Pod和节点,一个只读ClusterRole完全够用,当时图方便留下的隐患,出事之后整个团队连续加班了一周收拾残局。
最小权限原则在这件事上不是一句口号,外部系统的权限边界应该就覆盖它的正常业务范围,多出一点点都是在放大风险。
5.2 token的存放、传输与引用
SA token的敏感程度等同于一套API凭据,绝对不能直接写死在代码仓库或者镜像里。外部kubeconfig尽量放在专门的配置目录,权限收紧到只有运行用户能读。传输的话,如果系统支持,优先走密钥管理系统下发,而不是在聊天工具里转发明文token。
生产环境里我还见过一个细节:某些系统的日志会把HTTP头打出来,Authorization: Bearer后面紧跟的就是完整token,一次报错日志就能泄干净。如果外部系统是自己开发维护的,建议在日志脱敏处理上把Authorization字段排除掉。
5.3 轮换节奏与巡检
短期token的轮换机制可以交给kubelet或者定时任务处理,长期token就得人工规划轮换周期。我的经验是每90天轮换一次Secret绑定SA的token,做法是新建一个新名字的Secret,让外部系统切到新token后,再删除旧Secret。这个过程可以做成脚本,配合监控告警提示用到多少天。
巡检方面,我把下面几个SQL和维护动作固定在月度运维检查里:
- 罗列所有type为kubernetes.io/service-account-token的Secret,标记出那些创建超过90天且不在白名单里的。
- 检查ClusterRoleBinding里是否有绑定到外部SA的cluster-admin,一有就报警。
- 检查外部kubeconfig文件真实使用的token对应的SA,是否仍然存在。
这套巡检看起来琐碎,但它能在token泄漏或者权限放大问题酿成大祸之前,把风险提前暴露出来。
我个人在实际运维中的体会是,K8s从自动生成长期token走向按需签发短期token,这个方向本身就是安全和易用性的一个平衡结果。短期token确实更安全,但也逼着我们这些集成方把认证的逻辑理顺,把token当成有生命周期的资源来管理。如果你在这篇文章里只记住一件事,那就记住:外部系统能用短期token就别用长期token,能拿到最小权限就别贪图admin权限。真按这个原则走,1.32集群里手动生成SA token这件小事,就能既满足业务需求又守住安全底线。