azuredockerterraformazure-web-app-serviceazure-container-registry

How to configure an Azure app service to pull images from an ACR with terraform?


I have the following terraform module to setup app services under the same plan:

provider "azurerm" {
}

variable "env" {
    type = string
    description = "The SDLC environment (qa, dev, prod, etc...)"
}

variable "appsvc_names" {
    type = list(string)
    description = "The names of the app services to create under the same app service plan"
}

locals {
    location = "eastus2"
    resource_group_name = "app505-dfpg-${var.env}-web-${local.location}"
    acr_name = "app505dfpgnedeploycr88836"
}

resource "azurerm_app_service_plan" "asp" {
    name                = "${local.resource_group_name}-asp"
    location            = local.location
    resource_group_name = local.resource_group_name
    kind                = "Linux"
    reserved            = true

    sku {
        tier = "Basic"
        size = "B1"
    }
}

resource "azurerm_app_service" "appsvc" {
    for_each            = toset(var.appsvc_names)

    name                = "${local.resource_group_name}-${each.value}-appsvc"
    location            = local.location
    resource_group_name = local.resource_group_name
    app_service_plan_id = azurerm_app_service_plan.asp.id

    site_config {
        linux_fx_version = "DOCKER|${local.acr_name}/${each.value}:latest"
    }

    app_settings = {
        DOCKER_REGISTRY_SERVER_URL = "https://${local.acr_name}.azurecr.io"
    } 
}

output "hostnames" {
  value = {
    for appsvc in azurerm_app_service.appsvc: appsvc.name => appsvc.default_site_hostname
  }
}

I am invoking it through the following configuration:

terraform {
    backend "azurerm" {
    }
}

locals {
    appsvc_names = ["gateway"]
}

module "web" {
    source = "../../modules/web"
    env = "qa"
    appsvc_names = local.appsvc_names
}

output "hostnames" {
    description = "The hostnames of the created app services"
    value       = module.web.hostnames
}

The container registry has the images I need:

C:\> az acr login --name app505dfpgnedeploycr88836
Login Succeeded
C:\> az acr repository list  --name app505dfpgnedeploycr88836
[
  "gateway"
]
C:\> az acr repository show-tags --name app505dfpgnedeploycr88836 --repository gateway
[
  "latest"
]
C:\>

When I apply the terraform configuration everything is created fine, but inspecting the created app service resource in Azure Portal reveals that its Container Settings show no docker image:

enter image description here

Now, I can manually switch to another ACR and then back to the one I want only to get this:

enter image description here

Cannot perform credential operations for /subscriptions/0f1c414a-a389-47df-aab8-a351876ecd47/resourceGroups/app505-dfpg-ne-deploy-eastus2/providers/Microsoft.ContainerRegistry/registries/app505dfpgnedeploycr88836 as admin user is disabled. Kindly enable admin user as per docs: https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication#admin-account

This is confusing me. According to https://learn.microsoft.com/en-us/azure/container-registry/container-registry-authentication#admin-account the admin user should not be used and so my ACR does not have one. On the other hand, I understand that I need somehow configure the app service to authenticate with the ACR.

What is the right way to do it then?


Solution

  • So this is now possible since the v2.71 version of the Azure RM provider. A couple of things have to happen...

    1. Assign a Managed Identity to the application (can also use User Assigned but a bit more work)
    2. Set the site_config.acr_use_managed_identity_credentials property to true
    3. Grant the application's identity ACRPull rights on the container.

    Below is a modified version of the code above, not tested but should be ok

    provider "azurerm" {
    }
    
    variable "env" {
        type = string
        description = "The SDLC environment (qa, dev, prod, etc...)"
    }
    
    variable "appsvc_names" {
        type = list(string)
        description = "The names of the app services to create under the same app service plan"
    }
    
    locals {
        location = "eastus2"
        resource_group_name = "app505-dfpg-${var.env}-web-${local.location}"
        acr_name = "app505dfpgnedeploycr88836"
    }
    
    resource "azurerm_app_service_plan" "asp" {
        name                = "${local.resource_group_name}-asp"
        location            = local.location
        resource_group_name = local.resource_group_name
        kind                = "Linux"
        reserved            = true
    
        sku {
            tier = "Basic"
            size = "B1"
        }
    }
    
    resource "azurerm_app_service" "appsvc" {
        for_each            = toset(var.appsvc_names)
    
        name                = "${local.resource_group_name}-${each.value}-appsvc"
        location            = local.location
        resource_group_name = local.resource_group_name
        app_service_plan_id = azurerm_app_service_plan.asp.id
    
        site_config {
            linux_fx_version = "DOCKER|${local.acr_name}/${each.value}:latest"
            acr_use_managed_identity_credentials = true
        }
    
        app_settings = {
            DOCKER_REGISTRY_SERVER_URL = "https://${local.acr_name}.azurecr.io"
        }
    
        identity {
            type = "SystemAssigned"
        }   
    }
    
    data "azurerm_container_registry" "this" {
      name                = local.acr_name
      resource_group_name = local.resource_group_name
    }
    
    resource "azurerm_role_assignment" "acr" {
      for_each             = azurerm_app_service.appsvc
      
      role_definition_name = "AcrPull"
      scope                = azurerm_container_registry.this.id
      principal_id         = each.value.identity[0].principal_id
    }
    
    output "hostnames" {
      value = {
        for appsvc in azurerm_app_service.appsvc: appsvc.name => appsvc.default_site_hostname
      }
    }
    

    EDITED 21 Dec 2021 The MS documentation issue regarding the value being reset by Azure has now been resolved and you can also control Managed Identity via the portal.