title | description | ms.service | ms.topic | ms.date | author |
---|---|---|---|---|---|
How to manage public content in private registry |
.... |
container-registry |
article |
10/27/2020 |
stevelas |
An Azure container registry hosts your container images and other OCI artifacts in a private, authenticated environment. However, your environment may have dependencies on public content such as public container images, helm charts, Open Policy Agent (OPA) policies or other artifacts. For example, you might run [nginx] for service routing or docker build FROM
alpine by pulling images directly from Docker Hub or another public registry. As upstream changes occur, this article will explain how to import and maintain these public artifacts.
For more information about the risks introduced by dependencies on public content and best practices see the OCI Consuming Public Content Blog post.
This article covers features and workflows in Azure Container Registry to help you manage consuming and maintaining public content:
- Import local copies of dependent public images.
- Validate public images through security scanning and functional testing.
- Promoting to private registries for internal usage.
- Triggering base image updates for applications dependent upon public content.
- Using ACR Tasks to automate this workflow.
This article refers mainly to container images, but the concepts apply to other supported registry artifacts.
The gated import workflow refers to decoupling your organizations dependencies on externally managed artifacts. For instance, images sourced from public registries like: docker hub, gcr, quay, github container registry, Microsoft Container Registry or even other public Azure Container Registries.
Consider balancing these two, possibly conflicting goals:
- Do you really want an unexpected upstream change to possibly take out your production system?
- Do you want upstream security fixes, for the versions you depend upon, to be automatically deployed?
- Create three registries to represent the workflow
- A simulated copy of docker hub for public images.
- This allows us simulate a base image update, which would normally be initiated on Docker Hub or other public registries.
- A development team registry, that will host one more more teams that build and manage images.
- Note: repository based RBAC (preview) is now available, enabling multiple teams to share a single registry, with unique permission sets
- A registry to host imported base artifacts.
- A simulated copy of docker hub for public images.
- An Azure KeyVault for storing access keys to the registries
- An Azure Container Instance to host the
hello-world
image.
The following steps will:
- Configure unique values for your environment
- Simulate a Public Registry
- Automate building a hello-world image
- Automate deploying to an Azure Container Instance
- Simulate upstream changes directly to your environment
- Create a gated import, that validates upstream changes are appropriate for your environment
This walk through will:
- Configure three registries representing:
- Simulated Docker Hub (
publicregistry
)to support changing the base image - Team registry (
contoso
) for private images - Company/team shared registry (
baseartifacts
) for imported public content
- Simulated Docker Hub (
- Configure ACR Tasks to:
- build the simulated public node image
- import and validate the public node image to the company/team shared registry
- build and deploy the hello-world image
- ACR Task definitions, including configurations for:
- Collection of registry credentials which can be pointers to KeyVault
- Collection of secrets, available within an
acr-task.yaml
, which are pointers to KeyVault - Collection of configured values used within an
acr-task.yaml
. - An Azure KeyVault, securing all secrets
- An Azure Container Instance, hosting the hello-world build application
Configure variables unique to your environment. We follow best practices for placing resources with durable content in their own resource group to minimize accidental deletion, however you can place these in a single resource group if desired.
# Set the three registry names, unique to your environment:
REGISTRY_PUBLIC=publicregistry
REGISTRY_BASE_ARTIFACTS=contosobaseartifacts
REGISTRY=contoso
# set the location all resources will be created in:
RESOURCE_GROUP_LOCATION=eastus
# 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=docker.io
REGISTRY_PUBLIC_URL=${REGISTRY_PUBLIC}.azurecr.io
REGISTRY_BASE_ARTIFACTS_URL=${REGISTRY_BASE_ARTIFACTS}.azurecr.io
REGISTRY_URL=${REGISTRY}.azurecr.io
# Azure KeyVault for storing secrets
AKV=acr-task-credentials
AKV_RG=${AKV}-rg
# ACI for hosting the deployed application
ACI=hello-world-aci
ACI_RG=${ACI}-rg
To simulate your environment, fork each of these into repositories you can mange. Then, update the variables for your forked repositories.
Notice :main
concatenated to the end of the git URLs representing the default repository branch.
GIT_BASE_IMAGE_NODE=https://github.com/importing-public-content/base-image-node.git#main
GIT_NODE_IMPORT=https://github.com/importing-public-content/import-baseimage-node.git#main
GIT_HELLO_WORLD=https://github.com/importing-public-content/hello-world.git#main
Establish a Git Token for ACR Tasks to clone and establish git webhooks. See: @DAN, CAN YOU UPDATE TO A REFERENCE FOR REQUIRED PERMISSIONS?
GIT_TOKEN=<set-git-token-here>
Docker Hub Credentials
To avoid throttling and identify requests, create a Docker Hub token
REGISTRY_DOCKERHUB_USER=<yourusername>
REGISTRY_DOCKERHUB_PASSWORD=<yourtoken>
Create the three registries:
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
Create a KeyVault for secrets
az group create --name $AKV_RG --location $RESOURCE_GROUP_LOCATION
az keyvault create --resource-group $AKV_RG --name $AKV
Create a Docker Hub token
To avoid throttling and identify requests, create a Docker Hub token
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
Set and Verify a Git token within KeyVault
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
Create a Resource Group for an Azure Container Instance
az group create --name $ACI_RG --location $RESOURCE_GROUP_LOCATION
To simulate the node image on Docker Hub, create an ACR Task to build and maintain the public image. This allows simulating changes by the node image maintainers.
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
To avoid Docker throttling, add Docker Hub credentials:
az acr task credential add \
-n node-public \
-r $REGISTRY_PUBLIC \
--login-server $REGISTRY_DOCKERHUB_URL \
-u https://${AKV}.vault.azure.net/secrets/registry-dockerhub-user \
-p https://${AKV}.vault.azure.net/secrets/registry-dockerhub-password \
--use-identity [system]
Grant access to ACR for reading values from KeyVault
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
Tasks can be triggered by git commits, base image updates, scheduled runs or manually executed.
Run the task to generate the node
image
az acr task run -r $REGISTRY_PUBLIC -n node-public
List the image in the simulated public registry
az acr repository show-tags -n $REGISTRY_PUBLIC --repository node
Based on the simulated public node image, build a hello-world image.
Using ACR Tokens, create access tokens, scoped to 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)
A token to the registry with hello-world
is created. Permissions are scoped to read (pull)
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)
Simulating a public registry, which could be docker hub, provide credentials using acr task credentials. Since the registry is an ACR, use the token created above. The acr task credentials may be used to pass docker credentials to any registry, including Docker Hub.
Within the acr-task.yaml
, we deploy the newly built image to ACI. The resource group was created above. By calling az container create
with only a difference in the image:tag
, the same instance is used.
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
Add credentials for our Public Registry
az acr task credential add \
-n hello-world \
-r $REGISTRY \
--login-server $REGISTRY_PUBLIC_URL \
-u https://${AKV}.vault.azure.net/secrets/registry-${REGISTRY_PUBLIC}-user \
-p https://${AKV}.vault.azure.net/secrets/registry-${REGISTRY_PUBLIC}-password \
--use-identity [system]
Grant access to read values from the KeyVault
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
Grant the task access to create and manage ACI by granting access to the resource group:
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
With the task created, run the task to build/deploy the hello-world image:
az acr task run -r $REGISTRY -n hello-world
Once created, browse the site hosting the hell-world
image.
explorer.exe "http://"$(az container show \
--resource-group $ACI_RG \
--name ${ACI} \
--query ipAddress.ip \
--out tsv)
Open the Dockerfile
in base-image-node repo
Change the BACKGROUND_COLOR
to Red
to simulate a change that would break our environment.
ARG REGISTRY_NAME=
FROM ${REGISTRY_NAME}node:15-alpine
ENV NODE_VERSION 15-alpine
ENV BACKGROUND_COLOR Red
Commit the change and watch for ACR Tasks to automatically start building.
Watch for the task to start executing:
watch -n1 az acr task list-runs -r $REGISTRY_PUBLIC
You should eventually see STATUS Succeeded
based on a TRIGGER of Commit
:
RUN ID TASK PLATFORM STATUS TRIGGER STARTED DURATION
-------- -------- ---------- --------- --------- -------------------- ----------
ca4 hub-node linux Succeeded Commit 2020-10-24T05:02:29Z 00:00:22
Type CTRL-C
to exit the watch command, then view the logs for the most recent run:
az acr task logs -r $REGISTRY_PUBLIC
Once the node image is completed, watch
for ACR Tasks to automatically start the hello-world image:
watch -n1 az acr task list-runs -r $REGISTRY
You should eventually see STATUS Succeeded
based on a TRIGGER of Image Update
RUN ID TASK PLATFORM STATUS TRIGGER STARTED DURATION
-------- ----------- ---------- --------- ------------ -------------------- ----------
dau hello-world linux Succeeded Image Update 2020-10-24T05:08:45Z 00:00:31
Type CTRL-C
to exit the watch command, then view the logs for the most recent run:
az acr task logs -r $REGISTRY
Once completed, browse the site hosting the updated hell-world
image, which should have a red (broken) background.
explorer.exe "http://"$(az container show \
--resource-group $ACI_RG \
--name ${ACI} \
--query ipAddress.ip \
--out tsv)
At this point, you've created a hello-world
image that is automatically built on git commits, and changes to the base node
image. While we've built against a base image in ACR, this could be any supported registry.
The ACR Task base image update trigger automatically re-executes as the node image is updated. As seen here, not all updates are wanted.
To prevent upstream changes from breaking critical workloads, security scanning and functional tests may be addedd.
This section covers:
- Build a test image
- Run a functional test script
./test.sh
against the test image - If the image tests successfully, import the public image to the baseimages registry
To gate any upstream content, automated testing is implemented. In this example, a test.sh
is provided which checks the $BACKGROUND_COLOR
. If the test fails, an EXIT_CODE
of 1
is returned which causes the ACR Task step to fail, ending the task run. The tests can be expanded in any form of tools, including logging results. The gate is managed by a pass/fail response.
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}
The acr-task.yaml
performs the following steps:
- Build the test base image using the following dockerfile:
ARG REGISTRY_FROM_URL= FROM ${REGISTRY_FROM_URL}node:15-alpine WORKDIR /test COPY ./test.sh . CMD ./test.sh
- When completed, validate the image by running the container, which runs
./test.sh
- Only if successfully completed, run the import steps, which are gated with
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}}"
Create an ACR Task to import and test the node base image
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
Add credentials for our public registry
az acr task credential add \
-n base-import-node \
-r $REGISTRY_BASE_ARTIFACTS \
--login-server $REGISTRY_PUBLIC_URL \
-u https://${AKV}.vault.azure.net/secrets/registry-${REGISTRY_PUBLIC}-user \
-p https://${AKV}.vault.azure.net/secrets/registry-${REGISTRY_PUBLIC}-password \
--use-identity [system]
Grant access to read values from the KeyVault
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
Run the import task:
az acr task run -n base-import-node -r $REGISTRY_BASE_ARTIFACTS
If the task fails due to ./test.sh: Permission denied
assure the script has execution permissions and commit back to the git repo:
chmod +x ./test.sh
Add a AcrPull
token to access the base-artifacts registry
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)
Add credentials for our Public Registry
az acr task credential add \
-n hello-world \
-r $REGISTRY \
--login-server $REGISTRY_BASE_ARTIFACTS_URL \
-u https://${AKV}.vault.azure.net/secrets/registry-${REGISTRY_BASE_ARTIFACTS}-user \
-p https://${AKV}.vault.azure.net/secrets/registry-${REGISTRY_BASE_ARTIFACTS}-password \
--use-identity [system]
Change the REGISTRY_FROM_URL to use the BASE_ARTIFACTS registry
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
Run the hello-world task to change it's base image dependency
az acr task run -r $REGISTRY -n hello-world
Open the Dockerfile
in base-image-node repo
Change the BACKGROUND_COLOR
to Green
to simulate a valid change.
ARG REGISTRY_NAME=
FROM ${REGISTRY_NAME}node:15-alpine
ENV NODE_VERSION 15-alpine
ENV BACKGROUND_COLOR Green
Commit the change and monitor the sequence of updates
watch -n1 az acr task list-runs -r $REGISTRY_PUBLIC
Once running, ctrl+C
and monitor the logs
az acr task logs -r $REGISTRY_PUBLIC
Once complete, monitor the base-image-import task
watch -n1 az acr task list-runs -r $REGISTRY_BASE_ARTIFACTS
Once running, ctrl+C
and monitor the logs
az acr task logs -r $REGISTRY_BASE_ARTIFACTS
Once complete, monitor the hello-world task
watch -n1 az acr task list-runs -r $REGISTRY
Once running, ctrl+C
and monitor the logs
az acr task logs -r $REGISTRY
Once complete, view the ACI hello-world image.
explorer.exe "http://"$(az container show \
--resource-group $ACI_RG \
--name ${ACI} \
--query ipAddress.ip \
--out tsv)
Perform the above steps again, with a background color of red
Open the Dockerfile
in base-image-node repo
Change the BACKGROUND_COLOR
to Red
to simulate a valid change.
ARG REGISTRY_NAME=
FROM ${REGISTRY_NAME}node:15-alpine
ENV NODE_VERSION 15-alpine
ENV BACKGROUND_COLOR Red
Commit the change and monitor the sequence of updates
watch -n1 az acr task list-runs -r $REGISTRY_PUBLIC
Once running, ctrl+C
and monitor the logs
az acr task logs -r $REGISTRY_PUBLIC
Once complete, monitor the base-image-import task
watch -n1 az acr task list-runs -r $REGISTRY_BASE_ARTIFACTS
Once running, ctrl+C
and monitor the logs
az acr task logs -r $REGISTRY_BASE_ARTIFACTS
At this point, you should see base-import-node fail validation and stop the sequence to publish a hello-world update.
Changes to the hello-world image will continue using the last validated node image.
Any additional changes to the base-node image that pass the gated validations will trigger base-updates to the hello-world image.
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