Deploy Container App and pull image from Azure Container Registry using Terraform and AzAPI

In this blog I am going to show how to deploy Container App and pull an image from Azure Container Registry using user assigned managed identity for authentication. I will be achieving this using Terraform and AzAPI.

Image below shows a diagram of what I will be deploying; it shows container app accessing a container registry using a user created identity.

Terraform

The terraform will create:

  • Resource Group
  • Log Analytics Workspace
  • Container App Environment
  • Container App
  • User created identity
  • Assign IAM permissions to an already created container registry for the user created identity
  • Assign user created identity to the container App
  • Successfully deploy container app with image stored in container registry

Resource Group

resource "azurerm_resource_group" "rg" {
  name     = "${var.aca_name}-rg"
  location = var.location
}

Log Analytics Workspace

resource "azurerm_log_analytics_workspace" "loganalytics" {
  name                = "${var.aca_name}-la"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  sku                 = "PerGB2018"
  retention_in_days   = 30
}

Data resources associated with already created Container Registry

data "azurerm_resource_group" "acr" {
  name = "tamops-acr-github"
}

data "azurerm_container_registry" "acr" {
  name                = "tamopsactionacr"
  resource_group_name = data.azurerm_resource_group.acr.name
}

Container App Environment deployed using AzAPI and Log Analytics workspace associated to send logs

resource "azapi_resource" "containerapp_environment" {
  type      = "Microsoft.App/managedEnvironments@2022-03-01"
  name      = "${var.aca_name}env"
  parent_id = azurerm_resource_group.rg.id
  location  = azurerm_resource_group.rg.location

  body = jsonencode({
    properties = {
      appLogsConfiguration = {
        destination = "log-analytics"
        logAnalyticsConfiguration = {
          customerId = azurerm_log_analytics_workspace.loganalytics.workspace_id
          sharedKey  = azurerm_log_analytics_workspace.loganalytics.primary_shared_key
        }
      }
    }
  })
}

Create user assigned identity and associated IAM role assignment

resource "azurerm_user_assigned_identity" "containerapp" {
  location            = azurerm_resource_group.rg.location
  name                = "containerappmi"
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_role_assignment" "containerapp" {
  scope                = data.azurerm_resource_group.acr.id
  role_definition_name = "acrpull"
  principal_id         = azurerm_user_assigned_identity.containerapp.principal_id
  depends_on = [
    azurerm_user_assigned_identity.containerapp
  ]
}

Create Azure Container App using AzAPI, notice:

  • Highlighted lines 8-11: Showing assignment of user assigned identity created above
  • Highlighted lines 21-26: Showing registry being assigned to the container registry
  • Highlighted line 31: Showing image assigned from container registry
resource "azapi_resource" "containerapp" {
  type      = "Microsoft.App/containerapps@2022-03-01"
  name      = var.aca_name
  parent_id = azurerm_resource_group.rg.id
  location  = azurerm_resource_group.rg.location


  identity {
    type         = "SystemAssigned, UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.containerapp.id]
  }
  body = jsonencode({

    properties = {
      managedEnvironmentId = azapi_resource.containerapp_environment.id
      configuration = {
        ingress = {
          external : true,
          targetPort : 80
        },
        "registries" : [
          {
            "server" : data.azurerm_container_registry.acr.login_server,
            "identity" : azurerm_user_assigned_identity.containerapp.id
          }
        ]
      }
      template = {
        containers = [
          {
            image = "${data.azurerm_container_registry.acr.login_server}/aspcoresample:76ef8d9511d310649729a28563fdf6d133338e30",
            name  = "firstcontainerappacracr"
            resources = {
              cpu    = 0.25
              memory = "0.5Gi"
            },
            "probes" : [
              {
                "type" : "Liveness",
                "httpGet" : {
                  "path" : "/",
                  "port" : 80,
                  "scheme" : "HTTP"
                },
                "periodSeconds" : 10
              },
              {
                "type" : "Readiness",
                "httpGet" : {
                  "path" : "/",
                  "port" : 80,
                  "scheme" : "HTTP"
                },
                "periodSeconds" : 10
              },
              {
                "type" : "Startup",
                "httpGet" : {
                  "path" : "/",
                  "port" : 80,
                  "scheme" : "HTTP"
                },
                "periodSeconds" : 10
              }
            ]
          }
        ]
        scale = {
          minReplicas = 0,
          maxReplicas = 2
        }
      }
    }

  })
  ignore_missing_property = true
  depends_on = [
    azapi_resource.containerapp_environment
  ]
}

