操作场景
在多账号治理的企业中,镜像资产(TCR 企业版实例)通常集中托管在一个账号中,而业务应用(TKE 集群)会分散在多个业务账号中。这种情况下,业务账号的 TKE 集群需要拉取镜像账号下的容器镜像,传统做法是将镜像账号的长期访问凭证(用户名/密码)以 ImagePullSecret 的形式分发到各业务账号集群中,存在以下痛点:
凭证难以管理:长期凭证需要人工在多账号间同步、轮换,运维成本高。
安全风险高:长期凭证一旦泄露即长期有效,且难以追溯到具体使用方。
tcr-assistant-oidc 插件基于 OIDC 身份认证机制,通过 CAM 角色与 STS 动态临时凭证实现跨账号免密拉取镜像,适用于以下典型业务场景:
多账号镜像统一治理:镜像账号统一存放 TCR 企业版实例,业务账号 TKE 集群跨账号免密拉取。
临时凭证自动轮转:需要满足合规要求,禁止在集群中长期保存镜像仓库长期凭证的场景。
按仓库/命名空间精细授权:业务方仅能拉取被授权的镜像仓库或命名空间下的镜像。
自定义域名拉取:TCR 实例已绑定企业自定义域名(如
docker.mycompany.com),需要通过自定义域名拉取镜像。与原有 tcr-assistant 插件相比,tcr-assistant-oidc 具备以下增强能力:
能力 | tcr-assistant | tcr-assistant-oidc |
凭证类型 | 静态长期凭证(用户名+密码) | 动态临时密钥,自动轮转 |
跨账号拉取 | 仅支持同账号 | 支持同账号和跨账号 |
说明:
前提条件
已创建 TKE 集群(B 账号),且集群版本 ≥ 1.20(Kubernetes 1.20 版本将 ServiceAccountIssuerDiscovery 特性升级为正式可用)。
已创建 TCR 企业版实例(A 账号),且实例状态为“运行中”。
实现原理
跨账号场景下,TKE 集群位于 B 账号(镜像消费方),TCR 实例位于 A 账号(镜像提供方),信任边界从“账号内部”扩展到“跨账号”。端到端调用可抽象为五段协作:① OIDC 身份注入 → ② 跨账号 CAM 授权换取临时凭证 → ③ 换取 docker 登录密码 → ④ Secret 分发 → ⑤ 跨 VPC 免密拉取。

链路解读:
编号 | 环节 | 关键动作 |
① | OIDC 身份注入 | pod-identity-webhook 将 B 账号 TKE 集群签发的 OIDC JWT 挂载到 Controller Pod,JWT 的 iss 指向 B 集群 |
② | 跨账号 CAM 授权 | Controller 携 B 集群的 JWT 跨账号调用 STS;STS 用登记在 A 账号的 B 集群 JWKS 验签,匹配 Role 信任策略中的 federated OIDC Provider(B),返回 A 账号 2h 临时凭证 |
③ | 换取 docker 密码 | Controller 用 A 账号临时凭证调用 A 账号 TCR 获取 docker 登录用户名/密码 |
④ | Secret 分发 | Controller 渲染 dockerconfigjson(同时包含公网、VPC、自定义域名),按 CR 规则分发到 B 账号集群目标 ns / SA |
⑤ | 跨 VPC 免密拉取 | 业务 Pod 通过 SA 的 imagePullSecrets 完成 docker pull,依赖 VPC 对等/云联网 + Private DNS 保障网络可达 |
说明:
跨账号关键特征:A 账号 CAM 需要手动登记 B 账号 TKE 集群的 OIDC Issuer 与 JWKS,并在 Role 信任策略中显式声明“允许 B 账号 OIDC 身份扮演此角色”;此外,业务 Pod 拉取镜像时需要跨 VPC 网络互通。
账号角色说明
本文操作步骤涉及两个腾讯云账号,为便于区分,统一使用 A / B 指代:
角色 | 说明 | 示例 UIN |
A 账号 | TCR 实例所在账号(镜像提供方),负责创建 CAM OIDC 身份提供商、CAM 策略、CAM 角色。 | 100001113387 |
B 账号 | TKE 集群所在账号(镜像消费方),负责开启 OIDC、安装 tcr-assistant-oidc 插件。 | 100002223233 |
跨账号免密拉取的核心机制是:B 账号 TKE 集群通过 OIDC 向 A 账号申请扮演 CAM 角色,STS 返回临时密钥后由插件写入 ImagePullSecret,实现跨账号镜像拉取。
操作步骤
步骤1:B 账号 TKE 集群开启 OIDC 功能
1. 登录 容器服务控制台,在左侧导航栏选择集群管理。
2. 选择目标集群,进入集群详情页,单击基本信息 > API Server 信息。
3. 单击 ServiceAccountIssuerDiscovery 右侧的编辑按钮。如下图所示:

