返回首页

6.2:ServiceAccount 与工作负载身份

本章你将学会

  • ServiceAccount 的作用是什么,默认行为是什么
  • Pod 是怎样拿着 ServiceAccount 身份去访问 API Server 的
  • v1.24+ 之后,ServiceAccount Token 为什么变了
  • 什么是 Token 投射(projected token)与身份边界控制
  • 工作负载身份管理在生产环境里的最佳实践

1. ServiceAccount 到底是什么

ServiceAccount 是 Kubernetes 为 Pod / 工作负载 提供的身份对象。它和“给人使用的账号”不是一类东西:用户账号更偏向人类运维、开发者、CI 系统,而 ServiceAccount 更偏向运行在集群里的应用进程::cite[174]。

从使用角度看,ServiceAccount 主要解决这些问题:

  • Pod 调用 Kubernetes API 时,用什么身份发起请求
  • 某个工作负载应该拥有哪些 RBAC 权限
  • 某个工作负载如何和外部云厂商或密钥系统建立信任关系
  • 镜像拉取、控制器运行、自动化任务等场景如何获得独立身份::cite[174]

2. 默认行为:每个命名空间都有一个 default ServiceAccount

Kubernetes 会在每个命名空间里自动创建一个名为 default 的 ServiceAccount。如果你创建 Pod 时没有显式指定 spec.serviceAccountName,那这个 Pod 就会自动使用该 namespace 下的 default ServiceAccount::cite[174]。

这一点非常关键,因为它意味着:

  • 不写 serviceAccountName,不等于“没有身份”
  • 你只是退回到了 default 这个身份

官方同时说明,default ServiceAccount 默认除了认证后的基础 API discovery 权限外,并不会自动拥有额外业务权限;但如果团队后续给它绑定了更大权限,那么所有没显式声明账号的 Pod 都会一起继承这些权限::cite[174]。

3. ServiceAccount 的使用路径:创建、授权、分配给 Pod

一个典型流程通常分 3 步:

  1. 创建 ServiceAccount
  2. 使用 RBAC 给它授予最小权限
  3. 在 Pod 或 Deployment 中通过 serviceAccountName 使用它::cite[174]

4. 实操:给一个只读工作负载配置独立身份

4.1 场景目标

我们希望一个应用:

  • 运行在 app-demo 命名空间
  • 使用独立的 ServiceAccount
  • 只允许读取本命名空间中的 Pod
  • 禁止自动挂载不必要的 token 到不需要访问 API 的容器

4.2 完整 YAML

apiVersion: v1
kind: Namespace
metadata:
  name: app-demo
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: pod-viewer
  namespace: app-demo
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-read-only
  namespace: app-demo
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-read-only-binding
  namespace: app-demo
subjects:
  - kind: ServiceAccount
    name: pod-viewer
    namespace: app-demo
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: pod-read-only
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: viewer
  namespace: app-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: viewer
  template:
    metadata:
      labels:
        app: viewer
    spec:
      serviceAccountName: pod-viewer
      automountServiceAccountToken: true
      containers:
        - name: main
          image: curlimages/curl:8.8.0
          command: ["sleep", "3600"]

4.3 应用配置

kubectl apply -f sa-demo.yaml

4.4 验证权限

kubectl auth can-i list pods \
  --as=system:serviceaccount:app-demo:pod-viewer \
  -n app-demo
kubectl auth can-i get secrets \
  --as=system:serviceaccount:app-demo:pod-viewer \
  -n app-demo

如果第一条是 yes、第二条是 no,说明这个工作负载身份已经被限制在恰当范围内。

5. Pod 是如何使用 ServiceAccount 访问 API Server 的

当 Pod 指定了 spec.serviceAccountName 之后,Kubernetes 会为它准备访问 API Server 所需的凭据。自 v1.22 起,默认机制已经变成:通过 TokenRequest API 获取短时、自动轮换的 token,并作为 projected volume 挂载到 Pod 内::cite[174]。

也就是说,现代 Kubernetes 的默认思路不是“塞一个长期 Secret 给 Pod”,而是“按需发一个短期、可轮换、和 Pod 绑定的令牌”。这比旧式静态 Token 安全得多::cite[175]。

5.1 默认挂载位置

当 ServiceAccount admission controller 生效时,Pod 创建过程中会被自动补充一个卷,并挂载到下面这个路径:

/var/run/secrets/kubernetes.io/serviceaccount

这个卷里通常包含:

  • token:访问 API Server 的令牌
  • ca.crt:集群 CA 证书
  • namespace:当前 Pod 所在命名空间信息::cite[175]

5.2 API 调用过程怎么理解

工作负载容器拿到 token 后,向 https://kubernetes.default.svc 发请求时会把它作为 Bearer Token 带上。API Server 识别出这个 token 对应的 ServiceAccount,再走授权判断,看它是否可以执行相应操作::cite[174]。

6. Token 投射:v1.24+ 之后最重要的变化之一

在旧版本 Kubernetes 里,ServiceAccount 往往会自动生成一个长期存在的 Secret 型 token。这个机制的问题很明显:

  • 生命周期长
  • 泄露后可长期滥用
  • 很难做边界收敛与失效控制

Kubernetes 从 v1.24 开始不再自动为每个 ServiceAccount 创建这类长期 token Secret;此后默认推荐使用 TokenRequest + projected volume 机制。长期 token 仍然可以手工创建,但已经不再是推荐路径::cite[174]。

6.1 投射 token 的几个安全特征

根据官方说明,基于 TokenRequest 的 Pod 内 token 具备以下特点:

  • 短生命周期:默认有效期通常为 1 小时
  • 自动刷新:kubelet 会在过期前自动轮换
  • Pod 绑定:token 与具体 Pod 关联
  • 默认 audience 为 kube-apiserver:降低滥用面::cite[175]

6.2 如果 Pod 被删除,会发生什么

对于 Pod 绑定的 ServiceAccount token,如果 Pod 被删除,这些 token 也会随之失效。因此它们比过去那种长期 Secret token 更符合“最小暴露时间”的安全原则::cite[175]。

7. 实操:显式使用 ServiceAccount Token Projection

如果你的应用确实需要 API Token,但你希望更明确地控制 token 的挂载路径、受众(audience)和有效期,可以显式写出 projected volume。

7.1 完整 YAML

apiVersion: v1
kind: Pod
metadata:
  name: api-client
  namespace: app-demo
spec:
  serviceAccountName: pod-viewer
  automountServiceAccountToken: false
  containers:
    - name: client
      image: curlimages/curl:8.8.0
      command: ["sleep", "3600"]
      volumeMounts:
        - name: k8s-api-token
          mountPath: /var/run/secrets/tokens
          readOnly: true
  volumes:
    - name: k8s-api-token
      projected:
        sources:
          - serviceAccountToken:
              path: token
              expirationSeconds: 3600
              audience: https://kubernetes.default.svc
          - configMap:
              name: kube-root-ca.crt
              items:
                - key: ca.crt
                  path: ca.crt
          - downwardAPI:
              items:
                - path: namespace
                  fieldRef:
                    fieldPath: metadata.namespace

7.2 为什么推荐这么写

这种方式的好处是:

  • 明确知道 token 放在哪
  • 控制 token 生命周期
  • 控制 token 的 audience
  • 避免把默认挂载能力“无意中”给到所有容器

这正是“身份边界控制”的核心:不是只有有没有权限的问题,还包括凭据如何分发、挂载到哪里、多久失效、谁能看到::cite[175]。

8. 自动挂载控制:automountServiceAccountToken

官方最佳实践非常强调一点:不是所有 Pod 都应该自动拿到 ServiceAccount token。如果某个工作负载根本不需要访问 Kubernetes API,就应该把自动挂载关掉::cite[167]。

你可以在两个层面控制它:

  • 在 ServiceAccount 上设置 automountServiceAccountToken: false
  • 在 Pod 上设置 automountServiceAccountToken: false

Pod 级配置可以覆盖 ServiceAccount 级配置,这在“同一个 ServiceAccount 供多个工作负载使用,但只有少数 Pod 需要 token”时会很有用::cite[175]。

一个完全不需要 API 身份的 Pod,可以这样写:

apiVersion: v1
kind: Pod
metadata:
  name: no-api-access
  namespace: app-demo
spec:
  serviceAccountName: pod-viewer
  automountServiceAccountToken: false
  containers:
    - name: app
      image: nginx:1.27

9. 手动创建长期 token,为什么不推荐

虽然 Kubernetes 仍允许你手动创建 kubernetes.io/service-account-token 类型的 Secret,并由控制平面填充 token,但官方已经明确建议:优先使用短期 TokenRequest,而不是长期 Secret token::cite[175]。

原因主要有三个:

  1. 长期 bearer token 一旦泄露,风险持续时间长
  2. 轮换和吊销成本高
  3. 很容易被脚本、日志、临时调试文件无意保留

从 v1.29 开始,历史上自动生成但长期未使用的 legacy ServiceAccount token 还会被标记失效,并在持续未使用后被清理。这说明 Kubernetes 整体方向就是逐步淘汰长期自动 token::cite[175]。

10. 工作负载身份管理的最佳实践

10.1 一个应用一个 ServiceAccount

不要让一堆 Deployment 共用 default。一旦共用,你很难回答下面这个问题:

这个 token 到底代表哪个应用?

独立 ServiceAccount 可以让 RBAC、审计、问题定位都更清晰::cite[174]。

10.2 不需要 API,就不要挂 token

这是一条最容易落地、收益又很高的规则。很多业务容器根本不需要访问 Kubernetes API,把 token 挂进去只是在扩大暴露面::cite[167]。

10.3 ServiceAccount 只配最小权限

ServiceAccount 只是身份,不应该天然拥有大权限。真正的权限来自 RBAC 绑定,因此你应该按场景拆账号:

  • 只读账号
  • 控制器账号
  • Job 账号
  • 外部系统同步账号

不要一个 ServiceAccount 既能读 Secret、又能创建 Deployment、还能 patch Admission 配置。

10.4 跨命名空间访问要显式设计

ServiceAccount 本身是 namespaced 对象,但通过 RBAC 绑定,它可以访问其他命名空间的资源。因此“账号在哪个 namespace”不等于“权限就只在这个 namespace 内”。跨 namespace 权限要单独审查::cite[174]。

10.5 外部身份集成优先用短期凭据

如果你的应用要和云厂商 IAM、Vault、密钥系统打通,优先考虑基于短期 token、受众约束、信任映射的方式,而不是把永久云凭证直接写进 Secret 或镜像。ServiceAccount 应该是身份桥梁,而不是长期密钥仓库::cite[174]。

11. 常见误区

误区一:默认 ServiceAccount 没权限,所以可以放心共用

今天没权限,不代表明天没人给它绑上角色。平台上最常见的权限污染,就是默认账号一点点变成“历史包袱账号”。

误区二:只要是只读 token 就没风险

不对。只读也可能读到 Secret、Pod 规范、镜像地址、环境变量引用、控制器状态等敏感信息。

误区三:Pod 里有 token 是正常现象,不用管

正常不等于合理。你应该问的是:这个 Pod 真的需要它吗?

12. 本章小结

ServiceAccount 是 Kubernetes 工作负载身份的基础设施。你要记住的重点有这几条:

  • 每个 namespace 都有 default ServiceAccount,不显式指定就会自动使用它::cite[174]
  • 现代 Kubernetes 默认通过 TokenRequest + projected volume 给 Pod 提供短期、自动轮换的 token::cite[174]
  • v1.24+ 不再自动为每个 ServiceAccount 创建长期 Secret token,长期 token 只应在特殊场景下手工创建并严格管控::cite[175]
  • automountServiceAccountToken 是一个非常重要的身份暴露控制开关::cite[167]
  • 最佳实践是:每个工作负载使用独立 ServiceAccount、只授予最小权限、按需投射 token、尽量缩短凭据生命周期

掌握了 ServiceAccount,你就真正进入了 Kubernetes 安全体系的中层:身份怎么发、权限怎么收、边界怎么控。下一章我们继续看 Pod 安全标准,理解“就算有身份,也不能随便用危险能力”。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

Kubernetes 从入门到精通(教程笔记大纲)

下一篇

Chapter 6.5:Admission Controller