如何使用 Azure 容器注册表任务消耗并维护公共内容

本文提供了 Azure 容器注册表中的示例工作流,以帮助你管理公共内容的消耗和维护:

  1. 导入所依赖的公共映像的本地副本。
  2. 通过安全扫描和功能测试来验证公共映像。
  3. 将映像提升到专用注册表供内部使用。
  4. 为依赖于公共内容的应用程序触发基础映像更新。
  5. 使用 Azure 容器注册表任务自动执行此工作流。

下图汇总了此工作流:

消耗公共内容工作流

门控式导入工作流帮助你管理组织对外部托管项目(例如,源自公共注册表的映像,这些公共注册表包括 Docker HubQuayGitHub 容器注册表Microsoft 容器注册表甚至其他 Azure 容器注册表)的依赖性。

若要了解依赖于公共内容所带来的风险的背景信息以及如何使用 Azure 容器注册表来缓解这些风险,请参阅消耗公共内容的 OCI 博客文章使用 Azure 容器注册表管理公共内容

可使用 Azure 本地安装的 Azure CLI 来完成本演练。 建议使用 Azure CLI 2.10 或更高版本。 如果需要进行安装或升级,请参阅安装 Azure CLI

方案概述

导入工作流组件

本演练设置:

  1. 三个容器注册表,表示:
    • 模拟的 Docker Hub (publicregistry),支持更改基础映像
    • 用于共享专用映像的团队注册表 (contoso)
    • 导入的公共内容的公司/团队共享注册表 (baseartifacts)
  2. 每个注册表中的 ACR 任务。 这些任务:
    1. 构建模拟的公共 node 映像
    2. node 映像导入到公司/团队共享注册表并对其进行验证
    3. 构建并部署 hello-world 映像
  3. ACR 任务定义,其中包括以下项的配置:
    • 注册表凭据的集合,它们是指向密钥保管库的指针
    • acr-task.yaml 中提供的机密的集合,它们是指向密钥保管库的指针
    • acr-task.yaml 中使用的已配置值的集合
  4. 一个用来保护所有机密的 Azure 密钥保管库
  5. 一个承载 hello-world 生成应用程序的 Azure 容器实例

先决条件

以下步骤配置在演练中创建和使用的资源的值。

设置环境变量

配置你的环境特有的变量。 我们会遵循最佳做法,将具有持久性内容的资源置于其自己的资源组中,以最大程度地减少意外删除情况。 不过,你可以根据需要将这些变量放在单个资源组中。

本文中的示例针对 Bash shell 设置了格式。

# Set the three registry names, must be globally unique:
REGISTRY_PUBLIC=publicregistry
REGISTRY_BASE_ARTIFACTS=contosobaseartifacts
REGISTRY=contoso

# set the location all resources will be created in:
RESOURCE_GROUP_LOCATION=chinaeast2

# default resource groups
REGISTRY_PUBLIC_RG=${REGISTRY_PUBLIC}-rg
REGISTRY_BASE_ARTIFACTS_RG=${REGISTRY_BASE_ARTIFACTS}-rg
REGISTRY_RG=${REGISTRY}-rg

# fully qualified registry urls
REGISTRY_DOCKERHUB_URL=dockerhub.azk8s.cn
REGISTRY_PUBLIC_URL=${REGISTRY_PUBLIC}.azurecr.cn
REGISTRY_BASE_ARTIFACTS_URL=${REGISTRY_BASE_ARTIFACTS}.azurecr.cn
REGISTRY_URL=${REGISTRY}.azurecr.cn

# Azure key vault for storing secrets, name must be globally unique
AKV=acr-task-credentials
AKV_RG=${AKV}-rg

# ACI for hosting the deployed application
ACI=hello-world-aci
ACI_RG=${ACI}-rg

Git 存储库和令牌

若要模拟你的环境,请为以下每个 Git 存储库创建分支以获得你可以管理的存储库。

然后,为你的分支存储库更新下列变量:

追加到 git URL 末尾的 :main 表示默认存储库分支。

GIT_BASE_IMAGE_NODE=https://github.com/<your-fork>/base-image-node.git#main
GIT_NODE_IMPORT=https://github.com/<your-fork>/import-baseimage-node.git#main
GIT_HELLO_WORLD=https://github.com/<your-fork>/hello-world.git#main

