为 Pod 配置服务账号

Kubernetes 提供了两种不同的方式,供集群内运行的客户端,或与集群的 控制平面 建立关系的客户端,用于向 API 服务器 进行身份验证。

服务帐户为在 Pod 中运行的进程提供身份,并映射到 ServiceAccount 对象。当您向 API 服务器进行身份验证时,您将标识自己为特定的用户。Kubernetes 识别用户概念,但是 Kubernetes 本身具有用户 API。

本任务指南是关于 ServiceAccount 的,它们确实存在于 Kubernetes API 中。本指南向您展示了一些为 Pod 配置 ServiceAccount 的方法。

开始之前

您需要一个 Kubernetes 集群,并且 kubectl 命令行工具必须配置为与您的集群通信。建议在至少具有两个非控制平面主机的集群上运行本教程。如果您还没有集群,可以使用 minikube 创建一个,或者可以使用以下 Kubernetes 游乐场

使用默认服务帐户访问 API 服务器

当 Pod 联系 API 服务器时,Pod 会以特定的 ServiceAccount(例如,default)的身份进行身份验证。每个 命名空间 中始终至少存在一个 ServiceAccount。

每个 Kubernetes 命名空间都包含至少一个 ServiceAccount:该命名空间的默认 ServiceAccount,名为 default。如果您在创建 Pod 时未指定 ServiceAccount,Kubernetes 会自动分配该命名空间中的名为 default 的 ServiceAccount。

您可以获取您创建的 Pod 的详细信息。例如

kubectl get pods/<podname> -o yaml

在输出中,您会看到一个字段 spec.serviceAccountName。如果您在创建 Pod 时未指定,Kubernetes 会自动设置该值。

在 Pod 内部运行的应用程序可以使用自动挂载的服务帐户凭据访问 Kubernetes API。请参阅 访问集群 以了解更多信息。

当 Pod 以 ServiceAccount 的身份进行身份验证时,其访问级别取决于所使用的 授权插件和策略

即使存在 finalizer,API 凭据也会在 Pod 删除时自动撤销。特别是,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 规范优先。

使用多个服务帐户

每个命名空间至少有一个 ServiceAccount:默认 ServiceAccount 资源,名为 default。您可以使用以下命令列出您的 当前命名空间 中的所有 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 子域名

如果您获取 ServiceAccount 对象的完整转储,如下所示

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 字段设置为您想要使用的 ServiceAccount 的名称。

您只能在创建 Pod 时或在新的 Pod 模板中设置 serviceAccountName 字段。您无法更新现有 Pod 的 .spec.serviceAccountName 字段。

清理

如果您尝试创建上述示例中的 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.33 [稳定](默认启用)

使用 kubectl v1.31 或更高版本,可以创建直接绑定到节点的 ServiceAccount 令牌

kubectl create token build-robot --bound-object-kind Node --bound-object-name node-001 --bound-object-uid 123...456

该令牌在到期或关联的节点或服务帐户被删除之前有效。

手动为 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

如果您使用

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

查看 Secret,您会看到该 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:          ...

当您删除具有关联 Secret 的 ServiceAccount 时,Kubernetes 控制平面会自动从该 Secret 中清理长期令牌。

将 ImagePullSecrets 添加到 ServiceAccount

首先,创建一个 imagePullSecret。 接下来,验证它是否已创建。 例如

  • 按照 在 Pod 上指定 ImagePullSecrets 中所述创建一个 imagePullSecret。

    kubectl create secret docker-registry myregistrykey --docker-server=<registry name> \
            --docker-username=DUMMY_USERNAME --docker-password=DUMMY_DOCKER_PASSWORD \
            --docker-email=DUMMY_DOCKER_EMAIL
    
  • 验证它是否已创建。

    kubectl get secrets myregistrykey
    

    输出类似于此

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

将 image pull secret 添加到 ServiceAccount

