使用 Azure 托管标识在 ACR 任务中进行跨注册表的身份验证

ACR 任务中,可以启用 Azure 资源的托管标识。 该任务可以使用该标识来访问其他 Azure 资源,而无需提供或管理凭据。

本文介绍如何在任务中启用托管标识,从与用于运行任务的注册表不同的其他注册表中提取映像。

为了创建 Azure 资源,本文要求运行 Azure CLI 版本 2.0.68 或更高版本。 运行 az --version 即可查找版本。 如果需要进行安装或升级,请参阅安装 Azure CLI

方案概述

此示例任务从其他 Azure 容器注册表中拉取基础映像,用于生成和推送应用程序映像。 若要拉取基础映像,应为任务配置托管标识,并向其分配适当的权限。

此示例演示了使用用户分配的或系统分配的托管标识的步骤。 选择哪种标识取决于组织的需求。

在实际情况中,组织可能会维护一组供各开发团队用来生成应用程序的基础映像。 这些基础映像存储在企业注册表中,每个开发团队仅拥有相应的拉取权限。

先决条件

在本文中,需要两个 Azure 容器注册表:

  • 使用第一个注册表来创建和执行 ACR 任务。 在本文中,此注册表名为 myregistry
  • 第二个注册表承载任务生成映像时要使用的基础映像。 在本文中,第二个注册表名为 mybaseregistry。

请在后续步骤中将其替换为你自己的注册表名称。

如果还没有所需的 Azure 容器注册表,请参阅快速入门:使用 Azure CLI 创建专用容器注册表。 暂时不需要将映像推送到注册表。

准备基础注册表

出于演示目的,以一次性操作的方式,运行 [az acr import][az-acr-import],将公共 Node.js 映像从 Docker Hub 导入到基础注册表。 在实践中,组织中的另一个团队或流程可能会维护基础注册表中的映像。

az acr import --name mybaseregistry \
  --source dockerhub.azk8s.cn/library/node:15-alpine \
  --image baseimages/node:15-alpine 

在 YAML 文件中定义任务步骤

此示例多步骤任务的步骤在 YAML 文件中进行定义。 在本地工作目录中创建名为 helloworldtask.yaml 的文件并粘贴以下内容。 将生成步骤中 REGISTRY_NAME 的值更新为基础注册表的服务器名称。

version: v1.1.0
steps:
# Replace mybaseregistry with the name of your registry containing the base image
  - build: -t $Registry/hello-world:$ID  https://github.com/Azure-Samples/acr-build-helloworld-node.git#main -f Dockerfile-app --build-arg REGISTRY_NAME=mybaseregistry.azurecr.cn
  - push: ["$Registry/hello-world:$ID"]

生成步骤使用 Azure-Samples/acr-build-helloworld-node 存储库中的 Dockerfile-app 文件来构建映像。 --build-arg 引用基础注册表来拉取基础映像。 成功生成映像后,会将映像推送到用于运行任务的注册表。

选项 1:创建使用用户分配的标识的任务

本部分中的步骤将创建一个任务并启用用户分配的标识。 若要改为启用系统分配的标识,请参阅选项 2:创建使用系统分配的标识的任务

创建用户分配的标识

使用 az identity create 命令在订阅中创建一个名为 myACRTasksId 的标识。 可以使用之前用于创建容器注册表的同一资源组,也可以使用其他资源组。

az identity create \
  --resource-group myResourceGroup \
  --name myACRTasksId

为了在以下步骤中配置用户分配的标识,请使用 az identity show 命令将标识的资源 ID、主体 ID 和客户端 ID 存储在变量中。

# Get resource ID of the user-assigned identity
resourceID=$(az identity show \
  --resource-group myResourceGroup \
  --name myACRTasksId \
  --query id --output tsv)

# Get principal ID of the task's user-assigned identity
principalID=$(az identity show \
  --resource-group myResourceGroup \
  --name myACRTasksId \
  --query principalId --output tsv)

# Get client ID of the user-assigned identity
clientID=$(az identity show \
  --resource-group myResourceGroup \
  --name myACRTasksId \
  --query clientId --output tsv)

创建任务

通过执行以下 az acr task create 命令,创建任务 helloworldtask。 该任务无需源代码上下文即可运行,该命令将引用工作目录中的 helloworldtask.yaml 文件。 --assign-identity 参数传递用户分配的标识的资源 ID。

az acr task create \
  --registry myregistry \
  --name helloworldtask \
  --context /dev/null \
  --file helloworldtask.yaml \
  --assign-identity $resourceID

在命令输出中,identity 部分显示在任务中设置了 UserAssigned 类型的标识:

[...]
"identity": {
    "principalId": null,
    "tenantId": null,
    "type": "UserAssigned",
    "userAssignedIdentities": {
      "/subscriptions/xxxxxxxx-d12e-4760-9ab6-xxxxxxxxxxxx/resourcegroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myACRTasksId": {
        "clientId": "xxxxxxxx-f17e-4768-bb4e-xxxxxxxxxxxx",
        "principalId": "xxxxxxxx-1335-433d-bb6c-xxxxxxxxxxxx"
      }
[...]

为标识授予对基础注册表的提取权限

在本部分中,向托管标识授予从基础注册表 mybaseregistry 中进行拉取的权限。

使用 az acr show 命令获取基础注册表的资源 ID,并将其存储在变量中:

baseregID=$(az acr show --name mybaseregistry --query id --output tsv)

使用 az role assignment create 命令向标识分配用于访问基础注册表的 acrpull 角色。 此角色仅具有从注册表拉取映像的权限。

az role assignment create \
  --assignee $principalID \
  --scope $baseregID \
  --role acrpull

执行下一步,将目标注册表凭据添加到任务

选项 2:创建具有系统分配的标识的任务

本部分中的步骤创建一个任务并启用系统分配的标识。 如果要改为启用用户分配的标识,请参阅选项 1:创建具有用户分配的标识的任务

创建任务

通过执行以下 az acr task create 命令,创建任务 helloworldtask。 该任务无需源代码上下文即可运行,该命令将引用工作目录中的 helloworldtask.yaml 文件。 不带任何值的 --assign-identity 参数将在任务中启用系统分配的标识。

az acr task create \
  --registry myregistry \
  --name helloworldtask \
  --context /dev/null \
  --file helloworldtask.yaml \
  --assign-identity 

在命令输出中,identity 部分显示在任务中设置了 SystemAssigned 类型的标识。 principalId 是任务标识的主体 ID:

[...]
  "identity": {
    "principalId": "xxxxxxxx-2703-42f9-97d0-xxxxxxxxxxxx",
    "tenantId": "xxxxxxxx-86f1-41af-91ab-xxxxxxxxxxxx",
    "type": "SystemAssigned",
    "userAssignedIdentities": null
  },
  "location": "chinanorth",
[...]

使用 az acr task show 命令将 principalId 存储在一个变量中,以便在以后的命令中使用。 在以下命令中替换任务和注册表的名称:

principalID=$(az acr task show \
  --name <task_name> --registry <registry_name> \
  --query identity.principalId --output tsv)

为标识授予对基础注册表的提取权限

在本部分中,向托管标识授予从基础注册表 mybaseregistry 中进行拉取的权限。

使用 az acr show 命令获取基础注册表的资源 ID,并将其存储在变量中:

baseregID=$(az acr show --name mybaseregistry --query id --output tsv)

使用 az role assignment create 命令向标识分配用于访问基础注册表的 acrpull 角色。 此角色仅有权从该注册表提取映像。

az role assignment create \
  --assignee $principalID \
  --scope $baseregID \
  --role acrpull

向任务添加目标注册表凭据

现在,使用 az acr task credential add 命令,使任务能够使用标识的凭据进行基本注册表的身份验证。 运行与任务中启用的托管标识类型对应的命令。 如果启用了用户分配的标识,传递带有标识的客户端 ID 的 --use-identity。 如果启用了系统分配的标识,则传递 --use-identity [system]

# Add credentials for user-assigned identity to the task
az acr task credential add \
  --name helloworldtask \
  --registry myregistry \
  --login-server mybaseregistry.azurecr.cn \
  --use-identity $clientID

# Add credentials for system-assigned identity to the task
az acr task credential add \
  --name helloworldtask \
  --registry myregistry \
  --login-server mybaseregistry.azurecr.cn \
  --use-identity [system]

手动运行任务

若要验证启用了托管标识的任务是否成功运行,请使用 az acr task run 命令手动触发该任务。

az acr task run \
  --name helloworldtask \
  --registry myregistry

如果任务成功运行,输出会类似于:

Queued a run with ID: cf10
Waiting for an agent...
2019/06/14 22:47:32 Using acb_vol_dbfbe232-fd76-4ca3-bd4a-687e84cb4ce2 as the home volume
2019/06/14 22:47:39 Creating Docker network: acb_default_network, driver: 'bridge'
2019/06/14 22:47:40 Successfully set up Docker network: acb_default_network
2019/06/14 22:47:40 Setting up Docker configuration...
2019/06/14 22:47:41 Successfully set up Docker configuration
2019/06/14 22:47:41 Logging in to registry: myregistry.azurecr.cn
2019/06/14 22:47:42 Successfully logged into myregistry.azurecr.cn
2019/06/14 22:47:42 Logging in to registry: mybaseregistry.azurecr.cn
2019/06/14 22:47:43 Successfully logged into mybaseregistry.azurecr.cn
2019/06/14 22:47:43 Executing step ID: acb_step_0. Timeout(sec): 600, Working directory: '', Network: 'acb_default_network'
2019/06/14 22:47:43 Scanning for dependencies...
2019/06/14 22:47:45 Successfully scanned dependencies
2019/06/14 22:47:45 Launching container with name: acb_step_0
Sending build context to Docker daemon   25.6kB
Step 1/6 : ARG REGISTRY_NAME
Step 2/6 : FROM ${REGISTRY_NAME}/baseimages/node:15-alpine
15-alpine: Pulling from baseimages/node
[...]
Successfully built 41b49a112663
Successfully tagged myregistry.azurecr.cn/hello-world:cf10
2019/06/14 22:47:56 Successfully executed container: acb_step_0
2019/06/14 22:47:56 Executing step ID: acb_step_1. Timeout(sec): 600, Working directory: '', Network: 'acb_default_network'
2019/06/14 22:47:56 Pushing image: myregistry.azurecr.cn/hello-world:cf10, attempt 1
The push refers to repository [myregistry.azurecr.cn/hello-world]
[...]
2019/06/14 22:48:00 Step ID: acb_step_1 marked as successful (elapsed time in seconds: 2.517011)
2019/06/14 22:48:00 The following dependencies were found:
2019/06/14 22:48:00
- image:
    registry: myregistry.azurecr.cn
    repository: hello-world
    tag: cf10
    digest: sha256:611cf6e3ae3cb99b23fadcd89fa144e18aa1b1c9171ad4a0da4b62b31b4e38d1
  runtime-dependency:
    registry: mybaseregistry.azurecr.cn
    repository: baseimages/node
    tag: 15-alpine
    digest: sha256:e8e92cffd464fce3be9a3eefd1b65dc9cbe2484da31c11e813a4effc6105c00f
  git:
    git-head-revision: 0f988779c97fe0bfc7f2f74b88531617f4421643

Run ID: cf10 was successful after 32s

运行 az acr repository show-tags 命令,验证映像已构建并成功推送到 myregistry:

az acr repository show-tags --name myregistry --repository hello-world --output tsv

示例输出:

cf10

后续步骤