你需要一个适用于 ACR 任务的 GitHub 访问令牌 (PAT),才能克隆和建立 Git Webhook。 如需通过相关步骤创建一个具有访问专用存储库所需权限的令牌,请参阅创建 GitHub 访问令牌

GIT_TOKEN=<set-git-token-here>

Docker Hub 凭据

若要避免在从 Docker Hub 拉取映像时出现限制和标识请求,请创建一个 Docker Hub 令牌。 然后设置以下环境变量:

REGISTRY_DOCKERHUB_USER=<yourusername>
REGISTRY_DOCKERHUB_PASSWORD=<yourtoken>

创建注册表

使用 Azure CLI 命令创建三个高级层容器注册表,每个表都位于其自己的资源组中:

az group create --name $REGISTRY_PUBLIC_RG --location $RESOURCE_GROUP_LOCATION
az acr create --resource-group $REGISTRY_PUBLIC_RG --name $REGISTRY_PUBLIC --sku Premium

az group create --name $REGISTRY_BASE_ARTIFACTS_RG --location $RESOURCE_GROUP_LOCATION
az acr create --resource-group $REGISTRY_BASE_ARTIFACTS_RG --name $REGISTRY_BASE_ARTIFACTS --sku Premium

az group create --name $REGISTRY_RG --location $RESOURCE_GROUP_LOCATION
az acr create --resource-group $REGISTRY_RG --name $REGISTRY --sku Premium

创建密钥保管库并设置机密

创建密钥保管库:

az group create --name $AKV_RG --location $RESOURCE_GROUP_LOCATION
az keyvault create --resource-group $AKV_RG --name $AKV

在密钥保管库中设置 Docker Hub 用户名和令牌:

az keyvault secret set \
--vault-name $AKV \
--name registry-dockerhub-user \
--value $REGISTRY_DOCKERHUB_USER

az keyvault secret set \
--vault-name $AKV \
--name registry-dockerhub-password \
--value $REGISTRY_DOCKERHUB_PASSWORD

在密钥保管库中设置并验证 Git PAT:

az keyvault secret set --vault-name $AKV --name github-token --value $GIT_TOKEN

az keyvault secret show --vault-name $AKV --name github-token --query value -o tsv

为 Azure 容器实例创建资源组

部署 hello-world 映像时,会在后续任务中使用此资源组。

az group create --name $ACI_RG --location $RESOURCE_GROUP_LOCATION

创建公共 node 基础映像

若要在 Docker Hub 上模拟 node 映像,请创建一个 ACR 任务来构建和维护公共映像。 此设置允许 node 映像维护人员模拟更改。

az acr task create \
  --name node-public \
  -r $REGISTRY_PUBLIC \
  -f acr-task.yaml \
  --context $GIT_BASE_IMAGE_NODE \
  --git-access-token $(az keyvault secret show \
                        --vault-name $AKV \
                        --name github-token \
                        --query value -o tsv) \
  --set REGISTRY_FROM_URL=${REGISTRY_DOCKERHUB_URL}/ \
  --assign-identity

若要避免 Docker 限制,请将 Docker Hub 凭据添加到任务中。 可以使用 acr task credentials 命令将 Docker 凭据传递给任何注册表,包括 Docker Hub。

az acr task credential add \
  -n node-public \
  -r $REGISTRY_PUBLIC \
  --login-server $REGISTRY_DOCKERHUB_URL \
  -u https://${AKV}.vault.azure.cn/secrets/registry-dockerhub-user \
  -p https://${AKV}.vault.azure.cn/secrets/registry-dockerhub-password \
  --use-identity [system]

授予任务从密钥保管库读取值所需的访问权限:

az keyvault set-policy \
  --name $AKV \
  --resource-group $AKV_RG \
  --object-id $(az acr task show \
                  --name node-public \
                  --registry $REGISTRY_PUBLIC \
                  --query identity.principalId --output tsv) \
  --secret-permissions get

可以通过 Git 提交、基础映像更新、计时器或手动运行来触发任务

手动运行任务以生成 node 映像:

az acr task run -r $REGISTRY_PUBLIC -n node-public

列出模拟的公共注册表中的映像:

az acr repository show-tags -n $REGISTRY_PUBLIC --repository node

创建 hello-world 映像

根据模拟的公共 node 映像生成 hello-world 映像。

创建用于对模拟的公共注册表进行拉取访问的令牌

创建模拟的公共注册表的访问令牌,其作用域为 pull。 然后,在密钥保管库中对其进行设置:

az keyvault secret set \
  --vault-name $AKV \
  --name "registry-${REGISTRY_PUBLIC}-user" \
  --value "registry-${REGISTRY_PUBLIC}-user"

az keyvault secret set \
  --vault-name $AKV \
  --name "registry-${REGISTRY_PUBLIC}-password" \
  --value $(az acr token create \
              --name "registry-${REGISTRY_PUBLIC}-user" \
              --registry $REGISTRY_PUBLIC \
              --scope-map _repositories_pull \
              -o tsv \
              --query credentials.passwords[0].value)

按 Azure 容器实例创建用于拉取访问的令牌

为承载 hello-world 映像的注册表创建访问令牌,其作用域为拉取。 然后,在密钥保管库中对其进行设置:

az keyvault secret set \
  --vault-name $AKV \
  --name "registry-${REGISTRY}-user" \
  --value "registry-${REGISTRY}-user"

az keyvault secret set \
  --vault-name $AKV \
  --name "registry-${REGISTRY}-password" \
  --value $(az acr token create \
              --name "registry-${REGISTRY}-user" \
              --registry $REGISTRY \
              --repository hello-world content/read \
              -o tsv \
              --query credentials.passwords[0].value)

创建用于生成和维护 hello-world 映像的任务

以下命令基于 hello-world 存储库的 acr-tasks.yaml 中的定义创建任务。 任务步骤生成 hello-world 映像,然后将其部署到 Azure 容器实例。 Azure 容器实例的资源组已在前面的一个部分创建。 通过在任务中调用 az container create(只是在 image:tag 中有差别),任务在整个演练中将部署到同一实例。

az acr task create \
  -n hello-world \
  -r $REGISTRY \
  -f acr-task.yaml \
  --context $GIT_HELLO_WORLD \
  --git-access-token $(az keyvault secret show \
                        --vault-name $AKV \
                        --name github-token \
                        --query value -o tsv) \
  --set REGISTRY_FROM_URL=${REGISTRY_PUBLIC_URL}/ \
  --set KEYVAULT=$AKV \
  --set ACI=$ACI \
  --set ACI_RG=$ACI_RG \
  --assign-identity

向任务添加用于模拟的公共注册表的凭据:

az acr task credential add \
  -n hello-world \
  -r $REGISTRY \
  --login-server $REGISTRY_PUBLIC_URL \
  -u https://${AKV}.vault.azure.cn/secrets/registry-${REGISTRY_PUBLIC}-user \
  -p https://${AKV}.vault.azure.cn/secrets/registry-${REGISTRY_PUBLIC}-password \
  --use-identity [system]

授予任务从密钥保管库读取值所需的访问权限:

az keyvault set-policy \
  --name $AKV \
  --resource-group $AKV_RG \
  --object-id $(az acr task show \
                  --name hello-world \
                  --registry $REGISTRY \
                  --query identity.principalId --output tsv) \
  --secret-permissions get

通过向任务授予对资源组的访问权限,授予任务创建和管理 Azure 容器实例所需的访问权限:

az role assignment create \
  --assignee $(az acr task show \
  --name hello-world \
  --registry $REGISTRY \
  --query identity.principalId --output tsv) \
  --scope $(az group show -n $ACI_RG --query id -o tsv) \
  --role owner

创建并配置任务后,运行任务以生成并部署 hello-world 映像:

az acr task run -r $REGISTRY -n hello-world

创建后,获取承载 hello-world 映像的容器的 IP 地址。

az container show \
  --resource-group $ACI_RG \
  --name ${ACI} \
  --query ipAddress.ip \
  --out tsv

在浏览器中,转到该 IP 地址以查看正在运行的应用程序。

使用“可疑”更改更新基础映像

本部分模拟一项可能会导致环境中出现问题的基础映像更改。

  1. 打开分支的 base-image-node 存储库中的 Dockerfile
  2. BACKGROUND_COLOR 更改为 Orange 以模拟更改。
