Deploying Azure Container Apps into your virtual network using Terraform and AzAPI

In this blog, we will be looking at deploying Azure Container Apps into your virtual network using Terraform and AzAPI. A follow on blog from my previous – Deploying multiple Container Apps in Azure using Terraform and AzAPI

Looking at the diagram below, I will be deploying the container app so it is only accessible within the virtual network and not accessible via the public internet.

The Terraform will create:

  • Resource Group
  • Log Analytics workspace that will be connected to the Container App environment
  • Virtual network with aca subnet
  • Container App environment deployed into above subnet
  • Container App
  • Private dns zone created with the associated defaultDomain property of the container app environment
  • A record within the private dns zone for the Container App with associated staticIp property of the container app environment

The Terraform

  • 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
}
  • Virtual Network and subnet
resource "azurerm_virtual_network" "virtual_network" {
  name                = "${var.aca_name}-vnet"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  address_space       = [var.network_address_space]
}

resource "azurerm_subnet" "aca_subnet" {
  name                 = var.aca_subnet_address_name
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.virtual_network.name
  address_prefixes     = [var.aca_subnet_address_prefix]
}
  • Container App environment

Notice the use of response_export_values – this extracts the value data needed to create private dns zone & A record

resource "azapi_resource" "containerapp_environment" {
  type      = "Microsoft.App/managedEnvironments@2022-03-01"
  name      = "${var.aca_name}acae"
  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
        }
      }
      vnetConfiguration = {
        internal               = true
        infrastructureSubnetId = azurerm_subnet.aca_subnet.id
        dockerBridgeCidr       = "10.2.0.1/16"
        platformReservedCidr   = "10.1.0.0/16"
        platformReservedDnsIP  = "10.1.0.2"
      }
    }
  })
  depends_on = [
    azurerm_virtual_network.virtual_network
  ]
  response_export_values  = ["properties.defaultDomain", "properties.staticIp"]
  ignore_missing_property = true
}
  • Container App
resource "azapi_resource" "containerapp" {
  type      = "Microsoft.App/containerapps@2022-03-01"
  name      = "app01acae"
  parent_id = azurerm_resource_group.rg.id
  location  = azurerm_resource_group.rg.location

  body = jsonencode({
    properties = {
      managedEnvironmentId = azapi_resource.containerapp_environment.id
      configuration = {
        ingress = {
          external : true,
          targetPort : 80
        },

      }
      template = {
        containers = [
          {
            image = "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest"
            name  = "simple-hello-world-container"
            resources = {
              cpu    = 0.25
              memory = "0.5Gi"
            }
          }
        ]
        scale = {
          minReplicas = 0,
          maxReplicas = 2
        }
      }
    }

  })
  depends_on = [
    azapi_resource.containerapp_environment
  ]
}
  • Private dns zone, vnet link & a record creation
resource "azurerm_private_dns_zone" "private_dns_zone" {
  name                = jsondecode(azapi_resource.containerapp_environment.output).properties.defaultDomain
  resource_group_name = azurerm_resource_group.rg.name
  #   depends_on = [
  #     azapi_resource.containerapp_environment
  #   ]
}

resource "azurerm_private_dns_zone_virtual_network_link" "vnet_link" {
  name                  = "containerapplink"
  resource_group_name   = azurerm_resource_group.rg.name
  private_dns_zone_name = azurerm_private_dns_zone.private_dns_zone.name
  virtual_network_id    = azurerm_virtual_network.virtual_network.id
}

resource "azurerm_private_dns_a_record" "containerapp_record" {
  name                = "app01acae"
  zone_name           = azurerm_private_dns_zone.private_dns_zone.name
  resource_group_name = azurerm_resource_group.rg.name
  ttl                 = 300
  records             = ["${jsondecode(azapi_resource.containerapp_environment.output).properties.staticIp}"]

}

Once the private zone & A record has been deployed, as below – you can see what it creates

The app can now be accessible from within the vnet using the URL: https://app01acae.orangemeadow-be982bb4.uksouth.azurecontainerapps.io and not accessible via the public internet

Full terraform setup is in this GitHub repository & can be ran locally with:

terraform init
terraform apply -var-file="prod.tfvars"

Another blog post to assist in creating Azure Container Apps with Terraform and & AzAPI!

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