在 Kubernetes 的权限体系中,ServiceAccount 是一个容易被忽视却至关重要的概念。很多人在 Pod 里调用 API Server 遇到 403 Forbidden 时,第一反应是"RBAC 配错了",却往往忽略了更基础的一层:Pod 到底以什么身份在说话?这个"身份"就是 ServiceAccount。
本文从概念出发,逐步拆解 ServiceAccount 的工作原理,并通过一个实战示例演示如何让 Pod 安全地访问 Kubernetes API。
一、ServiceAccount 是什么
ServiceAccount(服务账号)是 Kubernetes 中用于非人类用户的身份标识。它和普通用户账号的核心区别在于:
| 对比项 | ServiceAccount | 用户账号 |
|---|---|---|
| 存在位置 | Kubernetes API 中的对象 | 外部系统 |
| 作用域 | 命名空间级别 | 集群或全局 |
| 目标用途 | 工作负载、自动化工具 | 人类用户 |
简单说,当你的 Pod 需要和 API Server 对话、需要拉取私有镜像、或者需要向外部服务证明"我是谁"时,用的就是 ServiceAccount。
二、默认 ServiceAccount 与它的局限
每个命名空间在创建时,Kubernetes 会自动生成一个名为 default 的 ServiceAccount。如果你在部署 Pod 时没有显式指定 serviceAccountName,Kubernetes 就会把这个 default 账号分配给 Pod。
但这个默认账号的权限非常有限 。在启用 RBAC 的集群中,default ServiceAccount 除了拥有公开的 API 发现权限(比如可以查看 API 版本信息)之外,没有任何额外的访问权限。如果你的应用需要读取 Pod 列表、创建 Job 或者访问其他资源,用默认账号一定会被拒绝。
三、Pod 是如何"拿到" ServiceAccount 身份的
理解这一点,就能理解为什么有时候"明明配了 RBAC 却还是不行"。
当 Pod 启动时,Kubernetes 会通过准入控制器自动向 Pod 中注入一个投射卷(projected volume),其中包含三样东西:
- ServiceAccount Token:一个短期有效的 JWT 令牌,kubelet 通过 TokenRequest API 获取,默认有效期 1 小时,会随 Pod 删除而失效。
- CA 证书 (
ca.crt):用于验证 API Server 的证书是否可信。 - 命名空间信息 (
namespace):当前 Pod 所在的命名空间名称。
这些内容被挂载到容器内的固定路径:
/var/run/secrets/kubernetes.io/serviceaccount/
├── token
├── ca.crt
└── namespace
你的应用代码(或者 curl 命令)就是通过读取这些文件来完成身份认证的。
历史变化 :在 Kubernetes 1.24 之前,ServiceAccount 的 Token 是以 Secret 形式长期存在的。从 1.24 开始,默认改为使用 TokenRequest API 生成有期限的动态 Token。这意味着旧的"挂载 Secret 然后无限期使用"的做法已经过时。
四、实战:让 Pod 以自定义 ServiceAccount 访问 API Server
下面通过一个完整示例,演示从零创建 ServiceAccount、授权、并在 Pod 中调用 API 的全过程。
第一步:创建 ServiceAccount
yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: api-reader
namespace: default
bash
kubectl apply -f api-reader-sa.yaml
第二步:通过 RBAC 授权
创建一个 Role,只允许读取 Pod 信息:
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
然后把这个 Role 绑定到 api-reader 这个 ServiceAccount 上:
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: api-reader-binding
namespace: default
subjects:
- kind: ServiceAccount
name: api-reader
namespace: default
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
第三步:部署 Pod 并指定 ServiceAccount
yaml
apiVersion: v1
kind: Pod
metadata:
name: api-client
spec:
serviceAccountName: api-reader
containers:
- name: curl
image: curlimages/curl:latest
command: ["sleep", "3600"]
注意 serviceAccountName 字段。如果不写,Pod 会使用 default 账号,上面创建的 RoleBinding 就不会生效。
第四步:在 Pod 内调用 API
进入 Pod 后,用挂载的 Token 和 CA 证书调用 API Server:
bash
# 进入 Pod
kubectl exec -it api-client -- sh
# 设置变量
APISERVER=https://kubernetes.default.svc
SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
TOKEN=$(cat ${SERVICEACCOUNT}/token)
CACERT=${SERVICEACCOUNT}/ca.crt
# 调用 API 列出当前命名空间的 Pod
curl --cacert ${CACERT} \
--header "Authorization: Bearer ${TOKEN}" \
${APISERVER}/api/v1/namespaces/default/pods
这段命令的核心逻辑是:用 Token 向 API Server 证明"我是 api-reader",用 CA 证书验证"对方确实是 API Server"。
五、常见问题排查
现象:Pod 内调用 API 返回 403 Forbidden
先确认 Pod 实际使用的 ServiceAccount 名称:
bash
kubectl get pod api-client -o jsonpath='{.spec.serviceAccountName}'
然后检查这个 ServiceAccount 是否有对应的 RoleBinding。很多"RBAC 配了但没生效"的情况,实际上是 Pod 用的还是 default 账号,而你的授权对象写的是自定义账号。
现象:挂载的 Token 文件为空或不存在
检查 Pod 是否显式禁用了 Token 自动挂载:
yaml
spec:
automountServiceAccountToken: false
如果设为 false,Kubernetes 不会向 Pod 注入 Token 卷。
六、什么时候该用 ServiceAccount
根据官方建议,以下场景应该考虑创建专用的 ServiceAccount:
- Pod 需要与 Kubernetes API Server 通信(比如 Operator、Controller)
- Pod 需要从私有镜像仓库拉取镜像(通过
imagePullSecrets) - Pod 需要向外部服务(如云厂商 API)证明自己的身份
- 外部 CI/CD 工具需要认证到集群
核心原则是"最小权限" :为每个工作负载创建独立的 ServiceAccount,只授予它完成工作所必需的最小权限集。而不是图省事把所有 Pod 都塞进 default,然后给 default 一堆权限。
小结
ServiceAccount 的本质是 "Pod 的身份" 。它解决了"我是谁"的问题,RBAC 则解决了"我能做什么"的问题。两者配合,才能让 Pod 安全、可控地访问 Kubernetes API 和其他外部资源。理解 Token 的挂载机制和 default 账号的权限边界,是排查大部分 403 错误的基础。