NOTE: The following module is preconfigured to use two regions, Canada Central and East US. It also expects that you have your site’s TLS certificate in an Azure Key Vault.
############################################## # # High Availability App Service # ############################################### locals { full_service_name = "${var.service-name}${var.environment_name == "prod" ? "" : "-${var.environment_name}"}" public_hostname = "${var.service-name}${var.environment_name == "prod" ? "" : "-${var.environment_name}"}.flyswoop.com" tags = { environment = "${var.environment_name}" owner = "${var.owner}" product = "${var.service-name}" department = "${var.department}" } } resource "azurerm_resource_group" "cancen-rg" { name = "${var.service-name}-cancen-rg-${var.environment_name}" location = "canadacentral" tags = "${local.tags}" } resource "azurerm_app_service_plan" "cancen-app-service-plan" { name = "${var.service-name}-cancen-app-service-plan-${var.environment_name}" location = "${azurerm_resource_group.cancen-rg.location}" resource_group_name = "${azurerm_resource_group.cancen-rg.name}" kind = "Linux" reserved = true sku { tier = "${var.app-service-plan-sku-tier}" size = "${var.app-service-plan-sku-size}" } tags = "${local.tags}" } resource "azurerm_app_service" "cancen-app-service" { name = "${var.service-name}-cancen-app-service-${var.environment_name}" location = "${azurerm_resource_group.cancen-rg.location}" resource_group_name = "${azurerm_resource_group.cancen-rg.name}" app_service_plan_id = "${azurerm_app_service_plan.cancen-app-service-plan.id}" tags = "${local.tags}" https_only = true site_config { always_on = "true" min_tls_version = "1.2" default_documents = [] linux_fx_version = "DOCKER|nginx" scm_type = "VSTSRM" http2_enabled = true } identity { type = "SystemAssigned" } logs { http_logs { file_system { retention_in_days = 30 retention_in_mb = 35 } } } lifecycle { ignore_changes = [ app_settings, site_config[0].linux_fx_version ] } } resource "azurerm_app_service_slot" "cancen-app-service-slot-blue" { # Only create the slots if we're in prod, since the DEV and QA # App Service SKUs don't support slots count = "${var.environment_name == "prod" ? 1 : 0}" name = "blue" app_service_name = "${azurerm_app_service.cancen-app-service.name}" location = "${azurerm_resource_group.cancen-rg.location}" resource_group_name = "${azurerm_resource_group.cancen-rg.name}" app_service_plan_id = "${azurerm_app_service_plan.cancen-app-service-plan.id}" https_only = true site_config { always_on = "true" min_tls_version = "1.2" default_documents = [] linux_fx_version = "DOCKER|nginx:alpine" scm_type = "VSTSRM" http2_enabled = true } identity { type = "SystemAssigned" } lifecycle { ignore_changes = [ app_settings, site_config[0].linux_fx_version ] } } resource "azurerm_resource_group" "centralus-rg" { name = "${var.service-name}-centralus-rg-${var.environment_name}" location = "centralus" tags = "${local.tags}" } resource "azurerm_app_service_plan" "centralus-app-service-plan" { name = "${var.service-name}-centralus-app-service-plan-${var.environment_name}" location = "${azurerm_resource_group.centralus-rg.location}" resource_group_name = "${azurerm_resource_group.centralus-rg.name}" kind = "Linux" reserved = true sku { tier = "${var.app-service-plan-sku-tier}" size = "${var.app-service-plan-sku-size}" } tags = "${local.tags}" } resource "azurerm_app_service" "centralus-app-service" { name = "${var.service-name}-centralus-app-service-${var.environment_name}" location = "${azurerm_resource_group.centralus-rg.location}" resource_group_name = "${azurerm_resource_group.centralus-rg.name}" app_service_plan_id = "${azurerm_app_service_plan.centralus-app-service-plan.id}" tags = "${local.tags}" https_only = true site_config { always_on = "true" min_tls_version = "1.2" default_documents = [] linux_fx_version = "DOCKER|nginx" scm_type = "VSTSRM" http2_enabled = true } logs { http_logs { file_system { retention_in_days = 30 retention_in_mb = 35 } } } identity { type = "SystemAssigned" } lifecycle { ignore_changes = [ app_settings, site_config[0].linux_fx_version ] } } resource "azurerm_app_service_slot" "centralus-app-service-slot-blue" { # Only create the slots if we're in prod, since the DEV and QA # App Service SKUs don't support slots count = "${var.environment_name == "prod" ? 1 : 0}" name = "blue" app_service_name = "${azurerm_app_service.centralus-app-service.name}" location = "${azurerm_resource_group.centralus-rg.location}" resource_group_name = "${azurerm_resource_group.centralus-rg.name}" app_service_plan_id = "${azurerm_app_service_plan.centralus-app-service-plan.id}" https_only = true site_config { always_on = "true" min_tls_version = "1.2" default_documents = [] linux_fx_version = "DOCKER|nginx:alpine" scm_type = "VSTSRM" http2_enabled = true } identity { type = "SystemAssigned" } lifecycle { ignore_changes = [ app_settings, site_config[0].linux_fx_version ] } } resource "azurerm_resource_group" "traffic-manager" { name = "${var.service-name}-traffic-manager-${var.environment_name}" location = "canadacentral" } resource "azurerm_traffic_manager_profile" "traffic-manager-profile" { name = "${var.service-name}-traffic-manager-${var.environment_name}" resource_group_name = "${azurerm_resource_group.traffic-manager.name}" traffic_routing_method = "Weighted" dns_config { relative_name = "${var.service-name}-traffic-manager-${var.environment_name}" ttl = 30 } monitor_config { protocol = "HTTPS" port = 443 path = "/" } tags = "${local.tags}" } resource "azurerm_traffic_manager_endpoint" "traffic-endpoint-cancen" { name = "${var.service-name}-traffic-endpoint-cancen-${var.environment_name}" resource_group_name = "${azurerm_resource_group.traffic-manager.name}" profile_name = "${azurerm_traffic_manager_profile.traffic-manager-profile.name}" type = "azureEndpoints" target_resource_id = "${azurerm_app_service.cancen-app-service.id}" weight = 100 } resource "azurerm_traffic_manager_endpoint" "traffic-endpoint-centralus" { name = "${var.service-name}-traffic-endpoint-centralus-${var.environment_name}" resource_group_name = "${azurerm_resource_group.traffic-manager.name}" profile_name = "${azurerm_traffic_manager_profile.traffic-manager-profile.name}" type = "azureEndpoints" target_resource_id = "${azurerm_app_service.centralus-app-service.id}" weight = 100 } resource "azuread_application" "enterprise-application" { name = "${var.service-name-proper} (${var.environment_name})" homepage = "https://${local.public_hostname}" available_to_other_tenants = true oauth2_allow_implicit_flow = true reply_urls = [ "https://${local.public_hostname}", "https://${local.public_hostname}/", "http://localhost:4000" ] lifecycle { ignore_changes = [ "required_resource_access" ] } } resource "azurerm_application_insights" "app-insights" { application_type = "Web" location = "${azurerm_resource_group.cancen-rg.location}" name = "${var.service-name}-app-insights-${var.environment_name}" resource_group_name = "${azurerm_resource_group.cancen-rg.name}" tags = "${local.tags}" } output "instrumentation_key" { value = "${azurerm_application_insights.app-insights.instrumentation_key}" } output "app_id" { value = "${azurerm_application_insights.app-insights.app_id}" } resource "azurerm_monitor_autoscale_setting" "cancen-app-service-autoscale" { name = "${var.service-name}-cancen-app-service-autoscale" resource_group_name = "${azurerm_resource_group.cancen-rg.name}" location = "${azurerm_resource_group.cancen-rg.location}" target_resource_id = "${azurerm_app_service_plan.cancen-app-service-plan.id}" profile { name = "${var.service-name}-cancen-app-service-autoscale-profile" capacity { default = 4 minimum = 4 maximum = 10 } rule { metric_trigger { metric_name = "CpuPercentage" metric_resource_id = "${azurerm_app_service_plan.cancen-app-service-plan.id}" time_grain = "PT1M" statistic = "Average" time_window = "PT5M" time_aggregation = "Average" operator = "GreaterThan" threshold = 75 } scale_action { direction = "Increase" type = "ChangeCount" value = "1" cooldown = "PT1M" } } rule { metric_trigger { metric_name = "CpuPercentage" metric_resource_id = "${azurerm_app_service_plan.cancen-app-service-plan.id}" time_grain = "PT1M" statistic = "Average" time_window = "PT5M" time_aggregation = "Average" operator = "LessThan" threshold = 50 } scale_action { direction = "Decrease" type = "ChangeCount" value = "1" cooldown = "PT1M" } } } notification { email { send_to_subscription_administrator = true send_to_subscription_co_administrator = true } } # This needs to be started later because it was reporting as "finished" but not *really* being done # so when we'd try to apply the custom hostname, it would fail saying there was already # an operation in progress depends_on = [azurerm_app_service_custom_hostname_binding.cancen] } resource "azurerm_monitor_autoscale_setting" "centralus-app-service-autoscale" { name = "${var.service-name}-centralus-app-service-autoscale" resource_group_name = "${azurerm_resource_group.centralus-rg.name}" location = "${azurerm_resource_group.centralus-rg.location}" target_resource_id = "${azurerm_app_service_plan.centralus-app-service-plan.id}" profile { name = "${var.service-name}-centralus-app-service-autoscale-profile" capacity { default = 4 minimum = 4 maximum = 10 } rule { metric_trigger { metric_name = "CpuPercentage" metric_resource_id = "${azurerm_app_service_plan.centralus-app-service-plan.id}" time_grain = "PT1M" statistic = "Average" time_window = "PT5M" time_aggregation = "Average" operator = "GreaterThan" threshold = 75 } scale_action { direction = "Increase" type = "ChangeCount" value = "1" cooldown = "PT1M" } } rule { metric_trigger { metric_name = "CpuPercentage" metric_resource_id = "${azurerm_app_service_plan.centralus-app-service-plan.id}" time_grain = "PT1M" statistic = "Average" time_window = "PT5M" time_aggregation = "Average" operator = "LessThan" threshold = 50 } scale_action { direction = "Decrease" type = "ChangeCount" value = "1" cooldown = "PT1M" } } } notification { email { send_to_subscription_administrator = true send_to_subscription_co_administrator = true } } # This needs to be started later because it was reporting as "finished" but not *really* being done # so when we'd try to apply the custom hostname, it would fail saying there was already # an operation in progress depends_on = [azurerm_app_service_custom_hostname_binding.centralus] } resource "cloudflare_record" "cloudflare-cancen-dns-record-awverify" { zone_id = "${var.cloudflare-zone-id}" name = "awverify.${local.public_hostname}" value = "awverify.${azurerm_app_service.cancen-app-service.default_site_hostname}" type = "TXT" depends_on = [azurerm_app_service.cancen-app-service] } resource "null_resource" "cancen-dns-awverify-pause" { provisioner "local-exec" { command = "sleep 30" } triggers = { "before" = "${cloudflare_record.cloudflare-cancen-dns-record-awverify.id}" } } resource "azurerm_app_service_custom_hostname_binding" "cancen" { hostname = "${local.public_hostname}" app_service_name = "${azurerm_app_service.cancen-app-service.name}" resource_group_name = "${azurerm_resource_group.cancen-rg.name}" ssl_state = "SniEnabled" thumbprint = "${azurerm_app_service_certificate.cancen.thumbprint}" depends_on = [ null_resource.cancen-dns-awverify-pause, azurerm_app_service_certificate.cancen, cloudflare_record.cloudflare-cancen-dns-record-awverify ] } resource "cloudflare_record" "cloudflare-centralus-dns-record-awverify" { zone_id = "${var.cloudflare-zone-id}" name = "awverify.${local.public_hostname}" value = "awverify.${azurerm_app_service.centralus-app-service.default_site_hostname}" type = "TXT" depends_on = [ azurerm_app_service.centralus-app-service ] } resource "null_resource" "centralus-dns-awverify-pause" { provisioner "local-exec" { command = "sleep 30" } triggers = { "before" = "${cloudflare_record.cloudflare-centralus-dns-record-awverify.id}" } } resource "azurerm_app_service_custom_hostname_binding" "centralus" { hostname = "${local.public_hostname}" app_service_name = "${azurerm_app_service.centralus-app-service.name}" resource_group_name = "${azurerm_resource_group.centralus-rg.name}" ssl_state = "SniEnabled" thumbprint = "${azurerm_app_service_certificate.centralus.thumbprint}" depends_on = [ null_resource.centralus-dns-awverify-pause, azurerm_app_service_certificate.centralus, cloudflare_record.cloudflare-centralus-dns-record-awverify ] } resource "cloudflare_record" "cloudflare-dns-record-proxied" { zone_id = "${var.cloudflare-zone-id}" name = "${local.full_service_name}" value = "${azurerm_traffic_manager_profile.traffic-manager-profile.fqdn}" type = "CNAME" ttl = 1 proxied = true depends_on = [ azurerm_traffic_manager_profile.traffic-manager-profile, cloudflare_record.cloudflare-cancen-dns-record-awverify, azurerm_app_service_custom_hostname_binding.cancen, cloudflare_record.cloudflare-centralus-dns-record-awverify, azurerm_app_service_custom_hostname_binding.centralus ] } data "azurerm_key_vault" "cert" { name = "${var.key-vault-name}" resource_group_name = "${var.key-vault-rg}" } data "azurerm_key_vault_secret" "cert" { name = "${var.key-vault-secret-name}" key_vault_id = "${data.azurerm_key_vault.cert.id}" } resource "azurerm_key_vault_access_policy" "azure-app-service" { key_vault_id = "${data.azurerm_key_vault.cert.id}" tenant_id = "${azurerm_app_service.centralus-app-service.identity.0.tenant_id}" # This object is the Microsoft Azure Web App Service magic SP # as per https://azure.github.io/AppService/2016/05/24/Deploying-Azure-Web-App-Certificate-through-Key-Vault.html object_id = "abfa0a7c-a6b6-4736-8310-5855508787cd" secret_permissions = [ "get" ] certificate_permissions = [ "get" ] } resource "azurerm_key_vault_access_policy" "centralus" { key_vault_id = "${data.azurerm_key_vault.cert.id}" tenant_id = "${azurerm_app_service.centralus-app-service.identity.0.tenant_id}" object_id = "${azurerm_app_service.centralus-app-service.identity.0.principal_id}" secret_permissions = [ "get" ] certificate_permissions = [ "get" ] } resource "azurerm_key_vault_access_policy" "cancen" { key_vault_id = "${data.azurerm_key_vault.cert.id}" tenant_id = "${azurerm_app_service.cancen-app-service.identity.0.tenant_id}" object_id = "${azurerm_app_service.cancen-app-service.identity.0.principal_id}" secret_permissions = [ "get" ] certificate_permissions = [ "get" ] } resource "azurerm_app_service_certificate" "centralus" { name = "${local.full_service_name}-cert" resource_group_name = "${azurerm_resource_group.centralus-rg.name}" location = "${azurerm_resource_group.centralus-rg.location}" key_vault_secret_id = "${data.azurerm_key_vault_secret.cert.id}" depends_on = [ azurerm_key_vault_access_policy.azure-app-service, data.azurerm_key_vault_secret.cert ] } resource "azurerm_app_service_certificate" "cancen" { name = "${local.full_service_name}-cert" resource_group_name = "${azurerm_resource_group.cancen-rg.name}" location = "${azurerm_resource_group.cancen-rg.location}" key_vault_secret_id = "${data.azurerm_key_vault_secret.cert.id}" depends_on = [ azurerm_key_vault_access_policy.azure-app-service, data.azurerm_key_vault_secret.cert ] }