摘要:为 Pod 配置 ServiceAccount 身份,理解 TokenRequest、投射卷、Token 轮换与 Audience,并通过最小权限和关闭自动挂载控制访问范围。
1. 为什么需要 ServiceAccount?
先看一个实际场景。
假设我们部署了一个应用程序,它运行在 Kubernetes 的 Pod 中。
这个应用程序需要完成以下工作:
- 获取当前 Namespace 中所有 Pod。
- 监听 Pod 状态变化。
- 根据 Pod 状态执行一些自动化操作。
例如,一个自定义控制器需要持续监听 Deployment 和 Pod 的变化。
但是,Pod 本质上只是一个容器运行环境。容器本身并不会天然拥有访问 Kubernetes API 的身份。
因此,需要给 Pod 分配一个身份。
这个身份就是 ServiceAccount。
flowchart TB
P["Kubernetes Pod"] -->|"运行"| A["业务程序 / Controller"]
P -->|"serviceAccountName"| S["ServiceAccount<br/>dev-sa"]
A -->|"携带 ServiceAccount Token"| K["Kubernetes API Server<br/>验证身份并检查 RBAC 权限"]
注意:
ServiceAccount 只提供身份,并不意味着它自动拥有访问所有 Kubernetes 资源的权限。
身份和权限仍然是两个独立的概念。
2. ServiceAccount 的基本结构
2.1 ServiceAccount 是什么?
ServiceAccount 是 Kubernetes 中的一种 API 资源。
它属于某个 Namespace。
查看当前集群中的 ServiceAccount:
kubectl get serviceaccount -A也可以简写:
kubectl get sa -A通常你会发现,每个 Namespace 默认都有一个名为 default 的 ServiceAccount。
例如:
kubectl get sa -n default输出类似:
NAME SECRETS AGEdefault 0 10d这里的 SECRETS 为 0 并不表示没有 Token。
较新版本 Kubernetes 默认采用 TokenRequest API 和投射卷机制为 Pod 提供短期 Token,而不是自动创建长期有效的 Secret。
2.2 每个 Namespace 为什么都有 default?
当你创建一个 Namespace 时,Kubernetes 通常会自动创建一个名为 default 的 ServiceAccount。
如果创建 Pod 时没有指定 ServiceAccount:
apiVersion: v1kind: Podmetadata: name: nginx namespace: defaultspec: containers: - name: nginx image: nginxKubernetes 会默认使用:
spec: serviceAccountName: default也就是说:
没有指定 ServiceAccount 的 Pod,默认使用所在 Namespace 的 default ServiceAccount。
可以通过以下命令查看 Pod 使用的身份:
kubectl get pod nginx \ -o jsonpath='{.spec.serviceAccountName}'也可以查看完整配置:
kubectl describe pod nginx关注:
Service Account: default2.3 创建自己的 ServiceAccount
实际开发中,不建议所有应用程序都使用 default ServiceAccount。
应该根据应用程序的职责创建独立身份。
例如:
kubectl create serviceaccount dev-sa \ -n dev也可以使用 YAML:
apiVersion: v1kind: ServiceAccountmetadata: name: dev-sa namespace: dev3. Pod 如何使用 ServiceAccount?
创建 ServiceAccount 后,需要将它关联到 Pod。
3.1 指定 ServiceAccount
apiVersion: v1kind: Podmetadata: name: app namespace: devspec: serviceAccountName: dev-sa containers: - name: app image: nginx关键配置:
spec: serviceAccountName: dev-sa它告诉 Kubernetes:
这个 Pod 使用 dev Namespace 中的 dev-sa 作为自己的身份。
注意:ServiceAccount 和 Pod 必须位于同一个 Namespace。
3.2 Kubernetes 如何向 Pod 提供 Token?
当 Pod 使用 ServiceAccount 时,Kubernetes 通常会自动将身份凭据挂载到容器内部。
默认挂载路径:
/var/run/secrets/kubernetes.io/serviceaccount/通常可以看到:
ca.crtnamespacetoken三个文件的作用:
| 文件 | 作用 |
|---|---|
| token | ServiceAccount 身份认证凭据 |
| ca.crt | 验证 API Server 的 TLS 证书 |
| namespace | 当前 Pod 所属 Namespace |
这里需要区分两个概念:
- Token:证明自己是谁。
- CA 证书:验证自己连接的 API Server 是否可信。
它们分别解决客户端身份认证和服务端身份验证问题。
4. ServiceAccount Token 的工作原理
这个 Token 是谁生成的?如何进入 Pod?什么时候失效?
这涉及 Kubernetes 的 Token 投射机制。
4.1 传统方式:Secret Token
在较早版本的 Kubernetes 中,创建 ServiceAccount 后,通常会自动创建一个长期有效的 Token Secret。
结构类似:
apiVersion: v1kind: Secretmetadata: name: dev-sa-token namespace: dev annotations: kubernetes.io/service-account.name: dev-satype: kubernetes.io/service-account-tokenSecret 中包含:
data: ca.crt: ... namespace: ... token: ...这种方式存在一些问题:
- Token 生命周期较长。
- Token 泄露后可能长期被滥用。
- 需要额外管理 Token 的创建、存储和轮换。
因此,现代 Kubernetes 已经不再默认自动为每个 ServiceAccount 创建长期有效的 Token Secret。
4.2 现代方式:TokenRequest API
从 Kubernetes 1.22 开始,Bound ServiceAccount Token 成为默认机制。
它通过 TokenRequest API 申请短期有效的 Token。
整个过程可以理解为:
flowchart TB
P["Pod 创建<br/>serviceAccountName: dev-sa"] --> K["Kubelet"]
K -->|"TokenRequest API 申请 Token"| S["API Server"]
S -->|"签发绑定 ServiceAccount、Pod 的 Token"| K
K -->|"写入并管理凭据"| V["Projected Volume"]
V -->|"投射到容器文件系统"| A["应用程序读取 Token"]
A -->|"携带 Token 访问 Kubernetes API"| S
这里有一个重要细节:
Token 并不是简单地由容器自己生成,而是由 Kubernetes API Server 签发,Kubelet 负责请求和管理投射到 Pod 中的凭据。
4.3 Token 的主要特点
现代 ServiceAccount Token 通常具有以下特点:
- 有时效性:Token 有过期时间。
- 身份绑定:可以绑定到特定 Pod。
- 自动轮换:Kubelet 会在 Token 到期前更新投射的凭据。
- Audience 限制:Token 可以限定适用的接收方。
Token 的 JWT Payload 示例:
{ "iss": "https://kubernetes.default.svc", "sub": "system:serviceaccount:dev:dev-sa", "aud": [ "https://kubernetes.default.svc" ], "exp": 1790000000, "kubernetes.io": { "namespace": "dev", "serviceaccount": { "name": "dev-sa", "uid": "..." }, "pod": { "name": "app", "uid": "..." } }}重点认识三个字段:
| 字段 | 含义 |
|---|---|
sub | Token 对应的身份 |
aud | Token 的目标受众 |
exp | Token 过期时间 |
例如:
system:serviceaccount:dev:dev-sa表示:
system:serviceaccount:ServiceAccount 身份类型。dev:Namespace。dev-sa:ServiceAccount 名称。
注意:JWT Payload 解码不等于验证 Token 的真实性。真正的签名、有效期、受众及身份绑定校验由 API Server 等验证方完成。
4.4 Token 自动轮换
假设一个 Pod 运行了 30 天,而 Token 的有效期远短于 30 天。
如果 Token 不会自动更新,应用程序就可能因为 Token 过期而无法访问 API Server。
Kubernetes 通过 Kubelet 的 Token 投射与轮换机制解决这个问题。
需要注意两点:
- Kubelet 会更新投射卷中的 Token 文件。
- 应用程序应当重新读取 Token 文件,不能一直使用启动时读取并缓存的旧 Token。
使用 Kubernetes 官方客户端库时,通常可以利用客户端库提供的凭据刷新能力。
如果是自己编写的程序,就需要正确处理文件更新和重新读取。
5. Projected Volume:理解 Token 投射
前面提到了 Projected Volume,它是 Kubernetes 将多个数据源组合到同一个目录中的一种机制。
ServiceAccount Token 就是其中一种典型应用。
5.1 查看 Pod 的 Token 挂载
kubectl describe pod app -n dev关注:
Mounts: /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-xxxxx (ro)
Volumes: kube-api-access-xxxxx: Type: Projected这说明 Kubernetes 使用 Projected Volume 为 Pod 提供访问 API Server 所需的凭据。
它可能组合以下数据源:
- ServiceAccount Token。
- API Server CA 证书。
- Pod 所属 Namespace 信息。
5.2 自定义 Projected Volume
除了默认挂载方式,我们还可以自己定义 Token 投射。
例如:
apiVersion: v1kind: Podmetadata: name: projected-token namespace: devspec: serviceAccountName: dev-sa containers: - name: app image: nginx volumeMounts: - name: token-volume mountPath: /var/run/custom-token readOnly: true volumes: - name: token-volume 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这里:
| 配置 | 含义 |
|---|---|
serviceAccountToken | 请求投射 ServiceAccount Token |
path | Token 在挂载目录中的文件名 |
expirationSeconds | 请求的 Token 有效期 |
audience | Token 的目标受众 |
configMap | 投射 CA 证书 |
注意:
expirationSeconds 是请求的有效期,实际签发期限由 API Server 配置和策略决定。通常不能设置低于 600 秒的请求期限。
自定义 Token 投射与默认 Token 挂载可以并存,但如果自定义 Token 已经满足应用需求,应该避免无意义地挂载多份凭据。
6. 如何禁止 Pod 自动挂载 Token?
这是一个非常重要的安全知识点。
不是所有 Pod 都需要访问 Kubernetes API。
例如:
- Nginx Web 服务。
- 普通 Java 微服务。
- 静态资源服务。
- 不需要访问集群 API 的任务容器。
这些应用如果没有 API 访问需求,就不应该默认获得 ServiceAccount Token。
6.1 在 Pod 中关闭
apiVersion: v1kind: Podmetadata: name: nginx namespace: devspec: automountServiceAccountToken: false containers: - name: nginx image: nginx6.2 在 ServiceAccount 中关闭
也可以在 ServiceAccount 层面设置:
apiVersion: v1kind: ServiceAccountmetadata: name: dev-sa namespace: devautomountServiceAccountToken: false这样,使用这个 ServiceAccount 的 Pod 默认不会自动挂载 Token。
如果 Pod 和 ServiceAccount 都设置了该字段,Pod 级别的设置优先。
生产环境建议
遵循最小权限原则:
- 不需要访问 API 的 Pod:关闭自动挂载。
- 需要访问 API 的 Pod:使用专属 ServiceAccount。
- 只授予完成业务所需的最小 RBAC 权限。
7. Pod 如何真正访问 Kubernetes API Server?
假设有一个应用程序运行在 Pod 中,它需要获取 dev Namespace 中的 Pod 列表。
完整过程是:
flowchart TB
P["Pod 使用 dev-sa ServiceAccount"] --> K["Kubelet 将 Token 投射并挂载到 Pod"]
K --> A["应用程序读取 Token"]
A -->|"HTTPS 请求携带 Bearer Token"| S["API Server 验证 Token"]
S --> R["RBAC 检查权限"]
R -->|"授权成功"| D["返回 dev Namespace 的 Pod 列表"]
7.1 Kubernetes API Server 地址
在 Pod 内部,通常可以通过 Kubernetes Service 访问 API Server:
https://kubernetes.default.svc查看 Kubernetes Service:
kubectl get svc kubernetes输出类似:
NAME TYPE CLUSTER-IPkubernetes ClusterIP 10.96.0.1其中:
kubernetes是默认 Service 名称。default是 Namespace。svc是 Kubernetes Service DNS 域名的一部分。
7.2 给 ServiceAccount 授予权限
先创建一个 Role:
apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: pod-reader namespace: devrules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"]再创建 RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: dev-sa-pod-reader namespace: devsubjects: - kind: ServiceAccount name: dev-sa namespace: devroleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.io7.3 使用 Token 调用 API Server
Pod 内部使用 Token 调用 API 的命令示例:
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt访问 API Server:
curl --cacert "$CACERT" \ -H "Authorization: Bearer $TOKEN" \ https://kubernetes.default.svc/api/v1/namespaces/dev/pods如果权限配置正确,API Server 会返回 JSON 格式的 Pod 列表。
8. ServiceAccount Token 的手动生成与管理
8.1 手动生成 Token
kubectl create token dev-sa -n dev指定有效期:
kubectl create token dev-sa \ -n dev \ --duration=1h指定 Audience:
kubectl create token dev-sa \ -n dev \ --audience=https://kubernetes.default.svc这个命令通过 TokenRequest API 申请 Token。
注意:手动生成的 Token 默认不是绑定到某个 Pod 的 Token,不能简单地认为它与 Pod 自动挂载的 Token 完全相同。
8.2 查看 ServiceAccount
kubectl get sa -n devkubectl describe sa dev-sa -n dev8.3 查看 Pod 使用的 ServiceAccount
kubectl get pod app -n dev \ -o jsonpath='{.spec.serviceAccountName}'8.4 查看 ServiceAccount 对应的权限
kubectl auth can-i --list \ --as=system:serviceaccount:dev:dev-sa \ -n dev8.5 查看 Token 是否自动挂载
kubectl get pod app -n dev \ -o jsonpath='{.spec.automountServiceAccountToken}'注意:如果输出为空,不代表关闭了自动挂载,而是表示 Pod 没有显式设置该字段,需要继续检查 ServiceAccount 配置及默认行为。
9. 生产环境中的 ServiceAccount 设计
假设有一个实际的微服务集群:
| 应用 | 需要的权限 |
|---|---|
| Nginx | 不需要访问 API |
| 日志采集器 | 根据需求读取 Pod 元数据 |
| Prometheus | 根据采集方式读取监控相关资源 |
| 自定义 Controller | 读取和更新指定资源 |
| CI/CD Agent | 根据发布流程创建或更新资源 |
合理的设计方式:
9.1 最小权限设计
flowchart LR
N["Nginx"] --> N1["automountServiceAccountToken: false<br/>不挂载 Token"]
P["Prometheus"] --> P1["独立 ServiceAccount + 只读 RBAC<br/>只读权限"]
C["Controller"] --> C1["独立 ServiceAccount + 精确资源授权<br/>按需授权"]
生产环境尤其需要注意:
- 不要让所有 Pod 共用具有高权限的 ServiceAccount。
- 不要随意创建长期有效的 ServiceAccount Token Secret。
- 不需要访问 API Server 的应用应关闭 Token 自动挂载。
- 尽量限制 Token 的 Audience 和有效期。
- 不要将 Token 写入镜像、代码仓库或日志。
- 对 Controller 等程序,精确限制其可操作的资源和 Namespace。