ARG REGISTRY_NAME=
FROM ${REGISTRY_NAME}node:15-alpine
ENV NODE_VERSION 15-alpine
ENV BACKGROUND_COLOR Orange

提交更改并监视那些自动开始生成的 ACR 任务。

监视要开始执行的任务:

watch -n1 az acr task list-runs -r $REGISTRY_PUBLIC -o table

最终,你应当会看到基于触发器 Commit 的状态“Succeeded”:

RUN ID    TASK      PLATFORM    STATUS     TRIGGER    STARTED               DURATION
-------- --------  ---------- ---------  --------- --------------------  ----------
ca4       hub-node  linux       Succeeded  Commit     2020-10-24T05:02:29Z  00:00:22

键入 Ctrl + C 退出监视命令,然后查看最近的运行的日志:

az acr task logs -r $REGISTRY_PUBLIC

node 映像完成后,ACR 任务的 watch 会动开始构建 hello-world 映像:

watch -n1 az acr task list-runs -r $REGISTRY -o table

最终,你应当会看到基于触发器 Image Update 的状态“Succeeded”:

RUN ID    TASK         PLATFORM    STATUS     TRIGGER       STARTED               DURATION
-------- -----------  ---------- ---------  ------------ --------------------  ----------
dau       hello-world  linux       Succeeded  Image Update  2020-10-24T05:08:45Z  00:00:31

键入 Ctrl + C 退出监视命令,然后查看最近的运行的日志:

az acr task logs -r $REGISTRY

完成后,获取承载更新后的 hello-world 映像的站点的 IP 地址:

az container show \
  --resource-group $ACI_RG \
  --name ${ACI} \
  --query ipAddress.ip \
  --out tsv

在浏览器中转到该站点,该站点应该具有橙色(可疑)背景。

签入

此时,你已创建了一个 hello-world 映像,该映像是基于 Git 提交和对基础 node 映像的更改自动构建的。 在此示例中,任务基于 Azure 容器注册表中的基础映像进行构建,但任何受支持的注册表都可以使用。

更新 node 映像时,基础映像更新会自动触发任务运行。 在这里可以看到,并非所有更新都是需要的。

公共内容的门控式导入

若要防止上游更改损坏关键工作负荷,可以添加安全扫描和功能测试。

在本部分,我们创建一个 ACR 任务来执行以下操作:

  • 构建测试映像
  • 针对测试映像运行功能测试脚本 ./test.sh
  • 如果映像测试成功,请将公共映像导入到 baseimages 注册表

添加自动化测试

为了对任何上游内容进行控制,将实施自动化测试。 在此示例中,提供了一个用于检查 $BACKGROUND_COLORtest.sh。 如果测试失败,将返回 EXIT_CODE1,这会导致 ACR 任务步骤失败,从而结束任务运行。 这些测试可以在任何形式的工具中进行扩展,其中包括日志记录结果。 此入口是通过脚本中的“通过/失败”响应进行管理的,此处重现了该响应:

if [ ""$(echo $BACKGROUND_COLOR | tr '[:lower:]' '[:upper:]') = 'RED' ]; then
    echo -e "\e[31mERROR: Invalid Color:\e[0m" ${BACKGROUND_COLOR}
    EXIT_CODE=1
else
  echo -e "\e[32mValidation Complete - No Known Errors\e[0m"
fi
exit ${EXIT_CODE}

任务 YAML

查看 import-baseimage-node 存储库中的 acr-task.yaml,它执行以下步骤:

  1. 使用以下 Dockerfile 构建测试基础映像:
    ARG REGISTRY_FROM_URL=
    FROM ${REGISTRY_FROM_URL}node:15-alpine
    WORKDIR /test
    COPY ./test.sh .
    CMD ./test.sh
    
  2. 完成后,通过运行容器来验证映像,这将运行 ./test.sh
  3. 只有在成功完成后才运行导入步骤,这些步骤通过 when: ['validate-base-image'] 进行控制
