管理服务帐户

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

Pod 内的进程可以使用其关联的服务帐户的身份向集群的 API 服务器进行身份验证。

有关服务帐户的介绍,请阅读 配置服务帐户

本任务指南解释了 ServiceAccount 背后的一些概念。本指南还解释了如何获取或撤销代表 ServiceAccount 的令牌,以及如何(可选)将 ServiceAccount 的有效期绑定到 API 对象的生命周期。

开始之前

你需要一个 Kubernetes 集群,并且必须配置 kubectl 命令行工具以与你的集群进行通信。建议在至少具有两个不充当控制平面主机的节点的集群上运行本教程。如果你还没有集群,可以使用 minikube 创建一个,也可以使用这些 Kubernetes 游乐场之一

为了能够完全按照这些步骤进行操作,请确保你有一个名为 examplens 的命名空间。如果没有,请运行以下命令创建一个

kubectl create namespace examplens

用户帐户与服务帐户

Kubernetes 出于多种原因区分了用户帐户和服务帐户的概念

  • 用户帐户是为人类设计的。服务帐户是为应用程序进程设计的,这些进程(对于 Kubernetes)在作为 Pod 一部分的容器中运行。
  • 用户帐户旨在是全局的:名称在集群的所有命名空间中必须是唯一的。无论你查看哪个命名空间,代表用户的特定用户名都代表同一用户。在 Kubernetes 中,服务帐户是命名空间的:两个不同的命名空间可以包含具有相同名称的 ServiceAccount。
  • 通常,集群的用户帐户可能与公司数据库同步,其中新用户帐户的创建需要特殊权限,并且与复杂的业务流程相关联。相比之下,服务帐户的创建旨在更加轻量级,允许集群用户按需为特定任务创建服务帐户。将 ServiceAccount 的创建与新用户加入的步骤分开,可以更轻松地使工作负载遵循最小权限原则。
  • 人类和服务帐户的审计注意事项可能不同;这种分离使其更容易实现。
  • 复杂系统的配置包可能包含该系统组件的各种服务帐户的定义。由于服务帐户可以在没有太多约束的情况下创建,并且具有命名空间的名称,因此这种配置通常是可移植的。

绑定的服务帐户令牌

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

  • Pod(用于投影卷挂载,请参见下文)
  • Secret(可用于通过删除 Secret 来撤销令牌)
  • Node(在 v1.32 中,创建新的节点绑定令牌是 beta 版,使用现有的节点绑定令牌是 GA 版)

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

当将绑定令牌呈现给 kube-apiserver 时,服务帐户身份验证器将提取并验证这些声明。如果引用的对象或 ServiceAccount 正在等待删除(例如,由于 finalizers),则在 .metadata.deletionTimestamp 日期之后 60 秒(或更长)的任何时刻,使用该令牌进行身份验证都将失败。如果引用的对象不再存在(或其 metadata.uid 不匹配),则请求将不会被身份验证。

Pod 绑定令牌中的其他元数据

功能状态: Kubernetes v1.32 [稳定](默认启用:true)

当服务帐户令牌绑定到 Pod 对象时,其他元数据也会嵌入到令牌中,指示绑定 Pod 的 spec.nodeName 字段的值以及该节点的 uid(如果可用)。

当令牌用于身份验证时,kube-apiserver 不会验证此节点信息。包含它是为了使集成商在检查 JWT 时不必获取 Pod 或 Node API 对象来检查关联的节点名称和 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: <token from step 2>
  1. 将此资源提交给 apiserver 进行审查
kubectl create -o yaml -f tokenreview.yaml # we use '-o yaml' so we can inspect the output

你应该看到如下输出

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

服务帐户私有声明的架构

JWT 令牌中 Kubernetes 特定的声明的架构目前尚未记录,但可以在 Kubernetes 代码库的 serviceaccount 包中找到相关代码区域。

你可以使用标准的 JWT 解码工具来检查 JWT。以下是一个绑定到名为 my-pod 的 Pod 对象,并调度到 my-namespace 命名空间中 my-node 节点的 my-serviceaccount ServiceAccount 的 JWT 示例

