管理服务账号

ServiceAccount 为 Pod 中运行的进程提供了一个身份。

Pod 内的进程可以使用其关联服务账号的身份,向集群的 API 服务器进行身份认证。

有关服务账号的介绍, 请参阅配置服务账号

本任务指南阐述有关 ServiceAccount 的几个概念。 本指南还讲解如何获取或撤销代表 ServiceAccount 的令牌。

准备开始

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

为了能够准确地跟随这些步骤,确保你有一个名为 examplens 的名字空间。 如果你没有,运行以下命令创建一个名字空间:

kubectl create namespace examplens

用户账号与服务账号

Kubernetes 区分用户账号和服务账号的概念,主要基于以下原因:

  • 用户账号是针对人而言的。而服务账号是针对运行在 Pod 中的应用进程而言的, 在 Kubernetes 中这些进程运行在容器中,而容器是 Pod 的一部分。
  • 用户账号是全局性的。其名称在某集群中的所有名字空间中必须是唯一的。 无论你查看哪个名字空间,代表用户的特定用户名都代表着同一个用户。 在 Kubernetes 中,服务账号是名字空间作用域的。 两个不同的名字空间可以包含具有相同名称的 ServiceAccount。
  • 通常情况下,集群的用户账号可能会从企业数据库进行同步, 创建新用户需要特殊权限,并且涉及到复杂的业务流程。 服务账号创建有意做得更轻量,允许集群用户为了具体的任务按需创建服务账号。 将 ServiceAccount 的创建与新用户注册的步骤分离开来, 使工作负载更易于遵从权限最小化原则。
  • 对人员和服务账号审计所考虑的因素可能不同;这种分离更容易区分不同之处。
  • 针对复杂系统的配置包可能包含系统组件相关的各种服务账号的定义。 因为服务账号的创建约束不多并且有名字空间域的名称,所以这种配置通常是轻量的。

绑定的服务账号令牌

ServiceAccount 令牌可以被绑定到 kube-apiserver 中存在的 API 对象。 这可用于将令牌的有效性与另一个 API 对象的存在与否关联起来。 支持的对象类型如下:

  • Pod(用于投射卷的挂载,见下文)
  • Secret(可用于允许通过删除 Secret 来撤销令牌)
  • 节点(在 v1.30 中,创建新的节点绑定令牌是 Alpha 特性,使用现有的节点绑定特性是 Beta 特性)

当将令牌绑定到某对象时,该对象的 metadata.namemetadata.uid 将作为额外的“私有声明”存储在所发布的 JWT 中。

当将被绑定的令牌提供给 kube-apiserver 时,服务帐户身份认证组件将提取并验证这些声明。 如果所引用的对象或 ServiceAccount 正处于删除中(例如,由于 finalizer 的原因), 那么在 .metadata.deletionTimestamp 时间戳之后的 60 秒(或更长时间)后的某一时刻, 使用该令牌进行身份认证将会失败。 如果所引用的对象不再存在(或其 metadata.uid 不匹配),则请求将无法通过认证。

Pod 绑定令牌中的附加元数据

特性状态: Kubernetes v1.30 [beta] (enabled by default: true)

当服务帐户令牌被绑定到某 Pod 对象时,一些额外的元数据也会被嵌入到令牌中, 包括所绑定 Pod 的 spec.nodeName 字段的值以及该节点的 uid(如果可用)。

当使用令牌进行身份认证时,kube-apiserver 不会检查此节点信息的合法性。 由于节点信息被包含在令牌内,所以集成商在检查 JWT 时不必获取 Pod 或 Node API 对象来检查所关联的 Node 名称和 uid。

查验和检视私有声明

TokenReview API 可用于校验并从令牌中提取私有声明:

  1. 首先,假设你有一个名为 test-pod 的 Pod 和一个名为 my-sa 的服务帐户。
  2. 创建绑定到此 Pod 的令牌:
kubectl create token my-sa --bound-object-kind="Pod" --bound-object-name="test-pod"
  1. 将此令牌复制到名为 tokenreview.yaml 的新文件中:
apiVersion: authentication.k8s.io/v1
kind: TokenReview
spec:
  token: <来自第二步的令牌内容>
  1. 将此资源提交给 API 服务器进行审核:
kubectl create -o yaml -f tokenreview.yaml # 我们使用 '-o yaml' 以便检视命令输出

你应该看到如下所示的输出:

apiVersion: authentication.k8s.io/v1
kind: TokenReview
metadata:
  creationTimestamp: null
spec:
  token: <token>