4. 进入修改 ServiceAccountIssuerDiscovery 相关参数页面。若系统提示您无法修改相关参数,请先进行服务授权。如下图所示:

5. 在角色管理页面,查看授权策略 QcloudAccessForTKERoleInOIDCConfig,单击同意授权。如下图所示:

6. 授权完成后,勾选创建 CAM OIDC 提供商和创建 webhook 组件,并填写客户端 ID,客户端 ID 的值固定为
sts.cloud.tencent.com,单击确定。如下图所示:
7. 返回基本信息 > API Server 信息页,当 ServiceAccountIssuerDiscovery 可再次编辑时,表明本次开启 OIDC 资源访问控制结束。
说明:
service-account-issuer 和 service-account-jwks-uri 参数值不允许编辑,采用默认规则。步骤2:B 账号确认 OIDC 身份提供商及 Webhook 组件创建成功
开启 OIDC 功能后,需要确认 CAM OIDC 身份提供商已创建且
pod-identity-webhook 组件已成功部署:1. 在集群详情页,切换到 API Server 信息选项,单击 ServiceAccountIssuerDiscovery 右侧的编辑按钮。
2. 进入修改 ServiceAccountIssuerDiscovery 相关参数页面,系统将提示“您创建的身份提供商已存在,前往查看”。单击前往查看,确认 CAM OIDC 身份提供商已创建成功。
3. 查看刚创建的 CAM OIDC 身份提供商详细信息,确认客户端 ID 配置正确。如下图所示:

4. 在集群信息 > 组件管理中,确认列表中
pod-identity-webhook 组件状态为"成功",即表示组件安装成功。5. 在集群详情 > 工作负载 > Deployment 页面,确认 kube-system 命名空间下
pod-identity-webhook 的状态为 Running 即表示安装成功。步骤3:A 账号创建 OIDC 身份提供商
B 账号 TKE 集群开启 OIDC 功能会默认在 B 账号创建一个身份提供商。跨账号访问 A 的 TCR 资源时,需要手动在 A 账号中也创建一个身份提供商,通过角色载体方式明确允许 B 账号通过 OIDC 访问 TCR 资源。
1. 通过 B 账号登录 CAM 控制台-身份提供商,选择对应集群查看 B 账号的 OIDC 身份提供商信息。创建 A 账号身份提供商需要填写的下列信息均可以在详情中获取。如下图所示:

2. 通过 A 账号登录 CAM 控制台-身份提供商,新建提供商,需填写以下信息:
身份提供商名称:填写 B 账号 TKE 集群的 ClusterID(如
cls-xxxxxxxx)。身份提供商 URL:填写 B 账号 TKE 集群 APIServer 信息中的
service-account-issuer 值。客户端 ID:固定填写
sts.cloud.tencent.com。签名公钥:填写 B 账号 TKE 集群的身份提供商公钥。
3. 准备好上述信息后,单击新建完成身份提供商创建。如下图所示:

步骤4:A 账号创建 TCR 实例的 CAM 自定义策略
根据您的业务需求,创建自定义策略来控制 TCR 实例的访问权限。策略可以精确控制命名空间级别的访问权限。
注意:
资源六段式中不能增加 region 和 uin 信息。例如
qcs::tcr:::instance/tcr-5jm4cabc,仅需替换最后的 TCR 实例 ID 部分。策略示例:
{"version": "2.0","statement": [{"action": ["tcr:DescribeInstances"],"effect": "allow","resource": ["qcs::tcr:::instance/tcr-5jm4cabc"]},{"action": ["tcr:PullRepository","tcr:PushRepository","tcr:CreateRepository"],"effect": "allow","resource": ["qcs::tcr:::repository/tcr-5jm4cabc/production-backend/*"]}]}
策略说明:
tcr:DescribeInstances:login 时需要的权限。tcr:PullRepository:拉取镜像时需要的策略权限。tcr:PushRepository:按需授予,不影响镜像拉取。tcr:CreateRepository:按需授予,推送时如果 repo 不存在,则需要此权限。resource 中
production-backend/* 表示仅控制命名空间粒度。步骤5:A 账号创建角色并绑定自定义策略
1. 登录 CAM 控制台-角色。
2. 在角色页中,单击新建角色 > 身份提供商。
3. 在新建自定义角色页,参考以下信息进行设置。如下图所示:
说明:
oidc:aud 的 value 值需要和 CAM OIDC 提供商的客户端 ID value 值保持一致。oidc:aud 的 value 值标识为 $my_pod_audience,当 oidc:aud 的 value 值有多个时,任选其中之一即可。
4. 绑定步骤4中创建的自定义策略,完成角色创建。如下图所示:

步骤6:打通不同账号间的 VPC 网络
步骤7:B 账号在 TKE 集群中安装 tcr-assistant-oidc 插件
在 B 账号的 TKE 集群中安装 tcr-assistant-oidc 插件,插件会基于 OIDC 身份动态生成临时 docker login 凭证,用于访问 A 账号的 TCR 资源。
根据实际场景选择以下安装方式之一:
方式一:通过云 API 安装(最新版本 1.0.2)

字段说明:
A 账号角色 RoleArn 可在 CAM 控制台角色详情页获取。如下图所示:

TCR 实例名称 registries 可在 TCR 控制台实例列表获取。如下图所示:

如果 A 账号的 TCR 实例绑定了自定义域名(如
docker.mycompany.com),需要额外配置 customDomains。请注意:Values 参数已弃用。Values 参数已弃用。如需使用,请通过 RawValues 传入,参数值为 JSON 进行 base64 编码后的内容。示例如下:
echo -n '{"roleArn": "qcs::cam::uin/100000000000:roleName/your-role-name","registries": ["your-tcr-registry"]}' | base64
RawValues JSON 完整示例:
{"roleArn": "qcs::cam::uin/100001113387:roleName/tcr-assistant-odic-read-harbor","registries": ["your-tcr-registry"],"customDomains": ["docker.mycompany.com"],"tokenExpiration": 3600,"namespaces": "*","serviceAccounts": "*"}
方式二:通过 Terraform 安装
resource "tencentcloud_kubernetes_addon" "tcr_assistant_oidc" {cluster_id = "cls-xxxxxxxx" # 替换为您的 TKE 集群 IDaddon_name = "tcr-assistant-oidc"raw_values = jsonencode({# CAM 角色 ARN(必填)# 格式: qcs::cam::uin/{UIN}:roleName/{ROLE_NAME}roleArn = "qcs::cam::uin/100000000000:roleName/your-role-name"# TCR 镜像仓库名称列表(必填)registries = ["your-tcr-registry"]# 目标 namespace("*" 表示所有,或 "ns1,ns2,ns3")namespaces = "*"# 目标 ServiceAccount("*" 表示所有,或 "sa1,sa2")serviceAccounts = "*"# 自定义域名(选填)customDomains = ["docker.mycompany.com"]})}
验证 B 账号验证插件安装状态
安装完成后,在 B 账号的 TKE 集群中执行以下命令确认插件正常运行:
# 确认 Controller Pod 正常运行kubectl get pods -n tcr-assistant-system# 预期:Pod 为 Running 状态# 检查 CR 状态,确认已成功从 A 账号获取临时密钥kubectl get oips tcr-oidc-public -o yaml# 关注 status.credentialStatus 应为 Ready# 确认 imagePullSecret 已分发到目标命名空间kubectl get secret tcr.ips.tcr-oidc-public -n default \\-o jsonpath='{.data.\\.dockerconfigjson}' | base64 -d | jq '.auths | keys'
预期输出包含 A 账号 TCR 实例的所有域名:
["your-tcr-registry.tencentcloudcr.com","docker.mycompany.com"]
步骤8:B 账号添加 TCR 实例的 Private DNS 解析
在 B 账号侧为 A 账号的 TCR 实例添加私有域解析,确保集群内可通过内网域名访问 TCR 实例。如下图所示:

典型使用场景
场景一:多个账号(B、C)共享访问 A 账号 TCR 镜像
A 账号的 TCR 实例需要被多个不同账号的 TKE 集群访问:
1. A 账号创建角色时配置多个载体:
qcs::cam::uin/222:roleName/a-policy-for-tcr # B 账号qcs::cam::uin/333:roleName/a-policy-for-tcr # C 账号
2. 各账号集群分别安装插件,使用相同的
roleArn。场景二:通过自定义域名跨账号拉取镜像
A 账号的 TCR 实例绑定了自定义域名
docker.mycompany.com,B 账号需要通过该域名拉取 。在安装组件时,指定 customDomains=["docker.mycompany.com"]。安装后 B 账号集群即可通过自定义域名拉取 A 账号的镜像:kubectl run nginx --image=docker.mycompany.com/my-namespace/nginx:latest
场景三:限定生效范围(指定命名空间和 ServiceAccount)
只在 B 账号集群的特定命名空间和 ServiceAccount 中生效:
helm install tcr-assistant-oidc ./tcr-assistant-oidc \\--namespace tcr-assistant-system --create-namespace \\--set roleArn="qcs::cam::uin/100000000000:roleName/your-role-name" \\--set "registries={your-tcr-registry}" \\--set namespaces="production,staging" \\--set serviceAccounts="default,app-sa"
升级与变更
升级组件版本
使用 UpdateAddon 升级 tcr-assistant-oidc 组件版本。升级时的参数无需重复指定,只需要更新组件版本和预期要更新的参数配置。tcr-assistant-oidc 组件目标版本列表参考 组件版本维护说明。
说明:
升级过程中已有 Secret 不会中断,Controller 重启后自动恢复 reconcile。
验证升级结果
升级完成后,确认 Controller Pod 已更新并正常运行:
# 检查 Pod 状态和镜像版本kubectl get pods -n tcr-assistant-system -o wide# 检查 CR 状态kubectl get oips tcr-oidc-public -o jsonpath='{.status.credentialStatus}'# 预期输出:Ready
故障排查
Pod 拉取镜像失败(ImagePullBackOff)
排查步骤:
1. 检查 Secret 是否存在:
kubectl get secret tcr.ips.tcr-oidc-public -n <pod-namespace>
2. 检查 ServiceAccount 是否注入了 imagePullSecrets:
kubectl get sa default -n <pod-namespace> -o jsonpath='{.imagePullSecrets}'
3. 检查 Controller 日志:
kubectl logs -n tcr-assistant-system deployment/tcr-assistant-oidc-controller
临时密钥过期
动态生成的临时密钥有效期固定为 2 小时(7200 秒),
tokenExpiration 控制轮转间隔。如果设置过大(>5400),可能导致密钥过期。解决方案:确保
tokenExpiration 在 3600~5400 范围内。OIDC 环境变量未注入
如果 Controller Pod 缺少 OIDC 相关环境变量(
TKE_WEB_IDENTITY_TOKEN_FILE 等),说明 B 账号集群的 pod-identity-webhook 未正常工作。解决方案:
1. 确认 B 账号 TKE 集群已开启 OIDC。
2. 确认
pod-identity-webhook Pod 正常运行。3. 重建 Controller Pod。
AssumeRole 失败(权限不足)
如果 Controller 日志中出现
AssumeRoleWithWebIdentity 失败,说明 A 账号的角色载体配置或 B 账号的策略配置有误。排查步骤:
1. 确认 A 账号角色的信任策略中包含 B 账号 TKE 集群的 OIDC Provider。
2. 确认 A 账号角色已关联 TCR 访问权限。
3. 确认 B 账号策略中的
resource 字段与 A 账号角色 ARN 完全一致。手动验证临时密钥
从 Secret 中提取凭证手动登录验证:
# 获取凭证SECRET_JSON=$(kubectl get secret tcr.ips.tcr-oidc-public -n default \\-o jsonpath='{.data.\\.dockerconfigjson}' | base64 -d)# 提取用户名和密码,your-tcr-registry.tencentcloudcr.com 要替换成实际的访问域名USERNAME=$(echo $SECRET_JSON | jq -r '.auths["your-tcr-registry.tencentcloudcr.com"].username')PASSWORD=$(echo $SECRET_JSON | jq -r '.auths["your-tcr-registry.tencentcloudcr.com"].password')# 手动登录 A 账号的 TCR 实例,your-tcr-registry.tencentcloudcr.com 要替换成实际的访问域名。注意访问的客户端机器需要能够正常访问TCR域名。docker login your-tcr-registry.tencentcloudcr.com --username "$USERNAME" --password "$PASSWORD"