Deploying Azure App Service with Terraform

Learn how to set up and manage Azure App Service for web applications using Terraform, including scaling, monitoring, and best practices

Deploying Azure App Service with Terraform

Azure App Service is a fully managed platform for building, deploying, and scaling web apps. This guide demonstrates how to set up and manage Azure App Service using Terraform.

Video Tutorial

Learn more about managing Azure App Service with Terraform in this comprehensive video tutorial:

Prerequisites

  • Azure CLI configured with appropriate permissions
  • Terraform installed (version 1.0.0 or later)
  • Resource group created
  • Application code ready for deployment

Project Structure

terraform-azure-appservice/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── appservice/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── app/
    └── src/

App Service Configuration

Create modules/appservice/main.tf:

# App Service Plan
resource "azurerm_service_plan" "main" {
  name                = "${var.project_name}-plan"
  location            = var.location
  resource_group_name = var.resource_group_name
  os_type             = "Linux"
  sku_name            = var.sku_name

  tags = var.tags
}

# App Service
resource "azurerm_linux_web_app" "main" {
  name                = "${var.project_name}-app"
  location            = var.location
  resource_group_name = var.resource_group_name
  service_plan_id     = azurerm_service_plan.main.id

  site_config {
    always_on        = true
    http2_enabled    = true
    
    application_stack {
      node_version = "18-lts"  # Or your preferred runtime
    }

    ip_restriction {
      ip_address = var.allowed_ip_range
      priority   = 100
      action     = "Allow"
    }

    cors {
      allowed_origins = var.cors_allowed_origins
    }
  }

  app_settings = {
    "WEBSITE_NODE_DEFAULT_VERSION" = "~18"
    "WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false"
    "DOCKER_REGISTRY_SERVER_URL"    = var.docker_registry_url
    "DOCKER_REGISTRY_SERVER_USERNAME" = var.docker_registry_username
    "DOCKER_REGISTRY_SERVER_PASSWORD" = var.docker_registry_password
    "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_application_insights.main.instrumentation_key
  }

  identity {
    type = "SystemAssigned"
  }

  auth_settings {
    enabled                       = true
    default_provider             = "AzureActiveDirectory"
    unauthenticated_client_action = "RedirectToLoginPage"

    active_directory {
      client_id         = var.aad_client_id
      client_secret     = var.aad_client_secret
      allowed_audiences = [var.app_url]
    }
  }

  logs {
    http_logs {
      file_system {
        retention_in_days = 7
        retention_in_mb   = 35
      }
    }

    application_logs {
      file_system_level = "Information"
    }
  }

  tags = var.tags
}

# Application Insights
resource "azurerm_application_insights" "main" {
  name                = "${var.project_name}-appinsights"
  location            = var.location
  resource_group_name = var.resource_group_name
  application_type    = "web"

  tags = var.tags
}

# Custom Domain
resource "azurerm_app_service_custom_hostname_binding" "main" {
  hostname            = var.custom_domain
  app_service_name    = azurerm_linux_web_app.main.name
  resource_group_name = var.resource_group_name

  depends_on = [
    azurerm_dns_txt_record.verification
  ]
}

# SSL Certificate
resource "azurerm_app_service_certificate" "main" {
  name                = "${var.project_name}-cert"
  resource_group_name = var.resource_group_name
  location            = var.location
  pfx_blob           = filebase64(var.ssl_certificate_path)
  password           = var.ssl_certificate_password
}

# SSL Binding
resource "azurerm_app_service_certificate_binding" "main" {
  hostname_binding_id = azurerm_app_service_custom_hostname_binding.main.id
  certificate_id      = azurerm_app_service_certificate.main.id
  ssl_state          = "SniEnabled"
}

Scaling Configuration

  1. Auto-scaling Rules
resource "azurerm_monitor_autoscale_setting" "main" {
  name                = "${var.project_name}-autoscale"
  resource_group_name = var.resource_group_name
  location            = var.location
  target_resource_id  = azurerm_service_plan.main.id

  profile {
    name = "defaultProfile"

    capacity {
      default = var.default_instance_count
      minimum = var.min_instance_count
      maximum = var.max_instance_count
    }

    rule {
      metric_trigger {
        metric_name        = "CpuPercentage"
        metric_resource_id = azurerm_service_plan.main.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  = "PT5M"
      }
    }

    rule {
      metric_trigger {
        metric_name        = "CpuPercentage"
        metric_resource_id = azurerm_service_plan.main.id
        time_grain        = "PT1M"
        statistic         = "Average"
        time_window      = "PT5M"
        time_aggregation = "Average"
        operator         = "LessThan"
        threshold        = 25
      }

      scale_action {
        direction = "Decrease"
        type      = "ChangeCount"
        value     = "1"
        cooldown  = "PT5M"
      }
    }
  }
}

Network Configuration

  1. Virtual Network Integration
resource "azurerm_virtual_network" "main" {
  name                = "${var.project_name}-vnet"
  location            = var.location
  resource_group_name = var.resource_group_name
  address_space       = ["10.0.0.0/16"]

  subnet {
    name           = "integration-subnet"
    address_prefix = "10.0.1.0/24"

    delegation {
      name = "appservice"
      
      service_delegation {
        name    = "Microsoft.Web/serverFarms"
        actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
      }
    }
  }

  tags = var.tags
}

resource "azurerm_app_service_virtual_network_swift_connection" "main" {
  app_service_id = azurerm_linux_web_app.main.id
  subnet_id      = azurerm_virtual_network.main.subnet.*.id[0]
}

Monitoring Configuration

  1. Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "app" {
  name                       = "${var.project_name}-diag"
  target_resource_id        = azurerm_linux_web_app.main.id
  log_analytics_workspace_id = var.log_analytics_workspace_id

  log {
    category = "AppServiceHTTPLogs"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }

  log {
    category = "AppServiceConsoleLogs"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }

  metric {
    category = "AllMetrics"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }
}

resource "azurerm_monitor_metric_alert" "response_time" {
  name                = "${var.project_name}-response-time-alert"
  resource_group_name = var.resource_group_name
  scopes              = [azurerm_linux_web_app.main.id]
  description         = "Alert when response time is high"

  criteria {
    metric_namespace = "Microsoft.Web/sites"
    metric_name      = "HttpResponseTime"
    aggregation      = "Average"
    operator         = "GreaterThan"
    threshold        = 5
  }

  action {
    action_group_id = var.action_group_id
  }
}

Deployment Slots

  1. Staging Slot
resource "azurerm_linux_web_app_slot" "staging" {
  name           = "staging"
  app_service_id = azurerm_linux_web_app.main.id

  site_config {
    always_on     = true
    http2_enabled = true

    application_stack {
      node_version = "18-lts"
    }
  }

  app_settings = merge(
    azurerm_linux_web_app.main.app_settings,
    {
      "ASPNETCORE_ENVIRONMENT" = "Staging"
    }
  )

  identity {
    type = "SystemAssigned"
  }

  tags = var.tags
}

resource "azurerm_app_service_slot_virtual_network_swift_connection" "staging" {
  slot_name      = azurerm_linux_web_app_slot.staging.name
  app_service_id = azurerm_linux_web_app.main.id
  subnet_id      = azurerm_virtual_network.main.subnet.*.id[0]
}

Best Practices

  1. Security

    • Enable authentication
    • Use managed identities
    • Implement IP restrictions
    • Enable HTTPS only
  2. Performance

    • Enable auto-scaling
    • Configure staging slots
    • Use premium tier for production
    • Enable HTTP/2
  3. Monitoring

    • Enable Application Insights
    • Configure alerts
    • Set up logging
    • Monitor performance
  4. Cost Optimization

    • Right-size app service plan
    • Use auto-scaling
    • Monitor usage
    • Consider reserved instances

Advanced Features

  1. WebJobs
resource "azurerm_app_service_webjob_continuous" "example" {
  name            = "example-webjob"
  app_service_id  = azurerm_linux_web_app.main.id
  command         = "node worker.js"
  script_name     = "worker.js"
  script_content  = filebase64("${path.module}/worker.js")
}
  1. Hybrid Connections
resource "azurerm_app_service_hybrid_connection" "example" {
  app_service_name    = azurerm_linux_web_app.main.name
  resource_group_name = var.resource_group_name
  relay_id           = azurerm_relay_hybrid_connection.example.id
  hostname           = "internal.example.com"
  port               = 3306
}

Conclusion

You’ve learned how to set up and manage Azure App Service using Terraform. This setup provides:

  • Secure web app deployment
  • Auto-scaling capabilities
  • Monitoring and alerts
  • Custom domain and SSL

Remember to:

  • Monitor performance
  • Implement security best practices
  • Optimize costs
  • Use deployment slots for zero-downtime deployments