为 Pod 配置服务账号
Kubernetes 提供两种完全不同的方式来为客户端提供支持,这些客户端可能运行在你的集群中, 也可能与你的集群的控制面相关, 需要向 API 服务器完成身份认证。
服务账号(Service Account) 为 Pod 中运行的进程提供身份标识, 并映射到 ServiceAccount 对象。当你向 API 服务器执行身份认证时, 你会将自己标识为某个用户(User)。Kubernetes 能够识别用户的概念, 但是 Kubernetes 自身并不提供 User API。
本服务是关于 ServiceAccount 的,而 ServiceAccount 则确实存在于 Kubernetes 的 API 中。 本指南为你展示为 Pod 配置 ServiceAccount 的一些方法。
准备开始
你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
使用默认的服务账号访问 API 服务器
当 Pod 与 API 服务器联系时,Pod 会被认证为某个特定的 ServiceAccount(例如:default
)。
在每个名字空间中,至少存在一个
ServiceAccount。
每个 Kubernetes 名字空间至少包含一个 ServiceAccount:也就是该名字空间的默认服务账号,
名为 default
。如果你在创建 Pod 时没有指定 ServiceAccount,Kubernetes 会自动将该名字空间中名为
default
的 ServiceAccount 分配给该 Pod。
你可以检视你刚刚创建的 Pod 的细节。例如:
kubectl get pods/<podname> -o yaml
在输出中,你可以看到字段 spec.serviceAccountName
。当你在创建 Pod 时未设置该字段时,
Kubernetes 自动为 Pod 设置这一属性的取值。
Pod 中运行的应用可以使用这一自动挂载的服务账号凭据来访问 Kubernetes API。 参阅访问集群以进一步了解。
当 Pod 被身份认证为某个 ServiceAccount 时, 其访问能力取决于所使用的鉴权插件和策略。
当 Pod 被删除时,即使设置了终结器,API 凭据也会自动失效。
需要额外注意的是,API 凭据会在 Pod 上设置的 .metadata.deletionTimestamp
之后的 60 秒内失效
(删除时间戳通常是 delete 请求被接受的时间加上 Pod 的终止宽限期)。
放弃 API 凭据的自动挂载
如果你不希望 kubelet 自动挂载某
ServiceAccount 的 API 访问凭据,你可以选择不采用这一默认行为。
通过在 ServiceAccount 对象上设置 automountServiceAccountToken: false
,可以放弃在
/var/run/secrets/kubernetes.io/serviceaccount/token
处自动挂载该服务账号的 API 凭据。
例如:
apiVersion: v1
kind: ServiceAccount
metadata:
name: build-robot
automountServiceAccountToken: false
...
你也可以选择不给特定 Pod 自动挂载 API 凭据:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
serviceAccountName: build-robot
automountServiceAccountToken: false
...
如果 ServiceAccount 和 Pod 的 .spec
都设置了 automountServiceAccountToken
值,
则 Pod 上 spec 的设置优先于服务账号的设置。
使用多个服务账号
每个名字空间都至少有一个 ServiceAccount:名为 default
的默认 ServiceAccount 资源。
你可以用下面的命令列举你当前名字空间
中的所有 ServiceAccount 资源:
kubectl get serviceaccounts
输出类似于:
NAME SECRETS AGE
default 1 1d
你可以像这样来创建额外的 ServiceAccount 对象:
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: build-robot
EOF
ServiceAccount 对象的名字必须是一个有效的 DNS 子域名。
如果你查询服务账号对象的完整信息,如下所示:
kubectl get serviceaccounts/build-robot -o yaml
输出类似于:
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: 2019-06-16T00:12:34Z
name: build-robot
namespace: default
resourceVersion: "272500"
uid: 721ab723-13bc-11e5-aec2-42010af0021e
你可以使用鉴权插件来设置服务账号的访问许可。
要使用非默认的服务账号,将 Pod 的 spec.serviceAccountName
字段设置为你想用的服务账号名称。
只能在创建 Pod 时或者为新 Pod 指定模板时,你才可以设置 serviceAccountName
。
你不能更新已经存在的 Pod 的 .spec.serviceAccountName
字段。
说明:
.spec.serviceAccount
字段是 .spec.serviceAccountName
的已弃用别名。
如果要从工作负载资源中删除这些字段,请在
Pod 模板上将这两个字段显式设置为空。
清理
如果你尝试了创建前文示例中所给的 build-robot
ServiceAccount,
你可以通过运行下面的命令来完成清理操作:
kubectl delete serviceaccount/build-robot
手动为 ServiceAccount 创建 API 令牌
假设你已经有了一个前文所提到的名为 "build-robot" 的服务账号。
你可以使用 kubectl
为该 ServiceAccount 获得一个有时限的 API 令牌:
kubectl create token build-robot
这一命令的输出是一个令牌,你可以使用该令牌来将身份认证为对应的 ServiceAccount。
你可以使用 kubectl create token
命令的 --duration
参数来请求特定的令牌有效期
(实际签发的令牌的有效期可能会稍短一些,也可能会稍长一些)。
Kubernetes v1.31 [beta]
(enabled by default: true)
当启用了 ServiceAccountTokenNodeBinding
和 ServiceAccountTokenNodeBindingValidation
特性,并使用 v1.31 或更高版本的 kubectl
时,
可以创建一个直接绑定到 Node
的服务账号令牌:
kubectl create token build-robot --bound-object-kind Node --bound-object-name node-001 --bound-object-uid 123...456
此令牌将有效直至其过期或关联的 Node 或服务账户被删除。
说明:
Kubernetes 在 v1.22 版本之前自动创建用来访问 Kubernetes API 的长期凭据。 这一较老的机制是基于创建令牌 Secret 对象来实现的,Secret 对象可被挂载到运行中的 Pod 内。 在最近的版本中,包括 Kubernetes v1.31,API 凭据可以直接使用 TokenRequest API 来获得,并使用一个投射卷挂载到 Pod 中。使用此方法获得的令牌具有受限的生命期长度,并且能够在挂载它们的 Pod 被删除时自动被废弃。
你仍然可以通过手动方式来创建服务账号令牌 Secret 对象,例如你需要一个永远不过期的令牌时。 不过,使用 TokenRequest 子资源来获得访问 API 的令牌的做法仍然是推荐的方式。
手动为 ServiceAccount 创建长期有效的 API 令牌
如果你需要为 ServiceAccount 获得一个 API 令牌,你可以创建一个新的、带有特殊注解
kubernetes.io/service-account.name
的 Secret 对象。
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: build-robot-secret
annotations:
kubernetes.io/service-account.name: build-robot
type: kubernetes.io/service-account-token
EOF
如果你通过下面的命令来查看 Secret:
kubectl get secret/build-robot-secret -o yaml
你可以看到 Secret 中现在包含针对 "build-robot" ServiceAccount 的 API 令牌。
鉴于你所设置的注解,控制面会自动为该 ServiceAccount 生成一个令牌,并将其保存到相关的 Secret 中。控制面也会为已删除的 ServiceAccount 执行令牌清理操作。
kubectl describe secrets/build-robot-secret
输出类似于这样:
Name: build-robot-secret
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: build-robot
kubernetes.io/service-account.uid: da68f9c6-9d26-11e7-b84e-002dc52800da
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1338 bytes
namespace: 7 bytes
token: ...
说明:
这里将 token
的内容省略了。
注意在你的终端或者计算机屏幕可能被旁观者看到的场合,不要显示
kubernetes.io/service-account-token
的内容。
当你删除一个与某 Secret 相关联的 ServiceAccount 时,Kubernetes 的控制面会自动清理该 Secret 中长期有效的令牌。
说明:
如果你使用以下命令查看 ServiceAccount:
kubectl get serviceaccount build-robot -o yaml
在 ServiceAccount API 对象中看不到 build-robot-secret
Secret,
.secrets
字段,
因为该字段只会填充自动生成的 Secret。
为服务账号添加 ImagePullSecrets
首先,生成一个 imagePullSecret; 接下来,验证该 Secret 已被创建。例如:
-
按为 Pod 设置 imagePullSecret 所描述的,生成一个镜像拉取 Secret:
kubectl create secret docker-registry myregistrykey --docker-server=<registry name> \ --docker-username=DUMMY_USERNAME --docker-password=DUMMY_DOCKER_PASSWORD \ --docker-email=DUMMY_DOCKER_EMAIL
-
检查该 Secret 已经被创建。
kubectl get secrets myregistrykey
输出类似于这样:
NAME TYPE DATA AGE myregistrykey kubernetes.io/.dockerconfigjson 1 1d
将镜像拉取 Secret 添加到服务账号
接下来更改名字空间的默认服务账号,将该 Secret 用作 imagePullSecret。
kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "myregistrykey"}]}'
你也可以通过手动编辑该对象来实现同样的效果:
kubectl edit serviceaccount/default
sa.yaml
文件的输出类似于:
你所选择的文本编辑器会被打开,展示如下所示的配置:
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: 2021-07-07T22:02:39Z
name: default
namespace: default
resourceVersion: "243024"
uid: 052fb0f4-3d50-11e5-b066-42010af0d7b6
使用你的编辑器,删掉包含 resourceVersion
主键的行,添加包含 imagePullSecrets:
的行并保存文件。对于 uid
而言,保持其取值与你读到的值一样。
当你完成这些变更之后,所编辑的 ServiceAccount 看起来像是这样:
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: 2021-07-07T22:02:39Z
name: default
namespace: default
uid: 052fb0f4-3d50-11e5-b066-42010af0d7b6
imagePullSecrets:
- name: myregistrykey
检查 imagePullSecrets 已经被设置到新 Pod 上
现在,在当前名字空间中创建新 Pod 并使用默认 ServiceAccount 时,
新 Pod 的 spec.imagePullSecrets
会被自动设置。
kubectl run nginx --image=<registry name>/nginx --restart=Never
kubectl get pod nginx -o=jsonpath='{.spec.imagePullSecrets[0].name}{"\n"}'
输出为:
myregistrykey
服务账号令牌卷投射
Kubernetes v1.20 [stable]
说明:
为了启用令牌请求投射,你必须为 kube-apiserver
设置以下命令行参数:
--service-account-issuer
- 定义服务账号令牌发放者的身份标识(Identifier)。你可以多次指定
--service-account-issuer
参数,对于需要变更发放者而又不想带来业务中断的场景, 这样做是有用的。如果这个参数被多次指定,其第一个参数值会被用来生成令牌, 而所有参数值都会被用来确定哪些发放者是可接受的。你所运行的 Kubernetes 集群必须是 v1.22 或更高版本才能多次指定--service-account-issuer
。
--service-account-key-file
- 给出某文件的路径,其中包含 PEM 编码的 x509 RSA 或 ECDSA 私钥或公钥,用来检查 ServiceAccount 的令牌。所指定的文件中可以包含多个秘钥,并且你可以多次使用此参数,每个参数值为不同的文件。 多次使用此参数时,由所给的秘钥之一签名的令牌会被 Kubernetes API 服务器认为是合法令牌。
--service-account-signing-key-file
- 指向某文件的路径,其中包含当前服务账号令牌发放者的私钥。 此发放者使用此私钥来签署所发放的 ID 令牌。
--api-audiences
(可以省略)- 为 ServiceAccount 令牌定义其受众(Audiences)。
服务账号令牌身份检查组件会检查针对 API 访问所使用的令牌,
确认令牌至少是被绑定到这里所给的受众之一。
如果
api-audiences
被多次指定,则针对所给的多个受众中任何目标的令牌都会被 Kubernetes API 服务器当做合法的令牌。如果你指定了--service-account-issuer
参数,但沒有設置--api-audiences
,则控制面认为此参数的默认值为一个只有一个元素的列表, 且该元素为令牌发放者的 URL。
kubelet 还可以将 ServiceAccount 令牌投射到 Pod 中。你可以指定令牌的期望属性, 例如受众和有效期限。这些属性在 default ServiceAccount 令牌上无法配置。 当 Pod 或 ServiceAccount 被删除时,该令牌也将对 API 无效。
你可以使用类型为 ServiceAccountToken
的投射卷来为
Pod 的 spec
配置此行为。
来自此投射卷的令牌是一个 JSON Web Token (JWT)。 此令牌的 JSON 载荷遵循明确定义的模式,绑定到 Pod 的令牌的示例载荷如下:
{
"aud": [ # 匹配请求的受众,或当没有明确请求时匹配 API 服务器的默认受众
"https://kubernetes.default.svc"
],
"exp": 1731613413,
"iat": 1700077413,
"iss": "https://kubernetes.default.svc", # 匹配传递到 --service-account-issuer 标志的第一个值
"jti": "ea28ed49-2e11-4280-9ec5-bc3d1d84661a", # ServiceAccountTokenJTI 特性必须被启用才能出现此申领
"kubernetes.io": {
"namespace": "kube-system",
"node": { # ServiceAccountTokenPodNodeInfo 特性必须被启用,API 服务器才会添加此节点引用申领
"name": "127.0.0.1",
"uid": "58456cb0-dd00-45ed-b797-5578fdceaced"
},
"pod": {
"name": "coredns-69cbfb9798-jv9gn",
"uid": "778a530c-b3f4-47c0-9cd5-ab018fb64f33"
},
"serviceaccount": {
"name": "coredns",
"uid": "a087d5a0-e1dd-43ec-93ac-f13d89cd13af"
},
"warnafter": 1700081020
},
"nbf": 1700077413,
"sub": "system:serviceaccount:kube-system:coredns"
}
启动使用服务账号令牌投射的 Pod
要为某 Pod 提供一个受众为 vault
并且有效期限为 2 小时的令牌,你可以定义一个与下面类似的
Pod 清单:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- mountPath: /var/run/secrets/tokens
name: vault-token
serviceAccountName: build-robot
volumes:
- name: vault-token
projected:
sources:
- serviceAccountToken:
path: vault-token
expirationSeconds: 7200
audience: vault
创建此 Pod:
kubectl create -f https://k8s.io/examples/pods/pod-projected-svc-token.yaml
kubelet 组件会替 Pod 请求令牌并将其保存起来;通过将令牌存储到一个可配置的路径以使之在 Pod 内可用;在令牌快要到期的时候刷新它。kubelet 会在令牌存在期达到其 TTL 的 80% 的时候或者令牌生命期超过 24 小时的时候主动请求将其轮换掉。
应用负责在令牌被轮换时重新加载其内容。通常而言,周期性地(例如,每隔 5 分钟) 重新加载就足够了,不必跟踪令牌的实际过期时间。
发现服务账号分发者
Kubernetes v1.21 [stable]
如果你在你的集群中已经为 ServiceAccount 启用了令牌投射, 那么你也可以利用其发现能力。Kubernetes 提供一种方式来让客户端将一个或多个外部系统进行联邦, 作为标识提供者(Identity Provider),而这些外部系统的角色是依赖方(Relying Party)。
说明:
分发者的 URL 必须遵从
OIDC 发现规范。
实现上,这意味着 URL 必须使用 https
模式,并且必须在路径
{service-account-issuer}/.well-known/openid-configuration
处给出 OpenID 提供者(Provider)的配置信息。
如果 URL 没有遵从这一规范,ServiceAccount 分发者发现末端末端就不会被注册也无法访问。
当此特性被启用时,Kubernetes API 服务器会通过 HTTP 发布一个 OpenID 提供者配置文档。
该配置文档发布在 /.well-known/openid-configuration
路径。
这里的 OpenID 提供者配置(OpenID Provider Configuration)有时候也被称作
“发现文档(Discovery Document)”。
Kubernetes API 服务器也通过 HTTP 在 /openid/v1/jwks
处发布相关的
JSON Web Key Set(JWKS)。
说明:
对于在 /.well-known/openid-configuration
和 /openid/v1/jwks
上给出的响应而言,
其设计上是保证与 OIDC 兼容的,但并不严格遵从 OIDC 的规范。
响应中所包含的文档进包含对 Kubernetes 服务账号令牌进行校验所必需的参数。
使用 RBAC 的集群都包含一个的默认
RBAC ClusterRole, 名为 system:service-account-issuer-discovery
。
默认的 RBAC ClusterRoleBinding 将此角色分配给 system:serviceaccounts
组,
所有 ServiceAccount 隐式属于该组。这使得集群上运行的 Pod
能够通过它们所挂载的服务账号令牌访问服务账号发现文档。
此外,管理员可以根据其安全性需要以及期望集成的外部系统,选择是否将该角色绑定到
system:authenticated
或 system:unauthenticated
。
JWKS 响应包含依赖方可以用来验证 Kubernetes 服务账号令牌的公钥数据。
依赖方先会查询 OpenID 提供者配置,之后使用返回响应中的 jwks_uri
来查找 JWKS。
在很多场合,Kubernetes API 服务器都不会暴露在公网上,不过对于缓存并向外提供 API
服务器响应数据的公开末端而言,用户或者服务提供商可以选择将其暴露在公网上。
在这种环境中,可能会重载 OpenID 提供者配置中的
jwks_uri
,使之指向公网上可用的末端地址,而不是 API 服务器的地址。
这时需要向 API 服务器传递 --service-account-jwks-uri
参数。
与分发者 URL 类似,此 JWKS URI 也需要使用 https
模式。
接下来
另请参见:
- 阅读为集群管理员提供的服务账号指南
- 阅读 Kubernetes中的鉴权
- 阅读 Secret 的概念
- 或者学习使用 Secret 来安全地分发凭据
- 不过也要注意,使用 Secret 来完成 ServiceAccount 身份验证的做法已经过时。 建议的替代做法是执行 ServiceAccount 令牌卷投射。
- 阅读理解投射卷
- 关于 OIDC 发现的相关背景信息,阅读服务账号签署密钥检索 KEP 这一 Kubernetes 增强提案
- 阅读 OIDC 发现规范