ServiceAccount 服务账号
本教程共 65 篇 · 第 45 篇 · 更新于 2026-08-14 · 约 19 分钟阅读
本节目标:搞懂 Pod 是靠什么身份访问 API 服务器的,学会给工作负载配一个权限刚好够用的 ServiceAccount。
第 43 章提过一句:Kubernetes 里没有 User 对象,真正被它当对象管理的身份只有 ServiceAccount。这一章就来看这个东西。
ServiceAccount(服务账号,常缩写 SA)是给非人类用户准备的身份。应用 Pod、系统组件、集群内外的自动化工具,都可以用某个 SA 的凭据把自己标识成那个 SA。
45-1 和用户账号有什么不一样
一张表说清:
| ServiceAccount | 用户 / 组 | |
|---|---|---|
| 存在于哪 | Kubernetes API(ServiceAccount 对象) | 外部系统 |
| 访问控制 | RBAC 或其他鉴权机制 | RBAC 或外部 IAM |
| 给谁用 | 工作负载、自动化工具 | 人 |
关键差别是位置。用户身份对 API 服务器来说是不透明数据——它只认证书或令牌里的那个用户名字符串,不知道这个人从哪来、还存不存在。而 ServiceAccount 是集群里实打实的一个对象,你能 kubectl get sa 看到它。
SA 有三个特点:
- 命名空间限定——每个 SA 属于一个命名空间。
- 轻量——就是个 API 对象,几行 YAML 建一个,用完就删。
- 可移植——一套复杂应用的配置包可以把自己的 SA 定义一起带上,换集群不用改。
45-2 default 服务账号:那个你一直在用却没注意的东西
创建集群时,Kubernetes 会给每个命名空间自动建一个名叫 default 的 ServiceAccount。新建命名空间也一样。
kubectl get serviceaccounts -n default
# NAME SECRETS AGE
# default 0 12d
你前面 44 章建的所有 Pod,只要没指定 SA,用的都是它。
这个 default 账号的权限有多大?除了所有已认证主体都有的 API 发现权限,什么都没有。
也就是说,它能访问 /api、/version、/openapi 这类发现端点,但读不了 Pod、列不出 Service。默认是安全的。
Note删不掉。你
kubectl delete sa default,控制平面会立刻建一个新的补上。别费劲了。
Warning千万别给
default服务账号加权限。它是所有未指定 SA 的 Pod 的默认身份,给它绑一个edit,等于命名空间里每个”忘了配身份”的 Pod 都拿到了读写权限。这是我见过最常见的权限失控方式——加的时候图省事,出问题时根本查不到源头。正确做法永远是建一个专用 SA。
45-3 建一个专用 SA 并绑权限
三步:建 SA、建角色、绑起来。
apiVersion: v1
kind: ServiceAccount
metadata:
name: log-reader
namespace: monitoring
或者一行命令:
kubectl create serviceaccount log-reader -n monitoring
然后用第 44 章学的 RBAC 授权。注意 subjects 里 SA 的写法——要 namespace,不要 apiGroup:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-log-reader
namespace: default
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: log-reader-binding
namespace: default
subjects:
- kind: ServiceAccount
name: log-reader
namespace: monitoring # SA 在 monitoring
roleRef:
kind: Role
name: pod-log-reader
apiGroup: rbac.authorization.k8s.io
留意这个例子:SA 在 monitoring 命名空间,RoleBinding 在 default 命名空间。这就是跨命名空间授权的写法。
规则很好记:绑定放在被访问资源所在的命名空间,主体里写 SA 自己的命名空间。 上面的效果是——monitoring 里的 log-reader 能读 default 里的 Pod 日志。
监控组件、日志采集器基本都是这个模式:自己住在一个专门的命名空间,去读别人家的东西。
45-4 指派给 Pod
改一个字段就行:
apiVersion: v1
kind: Pod
metadata:
name: log-collector
namespace: monitoring
spec:
serviceAccountName: log-reader # 就这一行
containers:
- name: agent
image: busybox:1.36
command: ["sleep", "3600"]
Deployment 里要写在 spec.template.spec 下面,别写错层级:
apiVersion: apps/v1
kind: Deployment
metadata:
name: log-collector
spec:
replicas: 1
selector:
matchLabels:
app: log-collector
template:
metadata:
labels:
app: log-collector
spec:
serviceAccountName: log-reader # 在 template.spec 下
containers:
- name: agent
image: busybox:1.36
Tip还有个
serviceAccount字段(不带Name),那是老写法,已废弃。统一用serviceAccountName。
45-5 令牌是怎么进到容器里的
这是本章最值得搞明白的部分。
指定了 SA 之后,Kubernetes 自动把凭据送进 Pod。v1.22 及以后的做法是:通过 TokenRequest API 获取一个短期有效、自动轮换的令牌,以**投射卷(Projected Volume)**的形式挂进容器。
默认挂载路径固定:
/var/run/secrets/kubernetes.io/serviceaccount/
├── token # JWT 令牌
├── ca.crt # 集群 CA 证书
└── namespace # 当前命名空间名
进容器里能直接看到:
kubectl exec -it log-collector -- ls /var/run/secrets/kubernetes.io/serviceaccount/
kubectl exec -it log-collector -- cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
应用要访问 API,就读这个 token 文件,放到请求头里:
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
curl --cacert $CACERT \
-H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/api/v1/namespaces/default/pods
kubernetes.default.svc 是集群内访问 API 服务器的固定 DNS 名。官方客户端库(client-go、Python 的 kubernetes 库等)会自动读这几个文件,你一般不用手写。
为什么”自动轮换”很重要
v1.24 之前,Kubernetes 给每个 SA 自动创建一个 Secret,里面是永不过期的静态令牌。这东西一旦泄露,攻击者可以一直用,除非你手动删掉重建。
现在的投射令牌有到期时间,kubelet 会在过期前主动换新。泄露一份也只有有限的窗口期。
Note应用代码要重新读取令牌文件,别在启动时读一次就缓存到进程内存里用一整天。令牌轮换后旧的会失效,缓存的那份用着就 401 了。这个坑相当隐蔽——服务跑几个小时才突然开始报错,重启又好了。主流客户端库都已经处理了这件事,自己手撸 HTTP 请求就要注意。
自定义投射:改有效期和受众
想更精细地控制,可以自己写投射卷:
apiVersion: v1
kind: Pod
metadata:
name: custom-token-pod
spec:
serviceAccountName: log-reader
containers:
- name: app
image: nginx:1.27
volumeMounts:
- name: sa-token
mountPath: /var/run/secrets/tokens
readOnly: true
volumes:
- name: sa-token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600 # 有效期 1 小时
audience: vault # 限定受众
两个字段的含义:
expirationSeconds——令牌有效期。kubelet 会在过期前轮换。audience——受众。默认是 API 服务器;填别的值,这个令牌就只能拿去向那个受众认证,拿到 API 服务器这里是无效的。
audience 在对接外部系统时很有用。比如让 Pod 用 SA 令牌去 Vault 换取数据库密码——把 audience 设为 vault,即使令牌泄露,也不能用来操作 Kubernetes API。
45-6 手动拿令牌:给集群外的东西用
CI/CD 流水线要访问集群,也可以用 SA 身份。推荐用 kubectl create token:
# 签发一个短期令牌,默认 1 小时
kubectl create token ci-bot -n ci
# 指定有效期
kubectl create token ci-bot -n ci --duration=24h
这个命令背后就是 TokenRequest API。
老办法是手动创建一个 kubernetes.io/service-account-token 类型的 Secret:
apiVersion: v1
kind: Secret
metadata:
name: ci-bot-token
namespace: ci
annotations:
kubernetes.io/service-account.name: ci-bot
type: kubernetes.io/service-account-token
能用,但不推荐。这种令牌不过期、不轮换,属于长期有效的持有者令牌(Bearer Token),泄露风险大。
WarningKubernetes 官方明确建议:给集群外应用做认证,不要用长期 SA 令牌。更好的选择是保护良好的私钥 + 证书,或者自己实现认证 Webhook 对接企业身份系统。要是必须用令牌,就用
TokenRequest签短期的,让流水线每次跑的时候现签。
45-7 imagePullSecrets:拉私有镜像
SA 还有个常被忽略的用途——挂私有仓库凭据。
不用给每个 Pod 都写一遍 imagePullSecrets,绑到 SA 上,用这个 SA 的所有 Pod 自动生效:
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: production
imagePullSecrets:
- name: my-registry-secret
或者打补丁:
kubectl patch serviceaccount app-sa -n production \
-p '{"imagePullSecrets": [{"name": "my-registry-secret"}]}'
如果整个命名空间都从同一个私有仓库拉镜像,把这个配到 default SA 上是合理的——注意这里配的是镜像凭据,不是 API 权限,不违反前面”别给 default 加权限”的原则。
45-8 不需要 API 权限的 Pod:把令牌关掉
绝大多数应用根本不访问 Kubernetes API。一个 Nginx、一个 Java 后端,要 SA 令牌干什么?
但默认它们都挂载着。攻击者一旦拿下容器,第一件事就是去 /var/run/secrets/kubernetes.io/serviceaccount/ 摸令牌。
关掉:
apiVersion: v1
kind: Pod
metadata:
name: plain-web
spec:
automountServiceAccountToken: false # Pod 级别
containers:
- name: web
image: nginx:1.27
也可以在 SA 上一次性关掉,用这个 SA 的 Pod 全部不挂载:
apiVersion: v1
kind: ServiceAccount
metadata:
name: no-api-sa
namespace: production
automountServiceAccountToken: false
Pod 级别的设置优先级更高,可以覆盖 SA 级别的。
这是性价比最高的一条加固措施:改一行,零成本,直接掐掉一整类攻击路径。
45-9 常用排查命令
# 看某命名空间的所有 SA
kubectl get sa -n production
# 看 SA 详情(含 imagePullSecrets、挂载的 Secret)
kubectl describe sa app-sa -n production
# 这个 SA 能干什么?
kubectl auth can-i --list \
--as=system:serviceaccount:production:app-sa -n production
# 具体某个动作行不行
kubectl auth can-i get secrets \
--as=system:serviceaccount:production:app-sa -n production
# 找出所有绑定到这个 SA 的角色
kubectl get rolebindings,clusterrolebindings -A -o json \
| grep -B5 -A5 "app-sa"
--as=system:serviceaccount:<命名空间>:<名称> 这个格式建议背下来,排查 Pod 403 全靠它。
45-10 几个容易搞错的点
SA 名字写错了,Pod 起不来。 spec.serviceAccountName 指向一个不存在的 SA,Pod 会卡在创建阶段。kubectl describe pod 能看到类似 error looking up service account 的事件。
以为改了 SA 权限就立刻生效。 RBAC 规则改动是立即生效的,但已经签发出去的令牌里的身份不会变。权限是每次请求时现查的,所以加权限立即生效;不过如果你换了 Pod 的 serviceAccountName,必须重建 Pod——令牌是在 Pod 创建时投射进去的。
enforce-mountable-secrets 注解别再用了。 老文章里会提到 kubernetes.io/enforce-mountable-secrets: "true",用来限制 SA 的 Secret 只能挂到特定资源上。这个注解从 v1.32 起已弃用。官方建议改用独立命名空间来做隔离。
跨命名空间绑定放错位置。 再强调一次:RoleBinding 要建在被访问资源所在的命名空间,不是 SA 所在的命名空间。放错了不报错,就是不生效——最难查的那种问题。
小结
ServiceAccount是工作负载的身份,是 Kubernetes 唯一原生管理的身份对象。- 每个命名空间自带
defaultSA,删了会自动重建,永远不要给它加权限。 - 用
spec.serviceAccountName指派;Deployment 里写在template.spec下。 - v1.22 起令牌通过投射卷挂载,短期有效且自动轮换,路径固定在
/var/run/secrets/kubernetes.io/serviceaccount/。应用要重读文件,别缓存。 - 集群外用
kubectl create token签短期令牌,别造永久 Secret 令牌。 - 不访问 API 的 Pod 设
automountServiceAccountToken: false。 - 跨命名空间授权:绑定放资源侧,主体写 SA 侧。
下一章讲 Pod 安全标准和 Pod 安全准入——控制 Pod 自己能有多大权力。