status:
  audiences:
  - https://kubernetes.default.svc.cluster.local
  authenticated: true
  user:
    extra:
      authentication.kubernetes.io/credential-id:
      - JTI=7ee52be0-9045-4653-aa5e-0da57b8dccdc
      authentication.kubernetes.io/node-name:
      - kind-control-plane
      authentication.kubernetes.io/node-uid:
      - 497e9d9a-47aa-4930-b0f6-9f2fb574c8c6
      authentication.kubernetes.io/pod-name:
      - test-pod
      authentication.kubernetes.io/pod-uid:
      - e87dbbd6-3d7e-45db-aafb-72b24627dff5
    groups:
    - system:serviceaccounts
    - system:serviceaccounts:default
    - system:authenticated
    uid: f8b4161b-2e2b-11e9-86b7-2afc33b31a7e
    username: system:serviceaccount:default:my-sa

绑定的服务账号令牌卷机制

特性状态: Kubernetes v1.22 [stable] (enabled by default: true)

默认情况下,Kubernetes 控制平面(特别是 ServiceAccount 准入控制器) 添加一个投射卷到 Pod, 此卷包括了访问 Kubernetes API 的令牌。

以下示例演示如何查找已启动的 Pod:

...
  - name: kube-api-access-<随机后缀>
    projected:
      sources:
        - serviceAccountToken:
            path: token # 必须与应用所预期的路径匹配
        - configMap:
            items:
              - key: ca.crt
                path: ca.crt
            name: kube-root-ca.crt
        - downwardAPI:
            items:
              - fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.namespace
                path: namespace

该清单片段定义了由三个数据源组成的投射卷。在当前场景中,每个数据源也代表该卷内的一条独立路径。这三个数据源是:

  1. serviceAccountToken 数据源,包含 kubelet 从 kube-apiserver 获取的令牌。 kubelet 使用 TokenRequest API 获取有时间限制的令牌。为 TokenRequest 服务的这个令牌会在 Pod 被删除或定义的生命周期(默认为 1 小时)结束之后过期。该令牌绑定到特定的 Pod, 并将其 audience(受众)设置为与 kube-apiserver 的 audience 相匹配。 这种机制取代了之前基于 Secret 添加卷的机制,之前 Secret 代表了针对 Pod 的 ServiceAccount 但不会过期。
  2. configMap 数据源。ConfigMap 包含一组证书颁发机构数据。 Pod 可以使用这些证书来确保自己连接到集群的 kube-apiserver(而不是连接到中间件或意外配置错误的对等点上)。
  3. downwardAPI 数据源,用于查找包含 Pod 的名字空间的名称, 并使该名称信息可用于在 Pod 内运行的应用程序代码。

Pod 内挂载这个特定卷的所有容器都可以访问上述信息。

手动管理 ServiceAccount 的 Secret

v1.22 之前的 Kubernetes 版本会自动创建凭据访问 Kubernetes API。 这种更老的机制基于先创建令牌 Secret,然后将其挂载到正运行的 Pod 中。

在包括 Kubernetes v1.31 在内最近的几个版本中,使用 TokenRequest API 直接获得 API 凭据, 并使用投射卷挂载到 Pod 中。使用这种方法获得的令牌具有绑定的生命周期, 当挂载的 Pod 被删除时这些令牌将自动失效。

你仍然可以手动创建 Secret 来保存服务账号令牌;例如在你需要一个永不过期的令牌的时候。

一旦你手动创建一个 Secret 并将其关联到 ServiceAccount, Kubernetes 控制平面就会自动将令牌填充到该 Secret 中。

清理自动生成的传统 ServiceAccount 令牌

在 1.24 版本之前,Kubernetes 自动为 ServiceAccount 生成基于 Secret 的令牌。 为了区分自动生成的令牌和手动创建的令牌,Kubernetes 会检查 ServiceAccount 的 Secret 字段是否有引用。如果该 Secret 被 secrets 字段引用, 它被视为自动生成的传统令牌。否则,它被视为手动创建的传统令牌。例如:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-robot
  namespace: default
secrets:
  - name: build-robot-secret # 对于手动生成的令牌通常不会存在此字段

从 1.29 版本开始,如果传统 ServiceAccount 令牌在一定时间段(默认设置为一年)内未被使用,则会被标记为无效。 在定义的时间段(同样默认为一年)持续未被使用的令牌将由控制平面自动清除。

如果用户使用一个无效的自动生成的令牌,令牌验证器将执行以下操作:

  1. 为键值对 authentication.k8s.io/legacy-token-invalidated: <secret name>/<namespace> 添加审计注解,
  2. invalid_legacy_auto_token_uses_total 指标计数加一,
  3. 更新 Secret 标签 kubernetes.io/legacy-token-last-used 为新日期,
  4. 返回一个提示令牌已经无效的报错。

