为 Pod 配置 ServiceAccount

Kubernetes 为在集群内运行的客户端,或者与集群的 控制平面 有关联的客户端,提供了两种不同的向 API 服务器 进行身份认证的方式。

ServiceAccount 为在 Pod 中运行的进程提供了一个身份,并映射到一个 ServiceAccount 对象。当你对 API 服务器进行身份认证时,你将自己标识为一个特定的用户。Kubernetes 承认用户的概念,但是,Kubernetes 本身没有 User API。

本任务指南介绍 ServiceAccount,ServiceAccount 确实存在于 Kubernetes API 中。本指南向你展示了为 Pod 配置 ServiceAccount 的一些方法。

准备工作

你需要拥有一个 Kubernetes 集群,并且 kubectl 命令行工具已配置为与你的集群通信。建议你在至少有两个非控制平面主机的节点上运行本教程。如果你还没有集群,你可以使用 minikube 创建一个,或者使用以下任一 Kubernetes 实验环境

使用默认 ServiceAccount 访问 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 内的应用可以使用自动挂载的 ServiceAccount 凭据访问 Kubernetes API。参阅访问集群以了解更多信息。

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

API 凭据在 Pod 被删除时会自动撤销,即使存在 Finalizer 也是如此。特别是,API 凭据将在 Pod 上设置的 .metadata.deletionTimestamp 之后 60 秒撤销(删除时间戳通常是接受删除请求的时间加上 Pod 的终止宽限期)。

选择禁用 API 凭据自动挂载

如果你不希望 kubelet 自动挂载 ServiceAccount 的 API 凭据,你可以选择禁用此默认行为。你可以通过在 ServiceAccount 上设置 automountServiceAccountToken: false 来选择禁用为 ServiceAccount 在 /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:默认的 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

你可以使用授权插件来设置 ServiceAccount 的权限

要使用非默认的 ServiceAccount,请将 Pod 的 spec.serviceAccountName 字段设置为你希望使用的 ServiceAccount 的名称。

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

清理

如果你尝试创建了上述示例中的 build-robot ServiceAccount,你可以运行以下命令进行清理

kubectl delete serviceaccount/build-robot

手动为 ServiceAccount 创建 API Token

假设你有一个如前所述名为 "build-robot" 的现有 ServiceAccount。

你可以使用 kubectl 为该 ServiceAccount 获取有时限的 API Token

kubectl create token build-robot

该命令的输出是一个 Token,你可以使用它作为该 ServiceAccount 进行身份认证。你可以使用 kubectl create token 命令的 --duration 命令行参数请求一个特定的 Token 有效期(实际颁发的 Token 有效期可能更短,甚至更长)。

特性状态: Kubernetes v1.33 [stable] (默认为启用:true)

使用 kubectl v1.31 或更高版本,可以创建一个直接绑定到 Node 的 ServiceAccount Token

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

该 Token 将一直有效,直到过期或关联的 Node 或 ServiceAccount 被删除为止。

手动为 ServiceAccount 创建一个长期有效的 API Token

如果你想获取一个 ServiceAccount 的 API Token,你可以创建一个带有特殊注解 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

如果你使用以下命令查看 Secret

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

你会看到该 Secret 现在包含了 "build-robot" ServiceAccount 的 API Token。

由于你设置的注解,控制平面会自动为该 ServiceAccount 生成一个 Token,并将其存储到相关的 Secret 中。控制平面还会清理已删除 ServiceAccount 的 Token。

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 中的长期有效 Token。

将 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
    

将 ImagePullSecret 添加到 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

现在,当在当前命名空间中使用默认 ServiceAccount 创建新 Pod 时,新 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 [stable]

Kubelet 也可以将 ServiceAccount Token 投射到 Pod 中。你可以指定 Token 的所需属性,例如受众和有效期。这些属性不能在默认 ServiceAccount Token 上配置。当 Pod 或 ServiceAccount 被删除时,该 Token 也将对 API 无效。

你可以使用一个名为 ServiceAccountToken投射卷类型来为 Pod 的 spec 配置此行为。

此投射卷中的 Token 是一个 JSON Web Token (JWT)。此 Token 的 JSON 有效载荷遵循一个明确定义的模式 - Pod 绑定的 Token 的示例有效载荷如下

{
  "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;在可配置的文件路径上使 Token 对 Pod 可用;并在 Token 即将到期时刷新 Token。如果 Token 超过其总生存时间 (TTL) 的 80%,或者如果 Token 已超过 24 小时,Kubelet 会主动请求轮换 Token。

应用负责在 Token 轮换时重新加载 Token。对于应用来说,按计划加载 Token(例如:每 5 分钟一次)通常就足够了,无需跟踪实际的过期时间。

ServiceAccount 发行者发现

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

如果在你的集群中为 ServiceAccount 启用了Token 投射,那么你也可以利用发现特性。Kubernetes 提供了一种让客户端作为 身份提供者 进行联合的方式,这样就有一个或多个外部系统可以充当 信赖方

启用后,Kubernetes API 服务器通过 HTTP 发布 OpenID 提供者配置文档。该配置文档发布在 /.well-known/openid-configuration。OpenID 提供者配置有时也称为 发现文档。Kubernetes API 服务器也在 /openid/v1/jwks 通过 HTTP 发布相关的 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 提供者配置,并使用响应中的 jwks_uri 字段查找 JWKS。

在许多情况下,Kubernetes API 服务器无法在公共互联网上访问,但用户或服务提供商可以提供从 API 服务器提供缓存响应的公共端点。在这种情况下,可以通过向 API 服务器传递 --service-account-jwks-uri 参数来覆盖 OpenID 提供者配置中的 jwks_uri,使其指向公共端点而不是 API 服务器的地址。与发行者 URL 一样,JWKS URI 也必须使用 https 方案。

下一步

另请参见