version: v1.1.0
steps:
  - id: build-test-base-image
    # Build off the base image we'll track
    # Add a test script to do unit test validations
    # Note: the test validation image isn't saved to the registry
    # but the task logs captures log validation results
    build: >
      --build-arg REGISTRY_FROM_URL={{.Values.REGISTRY_FROM_URL}}
      -f ./Dockerfile
      -t {{.Run.Registry}}/node-import:test
      .
  - id: validate-base-image
    # only continues if node-import:test returns a non-zero code
    when: ['build-test-base-image']
    cmd: "{{.Run.Registry}}/node-import:test"
  - id: pull-base-image
    # import the public image to base-artifacts
    # Override the stable tag,
    # and create a unique tag to enable rollback
    # to a previously working image
    when: ['validate-base-image']
    cmd: >
        docker pull {{.Values.REGISTRY_FROM_URL}}node:15-alpine
  - id: retag-base-image
    when: ['pull-base-image']
    cmd: docker tag {{.Values.REGISTRY_FROM_URL}}node:15-alpine {{.Run.Registry}}/node:15-alpine
  - id: retag-base-image-unique-tag
    when: ['pull-base-image']
    cmd: docker tag {{.Values.REGISTRY_FROM_URL}}node:15-alpine {{.Run.Registry}}/node:15-alpine-{{.Run.ID}}
  - id: push-base-image
    when: ['retag-base-image', 'retag-base-image-unique-tag']
    push:
    - "{{.Run.Registry}}/node:15-alpine"
    - "{{.Run.Registry}}/node:15-alpine-{{.Run.ID}}"

创建用于导入和测试基础映像的任务

  az acr task create \
  --name base-import-node \
  -f acr-task.yaml \
  -r $REGISTRY_BASE_ARTIFACTS \
  --context $GIT_NODE_IMPORT \
  --git-access-token $(az keyvault secret show \
                        --vault-name $AKV \
                        --name github-token \
                        --query value -o tsv) \
  --set REGISTRY_FROM_URL=${REGISTRY_PUBLIC_URL}/ \
  --assign-identity

向任务添加用于模拟的公共注册表的凭据:

az acr task credential add \
  -n base-import-node \
  -r $REGISTRY_BASE_ARTIFACTS \
  --login-server $REGISTRY_PUBLIC_URL \
  -u https://${AKV}.vault.azure.cn/secrets/registry-${REGISTRY_PUBLIC}-user \
  -p https://${AKV}.vault.azure.cn/secrets/registry-${REGISTRY_PUBLIC}-password \
  --use-identity [system]

授予任务从密钥保管库读取值所需的访问权限:

az keyvault set-policy \
  --name $AKV \
  --resource-group $AKV_RG \
  --object-id $(az acr task show \
                  --name base-import-node \
                  --registry $REGISTRY_BASE_ARTIFACTS \
                  --query identity.principalId --output tsv) \
  --secret-permissions get

运行导入任务:

az acr task run -n base-import-node -r $REGISTRY_BASE_ARTIFACTS

注意

如果任务由于 ./test.sh: Permission denied 而失败,请确保该脚本具有执行权限,然后重新提交到 Git 存储库:

chmod +x ./test.sh

更新 hello-world 映像以基于门控式 node 映像进行构建

创建访问令牌,以从 node 存储库访问作用域为 read 的基础项目注册表。 然后,在密钥保管库中进行设置:

az keyvault secret set \
  --vault-name $AKV \
  --name "registry-${REGISTRY_BASE_ARTIFACTS}-user" \
  --value "registry-${REGISTRY_BASE_ARTIFACTS}-user"

az keyvault secret set \
  --vault-name $AKV \
  --name "registry-${REGISTRY_BASE_ARTIFACTS}-password" \
  --value $(az acr token create \
              --name "registry-${REGISTRY_BASE_ARTIFACTS}-user" \
              --registry $REGISTRY_BASE_ARTIFACTS \
              --repository node content/read \
              -o tsv \
              --query credentials.passwords[0].value)

hello-world 任务添加用于基础项目注册表的凭据:

az acr task credential add \
  -n hello-world \
  -r $REGISTRY \
  --login-server $REGISTRY_BASE_ARTIFACTS_URL \
  -u https://${AKV}.vault.azure.cn/secrets/registry-${REGISTRY_BASE_ARTIFACTS}-user \
  -p https://${AKV}.vault.azure.cn/secrets/registry-${REGISTRY_BASE_ARTIFACTS}-password \
  --use-identity [system]

通过更新任务来更改 REGISTRY_FROM_URL,以使用 BASE_ARTIFACTS 注册表

az acr task update \
  -n hello-world \
  -r $REGISTRY \
  --set KEYVAULT=$AKV \
  --set REGISTRY_FROM_URL=${REGISTRY_BASE_ARTIFACTS_URL}/ \
  --set ACI=$ACI \
  --set ACI_RG=$ACI_RG

运行 hello world 任务来更改其基础映像依赖项:

az acr task run -r $REGISTRY -n hello-world

使用“有效”更改更新基础映像

  1. 打开 base-image-node 存储库中的 Dockerfile
  2. BACKGROUND_COLOR 更改为 Green 以模拟有效的更改。
ARG REGISTRY_NAME=
FROM ${REGISTRY_NAME}node:15-alpine
ENV NODE_VERSION 15-alpine
ENV BACKGROUND_COLOR Green

提交更改并监视更新序列:

watch -n1 az acr task list-runs -r $REGISTRY_PUBLIC -o table

运行后,键入 Ctrl + C 并监视日志:

az acr task logs -r $REGISTRY_PUBLIC

完成后,监视 base-image-import 任务:

watch -n1 az acr task list-runs -r $REGISTRY_BASE_ARTIFACTS -o table

运行后,键入 Ctrl + C 并监视日志:

az acr task logs -r $REGISTRY_BASE_ARTIFACTS

完成后,监视 hello-world 任务:

watch -n1 az acr task list-runs -r $REGISTRY -o table

运行后,键入 Ctrl + C 并监视日志:

az acr task logs -r $REGISTRY

完成后,获取承载更新后的 hello-world 映像的站点的 IP 地址:

az container show \
  --resource-group $ACI_RG \
  --name ${ACI} \
  --query ipAddress.ip \
  --out tsv

在浏览器中转到该站点,该站点应当具有绿色(有效)背景。

查看门控式工作流

再次执行前一部分中的步骤,其背景色为红色。

  1. 打开 base-image-node 存储库中的 Dockerfile
  2. BACKGROUND_COLOR 更改为 Red 以模拟无效的更改。
ARG REGISTRY_NAME=
FROM ${REGISTRY_NAME}node:15-alpine
ENV NODE_VERSION 15-alpine
ENV BACKGROUND_COLOR Red

提交更改并监视更新序列:

watch -n1 az acr task list-runs -r $REGISTRY_PUBLIC -o table

运行后,键入 Ctrl + C 并监视日志:

az acr task logs -r $REGISTRY_PUBLIC

完成后,监视 base-image-import 任务:

watch -n1 az acr task list-runs -r $REGISTRY_BASE_ARTIFACTS -o table

运行后,键入 Ctrl + C 并监视日志:

az acr task logs -r $REGISTRY_BASE_ARTIFACTS

此时,应会看到 base-import-node 任务未通过验证并停止发布 hello-world 更新所需的序列。 输出类似于:

[...]
2020/10/30 03:57:39 Launching container with name: validate-base-image
Validating Image
NODE_VERSION: 15-alpine
BACKGROUND_COLOR: Red
ERROR: Invalid Color: Red
2020/10/30 03:57:40 Container failed during run: validate-base-image. No retries remaining.
failed to run step ID: validate-base-image: exit status 1

发布对 hello-world 的更新

hello-world 映像的更改会继续使用上次验证的 node 映像。

通过了门控式验证的对基础 node 映像的任何其他更改都会触发对 hello-world 映像的基础映像更新。

清理

如果不再需要本文中使用的资源,请将其删除:

az group delete -n $REGISTRY_RG --no-wait -y
az group delete -n $REGISTRY_PUBLIC_RG --no-wait -y
az group delete -n $REGISTRY_BASE_ARTIFACTS_RG --no-wait -y
az group delete -n $AKV_RG --no-wait -y
az group delete -n $ACI_RG --no-wait -y

后续步骤

在本文中,你使用了 ACR 任务来创建自动化门控工作流,以将更新的基础映像引入你的环境。 请参阅相关信息,以管理 Azure 容器注册表中的映像。