Skip to content

Commit 8e69199

Browse files
authored
[Feature] Add Service Bus, Container Apps and Container Registry Terraform modules (#12)
* feat: initial aca module * feat: initial acr module * feat: initial service bus module * feat: add aca ingress and workload profile config * feat: add additional acr network settings and formatting * feat: add zone redundancy to service bus * feat: add private dns to aca and separate dev and prod envs * feat: update acr networking options * feat: add prod options for service bus * feat: add comments to modules * feat: update acr output description
1 parent 39f70f5 commit 8e69199

File tree

9 files changed

+620
-0
lines changed

9 files changed

+620
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
terraform {
2+
required_providers {
3+
azurecaf = {
4+
source = "aztfmod/azurecaf"
5+
version = "1.2.26"
6+
}
7+
}
8+
}
9+
10+
# Create Azure Container Apps Environment in Dev
11+
12+
resource "azurerm_container_app_environment" "container_app_environment_dev" {
13+
count = var.environment == "dev" ? 1 : 0
14+
name = var.application_name
15+
location = var.location
16+
resource_group_name = var.resource_group
17+
log_analytics_workspace_id = var.log_analytics_workspace_id
18+
19+
workload_profile {
20+
name = "Consumption"
21+
workload_profile_type = "Consumption"
22+
}
23+
}
24+
25+
# Create Azure Container Apps Environment in Prod
26+
27+
resource "azurerm_container_app_environment" "container_app_environment_prod" {
28+
count = var.environment == "prod" ? 1 : 0
29+
name = var.application_name
30+
location = var.location
31+
resource_group_name = var.resource_group
32+
log_analytics_workspace_id = var.log_analytics_workspace_id
33+
zone_redundancy_enabled = true
34+
35+
internal_load_balancer_enabled = var.isNetworkIsolated
36+
infrastructure_subnet_id = var.infrastructure_subnet_id
37+
38+
workload_profile {
39+
name = "Consumption"
40+
workload_profile_type = "Consumption"
41+
}
42+
}
43+
44+
# Create Azure Container App in the specified environment
45+
46+
resource "azurerm_container_app" "container_app" {
47+
name = "email-processor"
48+
container_app_environment_id = var.environment == "dev" ? azurerm_container_app_environment.container_app_environment_dev[0].id : azurerm_container_app_environment.container_app_environment_prod[0].id
49+
resource_group_name = var.resource_group
50+
revision_mode = "Single"
51+
workload_profile_name = "Consumption"
52+
ingress {
53+
allow_insecure_connections = false
54+
external_enabled = true
55+
target_port = 80
56+
traffic_weight {
57+
percentage = 100
58+
latest_revision = true
59+
}
60+
}
61+
tags = {
62+
"environment" = var.environment
63+
"application-name" = var.application_name
64+
"azd-service-name" = "email-processor"
65+
}
66+
67+
lifecycle {
68+
ignore_changes = [
69+
template.0.container["image"]
70+
]
71+
}
72+
73+
identity {
74+
type = "SystemAssigned, UserAssigned"
75+
identity_ids = [
76+
var.container_registry_user_assigned_identity_id
77+
]
78+
}
79+
80+
registry {
81+
server = var.acr_login_server
82+
identity = var.container_registry_user_assigned_identity_id
83+
}
84+
85+
secret {
86+
name = "azure-servicebus-connection-string"
87+
value = var.servicebus_namespace_primary_connection_string
88+
}
89+
90+
template {
91+
container {
92+
name = "email-processor-app"
93+
94+
// A container image is required to deploy the ACA resource.
95+
// Since the rendering service image is not available yet,
96+
// we use a placeholder image for now.
97+
image = "mcr.microsoft.com/cbl-mariner/busybox:2.0"
98+
cpu = 1.0
99+
memory = "2.0Gi"
100+
env {
101+
name = "AZURE_SERVICEBUS_NAMESPACE"
102+
value = var.servicebus_namespace
103+
}
104+
env {
105+
name = "AZURE_SERVICEBUS_EMAIL_REQUEST_QUEUE_NAME"
106+
value = var.email_request_queue_name
107+
}
108+
env {
109+
name = "AZURE_SERVICEBUS_EMAIL_RESPONSE_QUEUE_NAME"
110+
value = var.email_response_queue_name
111+
}
112+
}
113+
max_replicas = 10
114+
min_replicas = 1
115+
116+
custom_scale_rule {
117+
name = "service-bus-queue-length-rule"
118+
custom_rule_type = "azure-servicebus"
119+
metadata = {
120+
messageCount = 10
121+
namespace = var.servicebus_namespace
122+
queueName = var.email_request_queue_name
123+
}
124+
authentication {
125+
secret_name = "azure-servicebus-connection-string"
126+
trigger_parameter = "connection"
127+
}
128+
}
129+
}
130+
}
131+
132+
# Create Private DNS Zone for Azure Container Apps in Prod if internal load balancer is enabled
133+
134+
resource "azurerm_private_dns_zone" "dns_for_aca" {
135+
count = var.environment == "prod" && var.isNetworkIsolated ? 1 : 0
136+
name = var.environment == "prod" ? azurerm_container_app_environment.container_app_environment_prod[0].default_domain : azurerm_container_app_environment.container_app_environment_dev[0].default_domain
137+
resource_group_name = var.resource_group
138+
}
139+
140+
resource "azurerm_private_dns_zone_virtual_network_link" "virtual_network_link_aca" {
141+
count = var.environment == "prod" && var.isNetworkIsolated ? 1 : 0
142+
name = "privatelink.azurecr.io"
143+
private_dns_zone_name = azurerm_private_dns_zone.dns_for_aca[0].name
144+
virtual_network_id = var.spoke_vnet_id
145+
resource_group_name = var.resource_group
146+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
output "identity_principal_id" {
2+
value = azurerm_container_app.container_app.identity[0].principal_id
3+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
variable "resource_group" {
2+
type = string
3+
description = "The resource group"
4+
}
5+
6+
variable "environment" {
7+
type = string
8+
description = "The environment (dev, test, prod...)"
9+
default = "dev"
10+
}
11+
12+
variable "location" {
13+
type = string
14+
description = "The Azure region where all resources in this example should be created"
15+
}
16+
17+
variable "application_name" {
18+
type = string
19+
description = "The name of your application"
20+
}
21+
22+
variable "log_analytics_workspace_id" {
23+
type = string
24+
description = "The ID of the Log Analytics workspace"
25+
}
26+
27+
variable "acr_login_server" {
28+
type = string
29+
description = "The login server of the Azure Container Registry"
30+
}
31+
32+
variable "container_registry_user_assigned_identity_id" {
33+
type = string
34+
description = "The ID of the user-assigned identity for the Azure Container Registry"
35+
}
36+
37+
variable "servicebus_namespace_primary_connection_string" {
38+
type = string
39+
description = "The primary connection string of the Azure Service Bus namespace"
40+
}
41+
42+
variable "servicebus_namespace" {
43+
type = string
44+
description = "The name of the Azure Service Bus namespace"
45+
}
46+
47+
variable "email_request_queue_name" {
48+
type = string
49+
description = "The name of the email request queue"
50+
}
51+
52+
variable "email_response_queue_name" {
53+
type = string
54+
description = "The name of the email response queue"
55+
}
56+
57+
variable "isNetworkIsolated" {
58+
type = bool
59+
description = "Indicates if the container app should be network isolated"
60+
default = false
61+
}
62+
63+
variable "infrastructure_subnet_id" {
64+
type = string
65+
description = "The ID of the subnet where the infrastructure resources should be created"
66+
default = null
67+
}
68+
69+
variable "spoke_vnet_id" {
70+
type = string
71+
description = "The ID of the spoke VNET"
72+
default = null
73+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
terraform {
2+
required_providers {
3+
azurecaf = {
4+
source = "aztfmod/azurecaf"
5+
version = "1.2.26"
6+
}
7+
}
8+
}
9+
10+
data "azuread_client_config" "current" {}
11+
12+
# Create Azure Container Registry
13+
14+
resource "azurerm_container_registry" "acr" {
15+
name = var.application_name
16+
resource_group_name = var.resource_group
17+
location = var.location
18+
19+
sku = var.environment == "prod" ? "Premium" : "Basic"
20+
21+
admin_enabled = false
22+
network_rule_bypass_option = "AzureServices"
23+
public_network_access_enabled = var.environment == "prod" ? false : true
24+
zone_redundancy_enabled = var.environment == "prod" ? true : false
25+
26+
dynamic "georeplications" {
27+
for_each = var.georeplications
28+
content {
29+
location = georeplications.value.location
30+
}
31+
32+
}
33+
dynamic "network_rule_set" {
34+
for_each = var.network_rules != null ? { this = var.network_rules } : {}
35+
content {
36+
default_action = network_rule_set.value.default_action
37+
38+
dynamic "ip_rule" {
39+
for_each = network_rule_set.value.ip_rules
40+
content {
41+
action = ip_rule.value.action
42+
ip_range = ip_rule.value.ip_range
43+
}
44+
}
45+
}
46+
}
47+
}
48+
49+
# Create role assignments
50+
51+
resource "azurerm_role_assignment" "container_app_acr_pull" {
52+
principal_id = var.aca_identity_principal_id
53+
role_definition_name = "AcrPull"
54+
scope = azurerm_container_registry.acr.id
55+
}
56+
57+
resource "azurerm_user_assigned_identity" "container_registry_user_assigned_identity" {
58+
name = "ContainerRegistryUserAssignedIdentity"
59+
resource_group_name = var.resource_group
60+
location = var.location
61+
}
62+
63+
resource "azurerm_role_assignment" "container_registry_user_assigned_identity_acr_pull" {
64+
scope = azurerm_container_registry.acr.id
65+
role_definition_name = "AcrPull"
66+
principal_id = azurerm_user_assigned_identity.container_registry_user_assigned_identity.principal_id
67+
}
68+
69+
70+
# For demo purposes, allow current user access to the container registry
71+
# Note: when running as a service principal, this is also needed
72+
resource "azurerm_role_assignment" "acr_contributor_user_role_assignement" {
73+
scope = azurerm_container_registry.acr.id
74+
role_definition_name = "Contributor"
75+
principal_id = data.azuread_client_config.current.object_id
76+
}
77+
78+
# Create Private DNS Zone and Endpoint for ACR
79+
80+
resource "azurerm_private_dns_zone" "dns_for_acr" {
81+
count = var.environment == "prod" ? 1 : 0
82+
name = "privatelink.azurecr.io"
83+
resource_group_name = var.resource_group
84+
}
85+
86+
resource "azurerm_private_dns_zone_virtual_network_link" "virtual_network_link_acr" {
87+
count = var.environment == "prod" ? 1 : 0
88+
name = "privatelink.azurecr.io"
89+
private_dns_zone_name = azurerm_private_dns_zone.dns_for_acr[0].name
90+
virtual_network_id = var.spoke_vnet_id
91+
resource_group_name = var.resource_group
92+
}
93+
94+
resource "azurerm_private_endpoint" "acr_pe" {
95+
count = var.environment == "prod" ? 1 : 0
96+
name = "private-endpoint-acr"
97+
location = var.location
98+
resource_group_name = var.resource_group
99+
subnet_id = var.private_endpoint_subnet_id
100+
101+
private_dns_zone_group {
102+
name = "privatednsacrzonegroup"
103+
private_dns_zone_ids = [azurerm_private_dns_zone.dns_for_acr[0].id]
104+
}
105+
106+
private_service_connection {
107+
name = "peconnection-acr"
108+
private_connection_resource_id = azurerm_container_registry.acr.id
109+
is_manual_connection = false
110+
subresource_names = ["registry"]
111+
}
112+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
output "acr_name" {
2+
value = azurerm_container_registry.acr.name
3+
description = "The Azure Container Registry Name."
4+
}
5+
6+
output "acr_login_server" {
7+
value = azurerm_container_registry.acr.login_server
8+
description = "The Azure Container Registry Login Server."
9+
}
10+
11+
output "container_registry_user_assigned_identity_id" {
12+
value = azurerm_user_assigned_identity.container_registry_user_assigned_identity.id
13+
description = "The ACR User Assigned Identity ID."
14+
15+
}

0 commit comments

Comments
 (0)