K8s ServiceAccount Token 默认1小时过期,因1.24+改用TokenRequest API动态签发短期凭证,静态Secret被弃用且不自动刷新;Go程序应每次请求前实时读取/var/run/secrets/.../token文件,而非缓存或自行刷新。

为什么 k8s ServiceAccount Token 会过期(或被吊销)
默认情况下,Kubernetes 1.24+ 的 ServiceAccount Token 不再是长期有效的 Secret,而是由 TokenRequest API 动态签发的短期凭证(默认 1 小时)。你看到的 secrets 里那种 base64 编码的 JWT,其实是静态挂载的 legacy token,已被弃用且不自动刷新。
- 集群启用
LegacyServiceAccountTokenNoAutoGeneration特性门控后,旧 Secret 不再自动生成 - 即使存在,其内容也不会随证书轮转而更新,硬编码使用会导致 401 错误
-
automountServiceAccountToken: true挂载的是只读的/var/run/secrets/kubernetes.io/serviceaccount/token,但该文件内容本身不“自动轮转”——它只是被 kubelet 定期替换(依赖节点配置)
怎么让 Go 程序安全地拿到最新 Token(推荐方式)
不要读取文件后缓存、不要手动解析 JWT 过期时间、不要靠定时 reload。正确做法是:每次需要认证时,从本地文件实时读取,并容忍短暂 IO 失败或内容变更。
- Token 文件路径固定为
/var/run/secrets/kubernetes.io/serviceaccount/token,Go 程序应每次 HTTP 请求前os.ReadFile一次 - 文件是原子更新的(kubelet 写入时先写临时文件再 rename),所以不会读到截断或损坏内容
- 如果遇到
ENOENT或空内容,说明 Pod 启动异常或 SA 配置错误,应快速失败而非重试 - 示例关键逻辑:
tokenBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
if err != nil {
return nil, fmt.Errorf("failed to read service account token: %w", err)
}
client := kubernetes.NewForConfigOrDie(&rest.Config{
Host: "https://kubernetes.default.svc",
BearerToken: string(tokenBytes),
TLSClientConfig: rest.TLSClientConfig{
Insecure: false,
CAFile: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
},
})
为什么不能自己实现 JWT 刷新逻辑
你无法用 Go 调用 TokenRequest API 来“主动刷新”当前 Pod 的 Token——因为该 API 要求你已有有效凭据,而你正试图解决凭据失效的问题,陷入循环依赖。
-
TokenRequest是集群内服务账户向 kube-apiserver 申请新 token 的机制,但它需要一个已有 token 做认证,不是无状态的“换发”接口 - Pod 内的 token 更新完全由 kubelet 控制:它监听 Secret 变更、调用
TokenRequest、然后把新 token 写入 volume。你的 Go 程序只需消费这个结果 - 自行封装 “refresh loop” 不仅多余,还会掩盖真实问题(比如权限不足、RBAC 拒绝、CA 校验失败)
容易被忽略的 RBAC 和 Mount 配置细节
Token 有效 ≠ 你能用它调用任意 API。很多 403 错误其实和 Token 本身无关,而是权限或挂载遗漏。
立即学习“go语言免费学习笔记(深入)”;
- 确认 Pod 的
serviceAccountName字段已显式设置,且对应 SA 已绑定合适RoleBinding - 检查是否意外设置了
automountServiceAccountToken: false—— 这会让/var/run/secrets/...目录为空 - CA 证书路径必须准确:
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt,少一个字符就 TLS 握手失败 - 若用
rest.InClusterConfig(),它内部已处理 token + ca 自动加载,但前提是环境变量KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT存在(通常由 kube-dns 注入)










