为 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 的细节。例如:

  1. kubectl get pods/<podname> -o yaml

在输出中,你可以看到字段 spec.serviceAccountName。当你在创建 Pod 时未设置该字段时, Kubernetes 自动为 Pod 设置这一属性的取值。

Pod 中运行的应用可以使用这一自动挂载的服务账号凭据来访问 Kubernetes API。 参阅访问集群以进一步了解。

当 Pod 被身份认证为某个 ServiceAccount 时, 其访问能力取决于所使用的鉴权插件和策略

放弃 API 凭据的自动挂载

如果你不希望 kubelet 自动挂载某 ServiceAccount 的 API 访问凭据,你可以选择不采用这一默认行为。 通过在 ServiceAccount 对象上设置 automountServiceAccountToken: false,可以放弃在 /var/run/secrets/kubernetes.io/serviceaccount/token 处自动挂载该服务账号的 API 凭据。

例如:

  1. apiVersion: v1
  2. kind: ServiceAccount
  3. metadata:
  4. name: build-robot
  5. automountServiceAccountToken: false
  6. ...

你也可以选择不给特定 Pod 自动挂载 API 凭据:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: my-pod
  5. spec:
  6. serviceAccountName: build-robot
  7. automountServiceAccountToken: false
  8. ...

如果 ServiceAccount 和 Pod 的 .spec 都设置了 automountServiceAccountToken 值, 则 Pod 上 spec 的设置优先于服务账号的设置。

使用多个服务账号

每个名字空间都至少有一个 ServiceAccount:名为 default 的默认 ServiceAccount 资源。 你可以用下面的命令列举你当前名字空间 中的所有 ServiceAccount 资源:

  1. kubectl get serviceaccounts

输出类似于:

  1. NAME SECRETS AGE
  2. default 1 1d

你可以像这样来创建额外的 ServiceAccount 对象:

  1. kubectl apply -f - <<EOF
  2. apiVersion: v1
  3. kind: ServiceAccount
  4. metadata:
  5. name: build-robot
  6. EOF

ServiceAccount 对象的名字必须是一个有效的 DNS 子域名.

如果你查询服务账号对象的完整信息,如下所示:

  1. kubectl get serviceaccounts/build-robot -o yaml

输出类似于:

  1. apiVersion: v1
  2. kind: ServiceAccount
  3. metadata:
  4. creationTimestamp: 2019-06-16T00:12:34Z
  5. name: build-robot
  6. namespace: default
  7. resourceVersion: "272500"
  8. uid: 721ab723-13bc-11e5-aec2-42010af0021e

你可以使用鉴权插件来设置服务账号的访问许可

要使用非默认的服务账号,将 Pod 的 spec.serviceAccountName 字段设置为你想用的服务账号名称。

只能在创建 Pod 时或者为新 Pod 指定模板时,你才可以设置 serviceAccountName。 你不能更新已经存在的 Pod 的 .spec.serviceAccountName 字段。

说明:

.spec.serviceAccount 字段是 .spec.serviceAccountName 的已弃用别名。 如果要从工作负载资源中删除这些字段,请在 Pod 模板上将这两个字段显式设置为空。

清理

如果你尝试了创建前文示例中所给的 build-robot ServiceAccount, 你可以通过运行下面的命令来完成清理操作:

  1. kubectl delete serviceaccount/build-robot

手动为 ServiceAccount 创建 API 令牌

假设你已经有了一个前文所提到的名为 “build-robot” 的服务账号。 你可以使用 kubectl 为该 ServiceAccount 获得一个时间上受限的 API 令牌:

  1. kubectl create token build-robot

这一命令的输出是一个令牌,你可以使用该令牌来将身份认证为对应的 ServiceAccount。 你可以使用 kubectl create token 命令的 --duration 参数来请求特定的令牌有效期 (实际签发的令牌的有效期可能会稍短一些,也可能会稍长一些)。

说明:

Kubernetes 在 v1.22 版本之前自动创建用来访问 Kubernetes API 的长期凭据。 这一较老的机制是基于创建令牌 Secret 对象来实现的,Secret 对象可被挂载到运行中的 Pod 内。 在最近的版本中,包括 Kubernetes v1.28,API 凭据可以直接使用 TokenRequest API 来获得,并使用一个投射卷挂载到 Pod 中。使用此方法获得的令牌具有受限的生命期长度,并且能够在挂载它们的 Pod 被删除时自动被废弃。

你仍然可以通过手动方式来创建服务账号令牌 Secret 对象,例如你需要一个永远不过期的令牌时。 不过,使用 TokenRequest 子资源来获得访问 API 的令牌的做法仍然是推荐的方式。

手动为 ServiceAccount 创建长期有效的 API 令牌

如果你需要为 ServiceAccount 获得一个 API 令牌,你可以创建一个新的、带有特殊注解 kubernetes.io/service-account.name 的 Secret 对象。

  1. kubectl apply -f - <<EOF
  2. apiVersion: v1
  3. kind: Secret
  4. metadata:
  5. name: build-robot-secret
  6. annotations:
  7. kubernetes.io/service-account.name: build-robot
  8. type: kubernetes.io/service-account-token
  9. EOF

