本篇教程将介绍 Kubernetes 中的 ServiceAccount 对象, 并讲述服务账号的工作原理、使用场景、限制、替代方案等信息。
一、服务账号介绍
服务账号在 Kubernetes 中是用于非人类用户的账号,它为集群中的不同实体提供了特定的身份标识。应用 Pod、系统组件以及集群内外的实体可以使用特定 ServiceAccount 的凭据来将自己标识为该 ServiceAccount。 这种身份可用于许多场景,包括向 API 服务器进行身份认证或实现基于身份的安全策略。
服务账号以 ServiceAccount 对象的形式存在于 API 服务器中,具有以下属性:
- 名字空间限定: 每个服务账号都与一个 Kubernetes 名字空间绑定; 每个名字空间在创建时,会获得一个名为 default 的 ServiceAccount。
- 轻量级: 服务账号存在于集群中,并在 Kubernetes API 中定义,可以快速创建服务账号以支持特定任务。
- 可移植性: 复杂的容器化工作负载的配置包中可能包括针对系统组件的服务账号定义, 服务账号的轻量级性质和名字空间作用域的身份使得这类配置可移植。
服务账号与用户账号不同,用户账号是集群中通过了身份认证的人类用户。默认情况下, 用户账号不存在于 Kubernetes API 服务器中;相反,API 服务器将用户身份视为不透明数据。 可以使用多种方法认证为某个用户账号,某些 Kubernetes 发行版可能会添加自定义扩展 API 来在 API 服务器中表示用户账号。
描述 | 服务账号 | 用户或组 |
位置 | Kubernetes API(ServiceAccount 对象) | 外部 |
访问控制 | Kubernetes RBAC 或其他鉴权机制 | Kubernetes RBAC 或其他身份和访问管理机制 |
目标用途 | 工作负载、自动化工具 | 人 |
1、默认服务账号
在创建集群时,Kubernetes 会自动为集群中的每个命名空间创建一个名为 default 的 ServiceAccount 对象。当启用了基于角色的访问控制(RBAC)时,Kubernetes 会为所有通过身份验证的主体赋予默认 API 发现权限。然而,每个命名空间中的 default 服务账号除了这些权限之外,默认没有其他访问权限。
如果基于角色的访问控制(RBAC)被启用,当删除命名空间中的 default ServiceAccount 对象时,控制平面会用新的 ServiceAccount 对象替换它。这意味着,如果某个名字空间中部署了 Pod,并且没有手动为该 Pod 指派 ServiceAccount,Kubernetes 将使用该名字空间的 default 服务账号来指派给该 Pod。这样可以确保 Pod 具有适当的访问权限,同时遵循 RBAC 规则。
二、服务账号使用场景
一般而言,可以在以下场景中使用服务账号来提供身份标识:
1、 Pod 需要与 Kubernetes API 服务器通信,例如在以下场景中:
- 提供对存储在 Secret 中的敏感信息的只读访问。
- 授予跨名字空间访问的权限,例如允许 example 名字空间中的 Pod 读取、列举和监视 kube-node-lease 名字空间中的 Lease 对象。
2、 Pod 需要与外部服务进行通信。例如,工作负载 Pod 需要一个身份来访问某商业化的云 API, 并且商业化 API 的提供商允许配置适当的信任关系。
3、使用 imagePullSecret 完成在私有镜像仓库上的身份认证。
4、外部服务需要与 Kubernetes API 服务器进行通信。例如,作为 CI/CD 流水线的一部分向集群作身份认证。
5、在集群中使用了第三方安全软件,该软件依赖不同 Pod 的 ServiceAccount 身份,按不同上下文对这些 Pod 分组。
三、使用服务账号
要使用 Kubernetes 服务账号,需要执行以下步骤:
- 使用像 kubectl 这样的 Kubernetes 客户端或定义对象的清单(manifest)创建 ServiceAccount 对象。
- 使用鉴权机制(如 RBAC)为 ServiceAccount 对象授权。
- 在创建 Pod 期间将 ServiceAccount 对象指派给 Pod。
- 如果所使用的是来自外部服务的身份,可以获取 ServiceAccount 令牌,并在该服务中使用这一令牌。
1、为 ServiceAccount 授权
Kubernetes 提供了基于角色的访问控制(RBAC)机制,允许您为每个服务账号授予所需的最低权限。通过使用 RBAC,您可以确保服务账号仅具有执行其任务所需的最小权限,从而遵循最小特权原则。
要实现这一点,您可以创建一个角色,该角色定义了一组权限,然后将此角色绑定到相应的ServiceAccount 上。这样,当服务账号被用于运行 Pod 时,它将只获得分配给它的角色所允许的权限。
2、使用 ServiceAccount 进行跨名字空间访问
可以使用 RBAC(基于角色的访问控制)来允许一个名字空间中的服务账号对集群中另一个名字空间的资源执行操作。这在多租户环境中非常有用,因为它可以确保不同命名空间之间的资源隔离和安全。
以下是一个示例,说明如何实现这一目标:
在 dev 名字空间中创建一个服务账号和一个 Pod。例如,可以使用以下 YAML 文件创建服务账号:
apiVersion: v1 kind: ServiceAccount metadata: name: dev-sa namespace: dev
然后,在 maintenance 名字空间中创建一个 Role 对象,该对象授予列举 Job 对象的权限。例如,可以使用以下 YAML 文件创建 Role:
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: maintenance-role namespace: maintenance rules: - apiGroups: ["batch"] resources: ["jobs"] verbs: ["get", "list"]
接下来,在 maintenance 名字空间中创建一个 RoleBinding 对象,将刚刚创建的 Role 绑定到 dev 名字空间中的服务账号上。例如,可以使用以下 YAML 文件创建 RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: maintenance-rolebinding namespace: maintenance subjects: - kind: ServiceAccount name: dev-sa namespace: dev roleRef: kind: Role name: maintenance-role apiGroup: rbac.authorization.k8s.io
现在,dev 名字空间中的 Pod 可以使用该服务账号列出 maintenance 名字空间中的 Job 对象集合。这样,您就可以实现跨命名空间的资源访问控制。
3、将 ServiceAccount 指派给 Pod
要将某个 ServiceAccount 指派给某个 Pod,需要在 Pod 的规约中设置 spec.serviceAccountName 字段。Kubernetes 将自动为 Pod 提供该 ServiceAccount 的凭据。在 Kubernetes v1.22 及更高版本中,Kubernetes 使用 TokenRequest API 获取一个短期的、自动轮换的令牌,并以投射卷的形式挂载此令牌。
- 默认情况下,Kubernetes 会将所指派的 ServiceAccount(无论是 default 服务账号还是指定的定制 ServiceAccount)的凭据提供给 Pod。
- 要防止 Kubernetes 自动注入指定的 ServiceAccount 或 default ServiceAccount 的凭据,可以将 Pod 规约中的 automountServiceAccountToken 字段设置为 false。
- 在 Kubernetes 1.22 之前的版本中,Kubernetes 会将一个长期有效的静态令牌以 Secret 形式提供给 Pod。
4、手动获取 ServiceAccount 凭据
如果需要 ServiceAccount 的凭据并将其挂载到非标准位置,或者用于如果需要 ServiceAccount 的凭据并将其挂载到非标准位置,或者用于 API 服务器之外的受众,可以使用以下方法之一:
- TokenRequest API(推荐):在自己的应用代码中请求一个短期的服务账号令牌,此令牌会自动过期,并可在过期时被轮换。如果有一个旧的、对 Kubernetes 无感知能力的应用,可以在同一个 Pod 内使用边车容器来获取这些令牌,并将其提供给应用工作负载。
- 令牌卷投射(同样推荐):在 Kubernetes v1.20 及更高版本中,使用 Pod 规约告知 kubelet 将服务账号令牌作为投射卷添加到 Pod 中,所投射的令牌会自动过期,在过期之前 kubelet 会自动轮换此令牌。
- 服务账号令牌 Secret(不推荐):可以将服务账号令牌以 Kubernetes Secret 的形式挂载到 Pod 中,虽然这些令牌不会过期且不会轮换,但是不推荐使用此方法,特别是在大规模场景下,这是因为静态、长期有效的凭据存在一定的风险。在 Kubernetes v1.24 及更高版本中,LegacyServiceAccountTokenNoAutoGeneration 特性门控阻止 Kubernetes 自动为 ServiceAccount 创建这些令牌。LegacyServiceAccountTokenNoAutoGeneration 默认被启用,也就是说,Kubernetes 不会创建这些令牌。
注意:对于运行在 Kubernetes 集群外的应用,可能考虑创建一个长期有效的 ServiceAccount 令牌, 并将其存储在 Secret 中。尽管这种方式可以实现身份认证,但 Kubernetes 项目建议避免使用此方法, 长期有效的持有者令牌(Bearer Token)会带来安全风险,一旦泄露,此令牌就可能被滥用。 为此,可以考虑使用其他替代方案,例如,外部应用可以使用一个保护得很好的私钥和证书进行身份认证, 或者使用自己实现的身份认证 Webhook 这类自定义机制。
四、鉴别服务账号凭据
ServiceAccount 使用签名的 JSON Web Token (JWT) 来向 Kubernetes API 服务器以及任何其他存在信任关系的系统进行身份认证。根据令牌的签发方式 (使用 TokenRequest 限制时间或使用传统的 Secret 机制),ServiceAccount 令牌也可能有到期时间、受众和令牌开始生效的时间点。 当客户端以 ServiceAccount 的身份尝试与 Kubernetes API 服务器通信时, 客户端会在 HTTP 请求中包含 Authorization: Bearer <token> 标头。
API 服务器按照以下方式检查该持有者令牌的有效性:
- 检查令牌签名;
- 检查令牌是否已过期;
- 检查令牌申明中的对象引用是否当前有效;
- 检查令牌是否当前有效;
- 检查受众申明。
TokenRequest API 为 ServiceAccount 生成绑定令牌,这种绑定与以该 ServiceAccount 身份运行的客户端(如 Pod)的生命期相关联。API 服务器还会检查正在使用 ServiceAccount 的特定对象引用是否仍然存在, 方式是通过该对象的唯一 ID 进行匹配。 对于以 Secret 形式挂载到 Pod 中的旧有令牌,API 服务器会基于 Secret 来检查令牌。
1、在自己的代码中检查服务账号凭据
如果服务需要检查 Kubernetes 服务账号凭据,可以使用以下方法:
- TokenReview API(推荐)
- OIDC 发现
Kubernetes 项目建议使用 TokenReview API,因为当删除某些 API 对象 (如 Secret、ServiceAccount 和 Pod)的时候,此方法将使绑定到这些 API 对象上的令牌失效。
例如,如果删除包含投射 ServiceAccount 令牌的 Pod,则集群立即使该令牌失效, 并且 TokenReview 操作也会立即失败。 如果使用的是 OIDC 验证,则客户端将继续将令牌视为有效,直到令牌达到其到期时间戳。
应用应始终定义其所接受的受众,并检查令牌的受众是否与应用期望的受众匹配。 这有助于将令牌的作用域最小化,这样它只能在应用内部使用,而不能在其他地方使用。
五、替代方案
1、使用其他机制签发自己的令牌,然后使用 Webhook 令牌身份认证通过自己的验证服务来验证持有者令牌。
2、为 Pod 提供自己的身份:
- 使用 SPIFFE CSI 驱动插件将 SPIFFE SVID 作为 X.509 证书对提供给 Pod。
- 使用 Istio 这类服务网格为 Pod 提供证书。
3、从集群外部向 API 服务器进行身份认证,而不使用服务账号令牌:
- 配置 API 服务器接受来自自己的身份驱动的 OpenID Connect (OIDC) 令牌。
- 使用来自云提供商等外部身份和访问管理 (IAM) 服务创建的服务账号或用户账号向集群进行身份认证。
- 使用 CertificateSigningRequest API 和客户端证书。
4、配置 kubelet 从镜像仓库中获取凭据。
5、使用设备插件访问虚拟的可信平台模块 (TPM),进而可以使用私钥进行身份认证。