In part one, I showed how I set up Terraform Modules for Storage Accounts,
Key Vaults and Virtual Machines.
In part two, I will detail Azure Kubernetes Clusters and one I called Data (essentially Service Bus, Postgres, and Mongo wrapped into one)
AKS
My AKS Module consists of:
main.tf (my main body of code)
outputs.tf (where I declare what outputs I want)
variables.tf (the variables used in the main.tf)
main.tf
locals {
mandatory_tags = {
source = "terraform"
company = "tri"
}
applied_tags = merge(var.resource_tags, local.mandatory_tags)
}
# Create resource group
resource "azurerm_resource_group" "aks-rgname" {
name = "rg-${var.bus_stack}-${var.env_stack}-${var.loc_code}-aks"
location = var.region
}
# Creates managed ID
resource "azurerm_user_assigned_identity" "mi-aks" {
name = "mi-${var.bus_stack}-${var.env_stack}-${var.loc_code}"
resource_group_name = azurerm_resource_group.aks-rgname.name
location = var.region
depends_on = [
azurerm_resource_group.aks-rgname,
]
}
resource "azurerm_role_assignment" "mi-aks-dns" {
scope = var.dns_scope
role_definition_name = "Private DNS Zone Contributor"
principal_id = azurerm_user_assigned_identity.mi-aks.principal_id
depends_on = [
azurerm_user_assigned_identity.mi-aks,
]
}
resource "azurerm_role_assignment" "mi-aks-net" {
scope = var.vnet_scope
role_definition_name = "Network Contributor"
principal_id = azurerm_user_assigned_identity.mi-aks.principal_id
depends_on = [
azurerm_user_assigned_identity.mi-aks,
]
}
resource "azurerm_role_assignment" "mi-kube_id-acrpull" {
scope = var.acr_scope
role_definition_name = "AcrPull"
principal_id = azurerm_kubernetes_cluster.aks.kubelet_identity[0].object_id
depends_on = [
azurerm_kubernetes_cluster.aks
]
}
# Create AKS Cluster
resource "azurerm_kubernetes_cluster" "aks" {
name = "aks-${var.bus_stack}-${var.env_stack}-${var.loc_code}"
location = var.region
resource_group_name = azurerm_resource_group.aks-rgname.name
sku_tier = var.sku_tier
dns_prefix = "aks-${var.bus_stack}-${var.env_stack}-${var.loc_code}"
node_resource_group = "rg-${var.bus_stack}-${var.env_stack}-${var.loc_code}-aks-node"
private_cluster_enabled = var.pri_cluster_enabled
private_dns_zone_id = var.pri_dns_zone_id
private_cluster_public_fqdn_enabled = var.pub_fqdn_enabled
http_application_routing_enabled = var.http_app_route_enabled
role_based_access_control_enabled = var.rbac_enabled
local_account_disabled = var.local_acc_disabled
kubernetes_version = var.kubes_version
azure_active_directory_role_based_access_control {
tenant_id = var.tenant_id
azure_rbac_enabled = var.az_rbac_enabled
}
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.mi-aks.id]
}
key_vault_secrets_provider {
secret_rotation_enabled = true
secret_rotation_interval = "2m"
}
default_node_pool {
name = var.np_name
vm_size = var.np_vm_size
vnet_subnet_id = var.subnet_id
node_count = var.np_node_count
zones = var.av_zones
temporary_name_for_rotation = var.temp_name_rotation
upgrade_settings {
max_surge = "10%"
}
}
maintenance_window_node_os {
frequency = var.nos_freq
interval = var.nos_int
duration = var.nos_dur
week_index = var.nos_week
day_of_week = var.nos_day_week
start_time = var.nos_start_time
utc_offset = var.utc_offset
}
network_profile {
network_plugin = var.net_plugin
network_policy = var.net_policy
network_data_plane = var.net_dp
network_plugin_mode = var.net_pm
load_balancer_sku = var.lb_sku
service_cidr = var.svc_cidr
dns_service_ip = var.dns_svc_ip
}
lifecycle {
ignore_changes = [
default_node_pool[0].upgrade_settings[0].max_surge, image_cleaner_interval_hours
]
}
depends_on = [
azurerm_user_assigned_identity.mi-aks,
azurerm_role_assignment.mi-aks-dns,
]
tags = local.applied_tags
}
outputs.tf
output "aks-id" {
value = azurerm_kubernetes_cluster.aks.id
description = "returns the ID of the AKS cluster"
}
output "aks-fqdn" {
value = azurerm_kubernetes_cluster.aks.fqdn
description = "returns the FQDN of the AKS cluster"
}
output "aks-pri-fqdn" {
value = azurerm_kubernetes_cluster.aks.private_fqdn
description = "returns the private FQDN of the AKS cluster"
}
output "aks-por-fqdn" {
value = azurerm_kubernetes_cluster.aks.portal_fqdn
description = "returns the portal FQDN of the AKS cluster"
}
output "aks-identity" {
value = azurerm_kubernetes_cluster.aks.identity
description = "returns the principal ID and tenant ID associated with Managed Service identity"
}
output "aks-node-rg" {
value = azurerm_kubernetes_cluster.aks.node_resource_group
description = "returns the auto-generated rg which contains the resources for AKS"
}
output "aks-node-rg-id" {
value = azurerm_kubernetes_cluster.aks.node_resource_group_id
description = "returns the ID of the auto-generated rg which contains the resources for AKS"
}
output "aks-kube-id" {
value = azurerm_kubernetes_cluster.aks.kubelet_identity
description = "returns client_id, object_id and user_assigned_identity_id of user-defined managed identity assigned to the kubelets"
}
output "aks-kube-admin-config" {
value = azurerm_kubernetes_cluster.aks.kube_admin_config
description = "returns certificate and username password authentication details for cluster"
}
output "aks-kube-config" {
value = azurerm_kubernetes_cluster.aks.kube_config
description = "returns certificate and username password authentication details for cluster"
}
variables.tf
variable "tenant_id" {
description = "the Entra tenant id"
type = string
}
variable "bus_stack" {
description = "business unit - stack might be opra, ops, tri etc"
type = string
}
variable "env_stack" {
description = "environment stack - might be dev, uq, qual, val, prod, dr"
type = string
}
variable "loc_code" {
description = "abbreviated location code"
type = string
}
variable "region" {
description = "Azure region"
type = string
}
variable "dns_scope" {
description = "the full scope of the dns to assign the managed id to"
type = string
}
variable "vnet_scope" {
description = "the full scope of the vnet to assign the managed id to"
type = string
}
variable "acr_scope" {
description = "the full scope of the ACR tritrials to assign the managed id to"
type = string
}
variable "sku_tier" {
description = "the SKU tier - Free, Standard or Premium"
type = string
}
variable "pri_cluster_enabled" {
description = "whether the cluster is private or public enabled"
type = string
}
variable "pri_dns_zone_id" {
description = "the Azure private dns zone hosted in Ops subscription"
type = string
}
variable "pub_fqdn_enabled" {
description = "Specifies whether a Public FQDN for this Private Cluster should be added"
type = string
}
variable "http_app_route_enabled" {
description = "Should HTTP Application Routing be enabled for this AKS cluster"
type = string
}
variable "rbac_enabled" {
description = "Should RBAC be enabled for this AKS cluster"
type = string
}
variable "az_rbac_enabled" {
description = "Should Azure RBAC be enabled for this AKS cluster. True = disabled"
type = string
}
variable "local_acc_disabled" {
description = "is local accounts enabled or disabled"
type = string
}
variable "np_name" {
description = "the name of the node pool"
type = string
}
variable "np_vm_size" {
description = "the VM size of the AKS cluster"
type = string
}
variable "subnet_id" {
description = "the subnet the AKS cluster is hosted"
type = string
}
variable "np_node_count" {
description = "the number of nodes that exist in the node pool"
type = string
}
variable "nos_freq" {
description = "Frequency of maintenance. Possible options are Daily, Weekly, AbsoluteMonthly and RelativeMonthly. AbsoluteMonthly is based on a specific date each month, while RelativeMonthly is based on a specific day of the week within a particular week of the month"
type = string
}
variable "nos_int" {
description = "The interval for maintenance runs. Depending on the frequency this interval is week or month based."
type = string
}
variable "nos_dur" {
description = "The duration of the window for maintenance to run in hours. Possible options are between 4 to 24. The duration should be specified in the ISO 8601 duration format. For example, if you want the duration to be 4 hours, it should be PT4H"
type = string
}
variable "nos_week" {
description = "The week in the month used for the maintenance run. Options are First, Second, Third, Fourth, and Last."
type = string
}
variable "nos_day_week" {
description = "The day of the week for the maintenance run. Required in combination with weekly frequency. Possible values are Friday, Monday, Saturday, Sunday, Thursday, Tuesday and Wednesday."
type = string
}
variable "nos_start_time" {
description = "The time for maintenance to begin, based on the timezone determined by utc_offset. Format is HH:mm."
type = string
}
variable "utc_offset" {
description = "Used to determine the timezone for cluster maintenance."
type = string
}
variable "net_plugin" {
description = "plugin used for networking - azure, kubenet or none"
type = string
}
variable "net_policy" {
description = "value to determine if should use cilium, calico or azure"
type = string
}
variable "net_dp" {
description = "specifies which value to use when building out the kubernetes network, either azure or cilium"
type = string
}
variable "net_pm" {
description = "used for building the AKS network - overlay"
type = string
}
variable "lb_sku" {
description = "value to dtermine which load balance skuto use"
type = string
}
variable "svc_cidr" {
description = "network range used by the AKS cluster"
type = string
}
variable "dns_svc_ip" {
description = "IP address used within the AKS cluster"
type = string
}
variable "resource_tags" {
description = "various tags depending on environment"
type = map(string)
}
variable "av_zones" {
description = "The availability zones enabled"
type = list(string)
}
variable "temp_name_rotation" {
description = "temporary name for rotation"
type = string
}
variable "kubes_version" {
description = "version of kubernetes"
type = string
}
Example
module "aks-poc" {
source = "app.terraform.io/----/aks/azure"
version = "2.0.6"
bus_stack = "ops"
env_stack = "poc"
loc_code = "ne"
region = "North Europe"
dns_scope = local.aks_dns_scope
vnet_scope = local.poc_vnet_scope
acr_scope = local.acr_scope
sku_tier = "Free"
pri_cluster_enabled = true
pri_dns_zone_id = local.dns_neaks
pub_fqdn_enabled = false
http_app_route_enabled = false
rbac_enabled = true
local_acc_disabled = false
tenant_id = local.tenant_id
az_rbac_enabled = true
kubes_version = "1.32"
np_name = "apopspoc"
np_vm_size = "Standard_D8s_v4"
subnet_id = azurerm_subnet.snet-aks-poc.id
np_node_count = "1"
av_zones = local.av_zones
temp_name_rotation = "devtemppool"
nos_freq = "RelativeMonthly"
nos_int = "1"
nos_dur = "4"
nos_week = "Second"
nos_day_week = "Monday"
nos_start_time = "03:00"
utc_offset = "+00:00"
net_plugin = "azure"
net_policy = "cilium"
net_dp = "cilium"
net_pm = "overlay"
lb_sku = "standard"
svc_cidr = "172.18.0.0/16"
dns_svc_ip = "172.18.0.10"
resource_tags = {
service = "opra"
env = "dev"
}
}
Data
My Data Module consists of:
locals.tf (any local variables I need to declare)
main.tf (my main body of code)
outputs.tf (where I declare what outputs I want)
variables.tf (the variables used in the main.tf)
provider.tf (needed for the Mongo aspects of the module)
locals.tf
locals {
users = data.azuread_users.admin_users
sb_loc_code = var.bus_stack == "dr" ? "euw" : var.sb_loc_code # Exception to accomodate the hard coding done by the devs
enable_partitioning = var.sbns_sku == "Premium" ? false : true
max_delivery_count = 10
queue_prefix = "sbq"
topic_prefix = "sbt"
subscription_prefix = "sbt"
queue_names = [
"email", "audit", "publish_request",
"reports", "general", "generate_request_message",
"new_org_publish_request_message", "audit_v2",
"import_id_claim", "import_data_view",
"new_dataview_config","create_cm2_snapshot",
"restore_cm2_snapshot"
]
topic_names = {
"user_upsert" = {
report_service = true
cm2 = true
session = false
}
"org_upsert" = {
report_service = true
cm2 = true
session = false
}
"study_upsert" = {
report_service = false
cm2 = true
session = false
}
"panel_upsert" = {
report_service = false
cm2 = true
session = false
}
"org-delete" = {
report_service = false
cm2 = true
session = false
}
"datatable_upsert" = {
report_service = false
cm2 = true
session = false
}
"import_id_claim_reply" = {
report_service = false
cm2 = true
session = true
}
"study_member_upsert" = {
report_service = false
cm2 = true
session = false
}
"study_function_upsert" = {
report_service = false
cm2 = true
session = false
}
"organisation_lov_upsert" = {
report_service = false
cm2 = true
session = false
}
"dataviewconfig_upsert" = {
report_service = false
cm2 = true
session = false
}
"create_cm2_snapshot_reply" = {
report_service = false
cm2 = true
session = true
}
"restore_cm2_snapshot_reply" = {
report_service = false
cm2 = true
session = true
}
}
}
main.tf
locals {
mandatory_tags = {
source = "terraform"
company = "tri"
}
applied_tags = merge(var.resource_tags, local.mandatory_tags)
}
# Get all members of Cloud Ops
data "azuread_group" "cloudops" {
display_name = "Cloud Operations"
include_transitive_members = true
}
data "azuread_users" "admin_users" {
object_ids = data.azuread_group.cloudops.members
}
################################################
# POSTGRES SQL #
################################################
resource "azurerm_postgresql_flexible_server" "psql" {
name = "psql-${var.bus_stack}-${var.env_stack}-${var.loc_code}"
location = var.region
resource_group_name = var.rg_name
version = var.postgres_version
administrator_login = var.psql_username
administrator_password = var.psql_kvs
public_network_access_enabled = false
delegated_subnet_id = var.subnet_id_psql
private_dns_zone_id = var.pri_dns_zone_id_psql
sku_name = var.psql_sku
backup_retention_days = var.psql_backup_retention_days
storage_tier = var.psql_storage_tier
storage_mb = var.psql_storage_mb
auto_grow_enabled = true
geo_redundant_backup_enabled = var.geo_redundant_backup_enabled
authentication {
tenant_id = var.tenant_id
active_directory_auth_enabled = true
}
dynamic "high_availability" {
for_each = var.env_stack == "prod" ? ["this"] : []
content {
mode = "ZoneRedundant"
}
}
lifecycle {
ignore_changes = [
zone,
]
}
tags = local.applied_tags
}
resource "azurerm_postgresql_flexible_server_configuration" "postgres-config-max-connections" {
name = "max_connections"
server_id = azurerm_postgresql_flexible_server.psql.id
value = var.psql_max_con
depends_on = [
azurerm_postgresql_flexible_server.psql
]
}
resource "azurerm_postgresql_flexible_server_configuration" "postgres-config-max-prep-transactions" {
name = "max_prepared_transactions"
server_id = azurerm_postgresql_flexible_server.psql.id
value = var.psql_max_prep_tran
depends_on = [
azurerm_postgresql_flexible_server.psql
]
}
resource "azurerm_postgresql_flexible_server_active_directory_administrator" "postgres-admins" {
count = length(local.users.users)
server_name = azurerm_postgresql_flexible_server.psql.name
resource_group_name = var.rg_name
tenant_id = var.tenant_id
object_id = local.users.users[count.index].object_id
principal_name = local.users.users[count.index].user_principal_name
principal_type = "User"
depends_on = [
azurerm_postgresql_flexible_server.psql
]
}
###############################################
# SERVICE BUS #
###############################################
data "azurerm_subscription" "current" {
}
data "azuread_service_principal" "sp_core" {
client_id = var.sp_core
}
data "azuread_service_principal" "sp_reportservice" {
client_id = var.sp_reportservice
}
resource "azurerm_servicebus_namespace" "sb-ns" {
name = "sbns-${var.bus_stack}-${var.env_stack}-${var.loc_code}"
location = var.region
resource_group_name = var.rg_name
sku = var.sbns_sku
local_auth_enabled = false
capacity = var.sbns_sku == "Premium" ? 1 : 0
premium_messaging_partitions = var.sbns_sku == "Premium" ? 1 : 0
public_network_access_enabled = var.sbns_pub_acc_en
dynamic "network_rule_set" {
for_each = var.sbns_sku == "Premium" ? [1] : []
content {
default_action = var.sbns_nfs_def_action
public_network_access_enabled = var.sbns_pub_acc_en
trusted_services_allowed = "true"
ip_rules = var.sbns_ip_rules
network_rules {
subnet_id = var.sbns_snet_id
}
}
}
tags = local.applied_tags
}
resource "azurerm_private_endpoint" "sn-pe" {
count = var.sbns_sku == "Premium" ? 1 : 0
name = "${azurerm_servicebus_namespace.sb-ns.name}-pep"
location = var.region
resource_group_name = var.rg_name
subnet_id = var.subnet_id_pep
custom_network_interface_name = "${azurerm_servicebus_namespace.sb-ns.name}-pep-nic"
private_service_connection {
name = "psc-${var.env_stack}${var.bus_stack}"
private_connection_resource_id = azurerm_servicebus_namespace.sb-ns.id
subresource_names = ["namespace"]
is_manual_connection = false
}
private_dns_zone_group {
name = "default"
private_dns_zone_ids = [var.pri_dns_zone_id_sbns]
}
}
resource "azurerm_servicebus_queue" "sb-q" {
for_each = toset(local.queue_names)
name = "${local.queue_prefix}-${each.key}-${local.sb_loc_code}"
namespace_id = azurerm_servicebus_namespace.sb-ns.id
partitioning_enabled = local.enable_partitioning
}
resource "azurerm_servicebus_topic" "sb-t" {
for_each = local.topic_names
name = "${local.topic_prefix}-${each.key}-${local.sb_loc_code}"
namespace_id = azurerm_servicebus_namespace.sb-ns.id
partitioning_enabled = local.enable_partitioning
}
resource "azurerm_servicebus_subscription" "subscription-report" {
for_each = { for key, val in local.topic_names : key => val if val.report_service == true }
name = "${local.subscription_prefix}-${each.key}-${local.sb_loc_code}-report"
topic_id = "${data.azurerm_subscription.current.id}/resourceGroups/${var.rg_name}/providers/Microsoft.ServiceBus/namespaces/sbns-${var.bus_stack}-${var.env_stack}-${var.loc_code}/topics/${local.topic_prefix}-${each.key}-${local.sb_loc_code}"
max_delivery_count = local.max_delivery_count
depends_on = [
azurerm_servicebus_topic.sb-t
]
}
resource "azurerm_servicebus_subscription" "subscription-cm2" {
for_each = { for key, val in local.topic_names : key => val if val.cm2 == true }
name = "${local.subscription_prefix}-${each.key}-${local.sb_loc_code}-cm2"
topic_id = "${data.azurerm_subscription.current.id}/resourceGroups/${var.rg_name}/providers/Microsoft.ServiceBus/namespaces/sbns-${var.bus_stack}-${var.env_stack}-${var.loc_code}/topics/${local.topic_prefix}-${each.key}-${local.sb_loc_code}"
max_delivery_count = local.max_delivery_count
requires_session = each.value.session
depends_on = [
azurerm_servicebus_topic.sb-t
]
}
resource "azurerm_role_assignment" "roleassignment_core" {
scope = azurerm_servicebus_namespace.sb-ns.id
role_definition_name = "Azure Service Bus Data Owner"
principal_id = data.azuread_service_principal.sp_core.object_id
}
resource "azurerm_role_assignment" "roleassignment_reportservice" {
scope = azurerm_servicebus_namespace.sb-ns.id
role_definition_name = "Azure Service Bus Data Owner"
principal_id = data.azuread_service_principal.sp_reportservice.object_id
}
###############################################
# Mongo Atlas #
###############################################
resource "mongodbatlas_project" "mg-proj" {
name = "${var.bus_stack}-${var.env_stack}"
org_id = var.at_org_id
}
resource "mongodbatlas_project_ip_access_list" "mg-proj-ip-whitelist" {
project_id = mongodbatlas_project.mg-proj.id
ip_address = var.mg_proj_ip_whitelist
depends_on = [
mongodbatlas_project.mg-proj
]
}
resource "mongodbatlas_privatelink_endpoint" "mg-pl-ep" {
project_id = mongodbatlas_project.mg-proj.id
provider_name = "AZURE"
region = var.mg_az_reg_name
}
resource "azurerm_private_endpoint" "mg-pl-pep" {
name = "mg-${var.bus_stack}-${var.env_stack}-pep"
location = var.region
resource_group_name = var.rg_name
subnet_id = var.subnet_id_pep
custom_network_interface_name = "mg-${var.bus_stack}-${var.env_stack}-nic"
private_service_connection {
name = mongodbatlas_privatelink_endpoint.mg-pl-ep.private_link_service_name
private_connection_resource_id = mongodbatlas_privatelink_endpoint.mg-pl-ep.private_link_service_resource_id
is_manual_connection = true
request_message = "setting up the link"
}
depends_on = [
mongodbatlas_privatelink_endpoint.mg-pl-ep
]
}
resource "mongodbatlas_privatelink_endpoint_service" "mg-pl-eps" {
project_id = mongodbatlas_privatelink_endpoint.mg-pl-ep.project_id
private_link_id = mongodbatlas_privatelink_endpoint.mg-pl-ep.private_link_id
endpoint_service_id = azurerm_private_endpoint.mg-pl-pep.id
private_endpoint_ip_address = azurerm_private_endpoint.mg-pl-pep.private_service_connection.0.private_ip_address
provider_name = "AZURE"
depends_on = [
mongodbatlas_privatelink_endpoint.mg-pl-ep, azurerm_private_endpoint.mg-pl-pep
]
}
resource "mongodbatlas_advanced_cluster" "mg-cl" {
project_id = mongodbatlas_project.mg-proj.id
name = "${var.bus_stack}-${var.env_stack}"
cluster_type = var.mg_cluster_type
replication_specs {
region_configs {
electable_specs {
instance_size = var.mg_instance_size
node_count = var.mg_node_count
disk_size_gb = var.mg_disk_size_gb
}
auto_scaling {
disk_gb_enabled = var.mg_disk_gb_enabled
}
provider_name = "AZURE"
priority = var.mg_priority
region_name = var.mg_region_name
}
}
mongo_db_major_version = var.mg_db_version
pit_enabled = var.mg_pit_enabled
backup_enabled = var.mg_backup_enabled
advanced_configuration {
minimum_enabled_tls_protocol = "TLS1_2"
oplog_size_mb = var.mg_oplog_size
}
depends_on = [
mongodbatlas_project.mg-proj,
mongodbatlas_privatelink_endpoint_service.mg-pl-eps
]
}
resource "mongodbatlas_cloud_backup_schedule" "mg-cb" {
count = var.mg_backup_enabled == "true" ? 1 : 0
project_id = mongodbatlas_project.mg-proj.id
cluster_name = mongodbatlas_advanced_cluster.mg-cl.name
reference_hour_of_day = 23
reference_minute_of_hour = 59
restore_window_days = 7
policy_item_hourly {
frequency_interval = 6
retention_unit = "days"
retention_value = 7
}
policy_item_daily {
frequency_interval = 1
retention_unit = "days"
retention_value = 7
}
policy_item_weekly {
frequency_interval = 6
retention_unit = "weeks"
retention_value = 4
}
policy_item_monthly {
frequency_interval = 40
retention_unit = "months"
retention_value = 12
}
dynamic "copy_settings" {
for_each = var.env_stack == "prod" ? ["this"] : []
content {
cloud_provider = "AZURE"
frequencies = [
"HOURLY", "DAILY", "WEEKLY", "MONTHLY"
]
region_name = "FRANCE_CENTRAL"
zone_id = mongodbatlas_advanced_cluster.mg-cl.replication_specs[0].*.zone_id[0]
should_copy_oplogs = false
}
}
depends_on = [
mongodbatlas_advanced_cluster.mg-cl
]
}
resource "mongodbatlas_maintenance_window" "mg-mw" {
project_id = mongodbatlas_project.mg-proj.id
day_of_week = 1
hour_of_day = 5
depends_on = [
mongodbatlas_advanced_cluster.mg-cl
]
}
###############################################
# Mongo Login/Passwords #
###############################################
# Mongo Admin Account
resource "mongodbatlas_database_user" "mg-admin-user" {
project_id = mongodbatlas_project.mg-proj.id
username = "admin-${var.env_stack}"
password = random_password.mg-admin-pw.result
auth_database_name = "admin"
roles {
role_name = "atlasAdmin"
database_name = "admin"
}
depends_on = [
mongodbatlas_advanced_cluster.mg-cl
]
}
resource "random_password" "mg-admin-pw" {
length = 26
min_lower = 4
min_upper = 4
min_numeric = 4
special = false
depends_on = [
mongodbatlas_advanced_cluster.mg-cl
]
}
# Pushing Admin user name into KV
resource "azurerm_key_vault_secret" "mg-admin-us-kv" {
name = "mongodb-cl-admin-user"
value = mongodbatlas_database_user.mg-admin-user.username
content_type = "MongoDB admin user"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_advanced_cluster.mg-cl
]
}
# Pushing Admin password into KV
resource "azurerm_key_vault_secret" "mg-admin-pw-kv" {
name = "mongodb-cl-admin-password"
value = random_password.mg-admin-pw.result
content_type = "MongoDB admin password"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_advanced_cluster.mg-cl
]
}
# Mongo Importer Account
resource "mongodbatlas_database_user" "mg-import-user" {
project_id = mongodbatlas_project.mg-proj.id
username = "tri_import"
password = random_password.mg-import-pw.result
auth_database_name = "admin"
roles {
role_name = "readAnyDatabase"
database_name = "admin"
}
depends_on = [
mongodbatlas_advanced_cluster.mg-cl
]
}
resource "random_password" "mg-import-pw" {
length = 26
min_lower = 4
min_upper = 4
min_numeric = 4
special = false
depends_on = [
mongodbatlas_advanced_cluster.mg-cl
]
}
# Pushing Importer user name into KV
resource "azurerm_key_vault_secret" "mg-import-us-kv" {
name = "mongodb-cl-import-user"
value = mongodbatlas_database_user.mg-import-user.username
content_type = "MongoDB import user"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_advanced_cluster.mg-cl
]
}
# Pushing Impoter password into KV
resource "azurerm_key_vault_secret" "mg-import-ps-kv" {
name = "mongodb-cl-import-password"
value = random_password.mg-import-pw.result
content_type = "MongoDB import password"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_advanced_cluster.mg-cl
]
}
###############################################
# Mongo KV Strings #
###############################################
# A set of strings imported into the env stack KV, for informational use
resource "azurerm_key_vault_secret" "mg-std-cs-kv" {
name = "mongodb-standard-cs-for-information-only"
value = mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].standard
content_type = "Connection String - standard connection string for the cluster"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_advanced_cluster.mg-cl
]
}
resource "azurerm_key_vault_secret" "mg-pe-cs-kv" {
name = "mongodb-privateendpoint-cs-for-information-only"
value = mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].private_endpoint[0].connection_string
content_type = "Connection String - private endpoint connection string for the cluster"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_database_user.mg-admin-user, random_password.mg-admin-pw, mongodbatlas_advanced_cluster.mg-cl,
mongodbatlas_privatelink_endpoint.mg-pl-ep, mongodbatlas_privatelink_endpoint_service.mg-pl-eps
]
}
resource "azurerm_key_vault_secret" "mg-pe-srv-cs-kv" {
name = "mongodb-privateendpoint-srv-cs-for-information-only"
value = mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].private_endpoint[0].srv_connection_string
content_type = "Connection String - private endpoint srv connection string for the cluster"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_database_user.mg-admin-user, random_password.mg-admin-pw, mongodbatlas_advanced_cluster.mg-cl,
mongodbatlas_privatelink_endpoint.mg-pl-ep, mongodbatlas_privatelink_endpoint_service.mg-pl-eps
]
}
# A set of strings imported into the env stack KV, historical OPRA use
resource "azurerm_key_vault_secret" "mg-dpdq-cs-kv" {
name = "opra5-deletepdquery-mongo-system"
value = "mongodb+srv://${mongodbatlas_database_user.mg-admin-user.username}:${random_password.mg-admin-pw.result}@${replace(mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].private_endpoint[0].srv_connection_string, "mongodb+srv://", "")}/"
content_type = "Connection String"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_database_user.mg-admin-user, random_password.mg-admin-pw, mongodbatlas_advanced_cluster.mg-cl,
mongodbatlas_privatelink_endpoint.mg-pl-ep, mongodbatlas_privatelink_endpoint_service.mg-pl-eps
]
}
resource "azurerm_key_vault_secret" "mg-sys-cs-kv" {
name = "opra5-mongo-system"
value = "mongodb+srv://${mongodbatlas_database_user.mg-admin-user.username}:${random_password.mg-admin-pw.result}@${replace(mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].private_endpoint[0].srv_connection_string, "mongodb+srv://", "")}/?retryWrites=true&w=majority"
content_type = "Connection String"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_database_user.mg-admin-user, random_password.mg-admin-pw, mongodbatlas_advanced_cluster.mg-cl,
mongodbatlas_privatelink_endpoint.mg-pl-ep, mongodbatlas_privatelink_endpoint_service.mg-pl-eps
]
}
resource "azurerm_key_vault_secret" "mg-eu-cs-kv" {
name = "opra5-mongo-eu"
value = "mongodb+srv://${mongodbatlas_database_user.mg-admin-user.username}:${random_password.mg-admin-pw.result}@${replace(mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].private_endpoint[0].srv_connection_string, "mongodb+srv://", "")}/?retryWrites=true&w=majority"
content_type = "Connection String"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_database_user.mg-admin-user, random_password.mg-admin-pw, mongodbatlas_advanced_cluster.mg-cl,
mongodbatlas_privatelink_endpoint.mg-pl-ep, mongodbatlas_privatelink_endpoint_service.mg-pl-eps
]
}
resource "azurerm_key_vault_secret" "mg-us-cs-kv" {
name = "opra5-mongo-us"
value = "mongodb+srv://${mongodbatlas_database_user.mg-admin-user.username}:${random_password.mg-admin-pw.result}@${replace(mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].private_endpoint[0].srv_connection_string, "mongodb+srv://", "")}/?retryWrites=true&w=majority"
content_type = "Connection String"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_database_user.mg-admin-user, random_password.mg-admin-pw, mongodbatlas_advanced_cluster.mg-cl,
mongodbatlas_privatelink_endpoint.mg-pl-ep, mongodbatlas_privatelink_endpoint_service.mg-pl-eps
]
}
resource "azurerm_key_vault_secret" "mg-im-eu-cs-kv" {
name = "opra5-importer-mongo-eu"
value = "mongodb+srv://${mongodbatlas_database_user.mg-import-user.username}:${random_password.mg-import-pw.result}@${replace(mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].private_endpoint[0].srv_connection_string, "mongodb+srv://", "")}/?retryWrites=true&w=majority"
content_type = "Connection String"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_database_user.mg-admin-user, random_password.mg-admin-pw, mongodbatlas_advanced_cluster.mg-cl,
mongodbatlas_privatelink_endpoint.mg-pl-ep, mongodbatlas_privatelink_endpoint_service.mg-pl-eps
]
}
resource "azurerm_key_vault_secret" "mg-im-us-cs-kv" {
name = "opra5-importer-mongo-us"
value = "mongodb+srv://${mongodbatlas_database_user.mg-import-user.username}:${random_password.mg-import-pw.result}@${replace(mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].private_endpoint[0].srv_connection_string, "mongodb+srv://", "")}/?retryWrites=true&w=majority"
content_type = "Connection String"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_database_user.mg-admin-user, random_password.mg-admin-pw, mongodbatlas_advanced_cluster.mg-cl,
mongodbatlas_privatelink_endpoint.mg-pl-ep, mongodbatlas_privatelink_endpoint_service.mg-pl-eps
]
}
resource "azurerm_key_vault_secret" "mg-im-sys-cs-kv" {
name = "opra5-importer-mongo-system"
value = "mongodb+srv://${mongodbatlas_database_user.mg-import-user.username}:${random_password.mg-import-pw.result}@${replace(mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].private_endpoint[0].srv_connection_string, "mongodb+srv://", "")}/?retryWrites=true&w=majority"
content_type = "Connection String"
key_vault_id = var.kv_id
depends_on = [
mongodbatlas_database_user.mg-admin-user, random_password.mg-admin-pw, mongodbatlas_advanced_cluster.mg-cl,
mongodbatlas_privatelink_endpoint.mg-pl-ep, mongodbatlas_privatelink_endpoint_service.mg-pl-eps
]
}
outputs.tf
output "psql-id" {
value = azurerm_postgresql_flexible_server.psql.id
description = "The ID of the PostgreSQL Flexible Server"
}
output "psql-fqdn" {
value = azurerm_postgresql_flexible_server.psql.fqdn
description = "The FQDN of the PostgreSQL Flexible Server"
}
output "sbns-id" {
value = azurerm_servicebus_namespace.sb-ns.id
description = "The ID of the Service Bus Namespace"
}
output "sbns-identity" {
value = azurerm_servicebus_namespace.sb-ns.identity
description = "The identity block including tenant id and principal id of servicebus namespace"
}
output "mgdb-proj-id" {
value = mongodbatlas_project.mg-proj.id
description = "The ID of the Mongo Atlas Project"
}
output "mgdb-cl-cid" {
value = mongodbatlas_advanced_cluster.mg-cl.cluster_id
description = "The cluster ID of the Mongo Atlas Cluster"
}
output "mgdb-cl-id" {
value = mongodbatlas_advanced_cluster.mg-cl.id
description = "The ID of the Mongo Atlas Cluster"
}
output "mgdb-cl-cs" {
value = mongodbatlas_advanced_cluster.mg-cl.connection_strings
description = "The connection string of the Mongo Atlas Cluster"
}
output "mgdb-cl-ad-cs" {
sensitive = true
value = "mongodb+srv://${mongodbatlas_database_user.mg-admin-user.username}:${random_password.mg-admin-pw.result}@${replace(mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].private_endpoint[0].srv_connection_string, "mongodb+srv://", "")}/"
description = "Connection string for admin access"
}
output "mgdb-cl-im-cs" {
sensitive = true
value = "mongodb+srv://${mongodbatlas_database_user.mg-import-user.username}:${random_password.mg-import-pw.result}@${replace(mongodbatlas_advanced_cluster.mg-cl.connection_strings[0].private_endpoint[0].srv_connection_string, "mongodb+srv://", "")}/"
description = "Connection string for importer access"
}
output "mgdb-pl-ep-id" {
value = mongodbatlas_privatelink_endpoint_service.mg-pl-eps.interface_endpoint_id
description = "The endpoint ID of the Mongo Atlas Private Link"
}
output "mgdb-pl-ep-cn" {
value = mongodbatlas_privatelink_endpoint_service.mg-pl-eps.private_endpoint_connection_name
description = "The endpoint connection name of the Mongo Atlas Private Link"
}
output "mgdb-pl-ep-ip" {
value = mongodbatlas_privatelink_endpoint_service.mg-pl-eps.private_endpoint_ip_address
description = "The endpoint IP of the Mongo Atlas Private Link"
}
output "mgdb-pl-ep-rid" {
value = mongodbatlas_privatelink_endpoint_service.mg-pl-eps.private_endpoint_resource_id
description = "The resource ID of the Mongo Atlas Private Link"
}
variables.tf
variable "tenant_id" {
type = string
description = "The Azure tenant ID"
}
variable "resource_tags" {
description = "various tags depending on environment"
type = map(string)
}
variable "bus_stack" {
description = "business unit - stack might be opra, ops, tri etc"
type = string
}
variable "env_stack" {
description = "environment stack - might be dev, uq, qual, val, prod, dr"
type = string
}
variable "loc_code" {
description = "abbreviated location code"
type = string
}
variable "region" {
description = "Azure region"
type = string
}
variable "rg_name" {
description = "resource group name"
type = string
}
variable "postgres_version" {
type = string
description = "Postgres version - 11,12,13,14,15,16"
}
variable "psql_username" {
type = string
description = "The username for the local Postgres admin"
}
variable "psql_kvs" {
type = string
description = "The key vault where the local password is stored"
sensitive = true
}
variable "subnet_id_psql" {
description = "the subnet the PSQL is hosted on"
type = string
}
variable "pri_dns_zone_id_psql" {
description = "the Azure private dns zone hosted in Ops subscription"
type = string
}
variable "pri_dns_zone_id_sbns" {
description = "the Azure private dns zone hosted in Ops subscription"
type = string
}
variable "psql_sku" {
type = string
description = "Azure SKU of the Postgres server - B_Standard_B1ms, GP_Standard_d2s_v3"
}
variable "psql_backup_retention_days" {
type = number
description = "Number of days to retain the backups - values between 7-35 days"
}
variable "psql_storage_tier" {
type = string
description = "Storage tier of the Postgres server. e.g. 'P4', P6, P10, P15, P20, P30, P40, P50, P60, P70, P80"
}
variable "psql_storage_mb" {
type = number
description = "Storage MB for Postgres server - 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4193280, 4194304, 8388608, 16777216 and 33553408"
}
variable "geo_redundant_backup_enabled" {
type = bool
description = "Enable geo redundancy backups - true or false"
}
variable "psql_max_con" {
type = number
description = "the maximum connections that can be made to the Postgres server."
}
variable "psql_max_prep_tran" {
type = number
description = "the maximum connections that can be made to the Postgres server."
}
variable "sp_core" {
type = string
description = "Service Principal Client ID for core."
}
variable "sp_reportservice" {
type = string
description = "Service Principal Client ID for report service."
}
variable "sbns_sku" {
type = string
description = "Azure SKU of the Service Bus Namespace - Basic, Standard or Premium"
}
variable "sbns_pub_acc_en" {
type = string
description = "Whether public access is enabled on the Service Bus or not"
}
variable "sbns_nfs_def_action" {
type = string
description = "Specifies the default action for the Network Rule Set. Should be Deny to enable only access from vnets and firewalled ip_rules"
}
variable "sbns_ip_rules" {
type = list(string)
description = "List of IP addresses or CIDR blocks allowed to access the Service Bus namespace"
}
variable "sbns_snet_id" {
type = string
description = "The Subnet ID which should be able to access this ServiceBus Namespace."
}
variable "at_org_id" {
type = string
description = "Location of Atlas Org key, in the Ops Key Vault"
}
variable "mg_proj_ip_whitelist" {
type = string
description = "IPs to whitelist access to the Atlas MongoDB project"
}
variable "mg_cluster_type" {
type = string
description = "type of Atlas MongoDB cluster - REPLICASET, SHARDED, GLOBAL & SERVERLESS"
}
variable "mg_instance_size" {
type = string
description = "instance size of the cluster - M10, M20, M30 & M40 etc"
}
variable "mg_node_count" {
type = number
description = "Number of electable nodes to deploy in the specified region - 0, 3, 5 & 7"
}
variable "mg_disk_gb_enabled" {
type = bool
description = "Part of the auto-scaling configuration. When set to true, it enables automatic scaling of the disk size for the cluster - true/false"
}
variable "mg_disk_size_gb" {
type = number
description = "Maximum size of the cluster - 16, 32, 64 etc"
}
variable "mg_priority" {
type = number
description = "Number that indicates the election priority of the region. To identify the Preferred Region of the cluster, set this parameter to 7. The primary node runs in the Preferred Region. To identify a read-only region, set this parameter to 0 - 0-7"
}
variable "mg_region_name" {
type = string
description = "Region where cluster is located - EUROPE_NORTH, EUROPE_WEST etc"
}
variable "mg_db_version" {
type = string
description = "Version of the cluster to deploy. Atlas supports the following MongoDB versions for M10+ clusters: 4.4, 5.0, 6.0 or 7.0"
}
variable "mg_pit_enabled" {
type = bool
description = "Point in time enabled - true or false"
}
variable "mg_backup_enabled" {
type = bool
description = "Backup enabled - true or false"
}
variable "mg_oplog_size" {
type = number
description = "Size of oplog - 10000 or 40000 depending on configuration of cluster"
}
variable "subnet_id_pep" {
type = string
description = "Subnet value where private endpoints are located"
}
variable "mg_az_reg_name" {
type = string
description = "Region where Azure PL is created in Azure - northeurope, westeurope, uksouth, ukwest, francecentral etc"
}
variable "kv_id" {
type = string
description = "ID of stack key vault"
}
variable "sb_loc_code" {
type = string
description = "Abbreviation of the Azure location. e.g. 'eun'. Hopefully this is temporary"
default = "eun"
}
provider.tf
# Configure the Azure provider
terraform {
required_providers {
mongodbatlas = {
source = "mongodb/mongodbatlas"
version = "1.34.0"
}
}
}
Example
module "data-opra-dev1-ne" {
source = "app.terraform.io/----/data/azure"
version = "2.0.10"
tenant_id = var.tenant_id
bus_stack = "ops"
env_stack = "poc"
loc_code = "ne"
region = var.region_ne
rg_name = azurerm_resource_group.opra-dev1-rg.name
postgres_version = "16"
psql_username = azurerm_key_vault_secret.dev1-psqllogin.value
psql_kvs = azurerm_key_vault_secret.dev1-psqlsecret.value
subnet_id_psql = local.snet_psql
pri_dns_zone_id_psql = local.dns_psql
psql_sku = local.psql_sku.non_production
psql_backup_retention_days = 7
psql_storage_tier = "P4"
psql_storage_mb = 32768
geo_redundant_backup_enabled = false
psql_max_con = 200
psql_max_prep_tran = 200
sbns_sku = local.sbns_sku.standard
sbns_pub_acc_en = "true"
pri_dns_zone_id_sbns = local.dns_sbns
sbns_nfs_def_action = "Deny"
sbns_ip_rules = local.sbns_ip_ruleset
sbns_snet_id = local.snet_id
sp_core = local.service_principal.dev_core
sp_reportservice = local.service_principal.dev_reportservice
at_org_id = local.kvs_atoid
mg_proj_ip_whitelist = "13.79.129.214"
mg_cluster_type = "REPLICASET"
mg_instance_size = "M10"
mg_node_count = 3
mg_disk_size_gb = 32
mg_priority = 7
mg_region_name = "EUROPE_NORTH"
mg_db_version = "7.0"
mg_disk_gb_enabled = false
mg_pit_enabled = false
mg_backup_enabled = false
mg_oplog_size = 10000
subnet_id_pep = local.snet_pep
mg_az_reg_name = "northeurope"
kv_id = module.kv-opra-dev1-ne.kv-id
resource_tags = {
service = "opra"
env = "dev1"
}
}