Skip to content

Instantly share code, notes, and snippets.

@torumakabe
Last active August 24, 2022 02:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save torumakabe/d81680ab13db82ffa2b0cf0094eddf04 to your computer and use it in GitHub Desktop.
Save torumakabe/d81680ab13db82ffa2b0cf0094eddf04 to your computer and use it in GitHub Desktop.
Sample Terraform codes for Azure Container Apps with Dapr

Sample Terraform codes for Azure Container Apps with Dapr

Overview

graph TB
    subgraph VNet
        subgraph Subnet-ACA-ControlPlane
            ACA-ControlPlane
        end
        subgraph Subnet-ACA-App
            subgraph NodeApp
                nodeapp --> nodeapp-dapr
            end
            subgraph PythonApp
                pythonapp --> pythonapp-dapr
                pythonapp-dapr --> nodeapp-dapr
            end
        end
        subgraph Subnet-Private-Endpoint
            nodeapp-dapr --> pe-state-store
        end
        dns-endpoint --> private-dns-zone-link
    end
    subgraph Azure-DNS
        private-dns-zone-link --> Private-DNS-Zones
    end
    subgraph Multitenants
        pe-state-store --> storage-account
    end

References

locals {
subnet_addrs = {
base_cidr_block = "192.168.0.0/16"
}
rg = {
name = "rg-aca-tf-sample"
location = "japaneast"
}
}
terraform {
required_version = "~> 1.2.4"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.12.0"
}
azapi = {
source = "azure/azapi"
version = "~> 0.4.0"
}
}
}
module "subnet_addrs" {
source = "hashicorp/subnets/cidr"
base_cidr_block = local.subnet_addrs.base_cidr_block
networks = [
{
name = "default"
new_bits = 8
},
{
name = "aca-cp"
// must have a size of at least /23
new_bits = 7
},
{
name = "pe"
new_bits = 8
},
]
}
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
}
}
provider "azapi" {}
data "http" "my_public_ip" {
url = "https://ipconfig.io"
}
resource "azurerm_resource_group" "aca_tf_sample" {
name = local.rg.name
location = local.rg.location
}
resource "azurerm_virtual_network" "default" {
name = "vnet-default"
resource_group_name = azurerm_resource_group.aca_tf_sample.name
location = azurerm_resource_group.aca_tf_sample.location
address_space = [module.subnet_addrs.base_cidr_block]
}
resource "azurerm_subnet" "default" {
name = "snet-default"
resource_group_name = azurerm_resource_group.aca_tf_sample.name
virtual_network_name = azurerm_virtual_network.default.name
address_prefixes = [module.subnet_addrs.network_cidr_blocks["default"]]
}
resource "azurerm_subnet" "aca_cp" {
// workaround: operate subnets one after another
// https://github.com/hashicorp/terraform-provider-azurerm/issues/3780
depends_on = [
azurerm_subnet.default
]
name = "snet-aca-cp"
resource_group_name = azurerm_resource_group.aca_tf_sample.name
virtual_network_name = azurerm_virtual_network.default.name
address_prefixes = [module.subnet_addrs.network_cidr_blocks["aca-cp"]]
}
resource "azurerm_subnet" "pe" {
// workaround: operate subnets one after another
// https://github.com/hashicorp/terraform-provider-azurerm/issues/3780
depends_on = [
azurerm_subnet.aca_cp
]
name = "snet-pe"
resource_group_name = azurerm_resource_group.aca_tf_sample.name
virtual_network_name = azurerm_virtual_network.default.name
address_prefixes = [module.subnet_addrs.network_cidr_blocks["pe"]]
}
resource "azurerm_private_dns_zone" "state_store" {
name = "privatelink.blob.core.windows.net"
resource_group_name = azurerm_resource_group.aca_tf_sample.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "state_store" {
name = "pdnsz-link-state-store"
resource_group_name = azurerm_resource_group.aca_tf_sample.name
private_dns_zone_name = azurerm_private_dns_zone.state_store.name
virtual_network_id = azurerm_virtual_network.default.id
}
resource "azurerm_storage_account" "state_store" {
name = "${var.prefix}acasampless"
resource_group_name = azurerm_resource_group.aca_tf_sample.name
location = azurerm_resource_group.aca_tf_sample.location
account_tier = "Standard"
account_replication_type = "LRS"
network_rules {
default_action = "Deny"
// If you could run Terraform in network reachable private endpoint, you do not need the following rule
ip_rules = ["${chomp(data.http.my_public_ip.body)}"]
virtual_network_subnet_ids = []
}
}
resource "azurerm_storage_container" "state_store" {
name = "state"
storage_account_name = azurerm_storage_account.state_store.name
container_access_type = "private"
}
resource "azurerm_private_endpoint" "state_store" {
name = "pe-state-store"
resource_group_name = azurerm_resource_group.aca_tf_sample.name
location = azurerm_resource_group.aca_tf_sample.location
subnet_id = azurerm_subnet.pe.id
private_dns_zone_group {
name = "pdnsz-group-state-store"
private_dns_zone_ids = [azurerm_private_dns_zone.state_store.id]
}
private_service_connection {
name = "pe-connection-state-store"
is_manual_connection = false
private_connection_resource_id = azurerm_storage_account.state_store.id
subresource_names = ["blob"]
}
}
// TODO: This will be replaced with AzureRM provider once it supports ACA
resource "azapi_resource" "aca_env" {
depends_on = [
azurerm_subnet.aca_cp
]
type = "Microsoft.App/managedEnvironments@2022-03-01"
name = "ca-env-aca-tf-sample"
parent_id = azurerm_resource_group.aca_tf_sample.id
location = azurerm_resource_group.aca_tf_sample.location
body = jsonencode({
properties = {
zoneRedundant = true
vnetConfiguration = {
internal = true
infrastructureSubnetId = azurerm_subnet.aca_cp.id
}
appLogsConfiguration = {
destination = "log-analytics"
logAnalyticsConfiguration = {
customerId = var.log_analytics.workspace.id
sharedKey = var.log_analytics.workspace.key
}
}
daprAIConnectionString = var.app_insights.connection_string
daprAIInstrumentationKey = var.app_insights.instrumentation_key
}
})
ignore_missing_property = true
response_export_values = ["properties.defaultDomain", "properties.staticIp"]
// Workaroud: delete operation complete immediatelly but it's actually running asynchronously, so occur "InUseSubnetCannotBeDeleted" error
provisioner "local-exec" {
when = destroy
command = "sleep 1800"
}
}
// TODO: This will be replaced with AzureRM provider once it supports ACA
resource "azapi_resource" "aca_env_dapr_component_state_store" {
depends_on = [
azapi_resource.aca_env
]
type = "Microsoft.App/managedEnvironments/daprComponents@2022-03-01"
name = "statestore"
parent_id = azapi_resource.aca_env.id
body = jsonencode({
properties = {
componentType = "state.azure.blobstorage"
version = "v1"
scopes = ["nodeapp"]
secrets = [
{
name = "account-key"
value = azurerm_storage_account.state_store.primary_access_key
}
]
metadata = [
{
name = "accountName"
value = azurerm_storage_account.state_store.name
},
{
name = "accountKey"
secretRef = "account-key"
},
{
name = "containerName"
value = azurerm_storage_container.state_store.name
}
]
}
})
ignore_missing_property = true
}
// TODO: This will be replaced with AzureRM provider once it supports ACA
resource "azapi_resource" "aca_app_nodeapp" {
depends_on = [
azapi_resource.aca_env, azapi_resource.aca_env_dapr_component_state_store
]
type = "Microsoft.App/containerApps@2022-03-01"
name = "ca-app-aca-tf-sample-nodeapp"
parent_id = azurerm_resource_group.aca_tf_sample.id
location = azurerm_resource_group.aca_tf_sample.location
identity {
type = "SystemAssigned"
}
body = jsonencode({
properties = {
managedEnvironmentId = azapi_resource.aca_env.id
configuration = {
ingress = {
targetPort = 3000
external = false
},
dapr = {
enabled = true
appId = "nodeapp"
appPort = 3000
appProtocol = "http"
}
},
template = {
containers = [
{
image = "dapriosamples/hello-k8s-node:latest"
name = "nodeapp"
}
]
scale = {
minReplicas = 1
maxReplicas = 1
}
}
}
})
ignore_missing_property = true
}
// TODO: This will be replaced with AzureRM provider once it supports ACA
resource "azapi_resource" "aca_app_pythonapp" {
depends_on = [
azapi_resource.aca_env, azapi_resource.aca_env_dapr_component_state_store
]
type = "Microsoft.App/containerApps@2022-03-01"
name = "ca-app-aca-tf-sample-pythonapp"
parent_id = azurerm_resource_group.aca_tf_sample.id
location = azurerm_resource_group.aca_tf_sample.location
identity {
type = "SystemAssigned"
}
body = jsonencode({
properties = {
managedEnvironmentId = azapi_resource.aca_env.id
configuration = {
dapr = {
enabled = true
appId = "pythonapp"
appProtocol = "http"
}
},
template = {
containers = [
{
image = "dapriosamples/hello-k8s-python:latest"
name = "pythonapp"
}
]
scale = {
minReplicas = 1
maxReplicas = 1
}
}
}
})
ignore_missing_property = true
}
resource "azurerm_private_dns_zone" "aca_env" {
name = jsondecode(azapi_resource.aca_env.output).properties.defaultDomain
resource_group_name = azurerm_resource_group.aca_tf_sample.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "aca_env" {
name = "pdnsz-link-aca-env"
resource_group_name = azurerm_resource_group.aca_tf_sample.name
private_dns_zone_name = azurerm_private_dns_zone.aca_env.name
virtual_network_id = azurerm_virtual_network.default.id
registration_enabled = true
}
resource "azurerm_private_dns_a_record" "aca_env" {
name = "*"
zone_name = azurerm_private_dns_zone.aca_env.name
resource_group_name = azurerm_resource_group.aca_tf_sample.name
ttl = 300
records = [jsondecode(azapi_resource.aca_env.output).properties.staticIp]
}
variable "prefix" {
type = string
}
variable "log_analytics" {
type = object({
workspace = object({
id = string
key = string
})
})
sensitive = true
}
variable "app_insights" {
type = object({
connection_string = string
instrumentation_key = string
})
sensitive = true
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment