terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.80"
}
azuread = {
source = "hashicorp/azuread"
version = "~> 2.45"
}
random = {
source = "hashicorp/random"
version = "~> 3.5"
}
}
backend "azurerm" {
resource_group_name = "rg-terraform-state"
storage_account_name = "stterraformstate"
container_name = "tfstate"
key = "azure-ai-mcp-server/prod/terraform.tfstate"
}
}
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
key_vault {
purge_soft_delete_on_destroy = true
recover_soft_deleted_key_vaults = true
}
}
}
provider "azuread" {}
# Local variables
locals {
environment = "prod"
project = "azure-ai-mcp-server"
location = var.location
common_tags = {
Environment = local.environment
Project = local.project
ManagedBy = "Terraform"
Owner = "pXLabs"
CostCenter = "Engineering"
}
# Resource naming convention
resource_prefix = "${local.project}-${local.environment}"
}
# Data sources
data "azurerm_client_config" "current" {}
data "azuread_client_config" "current" {}
# Resource Group
resource "azurerm_resource_group" "main" {
name = "rg-${local.resource_prefix}"
location = local.location
tags = local.common_tags
}
# Key Vault for secrets management
resource "azurerm_key_vault" "main" {
name = "kv-${local.resource_prefix}-${random_string.suffix.result}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "premium"
enabled_for_deployment = true
enabled_for_disk_encryption = true
enabled_for_template_deployment = true
enable_rbac_authorization = true
purge_protection_enabled = true
soft_delete_retention_days = 90
network_acls {
default_action = "Deny"
bypass = "AzureServices"
ip_rules = var.allowed_ip_ranges
}
tags = local.common_tags
}
# Random string for unique naming
resource "random_string" "suffix" {
length = 8
special = false
upper = false
}
# Container Registry
resource "azurerm_container_registry" "main" {
name = "acr${replace(local.resource_prefix, "-", "")}${random_string.suffix.result}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
sku = "Premium"
admin_enabled = false
identity {
type = "SystemAssigned"
}
encryption {
enabled = true
key_vault_key_id = azurerm_key_vault_key.acr_encryption.id
identity_client_id = azurerm_user_assigned_identity.acr_encryption.client_id
}
network_rule_set {
default_action = "Deny"
ip_rule {
action = "Allow"
ip_range = "0.0.0.0/0" # Restrict this in production
}
}
tags = local.common_tags
}
# User Assigned Identity for ACR encryption
resource "azurerm_user_assigned_identity" "acr_encryption" {
name = "id-acr-encryption-${local.resource_prefix}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
tags = local.common_tags
}
# Key Vault Key for ACR encryption
resource "azurerm_key_vault_key" "acr_encryption" {
name = "acr-encryption-key"
key_vault_id = azurerm_key_vault.main.id
key_type = "RSA"
key_size = 2048
key_opts = [
"decrypt",
"encrypt",
"sign",
"unwrapKey",
"verify",
"wrapKey",
]
depends_on = [azurerm_role_assignment.kv_admin]
}
# Role assignments for Key Vault
resource "azurerm_role_assignment" "kv_admin" {
scope = azurerm_key_vault.main.id
role_definition_name = "Key Vault Administrator"
principal_id = data.azurerm_client_config.current.object_id
}
resource "azurerm_role_assignment" "acr_encryption_kv" {
scope = azurerm_key_vault.main.id
role_definition_name = "Key Vault Crypto Service Encryption User"
principal_id = azurerm_user_assigned_identity.acr_encryption.principal_id
}
# Log Analytics Workspace
resource "azurerm_log_analytics_workspace" "main" {
name = "law-${local.resource_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
sku = "PerGB2018"
retention_in_days = 90
daily_quota_gb = 10
tags = local.common_tags
}
# Application Insights
resource "azurerm_application_insights" "main" {
name = "ai-${local.resource_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
workspace_id = azurerm_log_analytics_workspace.main.id
application_type = "Node.JS"
tags = local.common_tags
}
# Azure OpenAI Service
resource "azurerm_cognitive_account" "openai" {
name = "cog-openai-${local.resource_prefix}"
location = var.openai_location
resource_group_name = azurerm_resource_group.main.name
kind = "OpenAI"
sku_name = "S0"
identity {
type = "SystemAssigned"
}
network_acls {
default_action = "Deny"
ip_rules = var.allowed_ip_ranges
}
tags = local.common_tags
}
# Azure OpenAI Deployments
resource "azurerm_cognitive_deployment" "gpt4" {
name = "gpt-4"
cognitive_account_id = azurerm_cognitive_account.openai.id
model {
format = "OpenAI"
name = "gpt-4"
version = "0613"
}
scale {
type = "Standard"
capacity = 10
}
}
resource "azurerm_cognitive_deployment" "gpt35_turbo" {
name = "gpt-35-turbo"
cognitive_account_id = azurerm_cognitive_account.openai.id
model {
format = "OpenAI"
name = "gpt-35-turbo"
version = "0613"
}
scale {
type = "Standard"
capacity = 20
}
}
resource "azurerm_cognitive_deployment" "text_embedding" {
name = "text-embedding-ada-002"
cognitive_account_id = azurerm_cognitive_account.openai.id
model {
format = "OpenAI"
name = "text-embedding-ada-002"
version = "2"
}
scale {
type = "Standard"
capacity = 10
}
}
# Cognitive Services for Text Analytics, Computer Vision, Face API
resource "azurerm_cognitive_account" "text_analytics" {
name = "cog-text-${local.resource_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
kind = "TextAnalytics"
sku_name = "S"
identity {
type = "SystemAssigned"
}
network_acls {
default_action = "Deny"
ip_rules = var.allowed_ip_ranges
}
tags = local.common_tags
}
resource "azurerm_cognitive_account" "computer_vision" {
name = "cog-vision-${local.resource_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
kind = "ComputerVision"
sku_name = "S1"
identity {
type = "SystemAssigned"
}
network_acls {
default_action = "Deny"
ip_rules = var.allowed_ip_ranges
}
tags = local.common_tags
}
resource "azurerm_cognitive_account" "face" {
name = "cog-face-${local.resource_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
kind = "Face"
sku_name = "S0"
identity {
type = "SystemAssigned"
}
network_acls {
default_action = "Deny"
ip_rules = var.allowed_ip_ranges
}
tags = local.common_tags
}
# Storage Account
resource "azurerm_storage_account" "main" {
name = "st${replace(local.resource_prefix, "-", "")}${random_string.suffix.result}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "ZRS"
account_kind = "StorageV2"
identity {
type = "SystemAssigned"
}
blob_properties {
versioning_enabled = true
change_feed_enabled = true
change_feed_retention_in_days = 7
delete_retention_policy {
days = 30
}
container_delete_retention_policy {
days = 30
}
}
network_rules {
default_action = "Deny"
ip_rules = var.allowed_ip_ranges
bypass = ["AzureServices"]
}
tags = local.common_tags
}
# Container Apps Environment
resource "azurerm_container_app_environment" "main" {
name = "cae-${local.resource_prefix}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
tags = local.common_tags
}
# Container App for Blue-Green Deployment
resource "azurerm_container_app" "main" {
name = "ca-${local.resource_prefix}"
container_app_environment_id = azurerm_container_app_environment.main.id
resource_group_name = azurerm_resource_group.main.name
revision_mode = "Multiple"
identity {
type = "SystemAssigned"
}
registry {
server = azurerm_container_registry.main.login_server
identity = azurerm_container_app.main.identity[0].principal_id
}
template {
min_replicas = 2
max_replicas = 10
container {
name = "azure-ai-mcp-server"
image = "${azurerm_container_registry.main.login_server}/azure-ai-mcp-server:${var.image_tag}"
cpu = 1.0
memory = "2Gi"
env {
name = "AZURE_OPENAI_ENDPOINT"
value = azurerm_cognitive_account.openai.endpoint
}
env {
name = "AZURE_OPENAI_API_KEY"
secret_name = "azure-openai-api-key"
}
env {
name = "AZURE_COGNITIVE_SERVICES_ENDPOINT"
value = azurerm_cognitive_account.text_analytics.endpoint
}
env {
name = "AZURE_COGNITIVE_SERVICES_KEY"
secret_name = "azure-cognitive-services-key"
}
env {
name = "AZURE_STORAGE_CONNECTION_STRING"
secret_name = "azure-storage-connection-string"
}
env {
name = "AZURE_APPLICATION_INSIGHTS_CONNECTION_STRING"
value = azurerm_application_insights.main.connection_string
}
env {
name = "LOG_LEVEL"
value = "info"
}
liveness_probe {
transport = "HTTP"
port = 3000
path = "/health"
}
readiness_probe {
transport = "HTTP"
port = 3000
path = "/ready"
}
}
http_scale_rule {
name = "http-scale"
concurrent_requests = 100
}
}
secret {
name = "azure-openai-api-key"
value = azurerm_cognitive_account.openai.primary_access_key
}
secret {
name = "azure-cognitive-services-key"
value = azurerm_cognitive_account.text_analytics.primary_access_key
}
secret {
name = "azure-storage-connection-string"
value = azurerm_storage_account.main.primary_connection_string
}
ingress {
allow_insecure_connections = false
external_enabled = true
target_port = 3000
transport = "http"
traffic_weight {
percentage = var.switch_traffic ? 100 : 0
latest_revision = var.deployment_slot == "blue"
}
}
tags = local.common_tags
}
# Chaos Studio Target
resource "azurerm_chaos_studio_target" "container_app" {
location = azurerm_resource_group.main.location
target_resource_id = azurerm_container_app.main.id
target_type = "Microsoft-ContainerApp"
}
# Chaos Studio Capability
resource "azurerm_chaos_studio_capability" "container_app_stop" {
chaos_studio_target_id = azurerm_chaos_studio_target.container_app.id
capability_type = "Stop-1.0"
}
# Action Group for Alerts
resource "azurerm_monitor_action_group" "main" {
name = "ag-${local.resource_prefix}"
resource_group_name = azurerm_resource_group.main.name
short_name = "pxlabs"
email_receiver {
name = "admin"
email_address = var.alert_email
}
webhook_receiver {
name = "slack"
service_uri = var.slack_webhook_url
}
tags = local.common_tags
}
# Metric Alerts
resource "azurerm_monitor_metric_alert" "high_error_rate" {
name = "High Error Rate - ${local.resource_prefix}"
resource_group_name = azurerm_resource_group.main.name
scopes = [azurerm_container_app.main.id]
description = "Alert when error rate exceeds 5%"
severity = 2
frequency = "PT1M"
window_size = "PT5M"
criteria {
metric_namespace = "Microsoft.App/containerApps"
metric_name = "Requests"
aggregation = "Total"
operator = "GreaterThan"
threshold = 5
dimension {
name = "StatusCodeClass"
operator = "Include"
values = ["5xx"]
}
}
action {
action_group_id = azurerm_monitor_action_group.main.id
}
tags = local.common_tags
}
resource "azurerm_monitor_metric_alert" "high_response_time" {
name = "High Response Time - ${local.resource_prefix}"
resource_group_name = azurerm_resource_group.main.name
scopes = [azurerm_application_insights.main.id]
description = "Alert when average response time exceeds 1 second"
severity = 2
frequency = "PT1M"
window_size = "PT5M"
criteria {
metric_namespace = "Microsoft.Insights/components"
metric_name = "requests/duration"
aggregation = "Average"
operator = "GreaterThan"
threshold = 1000
}
action {
action_group_id = azurerm_monitor_action_group.main.id
}
tags = local.common_tags
}
# Role assignments for Container App to access ACR
resource "azurerm_role_assignment" "container_app_acr" {
scope = azurerm_container_registry.main.id
role_definition_name = "AcrPull"
principal_id = azurerm_container_app.main.identity[0].principal_id
}
# Role assignments for Container App to access Cognitive Services
resource "azurerm_role_assignment" "container_app_openai" {
scope = azurerm_cognitive_account.openai.id
role_definition_name = "Cognitive Services OpenAI User"
principal_id = azurerm_container_app.main.identity[0].principal_id
}
resource "azurerm_role_assignment" "container_app_cognitive" {
scope = azurerm_cognitive_account.text_analytics.id
role_definition_name = "Cognitive Services User"
principal_id = azurerm_container_app.main.identity[0].principal_id
}
# Store secrets in Key Vault
resource "azurerm_key_vault_secret" "openai_key" {
name = "azure-openai-api-key"
value = azurerm_cognitive_account.openai.primary_access_key
key_vault_id = azurerm_key_vault.main.id
depends_on = [azurerm_role_assignment.kv_admin]
}
resource "azurerm_key_vault_secret" "cognitive_key" {
name = "azure-cognitive-services-key"
value = azurerm_cognitive_account.text_analytics.primary_access_key
key_vault_id = azurerm_key_vault.main.id
depends_on = [azurerm_role_assignment.kv_admin]
}
resource "azurerm_key_vault_secret" "storage_connection" {
name = "azure-storage-connection-string"
value = azurerm_storage_account.main.primary_connection_string
key_vault_id = azurerm_key_vault.main.id
depends_on = [azurerm_role_assignment.kv_admin]
}