{
  "aud": [
    "https://my-audience.example.com"
  ],
  "exp": 1729605240,
  "iat": 1729601640,
  "iss": "https://my-cluster.example.com",
  "jti": "aed34954-b33a-4142-b1ec-389d6bbb4936",
  "kubernetes.io": {
    "namespace": "my-namespace",
    "node": {
      "name": "my-node",
      "uid": "646e7c5e-32d6-4d42-9dbd-e504e6cbe6b1"
    },
    "pod": {
      "name": "my-pod",
      "uid": "5e0bd49b-f040-43b0-99b7-22765a53f7f3"
    },
    "serviceaccount": {
      "name": "my-serviceaccount",
      "uid": "14ee3fa4-a7e2-420f-9f9a-dbc4507c3798"
    }
  },
  "nbf": 1729601640,
  "sub": "system:serviceaccount:my-namespace:my-serviceaccount"
}

在 Kubernetes 外部运行并希望执行 JWT 离线验证的服务可以使用此架构,以及配置了来自 API 服务器的 OpenID Discovery 信息的兼容 JWT 验证器,来验证呈现的 JWT,而无需使用 TokenReview API。

以这种方式验证 JWT 的服务 不验证 JWT 令牌中嵌入的声明是否是当前的且仍然有效。这意味着如果令牌绑定到某个对象,并且该对象不再存在,则该令牌仍将被视为有效(直到配置的令牌过期)。

需要保证令牌的绑定声明仍然有效的客户端 必须 使用 TokenReview API 将令牌呈现给 kube-apiserver,以便它验证并扩展嵌入的声明,使用类似于上面的 验证和检查私有声明 部分的步骤,但使用 支持的客户端库。有关 JWT 及其结构的更多信息,请参阅 JSON Web 令牌 RFC

绑定服务帐户令牌卷机制

功能状态: Kubernetes v1.22 [稳定](默认启用:true)

默认情况下,Kubernetes 控制平面(特别是 ServiceAccount 准入控制器)会将 投影卷添加到 Pod 中,并且此卷包含用于 Kubernetes API 访问的令牌。

以下是已启动的 Pod 的外观示例

...
  - name: kube-api-access-<random-suffix>
    projected:
      sources:
        - serviceAccountToken:
            path: token # must match the path the app expects
        - 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,并将 kube-apiserver 作为其受众。此机制取代了早期的基于 Secret 添加卷的机制,其中 Secret 代表 Pod 的 ServiceAccount,但不会过期。
  2. configMap 来源。ConfigMap 包含一个证书颁发机构数据包。Pod 可以使用这些证书来确保它们连接到集群的 kube-apiserver(而不是中间盒或意外配置错误的对等方)。
  3. downwardAPI 来源,它会查找包含 Pod 的命名空间名称,并将该名称信息提供给 Pod 内运行的应用程序代码。

Pod 中挂载此特定卷的任何容器都可以访问上述信息。

手动管理 ServiceAccounts 的 Secret

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

在包括 Kubernetes v1.32 在内的较新版本中,API 凭据是直接获取 使用 TokenRequest API,并使用投影卷挂载到 Pod 中。使用此方法获取的令牌具有有限的生命周期,并且当它们挂载到的 Pod 被删除时会自动失效。

您仍然可以手动创建一个 Secret 来保存服务帐户令牌;例如,如果您需要一个永不过期的令牌。

一旦您手动创建了一个 Secret 并将其链接到 ServiceAccount,Kubernetes 控制平面会自动将令牌填充到该 Secret 中。

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

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

apiVersion: v1
kind: ServiceAccount
metadata:
  name: build-robot
  namespace: default
secrets:
  - name: build-robot-secret # usually NOT present for a manually generated token                         

从 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. 返回错误,指示令牌已失效。

当收到此验证错误时,用户可以更新 Secret 以删除 kubernetes.io/legacy-token-invalid-since 标签,以暂时允许使用此令牌。

这是一个已标记有 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 控制器管理命名空间内的 ServiceAccounts,并确保每个活动命名空间中都存在名为“default”的 ServiceAccount。

令牌控制器

服务帐户令牌控制器作为 kube-controller-manager 的一部分运行。此控制器异步运行。它

  • 监视 ServiceAccount 删除并删除所有相应的 ServiceAccount 令牌 Secret。
  • 监视 ServiceAccount 令牌 Secret 添加,并确保引用的 ServiceAccount 存在,并在需要时向 Secret 添加令牌。
  • 监视 Secret 删除并在需要时从相应的 ServiceAccount 中删除引用。

您必须使用 --service-account-private-key-file 标志将服务帐户私钥文件传递给 kube-controller-manager 中的令牌控制器。私钥用于签署生成的服务帐户令牌。同样,您必须使用 --service-account-key-file 标志将相应的公钥传递给 kube-apiserver。公钥将在身份验证期间用于验证令牌。

功能状态: Kubernetes v1.32 [alpha](默认禁用:false)

设置 --service-account-private-key-file--service-account-key-file 标志的替代设置是为 外部 ServiceAccount 令牌签名和密钥管理 配置外部 JWT 签名器。请注意,这些设置是互斥的,不能一起配置。

ServiceAccount 准入控制器

Pod 的修改是通过名为准入控制器的插件实现的。它是 API 服务器的一部分。此准入控制器同步运行以在创建 Pod 时修改它们。当此插件处于活动状态时(并且在大多数发行版上默认处于活动状态),它会在创建 Pod 时执行以下操作

  1. 如果 Pod 没有设置 .spec.serviceAccountName,则准入控制器会将此传入 Pod 的 ServiceAccount 名称设置为 default
  2. 准入控制器确保传入 Pod 引用的 ServiceAccount 存在。如果没有具有匹配名称的 ServiceAccount,则准入控制器将拒绝传入的 Pod。该检查甚至适用于 default ServiceAccount。
  3. 如果 ServiceAccount 的 automountServiceAccountToken 字段和 Pod 的 automountServiceAccountToken 字段均未设置为 false
    • 准入控制器会更改传入的 Pod,添加一个额外的,其中包含用于 API 访问的令牌。
    • 准入控制器会将 volumeMount 添加到 Pod 中的每个容器,跳过任何已为路径 /var/run/secrets/kubernetes.io/serviceaccount 定义卷挂载的容器。对于 Linux 容器,该卷挂载在 /var/run/secrets/kubernetes.io/serviceaccount;在 Windows 节点上,挂载在等效路径上。
  4. 如果传入的 Pod 的规范不包含任何 imagePullSecrets,则准入控制器会添加 imagePullSecrets,并从 ServiceAccount 复制它们。

传统 ServiceAccount 令牌跟踪控制器

功能状态: Kubernetes v1.28 [stable](默认启用:true)

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

传统 ServiceAccount 令牌清理器

功能状态: Kubernetes v1.30 [stable](默认启用:true)

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

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

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

  • 该 Secret 是自动生成的,这意味着它被 ServiceAccount 双向引用。
  • 当前没有任何 Pod 挂载该 Secret。
  • 自创建或上次使用以来,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 # decimal equivalent of octal 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

该清单片段定义了一个 projected 卷,它组合了来自三个来源的信息

  1. 一个 serviceAccountToken 源,其中包含 kubelet 从 kube-apiserver 获取的令牌。kubelet 使用 TokenRequest API 获取有时限的令牌。为 TokenRequest 提供的令牌在 Pod 被删除或在定义的生命周期(默认情况下为 1 小时)后过期。该令牌绑定到特定的 Pod,并以 kube-apiserver 作为其受众。
  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 的名称

kubectl delete secret name-of-secret

否则,首先找到 ServiceAccount 的 Secret。

# This assumes that you already have a namespace named '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

外部 ServiceAccount 令牌签名和密钥管理

功能状态: Kubernetes v1.32 [alpha](默认禁用:false)

可以配置 kube-apiserver 以使用外部签名器进行令牌签名和令牌验证密钥管理。此功能使 Kubernetes 发行版能够与他们选择的密钥管理解决方案(例如:HSM、云 KMS)集成,以进行服务帐户凭证签名和验证。要配置 kube-apiserver 以使用 external-jwt-signer,请将 --service-account-signing-endpoint 标志设置为文件系统上的 Unix 域套接字 (UDS) 的位置,或以 @ 符号作为前缀并命名抽象套接字命名空间中的 UDS。在配置的 UDS 上,应有一个 RPC 服务器,该服务器实现ExternalJWTSigner。external-jwt-signer 必须是健康的,并且准备好为 kube-apiserver 提供支持的服务帐户密钥才能启动。

有关 ExternalJWTSigner 的更多详细信息,请查看 KEP-740

清理

如果您创建了一个名为 examplens 的命名空间用于实验,则可以将其删除

kubectl delete namespace examplens

下一步

上次修改时间:2024 年 11 月 26 日下午 6:09 PST:应用代码审查的建议 (4ef866967e)