当收到这个校验报错时,用户可以通过移除 kubernetes.io/legacy-token-invalid-since 标签更新 Secret,以临时允许使用此令牌。

以下是一个自动生成的传统令牌示例,它被标记了 kubernetes.io/legacy-token-last-usedkubernetes.io/legacy-token-invalid-since 标签:

apiVersion: v1
kind: Secret
metadata:
  name: build-robot-secret
  namespace: default
  labels:
    kubernetes.io/legacy-token-last-used: 2022-10-24
    kubernetes.io/legacy-token-invalid-since: 2023-10-25
  annotations:
    kubernetes.io/service-account.name: build-robot
type: kubernetes.io/service-account-token

控制平面细节

ServiceAccount 控制器

ServiceAccount 控制器管理名字空间内的 ServiceAccount, 并确保每个活跃的名字空间中都存在名为 default 的 ServiceAccount。

令牌控制器

服务账号令牌控制器作为 kube-controller-manager 的一部分运行,以异步的形式工作。 其职责包括:

  • 监测 ServiceAccount 的删除并删除所有相应的服务账号令牌 Secret。
  • 监测服务账号令牌 Secret 的添加,保证相应的 ServiceAccount 存在, 如有需要,向 Secret 中添加令牌。
  • 监测服务账号令牌 Secret 的删除,如有需要,从相应的 ServiceAccount 中移除引用。

你必须通过 --service-account-private-key-file 标志为 kube-controller-manager的令牌控制器传入一个服务账号私钥文件。 该私钥用于为所生成的服务账号令牌签名。同样地,你需要通过 --service-account-key-file 标志将对应的公钥通知给 kube-apiserver。公钥用于在身份认证过程中校验令牌。

ServiceAccount 准入控制器

对 Pod 的改动通过一个被称为准入控制器的插件来实现。 它是 API 服务器的一部分。当 Pod 被创建时,该准入控制器会同步地修改 Pod。 如果该插件处于激活状态(在大多数发行版中都是默认激活的),当 Pod 被创建时它会进行以下操作:

  1. 如果该 Pod 没有设置 .spec.serviceAccountName, 准入控制器为新来的 Pod 将 ServiceAccount 的名称设为 default
  2. 准入控制器保证新来的 Pod 所引用的 ServiceAccount 确实存在。 如果没有 ServiceAccount 具有匹配的名称,则准入控制器拒绝新来的 Pod。 这个检查甚至适用于 default ServiceAccount。
  1. 如果服务账号的 automountServiceAccountToken 字段或 Pod 的 automountServiceAccountToken 字段都未显式设置为 false
    • 准入控制器变更新来的 Pod,添加一个包含 API 访问令牌的额外
    • 准入控制器将 volumeMount 添加到 Pod 中的每个容器, 忽略已为 /var/run/secrets/kubernetes.io/serviceaccount 路径定义的卷挂载的所有容器。 对于 Linux 容器,此卷挂载在 /var/run/secrets/kubernetes.io/serviceaccount; 在 Windows 节点上,此卷挂载在等价的路径上。
  2. 如果新来 Pod 的规约不包含任何 imagePullSecrets,则准入控制器添加 imagePullSecrets, 并从 ServiceAccount 进行复制。

传统 ServiceAccount 令牌追踪控制器

特性状态: Kubernetes v1.28 [stable] (enabled by default: true)

此控制器在 kube-system 命名空间中生成名为 kube-apiserver-legacy-service-account-token-tracking 的 ConfigMap。 这个 ConfigMap 记录了系统开始监视传统服务账号令牌的时间戳。

传统 ServiceAccount 令牌清理器

特性状态: Kubernetes v1.30 [stable] (enabled by default: true)

传统 ServiceAccount 令牌清理器作为 kube-controller-manager 的一部分运行, 每 24 小时检查一次,查看是否有任何自动生成的传统 ServiceAccount 令牌在特定时间段内未被使用。如果有的话,清理器会将这些令牌标记为无效。

清理器的工作方式是首先检查控制平面创建的 ConfigMap(前提是启用了 LegacyServiceAccountTokenTracking)。如果当前时间是 ConfigMap 所包含日期之后的特定时间段,清理器会遍历集群中的 Secret 列表, 并评估每个类型为 kubernetes.io/service-account-token 的 Secret。

如果一个 Secret 满足以下所有条件,清理器会将其标记为无效:

  • Secret 是自动生成的,意味着它被 ServiceAccount 双向引用。
  • Secret 当前没有被任何 Pod 挂载。
  • Secret 自从创建或上次使用以来的特定时间段未被使用过。

清理器通过向 Secret 添加名为 kubernetes.io/legacy-token-invalid-since 的标签, 并将此值设置为当前日期,来标记 Secret 为无效。 如果一个无效的 Secret 在特定时间段内未被使用,清理器将会删除它。