如果你通过下面的命令来查看 Secret:

  1. kubectl get secret/build-robot-secret -o yaml

你可以看到 Secret 中现在包含针对 “build-robot” ServiceAccount 的 API 令牌。

鉴于你所设置的注解,控制面会自动为该 ServiceAccount 生成一个令牌,并将其保存到相关的 Secret 中。控制面也会为已删除的 ServiceAccount 执行令牌清理操作。

  1. kubectl describe secrets/build-robot-secret

输出类似于这样:

  1. Name: build-robot-secret
  2. Namespace: default
  3. Labels: <none>
  4. Annotations: kubernetes.io/service-account.name: build-robot
  5. kubernetes.io/service-account.uid: da68f9c6-9d26-11e7-b84e-002dc52800da
  6. Type: kubernetes.io/service-account-token
  7. Data
  8. ====
  9. ca.crt: 1338 bytes
  10. namespace: 7 bytes
  11. 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:

    1. kubectl create secret docker-registry myregistrykey --docker-server=DUMMY_SERVER \
    2. --docker-username=DUMMY_USERNAME --docker-password=DUMMY_DOCKER_PASSWORD \
    3. --docker-email=DUMMY_DOCKER_EMAIL
  • 检查该 Secret 已经被创建。

    1. kubectl get secrets myregistrykey

    输出类似于这样:

    1. NAME TYPE DATA AGE
    2. myregistrykey kubernetes.io/.dockerconfigjson 1 1d

将镜像拉取 Secret 添加到服务账号

接下来更改名字空间的默认服务账号,将该 Secret 用作 imagePullSecret。

  1. kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "myregistrykey"}]}'

你也可以通过手动编辑该对象来实现同样的效果:

  1. kubectl edit serviceaccount/default

sa.yaml 文件的输出类似于:

你所选择的文本编辑器会被打开,展示如下所示的配置:

  1. apiVersion: v1
  2. kind: ServiceAccount
  3. metadata:
  4. creationTimestamp: 2021-07-07T22:02:39Z
  5. name: default
  6. namespace: default
  7. resourceVersion: "243024"
  8. uid: 052fb0f4-3d50-11e5-b066-42010af0d7b6

使用你的编辑器,删掉包含 resourceVersion 主键的行,添加包含 imagePullSecrets: 的行并保存文件。对于 uid 而言,保持其取值与你读到的值一样。

当你完成这些变更之后,所编辑的 ServiceAccount 看起来像是这样:

  1. apiVersion: v1
  2. kind: ServiceAccount
  3. metadata:
  4. creationTimestamp: 2021-07-07T22:02:39Z
  5. name: default
  6. namespace: default
  7. uid: 052fb0f4-3d50-11e5-b066-42010af0d7b6
  8. imagePullSecrets:
  9. - name: myregistrykey

检查 imagePullSecrets 已经被设置到新 Pod 上

现在,在当前名字空间中创建新 Pod 并使用默认 ServiceAccount 时, 新 Pod 的 spec.imagePullSecrets 会被自动设置。

  1. kubectl run nginx --image=nginx --restart=Never
  2. kubectl get pod nginx -o=jsonpath='{.spec.imagePullSecrets[0].name}{"\n"}'

输出为:

  1. 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 配置此行为。

启动使用服务账号令牌投射的 Pod

要为某 Pod 提供一个受众为 vault 并且有效期限为 2 小时的令牌,你可以定义一个与下面类似的 Pod 清单:

pods/pod-projected-svc-token.yaml 为 Pod 配置服务账号 - 图1

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: nginx
  5. spec:
  6. containers:
  7. - image: nginx
  8. name: nginx
  9. volumeMounts:
  10. - mountPath: /var/run/secrets/tokens
  11. name: vault-token
  12. serviceAccountName: build-robot
  13. volumes:
  14. - name: vault-token
  15. projected:
  16. sources:
  17. - serviceAccountToken:
  18. path: vault-token
  19. expirationSeconds: 7200
  20. audience: vault

创建此 Pod:

  1. 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:authenticatedsystem:unauthenticated

JWKS 响应包含依赖方可以用来验证 Kubernetes 服务账号令牌的公钥数据。 依赖方先会查询 OpenID 提供者配置,之后使用返回响应中的 jwks_uri 来查找 JWKS。

在很多场合,Kubernetes API 服务器都不会暴露在公网上,不过对于缓存并向外提供 API 服务器响应数据的公开末端而言,用户或者服务提供商可以选择将其暴露在公网上。 在这种环境中,可能会重载 OpenID 提供者配置中的 jwks_uri,使之指向公网上可用的末端地址,而不是 API 服务器的地址。 这时需要向 API 服务器传递 --service-account-jwks-uri 参数。 与分发者 URL 类似,此 JWKS URI 也需要使用 https 模式。

接下来

另请参见: