Managing Azure CDN with Terraform

Learn how to set up and manage Azure CDN using Terraform, including endpoints, rules, and security configurations

Managing Azure CDN with Terraform

Azure Content Delivery Network (CDN) is a distributed network of servers that can efficiently deliver web content to users. This guide demonstrates how to set up and manage Azure CDN using Terraform.

Video Tutorial

Learn more about managing Azure CDN 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
  • Understanding of CDN concepts

Project Structure

terraform-azure-cdn/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── cdn/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── rules/
    └── routing.json

CDN Configuration

Create modules/cdn/main.tf:

# CDN Profile
resource "azurerm_cdn_profile" "main" {
  name                = "${var.project_name}-cdn"
  location            = var.location
  resource_group_name = var.resource_group_name
  sku                 = "Standard_Microsoft"

  tags = var.tags
}

# CDN Endpoint
resource "azurerm_cdn_endpoint" "main" {
  name                = "${var.project_name}-endpoint"
  profile_name        = azurerm_cdn_profile.main.name
  location            = var.location
  resource_group_name = var.resource_group_name

  origin {
    name       = "primary"
    host_name  = var.origin_hostname
    http_port  = 80
    https_port = 443

    private_link {
      request_message        = "Request access for CDN"
      target_resource_id    = var.origin_resource_id
      location             = var.location
    }
  }

  origin_host_header = var.origin_hostname

  is_compression_enabled = true
  content_types_to_compress = [
    "text/plain",
    "text/html",
    "text/css",
    "text/javascript",
    "application/x-javascript",
    "application/javascript",
    "application/json",
    "application/xml"
  ]

  optimization_type = "GeneralWebDelivery"

  geo_filter {
    relative_path = "/*"
    action        = "Allow"
    country_codes = ["US", "CA", "GB", "DE"]
  }

  global_delivery_rule {
    cache_expiration_action {
      behavior = "SetIfMissing"
      duration = "7.00:00:00"
    }

    modify_request_header_action {
      action = "Append"
      name   = "X-CDN-Header"
      value  = "Managed by Terraform"
    }
  }

  delivery_rule {
    name  = "EnforceHTTPS"
    order = 1

    request_scheme_condition {
      operator     = "Equal"
      match_values = ["HTTP"]
    }

    url_redirect_action {
      redirect_type = "Found"
      protocol      = "Https"
    }
  }

  delivery_rule {
    name  = "CacheImages"
    order = 2

    file_extension_condition {
      operator     = "Equal"
      match_values = ["jpg", "jpeg", "png", "gif"]
    }

    cache_expiration_action {
      behavior = "Override"
      duration = "30.00:00:00"
    }
  }

  tags = var.tags
}

# Front Door Profile
resource "azurerm_cdn_frontdoor_profile" "main" {
  name                = "${var.project_name}-fd"
  resource_group_name = var.resource_group_name
  sku_name            = "Premium_AzureFrontDoor"

  tags = var.tags
}

# Front Door Endpoint
resource "azurerm_cdn_frontdoor_endpoint" "main" {
  name                     = "${var.project_name}-fd-endpoint"
  cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.main.id

  enabled = true
}

# Front Door Origin Group
resource "azurerm_cdn_frontdoor_origin_group" "main" {
  name                     = "${var.project_name}-origin-group"
  cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.main.id

  load_balancing {
    sample_size                 = 4
    successful_samples_required = 3
    additional_latency_in_ms    = 50
  }

  health_probe {
    interval_in_seconds = 100
    path                = "/health"
    protocol            = "Https"
    request_type        = "HEAD"
  }
}

# Front Door Origin
resource "azurerm_cdn_frontdoor_origin" "main" {
  name                          = "${var.project_name}-origin"
  cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.main.id

  enabled                        = true
  host_name                      = var.origin_hostname
  http_port                      = 80
  https_port                     = 443
  origin_host_header            = var.origin_hostname
  priority                       = 1
  weight                         = 1000
  certificate_name_check_enabled = true

  private_link {
    request_message       = "Request access for Front Door"
    target_type          = "sites"
    location            = var.location
    private_link_target_id = var.origin_resource_id
  }
}

# Front Door Route
resource "azurerm_cdn_frontdoor_route" "main" {
  name                          = "${var.project_name}-route"
  cdn_frontdoor_endpoint_id     = azurerm_cdn_frontdoor_endpoint.main.id
  cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.main.id
  cdn_frontdoor_origin_ids      = [azurerm_cdn_frontdoor_origin.main.id]

  supported_protocols    = ["Http", "Https"]
  patterns_to_match     = ["/*"]
  forwarding_protocol   = "HttpsOnly"
  link_to_default_domain = true
  https_redirect_enabled = true

  cache {
    query_string_caching_behavior = "IgnoreSpecifiedQueryStrings"
    query_strings                 = ["account", "settings"]
    compression_enabled           = true
    content_types_to_compress     = ["text/html", "text/javascript", "text/css"]
  }
}

# WAF Policy
resource "azurerm_cdn_frontdoor_firewall_policy" "main" {
  name                = "${var.project_name}-waf"
  resource_group_name = var.resource_group_name
  sku_name            = azurerm_cdn_frontdoor_profile.main.sku_name
  enabled             = true
  mode                = "Prevention"

  managed_rule {
    type    = "DefaultRuleSet"
    version = "1.0"
  }

  managed_rule {
    type    = "Microsoft_BotManagerRuleSet"
    version = "1.0"
  }

  custom_rule {
    name     = "BlockIPRange"
    enabled  = true
    priority = 100
    type     = "MatchRule"
    action   = "Block"

    match_condition {
      match_variable     = "RemoteAddr"
      operator          = "IPMatch"
      negation_condition = false
      match_values      = ["192.168.1.0/24", "10.0.0.0/24"]
    }
  }
}

# Custom Domain
resource "azurerm_cdn_frontdoor_custom_domain" "main" {
  name                     = "${var.project_name}-domain"
  cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.main.id
  dns_zone_id              = var.dns_zone_id
  host_name                = "www.example.com"

  tls {
    certificate_type    = "ManagedCertificate"
    minimum_tls_version = "TLS12"
  }
}

Security Configuration

  1. Role Assignments
resource "azurerm_role_assignment" "cdn_contributor" {
  scope                = azurerm_cdn_profile.main.id
  role_definition_name = "CDN Profile Contributor"
  principal_id         = var.contributor_principal_id
}

resource "azurerm_role_assignment" "cdn_endpoint_contributor" {
  scope                = azurerm_cdn_endpoint.main.id
  role_definition_name = "CDN Endpoint Contributor"
  principal_id         = var.contributor_principal_id
}
  1. Custom Rules
resource "azurerm_cdn_frontdoor_rule_set" "main" {
  name                     = "${var.project_name}-ruleset"
  cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.main.id
}

resource "azurerm_cdn_frontdoor_rule" "redirect" {
  name                      = "redirect-rule"
  cdn_frontdoor_rule_set_id = azurerm_cdn_frontdoor_rule_set.main.id
  order                     = 1
  behavior_on_match         = "Continue"

  conditions {
    url_path_condition {
      operator         = "BeginsWith"
      negate_condition = false
      match_values     = ["/old-path/"]
    }
  }

  actions {
    url_redirect_action {
      redirect_type        = "Found"
      destination_path    = "/new-path/"
      destination_protocol = "Https"
    }
  }
}

Monitoring Configuration

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

  log {
    category = "FrontDoorAccessLog"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }

  metric {
    category = "AllMetrics"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }
}

resource "azurerm_monitor_metric_alert" "cdn" {
  name                = "${var.project_name}-latency-alert"
  resource_group_name = var.resource_group_name
  scopes              = [azurerm_cdn_profile.main.id]
  description         = "Alert when latency is high"

  criteria {
    metric_namespace = "Microsoft.Cdn/profiles"
    metric_name      = "TotalLatency"
    aggregation      = "Average"
    operator         = "GreaterThan"
    threshold        = 1000
  }

  action {
    action_group_id = var.action_group_id
  }
}

Best Practices

  1. Performance

    • Enable compression
    • Configure caching
    • Use custom domains
    • Implement rules
  2. Security

    • Enable WAF
    • Use HTTPS
    • Configure geo-filtering
    • Implement access control
  3. Cost Optimization

    • Choose appropriate SKU
    • Monitor bandwidth usage
    • Optimize caching
    • Use compression
  4. Reliability

    • Configure health probes
    • Implement load balancing
    • Use multiple origins
    • Monitor availability

Conclusion

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

  • Global content delivery
  • Performance optimization
  • Security and WAF
  • Custom domain support

Remember to:

  • Monitor performance
  • Review security rules
  • Update caching policies
  • Manage custom domains