Create Azure Container Registry (ACR) using terraform
Introduction
Azure Container Registry (ACR) is a managed Docker registry service provided by Microsoft Azure. It allows you to store and manage container images for your applications in a secure and private environment.
In this lab, I will guide you through the process of creating an Azure Container Registry using Terraform. Furthermore, I will demonstrate how to verify its successful deployment within the Azure portal and provide insights on how to utilize it effectively post-creation.
ACR provides a number of benefits, including:
-
Private repository: ACR provides a private Docker registry, which means that you can store your Docker images securely and privately, and only authorized users or services can access them.
-
High availability: ACR is built on Azure, so it benefits from Azure's global network and high availability features. This means that your container images are always available, and you can easily replicate them across regions for disaster recovery.
-
Integration with Azure services: ACR integrates seamlessly with other Azure services, such as Azure Kubernetes Service (AKS), Azure Web Apps, and Azure DevOps, making it easy to incorporate ACR into your existing workflows.
-
Security and compliance: ACR provides built-in security features, such as role-based access control, network security, and encryption at rest, to help you meet your security and compliance requirements.
-
Geo-replication: ACR allows you to replicate your container images across multiple regions for improved performance and disaster recovery.
To get started with ACR, we are going to use terraform to create a new Azure Container Registry. Once you have a registry, you can push your Docker images to it using the Docker CLI, Azure CLI, or other tools, and manage your images using the Azure portal or a variety of third-party tools.
Technical Scenario
As a Cloud Engineer
, you have been asked to store and manage organization application development private container images and helm charts in secure way in cloud so that these are not directly accessible outside of the company network. also, make sure that azure Container Registry should provide organization users with direct control of their container content, with integrated authentication.
Objective
In this exercise we will accomplish & learn how to implement following:
- Task-1: Create ACR resource group
- Task-2: Configure variables for ACR
- Task-3: Create ACR user assigned identity
- Task-4: Create Azure Container Registry (ACR) using terraform
- Task-5: Create Diagnostics Settings for ACR
- Task-6: Lock ACR resource group
- Task-7: Validate ACR resource
- Task-7.1: Log in to registry
- Task-7.2: Push image to registry
- Task-7.3: Pull image from registry
- Task-7.4: List container images
- Task-8: Restrict Access Using Private Endpoint
- Task-8.1: Configure the Private DNS Zone
- Task-8.2: Create a Virtual Network Link Association
- Task-8.3: Create a Private Endpoint Using Terraform
- Task-8.4: Validate private link connection using
nslookup
ordig
Through these tasks, you will gain practical experience on Azure Container Registry.
Architecture diagram
Here is the reference architecture diagram of Azure container registry.
Prerequisites
- Download & Install Terraform
- Download & Install Azure CLI
- Azure subscription
- Visual studio code
- Azure DevOps Project & repo
- Terraform Foundation
- Log Analytics workspace - for configuring diagnostic settings.
- Virtual Network with subnet - for configuring a private endpoint.
- Basic knowledge of terraform and azure concepts.
Implementation details
Open the terraform project folder in Visual Studio code and creating new file named acr.tf
for Azure container registry specific azure resources;
login to Azure
Verify that you are logged into the right Azure subscription before start anything in visual studio code
# Login to Azure
az login
# Shows current Azure subscription
az account show
# Lists all available Azure subscriptions
az account list
# Sets Azure subscription to desired subscription using ID
az account set -s "anji.keesari"
Task-1: Define and declare ACR variables
This section covers list of variables used to create Azure container registry with detailed description and purpose of each variable with default values.
Variable Name | Description | Type | Default Value |
---|---|---|---|
acr_name | (Required) Specifies the name of the Container Registry. Changing this forces a new resource to be created. | string | acr1dev |
acr_rg_name | (Required) The name of the resource group in which to create the Container Registry. Changing this forces a new resource to be created. | string | acr1 |
acr_location | Location in which to deploy the Container Registry | string | "East US" |
acr_admin_enabled | (Optional) Specifies whether the admin user is enabled. Defaults to false. | bool | false |
acr_sku | (Optional) The SKU name of the container registry. Possible values are Basic, Standard, and Premium. Defaults to Basic | string | "Basic" |
acr_georeplication_locations | (Optional) A list of Azure locations where the container registry should be geo-replicated. | list(string) | ["Central US", "East US"] |
acr_log_analytics_retention_days | Specifies the number of days of the retention policy | number | 7 |
acr_tags | (Optional) Specifies the tags of the ACR | map(any) | {} |
data_endpoint_enabled | (Optional) Whether to enable dedicated data endpoints for this Container Registry? Defaults to false. This is only supported on resources with the Premium SKU. | bool | true |
Variables Prefixed
Here is the list of new prefixes used in this lab
variable "acr_prefix" {
type = string
default = "acr"
description = "Prefix of the Azure Container Registry (ACR) name that's combined with name of the ACR"
}
Declare Variables
Here is the list of new variables used in this lab
// ========================== Azure Container Registry (ACR) ==========================
variable "acr_name" {
description = "(Required) Specifies the name of the Container Registry. Changing this forces a new resource to be created."
type = string
}
variable "acr_rg_name" {
description = "(Required) The name of the resource group in which to create the Container Registry. Changing this forces a new resource to be created."
type = string
}
variable "acr_location" {
description = "Location in which to deploy the Container Registry"
type = string
default = "East US"
}
variable "acr_admin_enabled" {
description = "(Optional) Specifies whether the admin user is enabled. Defaults to false."
type = string
default = false
}
variable "acr_sku" {
description = "(Optional) The SKU name of the container registry. Possible values are Basic, Standard and Premium. Defaults to Basic"
type = string
default = "Basic"
validation {
condition = contains(["Basic", "Standard", "Premium"], var.acr_sku)
error_message = "The container registry sku is invalid."
}
}
variable "acr_georeplication_locations" {
description = "(Optional) A list of Azure locations where the container registry should be geo-replicated."
type = list(string)
default = ["Central US", "East US"]
}
variable "acr_log_analytics_retention_days" {
description = "Specifies the number of days of the retention policy"
type = number
default = 7
}
variable "acr_tags" {
description = "(Optional) Specifies the tags of the ACR"
type = map(any)
default = {}
}
variable "data_endpoint_enabled" {
description = "(Optional) Whether to enable dedicated data endpoints for this Container Registry? Defaults to false. This is only supported on resources with the Premium SKU."
default = true
type = bool
}
variable "pe_acr_subresource_names" {
description = "(Optional) Specifies a subresource names which the Private Endpoint is able to connect to ACR."
type = list(string)
default = ["registry"]
}
Here is the list of new variables used in this lab
dev-variables.tfvar
- update this existing file for ACR values for development environment.
# container registry
acr_rg_name = "acr"
acr_name = "acr1dev"
acr_sku = "Basic"
acr_admin_enabled = true
data_endpoint_enabled = false
output variables
Here is the list of output variables used in this lab
// ========================== Azure Container Registry (ACR) ==========================
output "acr_name" {
description = "Specifies the name of the container registry."
value = azurerm_container_registry.acr.name
}
output "acr_id" {
description = "Specifies the resource id of the container registry."
value = azurerm_container_registry.acr.id
}
output "acr_resource_group_name" {
description = "Specifies the name of the resource group."
value = azurerm_container_registry.acr.resource_group_name
}
output "acr_login_server" {
description = "Specifies the login server of the container registry."
value = azurerm_container_registry.acr.login_server
}
output "acr_login_server_url" {
description = "Specifies the login server url of the container registry."
value = "https://${azurerm_container_registry.acr.login_server}"
}
output "acr_admin_username" {
description = "Specifies the admin username of the container registry."
value = azurerm_container_registry.acr.admin_username
}
Task-2: Create a resource group for ACR
We will create separate resource group for ACR and related resources. add following terraform configuration in acr.tf
file for creating ACR resource group.
In this task, we will create Azure resource group by using the terraform
# Create the resource group
resource "azurerm_resource_group" "rg_acr" {
name = lower("${var.rg_prefix}-${var.acr_rg_name}-${local.environment}")
location = var.acr_location
tags = merge(local.default_tags)
lifecycle {
ignore_changes = [
tags
]
}
}
run terraform plan & apply
terraform plan -out=dev-plan -var-file="./environments/dev-variables.tfvars"
terraform apply dev-plan
Task-3: Create ACR user assigned identity
Use the following terraform configuration for creating user assigned identity which is going be used in ACR
User assigned managed identities enable Azure resources to authenticate to cloud services (e.g. Azure Key Vault) without storing credentials in code.
User Assigned Identity in Azure Container Registry provides improved security, simplified management, better integration with Azure services, RBAC, and better compliance, making it a beneficial feature for organizations that use ACR.
# Create ACR user assigned identity
resource "azurerm_user_assigned_identity" "acr_identity" {
resource_group_name = azurerm_resource_group.rg_acr.name
location = azurerm_resource_group.rg_acr.location
tags = merge(local.default_tags, var.acr_tags)
name = "${var.acr_name}Identity"
depends_on = [
azurerm_resource_group.rg_acr,
]
lifecycle {
ignore_changes = [
tags
]
}
}
Task-4: Create Azure Container Registry (ACR) using terraform
Use the following terraform configuration for creating ACR.
# Create the Container Registry
resource "azurerm_container_registry" "acr" {
name = var.acr_name
resource_group_name = azurerm_resource_group.rg_acr.name
location = azurerm_resource_group.rg_acr.location
sku = var.acr_sku
admin_enabled = var.acr_admin_enabled
# zone_redundancy_enabled = true
data_endpoint_enabled = var.data_endpoint_enabled
identity {
type = "UserAssigned"
identity_ids = [
azurerm_user_assigned_identity.acr_identity.id
]
}
# dynamic "georeplications" {
# for_each = var.acr_georeplication_locations
# content {
# location = georeplications.value
# tags = merge(local.default_tags, var.acr_tags)
# }
# }
tags = merge(local.default_tags, var.acr_tags)
lifecycle {
ignore_changes = [
tags
]
}
depends_on = [
azurerm_resource_group.rg_acr,
azurerm_log_analytics_workspace.workspace
]
}
run terraform validate & format
run terraform plan & apply
terraform plan -out=dev-plan -var-file="./environments/dev-variables.tfvars"
terraform apply dev-plan
Task-5: Create Diagnostics Settings for ACR
we are going to use diagnostics settings for all kind of azure resources to manage logs and metrics etc... Let's create diagnostics settings for ACR for storing Logs and Metric with default retention of 30 days or as per the requirements.
# create Diagnostics Settings for ACR
resource "azurerm_monitor_diagnostic_setting" "diag_acr" {
name = "DiagnosticsSettings"
target_resource_id = azurerm_container_registry.acr.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.workspace.id
log {
category = "ContainerRegistryRepositoryEvents"
enabled = true
retention_policy {
enabled = true
days = var.acr_log_analytics_retention_days
}
}
log {
category = "ContainerRegistryLoginEvents"
enabled = true
retention_policy {
enabled = true
days = var.acr_log_analytics_retention_days
}
}
metric {
category = "AllMetrics"
retention_policy {
enabled = true
days = var.acr_log_analytics_retention_days
}
}
}
run terraform plan & apply
terraform plan -out=dev-plan -var-file="./environments/dev-variables.tfvars"
terraform apply dev-plan
Task-6: Lock the resource group
Finally, it is time to lock the resource group created part of this exercise, so that we can avoid the accidental deletion of the azure resources created here.
# Lock the resource group
resource "azurerm_management_lock" "rg_acr" {
name = "CanNotDelete"
scope = azurerm_resource_group.rg_acr.id
lock_level = "CanNotDelete"
notes = "This resource group can not be deleted - lock set by Terraform"
depends_on = [
azurerm_resource_group.rg_acr,
azurerm_monitor_diagnostic_setting.diag_acr,
]
}
run terraform plan & apply
terraform plan -out=dev-plan -var-file="./environments/dev-variables.tfvars"
terraform apply dev-plan
Task-7: Validate ACR resource
Task-7.1: Log in to registry
Task-7.2: Push image to registry
az acr login --name acr1dev
docker tag sample/aspnet-api:20230226.1 acr1dev.azurecr.io/sample/aspnet-api:20230226.1
docker push acr1dev.azurecr.io/sample/aspnet-api:20230226.1
or
az acr push --name acr1dev sample/aspnet-api:20230226.1
Task-7.3: Pull image from registry
Task-7.4: List container images
for more information look into the az acr cheat-sheet az-acr-cheat-sheet
Task-8: Restrict Access Using Private Endpoint
To enhance security and limit access to an Azure Container Registry (ACR), you can utilize private endpoints and Azure Private Link. This approach assigns virtual network private IP addresses to the registry endpoints, ensuring that network traffic between clients on the virtual network and the registry's private endpoints traverses a secure path on the Microsoft backbone network, eliminating exposure from the public internet.
Additionally, you can configure DNS settings for the registry's private endpoints, allowing clients and services in the network to access the registry using its fully qualified domain name, such as myregistry.azurecr.io
.
This section guides you through configuring a private endpoint for your ACR using Terraform. Note that this feature is available in the Premium container registry service tier.
Task-8.1: Configure the Private DNS Zone
acr.tf:
# Create private DNS zone for Azure container registry
resource "azurerm_private_dns_zone" "pdz_acr" {
name = "privatelink.azurecr.io"
resource_group_name = azurerm_virtual_network.vnet.resource_group_name
tags = merge(local.default_tags)
lifecycle {
ignore_changes = [
tags
]
}
depends_on = [
azurerm_virtual_network.vnet
]
}
run terraform validate & format
run terraform plan & apply
terraform plan -out=dev-plan -var-file="./environments/dev-variables.tfvars"
terraform apply dev-plan
Confirm the Private DNS zone configuration by navigating to rg-vnet1-dev -> privatelink.azurecr.io -> Overview blade
.
Task-8.2: Create a Virtual Network Link Association
acr.tf:
# Create private virtual network link to Virtual Network
resource "azurerm_private_dns_zone_virtual_network_link" "acr_pdz_vnet_link" {
name = "privatelink_to_${azurerm_virtual_network.vnet.name}"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_id = azurerm_virtual_network.vnet.id
private_dns_zone_name = azurerm_private_dns_zone.pdz_acr.name
lifecycle {
ignore_changes = [
tags
]
}
depends_on = [
azurerm_resource_group.rg,
azurerm_virtual_network.vnet,
azurerm_private_dns_zone.pdz_acr
]
}
run terraform validate & format
run terraform plan & apply
terraform plan -out=dev-plan -var-file="./environments/dev-variables.tfvars"
terraform apply dev-plan
Confirm the Virtual network links configuration by navigating to rg-vnet1-dev -> privatelink.azurecr.io -> Virtual network links
.
Task-8.3: Create a Private Endpoint Using Terraform
acr.tf:
# Create private endpoint for Azure container registry
resource "azurerm_private_endpoint" "pe_acr" {
name = lower("${var.private_endpoint_prefix}-${azurerm_container_registry.acr.name}")
location = azurerm_container_registry.acr.location
resource_group_name = azurerm_container_registry.acr.resource_group_name
subnet_id = azurerm_subnet.jumpbox.id
tags = merge(local.default_tags, var.acr_tags)
private_service_connection {
name = "pe-${azurerm_container_registry.acr.name}"
private_connection_resource_id = azurerm_container_registry.acr.id
is_manual_connection = false
subresource_names = var.pe_acr_subresource_names
request_message = try(var.request_message, null)
}
private_dns_zone_group {
name = "default" //var.pe_acr_private_dns_zone_group_name
private_dns_zone_ids = [azurerm_private_dns_zone.pdz_acr.id]
}
lifecycle {
ignore_changes = [
tags,
]
}
depends_on = [
azurerm_container_registry.acr,
azurerm_private_dns_zone.pdz_acr
]
}
run terraform validate & format
run terraform plan & apply
terraform plan -out=dev-plan -var-file="./environments/dev-variables.tfvars"
terraform apply dev-plan
Confirm the endpoint configuration by navigating to Container registry -> Networking -> Private access
— you will see the new private endpoint details.
Navigate to Private endpoint -> Overview
to verify the Virtual network/subnet and Network interface.
Navigate to Private endpoint -> DNS Configuration
to verify the Network Interface and Configuration name.
Navigate to Network interface -> Overview
to verify the private IP address attached to properties.
Task-8.4: Validate private link connection using nslookup
or dig
To validate the private link connection, connect to the virtual machine
you set up in the virtual network. Run a utility such as nslookup
or dig
to look up the IP address of your registry over the private link.
This will ensures that the private link connection is successfully established and allows for the verification of the expected private IP address associated with the registry in the given virtual network.
Validate using dig
example:
Positive test case connecting from internal vm (private access):
Run the dig
utility to look up the private IP address (10.64.3.5
) of your registry over the private link:
output
; <<>> DiG 9.16.1-Ubuntu <<>> acr1dev.azurecr.io
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 31549
;; flags: qr rd ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
;acr1dev.azurecr.io. IN A
;; ANSWER SECTION:
acr1dev.azurecr.io. 0 IN CNAME acr1dev.privatelink.azurecr.io.
acr1dev.privatelink.azurecr.io. 0 IN A 10.64.3.5
;; Query time: 10 msec
;; SERVER: 172.30.80.1#53(172.30.80.1)
;; WHEN: Tue Dec 26 14:57:14 UTC 2023
;; MSG SIZE rcvd: 160
Nagetive test case connecting from external (public access), compare this result with the public IP address in dig
output for the same registry over a public endpoint:
; <<>> DiG 9.16.1-Ubuntu <<>> acr1dev.azurecr.io
;; global options: +cmd
acr1dev.azurecr.io. 0 IN CNAME acr1dev.privatelink.azurecr.io.
acr1dev.privatelink.azurecr.io. 0 IN CNAME ncus.fe.azcr.io.
ncus.fe.azcr.io. 0 IN CNAME ncus-acr-reg.trafficmanager.net.
ncus-acr-reg.trafficmanager.net. 0 IN CNAME r1029ncus.northcentralus.cloudapp.azure.com.
r1029ncus.northcentralus.cloudapp.azure.com. 0 IN A 52.240.241.132
;; Query time: 50 msec
;; SERVER: 172.29.48.1#53(172.29.48.1)
;; WHEN: Tue Dec 26 06:56:26 PST 2023
;; MSG SIZE rcvd: 380
Validate using nslookup
example:
Connecting from internal VM (private access):
outputServer: 172.30.80.1
Address: 172.30.80.1#53
Non-authoritative answer:
acr1dev.azurecr.io canonical name = acr1dev.privatelink.azurecr.io.
Name: acr1dev.privatelink.azurecr.io
Address: 10.64.3.5
Connecting from external (public access):
output
Server: 172.29.48.1
Address: 172.29.48.1#53
Non-authoritative answer:
acr1dev.azurecr.io canonical name = acr1dev.privatelink.azurecr.io.
acr1dev.privatelink.azurecr.io canonical name = ncus.fe.azcr.io.
ncus.fe.azcr.io canonical name = ncus-acr-reg.trafficmanager.net.
ncus-acr-reg.trafficmanager.net canonical name = r1029ncus.northcentralus.cloudapp.azure.com.
Name: r1029ncus.northcentralus.cloudapp.azure.com
Address: 52.240.241.132
This process ensures that the private link connection is successfully established and allows expected private IP address associated with our resource in the private virtual network.
References
- Microsoft MSDN - Azure Container Registry documentation
- Microsoft MSDN - Connect privately to an Azure container registry using Azure Private Link
- Microsoft MSDN - Quickstart: Create an Azure container registry using the Azure portal
- Microsoft MSDN - Push your first image to your Azure container registry using the Docker CLI
- Microsoft MSDN - Push and pull Helm charts to an Azure container registry
- Terraform Registry - azurerm_resource_group
- Terraform Registry - azurerm_management_lock
- Terraform Registry - azurerm_container_registry
- Terraform Registry - azurerm_user_assigned_identity
- Terraform Registry - azurerm_monitor_diagnostic_setting
- Terraform Registry - azurerm_private_dns_zone
- Terraform Registry - azurerm_private_dns_zone_virtual_network_link
- Terraform Registry - azurerm_private_endpoint
- Azure-Samples/private-aks-cluster-terraform-devop