Managing Azure Cosmos DB with Terraform

Learn how to set up and manage Azure Cosmos DB using Terraform, including multi-region deployment, consistency levels, and backup configurations

Managing Azure Cosmos DB with Terraform

Azure Cosmos DB is a fully managed NoSQL database service. This guide demonstrates how to set up and manage Azure Cosmos DB using Terraform.

Video Tutorial

Learn more about managing Azure Cosmos DB with Terraform in this comprehensive video tutorial:

View Source Code

Prerequisites

  • Azure CLI configured with appropriate permissions
  • Terraform installed (version 1.0.0 or later)
  • Resource group created
  • Understanding of NoSQL database concepts

Project Structure

terraform-azure-cosmos/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── cosmos/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── policies/
    └── backup.json

Cosmos DB Configuration

Create modules/cosmos/main.tf:

# Cosmos DB Account
resource "azurerm_cosmosdb_account" "main" {
  name                = "${var.project_name}-cosmos"
  resource_group_name = var.resource_group_name
  location            = var.location
  offer_type         = "Standard"
  kind               = "GlobalDocumentDB"

  enable_automatic_failover = true
  enable_multiple_write_locations = true

  consistency_policy {
    consistency_level       = "BoundedStaleness"
    max_interval_in_seconds = 300
    max_staleness_prefix   = 100000
  }

  geo_location {
    location          = var.location
    failover_priority = 0
  }

  geo_location {
    location          = var.secondary_location
    failover_priority = 1
  }

  backup {
    type                = "Periodic"
    interval_in_minutes = 240
    retention_in_hours  = 8
    storage_redundancy  = "Geo"
  }

  identity {
    type = "SystemAssigned"
  }

  capabilities {
    name = "EnableServerless"
  }

  capabilities {
    name = "EnableAggregationPipeline"
  }

  capabilities {
    name = "mongoEnableDocLevelTTL"
  }

  public_network_access_enabled = false

  tags = var.tags
}

# SQL Database
resource "azurerm_cosmosdb_sql_database" "main" {
  name                = var.database_name
  resource_group_name = azurerm_cosmosdb_account.main.resource_group_name
  account_name        = azurerm_cosmosdb_account.main.name
  throughput          = var.database_throughput
}

# SQL Container
resource "azurerm_cosmosdb_sql_container" "main" {
  name                = var.container_name
  resource_group_name = azurerm_cosmosdb_account.main.resource_group_name
  account_name        = azurerm_cosmosdb_account.main.name
  database_name       = azurerm_cosmosdb_sql_database.main.name
  partition_key_path  = "/partitionKey"
  throughput         = var.container_throughput

  indexing_policy {
    indexing_mode = "consistent"

    included_path {
      path = "/*"
    }

    excluded_path {
      path = "/\"_etag\"/?"
    }
  }

  unique_key {
    paths = ["/id"]
  }

  conflict_resolution_policy {
    mode                     = "LastWriterWins"
    conflict_resolution_path = "/_ts"
  }
}

# Autoscale Settings
resource "azurerm_cosmosdb_sql_container" "autoscale" {
  name                = "${var.container_name}-autoscale"
  resource_group_name = azurerm_cosmosdb_account.main.resource_group_name
  account_name        = azurerm_cosmosdb_account.main.name
  database_name       = azurerm_cosmosdb_sql_database.main.name
  partition_key_path  = "/partitionKey"

  autoscale_settings {
    max_throughput = var.max_throughput
  }
}

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           = "cosmos-subnet"
    address_prefix = "10.0.1.0/24"
    service_endpoints = ["Microsoft.AzureCosmosDB"]
  }

  tags = var.tags
}

resource "azurerm_private_endpoint" "cosmos" {
  name                = "${var.project_name}-pe"
  location            = var.location
  resource_group_name = var.resource_group_name
  subnet_id           = azurerm_virtual_network.main.subnet.*.id[0]

  private_service_connection {
    name                           = "${var.project_name}-psc"
    private_connection_resource_id = azurerm_cosmosdb_account.main.id
    is_manual_connection          = false
    subresource_names            = ["Sql"]
  }

  private_dns_zone_group {
    name                 = "default"
    private_dns_zone_ids = [azurerm_private_dns_zone.cosmos.id]
  }
}

resource "azurerm_private_dns_zone" "cosmos" {
  name                = "privatelink.documents.azure.com"
  resource_group_name = var.resource_group_name
}

resource "azurerm_private_dns_zone_virtual_network_link" "cosmos" {
  name                  = "${var.project_name}-vnet-link"
  resource_group_name   = var.resource_group_name
  private_dns_zone_name = azurerm_private_dns_zone.cosmos.name
  virtual_network_id    = azurerm_virtual_network.main.id
}

Security Configuration

  1. Role Assignments
resource "azurerm_role_assignment" "cosmos_contributor" {
  scope                = azurerm_cosmosdb_account.main.id
  role_definition_name = "Cosmos DB Built-in Data Contributor"
  principal_id         = var.contributor_principal_id
}

resource "azurerm_role_assignment" "cosmos_reader" {
  scope                = azurerm_cosmosdb_account.main.id
  role_definition_name = "Cosmos DB Built-in Data Reader"
  principal_id         = var.reader_principal_id
}
  1. Customer-Managed Keys
resource "azurerm_key_vault_key" "cosmos" {
  name         = "${var.project_name}-key"
  key_vault_id = var.key_vault_id
  key_type     = "RSA"
  key_size     = 2048

  key_opts = [
    "decrypt",
    "encrypt",
    "sign",
    "unwrapKey",
    "verify",
    "wrapKey",
  ]
}

resource "azurerm_cosmosdb_account_customer_managed_key" "main" {
  cosmosdb_account_id = azurerm_cosmosdb_account.main.id
  key_vault_id        = var.key_vault_id
  key_name            = azurerm_key_vault_key.cosmos.name
}

Monitoring Configuration

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

  log {
    category = "DataPlaneRequests"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }

  log {
    category = "QueryRuntimeStatistics"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }

  metric {
    category = "Requests"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }
}

resource "azurerm_monitor_metric_alert" "cosmos" {
  name                = "${var.project_name}-ru-alert"
  resource_group_name = var.resource_group_name
  scopes              = [azurerm_cosmosdb_account.main.id]
  description         = "Alert when RU consumption is high"

  criteria {
    metric_namespace = "Microsoft.DocumentDB/databaseAccounts"
    metric_name      = "NormalizedRUConsumption"
    aggregation      = "Average"
    operator         = "GreaterThan"
    threshold        = 80
  }

  action {
    action_group_id = var.action_group_id
  }
}

Advanced Features

  1. Stored Procedures
resource "azurerm_cosmosdb_sql_stored_procedure" "main" {
  name                = "spExample"
  resource_group_name = azurerm_cosmosdb_account.main.resource_group_name
  account_name        = azurerm_cosmosdb_account.main.name
  database_name       = azurerm_cosmosdb_sql_database.main.name
  container_name      = azurerm_cosmosdb_sql_container.main.name

  body = <<BODY
function () {
    var context = getContext();
    var response = context.getResponse();
    response.setBody("Hello, World");
}
BODY
}
  1. Change Feed
resource "azurerm_cosmosdb_sql_container" "change_feed" {
  name                = "${var.container_name}-changes"
  resource_group_name = azurerm_cosmosdb_account.main.resource_group_name
  account_name        = azurerm_cosmosdb_account.main.name
  database_name       = azurerm_cosmosdb_sql_database.main.name
  partition_key_path  = "/partitionKey"

  change_feed_policy {
    retention_duration = "P1D"
  }
}

Best Practices

  1. Performance

    • Choose appropriate consistency level
    • Design efficient partition keys
    • Use indexing policies
    • Monitor RU consumption
  2. Security

    • Enable private endpoints
    • Use RBAC
    • Enable encryption
    • Implement network isolation
  3. High Availability

    • Enable multi-region writes
    • Configure automatic failover
    • Use geo-redundant backups
    • Monitor health
  4. Cost Optimization

    • Choose consistency level
    • Optimize queries
    • Choose appropriate APIs

Conclusion

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

  • Multi-region deployment
  • Security and encryption
  • Monitoring and alerts
  • Backup and disaster recovery

Remember to:

  • Monitor RU consumption
  • Review security settings
  • Maintain backups
  • Update access controls