管理 ServiceAccount

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

Pod 中的进程可以使用与其关联的服务账号身份向集群的 API Server 进行身份验证。

关于服务账号的介绍,请阅读配置服务账号

本任务指南解释了 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(用于 Projected Volume 挂载,见下文)
  • Secret(可用于通过删除 Secret 来撤销令牌)
  • Node(可用于在 Node 被删除时自动撤销令牌;创建新的绑定 Node 的令牌在 v1.33+ 中是 GA)

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

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

绑定 Pod 的令牌中的额外元数据

特征状态: Kubernetes v1.32 [stable](默认启用:true)

当服务账号令牌绑定到 Pod 对象时,令牌中也会嵌入额外的元数据,指示绑定的 Pod 的 spec.nodeName 字段的值,以及该 Node 的 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"
    
  3. 将此令牌复制到一个名为 tokenreview.yaml 的新文件中。

    apiVersion: authentication.k8s.io/v1
    kind: TokenReview
    spec:
      token: <token from step 2>
    
  4. 将此资源提交给 apiserver 进行审查。

    # use '-o yaml' to inspect the output
    kubectl create -o yaml -f tokenreview.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
    

服务账号私有声明的 Schema

JWT 令牌中特定于 Kubernetes 的声明的 Schema 目前尚未文档化,但相关代码区域可以在 Kubernetes 代码库中的 serviceaccount 包中找到。

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

{
  "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 离线验证的服务可以使用此 Schema,以及配置了来自 API Server 的 OpenID Discovery 信息的符合标准的 JWT 验证器,来验证提交的 JWT,而无需使用 TokenReview API。

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

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

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

特征状态: Kubernetes v1.22 [stable](默认启用:true)

默认情况下,Kubernetes 控制平面(特别是 ServiceAccount Admission Controller)会向 Pod 添加一个 Projected Volume,该卷包含用于 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

该 Manifest 片段定义了一个包含三个源的 Projected Volume。在这种情况下,每个源也代表该卷中的单个路径。这三个源是:

  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 中任何挂载此特定卷的容器都可以访问上述信息。

ServiceAccount 的手动 Secret 管理

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

在更近的版本中,包括 Kubernetes v1.33,API 凭据使用 TokenRequest API 直接获取,并使用 Projected Volume 挂载到 Pod 中。使用此方法获取的令牌具有有时效的生命周期,并在挂载它们的 Pod 被删除时自动失效。

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

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

自动生成的旧式 ServiceAccount 令牌清理

在 1.24 版本之前,Kubernetes 会为 ServiceAccount 自动生成基于 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 控制器管理命名空间内的 ServiceAccount,并确保每个活跃的命名空间中都存在一个名为 "default" 的 ServiceAccount。

Token 控制器

服务账号令牌控制器作为 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 标志的替代设置是配置一个外部 JWT 签名器,用于外部 ServiceAccount 令牌签名和密钥管理。请注意,这些设置是互斥的,不能同时配置。

ServiceAccount Admission Controller

Pod 的修改是通过一个称为Admission Controller 的插件实现的。它是 API Server 的一部分。此 Admission Controller 在 Pod 创建时同步修改 Pod。当此插件处于活动状态时(在大多数发行版中默认启用),它在创建 Pod 时执行以下操作:

  1. 如果 Pod 没有设置 .spec.serviceAccountName,Admission Controller 会将此传入 Pod 的 ServiceAccount 名称设置为 default
  2. Admission Controller 确保传入 Pod 引用的 ServiceAccount 存在。如果没有匹配名称的 ServiceAccount,Admission Controller 将拒绝传入的 Pod。即使是 default ServiceAccount,此检查也适用。
  3. 前提是 ServiceAccount 的 automountServiceAccountToken 字段和 Pod 的 automountServiceAccountToken 字段都没有设置为 false
    • Admission Controller 修改传入的 Pod,添加一个额外的,该卷包含用于 API 访问的令牌。
    • Admission Controller 向 Pod 中的每个容器添加一个 volumeMount,跳过任何已为路径 /var/run/secrets/kubernetes.io/serviceaccount 定义了卷挂载的容器。对于 Linux 容器,该卷挂载在 /var/run/secrets/kubernetes.io/serviceaccount;在 Windows 节点上,挂载在等效路径。
  4. 如果传入 Pod 的 Spec 中不包含任何 imagePullSecrets,则 Admission Controller 会添加 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 双向引用。
  • 该 Secret 当前未被任何 Pod 挂载。
  • 自创建或上次使用以来,该 Secret 在指定的时间段内未使用。

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

TokenRequest API

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

你可以使用 ServiceAccount 的 TokenRequest 子资源来获取该 ServiceAccount 的有时效的令牌。你无需调用此方法来获取在容器内使用的 API 令牌,因为 Kubelet 会使用 projected volume 为你设置好。

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

Kubernetes 控制平面(特别是 ServiceAccount Admission Controller)会向 Pod 添加一个 Projected Volume,并且 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

该 Manifest 片段定义了一个 Projected Volume,它结合了来自三个源的信息:

  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 的示例 Manifest:

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:          ...

如果你将一个新的 Pod 启动到 examplens 命名空间,它可以使用你刚刚创建的 myserviceaccount 服务账号令牌 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 处,应该是一个实现 ExternalJWTSigner 的 RPC 服务器。external-jwt-signer 必须健康并准备好为 kube-apiserver 启动提供支持的服务帐户密钥。

查看 KEP-740 以获取有关 ExternalJWTSigner 的更多详细信息。

清理

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

kubectl delete namespace examplens

下一步

  • 阅读有关 投射卷 的更多详细信息。
最后修改时间:2025年1月13日 太平洋标准时间上午11:07:KEP-4193:ServiceAccountTokenNodeBinding GA 的 1.33 更新 (1eae76928b)