为 Pod 配置服务账号

Kubernetes 提供两种完全不同的方式来为客户端提供支持,这些客户端可能运行在你的集群中, 也可能与你的集群的控制面相关, 需要向 API 服务器完成身份认证。

服务账号(Service Account) 为 Pod 中运行的进程提供身份标识, 并映射到 ServiceAccount 对象。当你向 API 服务器执行身份认证时, 你会将自己标识为某个用户(User)。Kubernetes 能够识别用户的概念, 但是 Kubernetes 自身并不提供 User API。

本服务是关于 ServiceAccount 的,而 ServiceAccount 则确实存在于 Kubernetes 的 API 中。 本指南为你展示为 Pod 配置 ServiceAccount 的一些方法。

准备开始

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

使用默认的服务账号访问 API 服务器

当 Pod 与 API 服务器联系时,Pod 会被认证为某个特定的 ServiceAccount(例如:default)。 在每个名字空间中,至少存在一个 ServiceAccount。

每个 Kubernetes 名字空间至少包含一个 ServiceAccount:也就是该名字空间的默认服务账号, 名为 default。如果你在创建 Pod 时没有指定 ServiceAccount,Kubernetes 会自动将该名字空间中名为 default 的 ServiceAccount 分配给该 Pod。

你可以检视你刚刚创建的 Pod 的细节。例如:

kubectl get pods/<podname> -o yaml

在输出中,你可以看到字段 spec.serviceAccountName。当你在创建 Pod 时未设置该字段时, Kubernetes 自动为 Pod 设置这一属性的取值。

Pod 中运行的应用可以使用这一自动挂载的服务账号凭据来访问 Kubernetes API。 参阅访问集群以进一步了解。

当 Pod 被身份认证为某个 ServiceAccount 时, 其访问能力取决于所使用的鉴权插件和策略

放弃 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 上 spec 的设置优先于服务账号的设置。

使用多个服务账号

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

如果你查询服务账号对象的完整信息,如下所示:

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 字段设置为你想用的服务账号名称。

只能在创建 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 参数来请求特定的令牌有效期 (实际签发的令牌的有效期可能会稍短一些,也可能会稍长一些)。

当启用了 ServiceAccountTokenNodeBindingServiceAccountTokenNodeBindingValidation 特性,并将 KUBECTL_NODE_BOUND_TOKENS 环境变量设置为 true 时, 可以创建一个直接绑定到 Node 的服务账号令牌:

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

此令牌将有效直至其过期或关联的 Node 或服务账户被删除。

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

如果你通过下面的命令来查看 Secret:

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

你可以看到 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

首先,生成一个 imagePullSecret; 接下来,验证该 Secret 已被创建。例如:

  • 为 Pod 设置 imagePullSecret 所描述的,生成一个镜像拉取 Secret:

    kubectl create secret docker-registry myregistrykey --docker-server=<registry name> \
            --docker-username=DUMMY_USERNAME --docker-password=DUMMY_DOCKER_PASSWORD \
            --docker-email=DUMMY_DOCKER_EMAIL
    
  • 检查该 Secret 已经被创建。

    kubectl get secrets myregistrykey
    

    输出类似于这样:

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

将镜像拉取 Secret 添加到服务账号

接下来更改名字空间的默认服务账号,将该 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

检查 imagePullSecrets 已经被设置到新 Pod 上

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

服务账号令牌卷投射

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

kubelet 还可以将 ServiceAccount 令牌投射到 Pod 中。你可以指定令牌的期望属性, 例如受众和有效期限。这些属性在 default ServiceAccount 令牌上无法配置。 当 Pod 或 ServiceAccount 被删除时,该令牌也将对 API 无效。

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

来自此投射卷的令牌是一个 JSON Web Token (JWT)。 此令牌的 JSON 载荷遵循明确定义的模式,绑定到 Pod 的令牌的示例载荷如下:

{
  "aud": [  # 匹配请求的受众,或当没有明确请求时匹配 API 服务器的默认受众
    "https://kubernetes.default.svc"
  ],
  "exp": 1731613413,
  "iat": 1700077413,
  "iss": "https://kubernetes.default.svc",  # 匹配传递到 --service-account-issuer 标志的第一个值
  "jti": "ea28ed49-2e11-4280-9ec5-bc3d1d84661a",  # ServiceAccountTokenJTI 特性必须被启用才能出现此申领
  "kubernetes.io": {
    "namespace": "kube-system",
    "node": {  # ServiceAccountTokenPodNodeInfo 特性必须被启用,API 服务器才会添加此节点引用申领
      "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"
}

启动使用服务账号令牌投射的 Pod

要为某 Pod 提供一个受众为 vault 并且有效期限为 2 小时的令牌,你可以定义一个与下面类似的 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 请求令牌并将其保存起来;通过将令牌存储到一个可配置的路径以使之在 Pod 内可用;在令牌快要到期的时候刷新它。kubelet 会在令牌存在期达到其 TTL 的 80% 的时候或者令牌生命期超过 24 小时的时候主动请求将其轮换掉。

应用负责在令牌被轮换时重新加载其内容。通常而言,周期性地(例如,每隔 5 分钟) 重新加载就足够了,不必跟踪令牌的实际过期时间。

发现服务账号分发者

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

如果你在你的集群中已经为 ServiceAccount 启用了令牌投射, 那么你也可以利用其发现能力。Kubernetes 提供一种方式来让客户端将一个或多个外部系统进行联邦, 作为标识提供者(Identity Provider),而这些外部系统的角色是依赖方(Relying Party)

当此特性被启用时,Kubernetes API 服务器会通过 HTTP 发布一个 OpenID 提供者配置文档。 该配置文档发布在 /.well-known/openid-configuration 路径。 这里的 OpenID 提供者配置(OpenID Provider Configuration)有时候也被称作 “发现文档(Discovery Document)”。 Kubernetes API 服务器也通过 HTTP 在 /openid/v1/jwks 处发布相关的 JSON Web Key Set(JWKS)。

使用 RBAC 的集群都包含一个的默认 RBAC ClusterRole, 名为 system:service-account-issuer-discovery。 默认的 RBAC ClusterRoleBinding 将此角色分配给 system:serviceaccounts 组, 所有 ServiceAccount 隐式属于该组。这使得集群上运行的 Pod 能够通过它们所挂载的服务账号令牌访问服务账号发现文档。 此外,管理员可以根据其安全性需要以及期望集成的外部系统,选择是否将该角色绑定到 system:authenticatedsystem:unauthenticated

JWKS 响应包含依赖方可以用来验证 Kubernetes 服务账号令牌的公钥数据。 依赖方先会查询 OpenID 提供者配置,之后使用返回响应中的 jwks_uri 来查找 JWKS。

在很多场合,Kubernetes API 服务器都不会暴露在公网上,不过对于缓存并向外提供 API 服务器响应数据的公开末端而言,用户或者服务提供商可以选择将其暴露在公网上。 在这种环境中,可能会重载 OpenID 提供者配置中的 jwks_uri,使之指向公网上可用的末端地址,而不是 API 服务器的地址。 这时需要向 API 服务器传递 --service-account-jwks-uri 参数。 与分发者 URL 类似,此 JWKS URI 也需要使用 https 模式。

接下来

另请参见: