Skip to main content
Jorge Bernhardt Jorge Bernhardt
  1. Posts/

Terraform - Provisioning Azure Service Bus Namespaces

·1661 words·8 mins· 100 views · 5 likes ·
Terraform IaC Azure Service Bus Microsoft Azure

Azure Service Bus is a messaging service on the cloud that enables communication between applications and services. It’s a key component in many cloud architectures, especially when dealing with large-scale applications or services that require reliable, secure message passing and processing.

Terraform, on the other hand, is an infrastructure-as-code tool that simplifies and automates the deployment of cloud resources, including those on Azure. Terraform allows you to easily set up, modify, and version your Azure Service Bus infrastructure.

This post is the first in a series where I’ll guide you through deploying the most important Azure Service Bus resources using Terraform. We’ll explore each component clearly and straightforwardly, making it easy for you to understand and apply these practices to your Azure Service Bus setup.

Prerequisites>

Prerequisites #

  • You need Terraform CLI on your local machine, if you’re new to using Terraform to deploy Microsoft Azure resources, then I recommend you check out this link.
  • A text editor or IDE of your choice (Visual Studio Code with Terraform extension is my recommendation)
Declare Azure Provider in Terraform>

Declare Azure Provider in Terraform #

The provider.tf file in Terraform is used to specify and configure the providers used in your Terraform configuration. A provider is a service or platform where the resources will be managed. This could be a cloud provider like Microsoft Azure, AWS, Google Cloud, etc.

This file is important because it tells Terraform which provider’s API to use when creating, updating, and deleting resources. Without it, Terraform wouldn’t know where to manage your resources.

provider "azurerm" {
  features {}
}
Deploy Azure Resources Using Terraform>

Deploy Azure Resources Using Terraform #

In the case of Azure Service Bus namespace, the main.tf Terraform file includes several key components tailored to set up a Service Bus namespace effectively:

  • Locals Block: This block defines a local variable is_premium_sku, which is a map. It checks each namespace in the var.namespaces to determine if its SKU type is ‘Premium’. This local variable is used later in the code to apply certain settings conditionally, only to namespaces with a ‘Premium’ SKU.

  • Azure Service Bus Namespace Resource: This resource block is where Azure Service Bus namespaces are actually created and configured. The block uses the for_each construct to iterate over each namespace defined in var.namespaces, creating a Service Bus namespace for each. Attributes like capacity and zone_redundant are set based on the SKU type. They are applied only if the namespace is of ‘Premium’ type, as determined by the is_premium_sku local variable.

  • Dynamic Blocks: These blocks are used to apply optional configurations based on certain conditions.

  • Error Handling and Validation: The use of can() and try() functions in dynamic blocks is a method to ensure that optional configurations are applied only when the necessary data is present and correctly structured, thus preventing errors during the Terraform run.

locals {
  // Create a map to determine if each namespace's SKU is 'Premium'
  is_premium_sku = { for k, v in var.namespaces : k => v.sku == "Premium" }
}

resource "azurerm_servicebus_namespace" "namespace" {
  // Iterate over each namespace defined in var.namespaces
  for_each            = var.namespaces
  name                = each.value.name
  location            = each.value.location
  resource_group_name = each.value.resource_group_name
  sku                 = each.value.sku

  // General attributes for all SKUs
  local_auth_enabled            = each.value.local_auth_enabled
  public_network_access_enabled = each.value.public_network_access_enabled
  minimum_tls_version           = each.value.minimum_tls_version
  tags                          = var.tags

  // Attributes and configurations specific to 'Premium' SKU
  capacity       = local.is_premium_sku[each.key] ? each.value.capacity : null
  zone_redundant = local.is_premium_sku[each.key] ? each.value.zone_redundant : null

  // Dynamic block for identity - conditionally created for 'Premium' SKU
  dynamic "identity" {
    for_each = local.is_premium_sku[each.key] && can(each.value.identity) && try(length(each.value.identity) > 0, false) ? [each.value.identity] : []
    content {
      type         = identity.value.type
      identity_ids = identity.value.type == "UserAssigned" ? identity.value.identity_ids : null
    }
  }

  // Dynamic block for customer managed key - only for 'Premium' SKU
  dynamic "customer_managed_key" {
    for_each = local.is_premium_sku[each.key] && can(each.value.customer_managed_key) && try(length(each.value.customer_managed_key) > 0, false) ? [each.value.customer_managed_key] : []
    content {
      key_vault_key_id                  = customer_managed_key.value.key_vault_key_id
      identity_id                       = customer_managed_key.value.identity_id
      infrastructure_encryption_enabled = customer_managed_key.value.infrastructure_encryption_enabled
    }
  }

  // Dynamic block for network rule set - applies only to 'Premium' SKU
  dynamic "network_rule_set" {
    for_each = local.is_premium_sku[each.key] && can(each.value.network_rule_set) && try(length(each.value.network_rule_set) > 0, false) ? [each.value.network_rule_set] : []
    content {
      default_action           = network_rule_set.value.default_action
      ip_rules                 = network_rule_set.value.ip_rules
      trusted_services_allowed = lookup(network_rule_set.value, "trusted_services_allowed", true)
    }
  }
}
Declaration of input variables>

Declaration of input variables #

The variables.tf file in Terraform defines the variables I will use in the main.tf file. These variables allow for more flexibility and reusability in the code. In this example, the variables are defined in the variables.tf include:

  • tags: This block declares a variable named tags, which is a map of strings. It is used to assign tags to the Azure resources being created. For example, you can use a key-value pair such as Terraform = true to indicate that the resource was deployed with Terraform.

  • namespaces: This complex object variable outlines the configurations for each Service Bus namespace. Here, you specify several details, including:

    • name: The specific name for each Service Bus namespace.
    • location: The geographic location where each namespace will be situated.
    • resource_group_name: This is a particularly important attribute. It specifies the name of the resource group where the namespace will be placed. It’s crucial to note that this resource group shoul already exist in your Azure subscription where the deployment is being performed.
    • sku: Defines the service level of the namespace, such as ‘Basic’, ‘Standard’, or ‘Premium’.

    Additional optional attributes for ‘Premium’ SKUs include capacity and zone_redundant, along with other settings such as local_auth_enabled, public_network_access_enabled, and minimum_tls_version, and configurations for identity, customer_managed_key, and network_rule_set.

    The namespaces variable is also accompanied by validation rules, which are essential to ensure that the provided SKU types and capacity settings adhere to Azure’s requirements for Service Bus namespaces.

// Definition of common tags for all Service Bus namespaces
variable "tags" {
  description = "Common tags for all Service Bus namespaces."
  type        = map(string) // Specifying the type as a map of strings
}

// Configuration details for Service Bus namespaces
variable "namespaces" {
  description = "A map of Service Bus Namespace configurations"
  type = map(object({
    name                = string // Name of the Service Bus namespace
    location            = string // Azure region where the namespace will be deployed
    resource_group_name = string // Name of the Azure resource group
    sku                 = string // SKU type of the Service Bus namespace

    // Following attributes are optional and specific to 'Premium' SKU
    capacity       = number // Messaging units for the namespace, applicable only for 'Premium'
    zone_redundant = bool   // Zone redundancy, applicable only for 'Premium'

    // General optional attributes for the namespace
    local_auth_enabled            = bool   // Local authorization enabled status
    public_network_access_enabled = bool   // Public network access enabled status
    minimum_tls_version           = string // Minimum TLS version for the namespace

    // Optional configurations for advanced features
    identity             = map(string) // Managed service identity configurations
    customer_managed_key = map(string) // Customer-managed key configurations
    network_rule_set = object({        // Network rule set configurations
      default_action           = string
      ip_rules                 = list(string)
      trusted_services_allowed = optional(bool)
    })
  }))
  default = {}

  // Validation to ensure 'sku' is either 'Basic', 'Standard', or 'Premium'
  validation {
    condition     = alltrue([for ns in var.namespaces : contains(["Basic", "Standard", "Premium"], ns.sku)])
    error_message = "Each namespace must have a 'sku' that is either 'Basic', 'Standard', or 'Premium'."
  }

  // Validation for 'capacity' attribute for 'Premium' SKU
  validation {
    condition     = alltrue([for ns in var.namespaces : ns.sku != "Premium" || (ns.sku == "Premium" && contains([0, 1, 2, 4, 8, 16], ns.capacity))])
    error_message = "For 'Premium' SKU, 'capacity' must be one of [0, 1, 2, 4, 8, 16]. For 'Basic' or 'Standard' SKUs, 'capacity' must be 0."
  }
}
Declaration of output values>

Declaration of output values #

The output.tf file in Terraform extracts and displays information about the resources created or managed by your Terraform configuration. These outputs are defined using the output keyword and can be used to return information that can be useful for the user, for other Terraform configurations, or for programmatically using the information in scripts or other tools.

In this specific example, the output.tf file is configured to return information about the Azure Service Bus namespaces that were created. This is accomplished using the output keyword in Terraform:

  • A for loop is used within the output block to iterate over each Service Bus namespace created in the azurerm_servicebus_namespace resource.
  • For each namespace, the output collects and displays the id and name to which each namespace belongs.
output "namespaces_details" {
  value = {
    for namespace in azurerm_servicebus_namespace.namespace : namespace.name => {
      id   = namespace.id
      name = namespace.name
    }
  }
  description = "A map of the created Service Bus Namespace objects, providing each namespace's ID and name."
}
Executing the Terraform Deployment>

Executing the Terraform Deployment #

Now that you’ve declared the resources correctly, it’s time to take the following steps to deploy them in your Azure environment.

  • Initialization: To begin, execute the terraform init command. This will initialize your working directory that holds the .tf files and download the provider specified in the provider.tf file, and configure the Terraform backend. I suggest looking at this link if you’re curious about the process.

  • Planning: Next, execute the terraform plan. This command creates an execution plan and shows Terraform’s actions to achieve the desired state defined in your .tf files. This gives you a chance to review the changes before applying them.

  • Apply: When you’re satisfied with the plan, execute the terraform apply command. This will implement the required modifications to attain the intended infrastructure state. Before making any changes, you will be asked to confirm your decision.

  • Inspection: After applying the changes, you can use terraform show command to see the current state of your infrastructure.

  • Destroy (optional): when a project is no longer needed or when resources have become outdated. You can use the terraform destroy command. This will remove all the resources that Terraform has created.

References and useful links #

Thank you for taking the time to read my post. I sincerely hope that you find it helpful.