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
]
}