接下来,修改命名空间的默认 ServiceAccount,以将此 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

验证为新的 Pod 设置了 imagePullSecrets

现在,当在当前命名空间中创建新的 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

ServiceAccount token 卷投影

功能状态: Kubernetes v1.20 [稳定]

kubelet 也可以将 ServiceAccount token 投影到 Pod 中。 你可以指定 token 的所需属性,例如受众和有效持续时间。 这些属性不能在默认 ServiceAccount token 上进行配置。 当 Pod 或 ServiceAccount 被删除时,token 也会对 API 无效。

你可以使用 projected volume 类型,名为 ServiceAccountToken,来配置 Pod 的 spec 中的此行为。

此 projected volume 中的 token 是 JSON Web Token (JWT)。 此 token 的 JSON payload 遵循定义良好的模式 - 一个绑定 token 的示例 payload

{
  "aud": [  # matches the requested audiences, or the API server's default audiences when none are explicitly requested
    "https://kubernetes.default.svc"
  ],
  "exp": 1731613413,
  "iat": 1700077413,
  "iss": "https://kubernetes.default.svc",  # matches the first value passed to the --service-account-issuer flag
  "jti": "ea28ed49-2e11-4280-9ec5-bc3d1d84661a", 
  "kubernetes.io": {
    "namespace": "kube-system",
    "node": {
      "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"
}

使用 ServiceAccount token 投影启动 Pod

为了向 Pod 提供具有 vault 受众和持续时间为两个小时的 token,你可以定义一个类似于以下内容的 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 请求和存储 token;在可配置的文件路径上向 Pod 提供 token;并在 token 接近到期时刷新 token。 如果 token 的总生存时间 (TTL) 超过 80%,或者 token 的年龄超过 24 小时,kubelet 会主动请求 token 轮换。

应用程序负责在 token 轮换时重新加载 token。 通常,应用程序只需按计划加载 token(例如:每 5 分钟一次),而无需跟踪实际的到期时间即可。

ServiceAccount issuer 发现

功能状态: Kubernetes v1.21 [稳定]

如果你已为集群中的 ServiceAccount 启用 token 投影,则还可以利用发现功能。 Kubernetes 提供了一种方法,让客户端可以联合作为身份提供者,以便一个或多个外部系统可以充当依赖方

启用后,Kubernetes API 服务器将通过 HTTP 发布 OpenID Provider Configuration 文档。 该配置文档发布在 /.well-known/openid-configuration。 OpenID Provider Configuration 有时被称为发现文档。 Kubernetes API 服务器还会通过 HTTP 在 /openid/v1/jwks 发布相关的 JSON Web Key Set (JWKS)。

使用 RBAC 的集群包含一个名为 system:service-account-issuer-discovery 的默认 ClusterRole。 默认 ClusterRoleBinding 将此角色分配给 system:serviceaccounts 组,所有 ServiceAccount 都隐式属于该组。 这允许在集群上运行的 Pod 通过其挂载的 ServiceAccount token 访问 ServiceAccount 发现文档。 管理员还可以选择将角色绑定到 system:authenticatedsystem:unauthenticated,具体取决于他们的安全要求以及他们打算与哪些外部系统联合。

JWKS 响应包含依赖方可用于验证 Kubernetes ServiceAccount token 的公钥。 依赖方首先查询 OpenID Provider Configuration,并使用响应中的 jwks_uri 字段找到 JWKS。

在许多情况下,Kubernetes API 服务器不可在公共互联网上访问,但用户或服务提供商可以提供提供来自 API 服务器的缓存响应的公共端点。 在这些情况下,可以通过将 --service-account-jwks-uri 标志传递给 API 服务器来覆盖 OpenID Provider Configuration 中的 jwks_uri,以便它指向公共端点,而不是 API 服务器的地址。 与 issuer URL 一样,JWKS URI 需要使用 https 方案。

接下来

参见