TokenRequest API

特性状态: Kubernetes v1.22 [stable]

你使用 ServiceAccount 的 TokenRequest 子资源为该 ServiceAccount 获取有时间限制的令牌。 你不需要调用它来获取在容器中使用的 API 令牌, 因为 kubelet 使用投射卷对此进行了设置。

如果你想要从 kubectl 使用 TokenRequest API, 请参阅为 ServiceAccount 手动创建 API 令牌

Kubernetes 控制平面(特别是 ServiceAccount 准入控制器)向 Pod 添加了一个投射卷, kubelet 确保该卷包含允许容器作为正确 ServiceAccount 进行身份认证的令牌。

(这种机制取代了之前基于 Secret 添加卷的机制,之前 Secret 代表了 Pod 所用的 ServiceAccount 但不会过期。)

以下示例演示如何查找已启动的 Pod:

...
  - name: kube-api-access-<random-suffix>
    projected:
      defaultMode: 420 # 这个十进制数等同于八进制 0644
      sources:
        - serviceAccountToken:
            expirationSeconds: 3607
            path: token
        - configMap:
            items:
              - key: ca.crt
                path: ca.crt
            name: kube-root-ca.crt
        - downwardAPI:
            items:
              - fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.namespace
                path: namespace

该清单片段定义了由三个数据源信息组成的投射卷。

  1. serviceAccountToken 数据源,包含 kubelet 从 kube-apiserver 获取的令牌。 kubelet 使用 TokenRequest API 获取有时间限制的令牌。为 TokenRequest 服务的这个令牌会在 Pod 被删除或定义的生命周期(默认为 1 小时)结束之后过期。在令牌过期之前,kubelet 还会刷新该令牌。 该令牌绑定到特定的 Pod,并将其 audience(受众)设置为与 kube-apiserver 的 audience 相匹配。
  2. configMap 数据源。ConfigMap 包含一组证书颁发机构数据。 Pod 可以使用这些证书来确保自己连接到集群的 kube-apiserver(而不是连接到中间件或意外配置错误的对等点上)。
  3. downwardAPI 数据源。这个 downwardAPI 卷获得包含 Pod 的名字空间的名称, 并使该名称信息可用于在 Pod 内运行的应用程序代码。

挂载此卷的 Pod 内的所有容器均可以访问上述信息。

创建额外的 API 令牌

要为 ServiceAccount 创建一个不过期、持久化的 API 令牌, 请创建一个类型为 kubernetes.io/service-account-token 的 Secret, 附带引用 ServiceAccount 的注解。控制平面随后生成一个长久的令牌, 并使用生成的令牌数据更新该 Secret。

以下是此类 Secret 的示例清单:

apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: mysecretname
  annotations:
    kubernetes.io/service-account.name: myserviceaccount

若要基于此示例创建 Secret,运行以下命令:

kubectl -n examplens create -f https://k8s.io/examples/secret/serviceaccount/mysecretname.yaml

若要查看该 Secret 的详细信息,运行以下命令:

kubectl -n examplens describe secret mysecretname

输出类似于:

Name:           mysecretname
Namespace:      examplens
Labels:         <none>
Annotations:    kubernetes.io/service-account.name=myserviceaccount
                kubernetes.io/service-account.uid=8a85c4c4-8483-11e9-bc42-526af7764f64

Type:   kubernetes.io/service-account-token

Data
====
ca.crt:         1362 bytes
namespace:      9 bytes
token:          ...

如果你在 examplens 名字空间中启动一个新的 Pod,它可以使用你刚刚创建的 myserviceaccount service-account-token Secret。

删除/废止 ServiceAccount 令牌

如果你知道 Secret 的名称且该 Secret 包含要移除的令牌:

kubectl delete secret name-of-secret

否则,先找到 ServiceAccount 所用的 Secret。

# 此处假设你已有一个名为 'examplens' 的名字空间
kubectl -n examplens get serviceaccount/example-automated-thing -o yaml

输出类似于:

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"name":"example-automated-thing","namespace":"examplens"}}      
  creationTimestamp: "2019-07-21T07:07:07Z"
  name: example-automated-thing
  namespace: examplens
  resourceVersion: "777"
  selfLink: /api/v1/namespaces/examplens/serviceaccounts/example-automated-thing
  uid: f23fd170-66f2-4697-b049-e1e266b7f835
secrets:
  - name: example-automated-thing-token-zyxwv

随后删除你现在知道名称的 Secret:

kubectl -n examplens delete secret/example-automated-thing-token-zyxwv

清理

如果创建了一个 examplens 名字空间进行试验,你可以移除它:

kubectl delete namespace examplens

接下来