diff --git "a/.github/workflows/ci\342\200\221cd.yml" b/.github/workflows/cicd.yml similarity index 97% rename from ".github/workflows/ci\342\200\221cd.yml" rename to .github/workflows/cicd.yml index 3462dbe..c7cff99 100644 --- "a/.github/workflows/ci\342\200\221cd.yml" +++ b/.github/workflows/cicd.yml @@ -59,7 +59,8 @@ jobs: run: bicep build main.bicep - name: Bicep what-if (simulated) - run: echo "Skipped: would run az deployment group what-if here" + run: | + echo "Skipped: would run az deployment group what-if here" # # OPTIONAL: OIDC Login to Azure (commented) diff --git a/README.md b/README.md index 3424b19..446def7 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,14 @@ This is a mock setup intended as a skills demonstration. Actual deployment steps - Traffic Manager actions are simulated via CLI echo commands - SLO and rollback are represented by a test script with a forced failure +## Terraform modules + +- **network**: Basic network module for hub-spoke architecture [(details)](./network/README.md) +- **aks**: AKS cluster module [(details)](./aks/README.md) +- **appgw**: Application Gateway module [(details)](./appgw/README.md) +- **cosmosdb**: Cosmos DB module [(details)](./cosmosdb/README.md) + + ## How to Use ```bash diff --git a/terraform/modules/aks/README.md b/terraform/modules/aks/README.md new file mode 100644 index 0000000..3eff1ee --- /dev/null +++ b/terraform/modules/aks/README.md @@ -0,0 +1,49 @@ +# Moduel to provision AKS cluster + + + +# Example usage +```hcl +# First create the networking +module "network" { + source = "./modules/network" + resource_group_id = azurerm_resource_group.example.id + location = "canadacentral" + vnet_name = "vnet-aks-prod" + + subnets = { + "aks-subnet" = { + address_prefixes = ["10.0.1.0/24"] + service_endpoints = ["Microsoft.ContainerRegistry"] + # Enable required features for AKS + private_endpoint_network_policies_enabled = true + private_link_service_network_policies_enabled = true + } + } + + tags = { + Environment = "Production" + Managed_By = "Terraform" + } +} + +# Then deploy AKS using the subnet from the network module +module "aks" { + source = "./modules/aks" + cluster_name = "aks-prod" + resource_group_name = azurerm_resource_group.example.name + location = "canadacentral" + subnet_id = module.network.subnet_ids["aks-subnet"] + + node_count = 2 + node_vm_size = "Standard_D2s_v3" + + tags = { + Environment = "Production" + Managed_By = "Terraform" + } + + depends_on = [module.network] +} +``` + diff --git a/terraform/modules/aks/main.tf b/terraform/modules/aks/main.tf index 38cb01c..bf95ca2 100644 --- a/terraform/modules/aks/main.tf +++ b/terraform/modules/aks/main.tf @@ -1,28 +1,39 @@ -variable "resource_group_name" {} -variable "location" {} -variable "cluster_name" {} + resource "azurerm_kubernetes_cluster" "aks" { name = var.cluster_name location = var.location resource_group_name = var.resource_group_name - dns_prefix = "aks" + dns_prefix = "${var.cluster_name}-dns" + kubernetes_version = "1.27.7" default_node_pool { - name = "default" - node_count = 1 - vm_size = "Standard_B2s" + name = "default" + node_count = var.node_count + vm_size = var.node_vm_size + vnet_subnet_id = var.subnet_id + min_count = 1 + max_count = 3 + os_disk_size_gb = 50 + + tags = var.tags + } + + network_profile { + network_plugin = "azure" + network_policy = "calico" + load_balancer_sku = "standard" + outbound_type = "loadBalancer" } identity { type = "SystemAssigned" } - tags = { - env = "demo" - } + tags = merge(var.tags, { + Environment = "Production" + Managed_By = "Terraform" + }) } -output "aks_id" { - value = azurerm_kubernetes_cluster.aks.id -} + diff --git a/terraform/modules/aks/outputs.tf b/terraform/modules/aks/outputs.tf new file mode 100644 index 0000000..4fb7035 --- /dev/null +++ b/terraform/modules/aks/outputs.tf @@ -0,0 +1,16 @@ +output "aks_id" { + description = "The ID of the AKS cluster" + value = azurerm_kubernetes_cluster.aks.id +} + +output "kube_config" { + description = "Kubeconfig for the AKS cluster" + value = azurerm_kubernetes_cluster.aks.kube_config_raw + sensitive = true +} + +output "cluster_identity" { + description = "System assigned identity of the AKS cluster" + value = azurerm_kubernetes_cluster.aks.identity[0].principal_id +} + diff --git a/terraform/modules/aks/variables.tf b/terraform/modules/aks/variables.tf new file mode 100644 index 0000000..92cac93 --- /dev/null +++ b/terraform/modules/aks/variables.tf @@ -0,0 +1,38 @@ +variable "resource_group_name" { + description = "Name of the resource group" + type = string +} + +variable "location" { + description = "Azure region" + type = string +} + +variable "cluster_name" { + description = "Name of the AKS cluster" + type = string +} + +variable "subnet_id" { + description = "ID of the subnet where AKS should be deployed" + type = string +} + +variable "node_count" { + description = "Number of nodes in the default node pool" + type = number + default = 1 +} + +variable "node_vm_size" { + description = "Size of the node VMs" + type = string + default = "Standard_D2s_v3" +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} + diff --git a/terraform/modules/appgw/README.md b/terraform/modules/appgw/README.md new file mode 100644 index 0000000..226800f --- /dev/null +++ b/terraform/modules/appgw/README.md @@ -0,0 +1,66 @@ +# Application Gateway Module + +This module creates an Azure Application Gateway with a WAF policy and a backend pool. + +## Example usage +```hcl + +# Create a resource group +resource "azurerm_resource_group" "rg" { + name = "example-resources" + location = "canadacentral" + tags = { + Environment = "Production" + Managed_By = "Terraform" + } +} + +# First create the networking +module "network" { + source = "./modules/network" + resource_group_id = azurerm_resource_group.rg.id + location = "canadacentral" + vnet_name = "vnet-agw-prod" + + subnets = { + "subnet" = { + address_prefixes = ["10.0.1.0/24"] + service_endpoints = ["Microsoft.ContainerRegistry"] + # Enable required features for AKS + private_endpoint_network_policies_enabled = true + private_link_service_network_policies_enabled = true + } + } + + tags = { + Environment = "Production" + Managed_By = "Terraform" + } +} + +# Create the Application Gateway + +module "appgw" { + source = "./modules/appgw" + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_resource_group.rg.location + vnet_name = module.network.vnet_name + subnet_name = module.network.subnet_name + subnet_id = module.network.subnet_id + + appgw_name = "appgw-prod" + sku = "Standard_v2" + capacity = 2 + tier = "Standard_v2" + frontend_ip_config = "frontend-ip-config" + frontend_port = "80" + backend_pool = "backend-pool" + backend_http_settings= "backend-http-settings" + + tags = { + Environment = "Production" + Managed_By = "Terraform" + } +} +``` + diff --git a/terraform/modules/appgw/main.tf b/terraform/modules/appgw/main.tf index f069ab7..22f897a 100644 --- a/terraform/modules/appgw/main.tf +++ b/terraform/modules/appgw/main.tf @@ -1,71 +1,69 @@ -variable "resource_group_name" {} -variable "location" {} -variable "gateway_name" {} - resource "azurerm_public_ip" "pip" { - name = "${var.gateway_name}-pip" + name = "${var.appgw_name}-pip" location = var.location resource_group_name = var.resource_group_name allocation_method = "Static" sku = "Standard" + tags = var.tags } resource "azurerm_application_gateway" "agw" { - name = var.gateway_name + name = var.appgw_name location = var.location resource_group_name = var.resource_group_name + tags = var.tags sku { - name = "Standard_v2" - tier = "Standard_v2" - capacity = 1 + name = var.sku + tier = var.tier + capacity = var.capacity } gateway_ip_configuration { - name = "gateway-ip" - subnet_id = "fake-subnet-id" # Use a dummy string for now + name = "gateway-ip-configuration" + subnet_id = var.subnet_id } frontend_port { - name = "http" - port = 80 + name = "frontend-port" + port = var.frontend_port } frontend_ip_configuration { - name = "frontend" + name = var.frontend_ip_config public_ip_address_id = azurerm_public_ip.pip.id } backend_address_pool { - name = "mock-backend-pool" - ip_addresses = "10.0.1.5" # dummy IP - + name = var.backend_pool } backend_http_settings { - name = "mock-http-settings" + name = var.backend_http_settings cookie_based_affinity = "Disabled" + path = "/" port = 80 - protocol = "Http" - request_timeout = 20 + protocol = "Http" + request_timeout = 60 } http_listener { - name = "listener" - frontend_ip_configuration_name = "frontend" - frontend_port_name = "http" + name = "basic-http-listener" + frontend_ip_configuration_name = var.frontend_ip_config + frontend_port_name = "frontend-port" protocol = "Http" } request_routing_rule { - name = "rule1" + name = "basic-routing-rule" rule_type = "Basic" - http_listener_name = "listener" - backend_address_pool_name = "mock-backend-pool" - backend_http_settings_name = "mock-http-settings" + http_listener_name = "basic-http-listener" + backend_address_pool_name = var.backend_pool + backend_http_settings_name = var.backend_http_settings + priority = 100 } -} -output "gateway_ip" { - value = azurerm_public_ip.pip.ip_address + lifecycle { + create_before_destroy = true + } } diff --git a/terraform/modules/appgw/outputs.tf b/terraform/modules/appgw/outputs.tf new file mode 100644 index 0000000..51d3619 --- /dev/null +++ b/terraform/modules/appgw/outputs.tf @@ -0,0 +1,20 @@ +# Outputs +output "application_gateway_id" { + description = "The ID of the Application Gateway" + value = azurerm_application_gateway.agw.id +} + +output "application_gateway_name" { + description = "The name of the Application Gateway" + value = azurerm_application_gateway.agw.name +} + +output "public_ip_address" { + description = "The public IP address of the Application Gateway" + value = azurerm_public_ip.pip.ip_address +} + +output "backend_pool_id" { + description = "The ID of the backend address pool" + value = one(azurerm_application_gateway.agw.backend_address_pool).id +} diff --git a/terraform/modules/appgw/variables.tf b/terraform/modules/appgw/variables.tf new file mode 100644 index 0000000..751ee01 --- /dev/null +++ b/terraform/modules/appgw/variables.tf @@ -0,0 +1,75 @@ +# Variables +variable "resource_group_name" { + description = "Name of the resource group" + type = string +} + +variable "location" { + description = "Azure region where the Application Gateway should be created" + type = string +} + +variable "vnet_name" { + description = "Name of the Virtual Network" + type = string +} + +variable "subnet_name" { + description = "Name of the subnet for Application Gateway" + type = string +} + +variable "subnet_id" { + description = "ID of the subnet for Application Gateway" + type = string +} + +variable "appgw_name" { + description = "Name of the Application Gateway" + type = string +} + +variable "sku" { + description = "SKU of the Application Gateway" + type = string + default = "Standard_v2" +} + +variable "tier" { + description = "Tier of the Application Gateway" + type = string + default = "Standard_v2" +} + +variable "capacity" { + description = "Capacity units of the Application Gateway" + type = number + default = 2 +} + +variable "frontend_ip_config" { + description = "Name of the frontend IP configuration" + type = string +} + +variable "frontend_port" { + description = "Frontend port number" + type = string + default = "80" +} + +variable "backend_pool" { + description = "Name of the backend pool" + type = string +} + +variable "backend_http_settings" { + description = "Name of the backend HTTP settings" + type = string +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} diff --git a/terraform/modules/network/networking.tf b/terraform/modules/network/networking.tf index 75c304f..0f75013 100644 --- a/terraform/modules/network/networking.tf +++ b/terraform/modules/network/networking.tf @@ -36,13 +36,14 @@ resource "azurerm_virtual_network" "vnet" { resource "azurerm_subnet" "subnets" { for_each = var.subnets - name = each.key - resource_group_name = local.resource_group_name - virtual_network_name = azurerm_virtual_network.vnet.name - address_prefixes = each.value.address_prefixes - service_endpoints = each.value.service_endpoints - private_endpoint_network_policies_enabled = each.value.private_endpoint_network_policies_enabled - private_link_service_network_policies_enabled = each.value.private_link_service_network_policies_enabled + name = each.key + resource_group_name = local.resource_group_name + virtual_network_name = azurerm_virtual_network.vnet.name + address_prefixes = each.value.address_prefixes + service_endpoints = each.value.service_endpoints + private_endpoint_network_policies = each.value.private_endpoint_network_policies_enabled + # To fix this + # private_link_service_network_policies = each.value.private_link_service_network_policies_enabled dynamic "delegation" { for_each = each.value.delegate