The terraform files here – have been setup to run locally, with running a successful plan

terraform init
terraform plan

Terraform Plan output:

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azapi_resource.containerapp will be created
  + resource "azapi_resource" "containerapp" {
      + body                      = (known after apply)
      + id                        = (known after apply)
      + ignore_casing             = false
      + ignore_missing_property   = true
      + location                  = "uksouth"
      + name                      = "tamopsacablog"
      + output                    = (known after apply)
      + parent_id                 = (known after apply)
      + schema_validation_enabled = true
      + tags                      = (known after apply)
      + type                      = "Microsoft.App/containerapps@2022-03-01"

      + identity {
          + identity_ids = (known after apply)
          + principal_id = (known after apply)
          + tenant_id    = (known after apply)
          + type         = "SystemAssigned, UserAssigned"
        }
    }

  # azapi_resource.containerapp_environment will be created
  + resource "azapi_resource" "containerapp_environment" {
      + body                      = (sensitive)
      + id                        = (known after apply)
      + ignore_casing             = false
      + ignore_missing_property   = true
      + location                  = "uksouth"
      + name                      = "tamopsacablogenv"
      + output                    = (known after apply)
      + parent_id                 = (known after apply)
      + schema_validation_enabled = true
      + tags                      = (known after apply)
      + type                      = "Microsoft.App/managedEnvironments@2022-03-01"

      + identity {
          + identity_ids = (known after apply)
          + principal_id = (known after apply)
          + tenant_id    = (known after apply)
          + type         = (known after apply)
        }
    }

  # azurerm_log_analytics_workspace.loganalytics will be created
  + resource "azurerm_log_analytics_workspace" "loganalytics" {
      + daily_quota_gb                     = -1
      + id                                 = (known after apply)
      + internet_ingestion_enabled         = true
      + internet_query_enabled             = true
      + location                           = "uksouth"
      + name                               = "tamopsacablog-la"
      + primary_shared_key                 = (sensitive value)
      + reservation_capacity_in_gb_per_day = (known after apply)
      + resource_group_name                = "tamopsacablog-rg"
      + retention_in_days                  = 30
      + secondary_shared_key               = (sensitive value)
      + sku                                = "PerGB2018"
      + workspace_id                       = (known after apply)
    }

  # azurerm_resource_group.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "uksouth"
      + name     = "tamopsacablog-rg"
    }

  # azurerm_role_assignment.containerapp will be created
  + resource "azurerm_role_assignment" "containerapp" {
      + id                               = (known after apply)
      + name                             = (known after apply)
      + principal_id                     = (known after apply)
      + principal_type                   = (known after apply)
      + role_definition_id               = (known after apply)
      + role_definition_name             = "acrpull"
      + scope                            = "/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamops-acr-github"
      + skip_service_principal_aad_check = (known after apply)
    }

  # azurerm_user_assigned_identity.containerapp will be created
  + resource "azurerm_user_assigned_identity" "containerapp" {
      + client_id           = (known after apply)
      + id                  = (known after apply)
      + location            = "uksouth"
      + name                = "containerappmi"
      + principal_id        = (known after apply)
      + resource_group_name = "tamopsacablog-rg"
      + tenant_id           = (known after apply)
    }

Plan: 6 to add, 0 to change, 0 to destroy.

Running terraform apply, we can review the deployed resources in Azure

terraform apply

Reviewing container app identities, we can see the user assigned identity successfully

Reviewing the container app URL, we can see the container image has been deployed successfully!

Awesome! Successfully deployed Container App and pulled an image from Azure Container Registry using user assigned managed identity for authentication.

GitHub repository containing example terraform

3 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s