Azure Blob Storage Backend Example¶
This example demonstrates using Azure Blob Storage for Terraform state management. The
backend "azurerm" block in main.tf points Terraform at a storage account and
container that you create before the first terraform init -upgrade:
terraform {
backend "azurerm" {
resource_group_name = "tfstate-rg"
storage_account_name = "tfstatequmulo"
container_name = "tfstate"
key = "qumulo/prod.terraform.tfstate"
}
}
Prerequisites¶
- Azure subscription with Contributor permissions
- Storage account and container for state (see below)
- Existing virtual network and subnet
- Terraform >= 1.0
Creating the Backend Storage¶
Before running terraform init -upgrade, create the storage account and container:
az group create -n tfstate-rg -l eastus2
az storage account create -n tfstatequmulo -g tfstate-rg -l eastus2 --sku Standard_LRS
az storage container create -n tfstate --account-name tfstatequmulo
Usage¶
- Edit
main.tf: - Update the
backend "azurerm"block with your storage details -
Update provider and resource values (look for
<-- Replacecomments) -
Initialize and deploy:
Backend Configuration Options¶
Optional attributes for the backend "azurerm" block:
use_azuread_auth: Use Azure AD instead of access keyssubscription_id: Specify if different from provideruse_oidc: For GitHub Actions or other OIDC providers
Best Practices¶
- Enable soft delete on the storage account for state recovery
- Use a dedicated storage account for Terraform state
- Enable versioning for state history
- Restrict access using Azure RBAC
Outputs¶
cluster_name: The name of your clusterendpoint_ips: IP addresses for client connectionsendpoints: Pre-formatted connection strings for web UI, API, NFS, and SMB
Full Configuration¶
The complete main.tf:
# Example: Using Azure Blob Storage as Terraform State Backend
# This example shows how to configure remote state with Azure Blob Storage.
# The resource group for the Qumulo cluster is automatically created if it doesn't exist.
# Edit the values below directly, then run: terraform init -upgrade && terraform apply
#
# PREREQUISITES:
# The storage account and container must exist before running terraform init -upgrade.
# Create them with:
# az group create -n tfstate-rg -l eastus2
# az storage account create -n tfstatequmulo -g tfstate-rg -l eastus2 --sku Standard_LRS
# az storage container create -n tfstate --account-name tfstatequmulo
terraform {
required_version = ">= 1.0"
# Azure Blob Storage backend configuration
backend "azurerm" {
resource_group_name = "tfstate-rg" # <-- Resource group containing storage account
storage_account_name = "tfstatequmulo" # <-- Storage account name (must be globally unique)
container_name = "tfstate" # <-- Container for state files
key = "qumulo/prod.terraform.tfstate" # <-- Path within container
# Optional: Use Azure AD authentication instead of access keys
# use_azuread_auth = true
# Optional: Specify subscription if different from provider
# subscription_id = "your-subscription-id"
# Optional: For GitHub Actions or other OIDC providers
# use_oidc = true
}
required_providers {
qumulo = {
source = "qumulo-terraform-registry.s3.us-east-1.amazonaws.com/qumulo/qumulo"
version = "~> 1.0"
}
}
}
provider "qumulo" {
azure {
subscription_id = "your-subscription-id" # <-- Replace with your Azure subscription ID
environment = "public" # "public" or "usgovernment"
}
}
variable "ssh_public_key_path" {
description = "Path to SSH public key file for cluster node access"
type = string
default = "~/.ssh/id_rsa.pub"
}
# Qumulo cluster (state stored in Azure Blob)
# Note: The resource group is automatically created if it doesn't exist.
# If it exists, the provider will use it (location must match).
resource "qumulo_filesystem_azure" "cluster" {
provider = qumulo
# =============================================================================
# All attributes (alphabetical order)
# Uncomment any attribute to use it. Required attributes are not commented.
# =============================================================================
# Admin password for cluster access.
# 8-128 characters, must include 3+ of: uppercase, lowercase, digit, special character.
# Immutable after creation - use Qumulo UI or CLI to change.
admin_password = "YourSecurePassword123!"
# CIDR blocks allowed to access the cluster (required, at least one).
# Example: ["10.0.0.0/8", "172.16.0.0/12"]
allow_cidrs = ["0.0.0.0/0"]
# # Availability zones for node distribution.
# # Controls both node placement and VM disk storage type:
# # - With zones: Uses PremiumV2_LRS disks (better price/performance).
# # - Without zones: Uses Premium_LRS disks (required for zoneless regions).
# # Omit for single-zone or zoneless regions (e.g., northcentralus).
# # Example: ["1", "2", "3"] for multi-AZ high availability.
# availability_zones = ["1", "2", "3"]
# # User-assigned managed identity for cluster nodes.
# # When provided, RBAC roles must be pre-configured (not auto-created).
# # Format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{name}
# cluster_node_identity_id = "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.ManagedIdentity/userAssignedIdentities/xxx"
# Cluster storage product type.
# HOT: Optimized for frequently accessed data (default).
# COLD: Optimized for archival/infrequently accessed data.
# Immutable after creation.
cluster_product_type = "HOT"
# # Qumulo software version (optional, defaults to latest).
# # Immutable after creation - use Qumulo UI or CLI to upgrade.
# cluster_version = "7.5.0"
# # Custom managed image ID for cluster nodes.
# # Use for hardened or pre-configured VMs.
# # WARNING: Changes trigger cluster replacement!
# # Format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/images/{name}
# # Create private endpoints in the cluster subnet.
# # Set the matching private_link_appconfig_dns_zone_id,
# # private_link_keyvault_dns_zone_id, or private_link_storage_dns_zone_id to also
# # register them in an Azure Private DNS zone. Without a zone, read the computed
# # appconfig_private_endpoint / keyvault_private_endpoint /
# # storage_private_endpoints outputs and create the records yourself.
# # create_keyvault_private_endpoint conflicts with key_vault_id.
# # Each endpoint carries a per-hour Azure cost.
# create_appconfig_private_endpoint = false
# create_keyvault_private_endpoint = false
# create_storage_private_endpoint = false
# custom_image_id = "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Compute/images/xxx"
# # Disable App Configuration public network access.
# # When true, requires private_link_appconfig_dns_zone_id.
# disable_appconfig_public_network_access = false
# # Disable Key Vault public network access.
# # When true, requires private_link_keyvault_dns_zone_id.
# disable_keyvault_public_network_access = false
# # Floating IP addresses for client access.
# # Use for DNS round-robin or custom load balancing.
# # When set, endpoint_ips output returns these instead of node IPs.
# # Number of floating IPs for client access. The provider picks the
# # addresses from the cluster subnet and reports them in floating_ips.
# floating_ip_count = 3
# Azure region for deployment.
location = "eastus2" # <-- Azure region
# # Azure Marketplace image for cluster nodes.
# # Alternative to custom_image_id for standard enterprise images.
# # WARNING: Changes trigger cluster replacement!
# marketplace_image = [{
# publisher = "Canonical"
# offer = "0001-com-ubuntu-server-jammy"
# sku = "22_04-lts-gen2"
# version = "latest"
# }]
# qfsd cluster name, shown in the Qumulo UI (2-15 chars, case preserved).
cluster_name = "qumuloblob"
# Prefix for the Azure resources this cluster creates (2-15 lowercase chars).
deployment_name = "qumuloblob"
# # Qumulo Nexus registration key for remote support.
# # Obtain from https://nexus.qumulo.com/user/registration-key
# # Only applied during initial cluster creation.
# nexus_registration_key = "your-nexus-key"
# Number of nodes in the cluster.
# Valid values: 1 (single node), or 3-24 (4 nodes: single-zone only).
# Note: 2 is not a valid node count.
node_count = 3
# # Enable ICMP ingress (ping) in network security group.
# # Useful for network diagnostics.
# nsg_allow_ingress_icmp = false
# # Private DNS zone ID for App Configuration private endpoint.
# # Required when disable_appconfig_public_network_access is true.
# # Requires create_appconfig_private_endpoint = true.
# # Format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/privateDnsZones/privatelink.azconfig.io
# private_link_appconfig_dns_zone_id = "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/privateDnsZones/privatelink.azconfig.io"
# # Private DNS zone ID for Key Vault private endpoint.
# # Required when disable_keyvault_public_network_access is true.
# # Requires create_keyvault_private_endpoint = true.
# # Format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/privateDnsZones/privatelink.vaultcore.azure.net
# private_link_keyvault_dns_zone_id = "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/privateDnsZones/privatelink.vaultcore.azure.net"
# # Private DNS zone ID for the storage private endpoints.
# # Requires create_storage_private_endpoint = true.
# # Format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/privateDnsZones/privatelink.blob.core.windows.net
# private_link_storage_dns_zone_id = "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Network/privateDnsZones/privatelink.blob.core.windows.net"
# # Custom managed image ID for provisioner VM.
# # Does NOT trigger cluster replacement when changed.
# provisioner_custom_image_id = "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.Compute/images/xxx"
# # User-assigned managed identity for provisioner VM.
# # When provided, RBAC roles must be pre-configured.
# provisioner_identity_id = "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.ManagedIdentity/userAssignedIdentities/xxx"
# # Azure Marketplace image for provisioner VM.
# # Does NOT trigger cluster replacement when changed.
# provisioner_marketplace_image = [{
# publisher = "Canonical"
# offer = "0001-com-ubuntu-server-jammy"
# sku = "22_04-lts-gen2"
# version = "latest"
# }]
# # VM size for the provisioner instance.
# # Used during deployment operations only.
# provisioner_vm_type = "Standard_B2s"
# Azure resource group name.
# The resource group is automatically created if it doesn't exist.
# If it exists, the provider will use it (location must match).
resource_group_name = "rg-qumulo-prod" # <-- Name your resource group
# Soft capacity limit in TB (50-10000).
# Can be increased to add storage, but cannot be decreased.
soft_capacity_limit_tb = 1000
# SSH public key for cluster node access.
# Point to your public key file (e.g., ~/.ssh/id_rsa.pub).
ssh_public_key = file(var.ssh_public_key_path)
# Azure storage replication type for storage accounts only (does not affect VM disks).
# LRS: Locally redundant (3 copies in single datacenter).
# ZRS: Zone redundant (3 copies across availability zones) - RECOMMENDED for multi-AZ clusters.
# Note: VM disk redundancy is controlled by availability_zones (see above).
# Immutable after creation.
storage_replication_type = "LRS"
# Storage class backing the cluster's persistent data.
# STANDARD: Plain hot storage. Required when storage_replication_type is LRS.
# INTELLIGENT_TIERING: Smart tiering (the default on Qumulo Core 7.8.4 and newer),
# which Azure only permits on zone-redundant storage (ZRS).
# Immutable after creation.
storage_class = "STANDARD"
# Full Azure resource ID of the subnet for cluster deployment.
# The subnet must already exist before deploying the cluster.
# Format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/virtualNetworks/{vnet}/subnets/{subnet}
subnet_id = "/subscriptions/your-subscription-id/resourceGroups/rg-network/providers/Microsoft.Network/virtualNetworks/vnet-main/subnets/subnet-qumulo"
# Tags to apply to all Azure resources (including the resource group if created).
tags = {
Environment = "Production"
ManagedBy = "Terraform"
}
# Azure VM size for cluster nodes.
# Only L-series storage-optimized VMs are supported.
# Examples: Standard_L8s_v3, Standard_L16s_v3, Standard_L32s_v3
vm_type = "Standard_L8s_v3"
deletion_protection = true # recommended: guard the cluster's VMs and storage accounts
timeouts {
create = "90m"
delete = "30m"
}
}
output "cluster_name" {
description = "Name of the Qumulo cluster"
value = qumulo_filesystem_azure.cluster.cluster_name
}
output "endpoint_ips" {
description = "Endpoint IP addresses for client connections"
value = qumulo_filesystem_azure.cluster.endpoint_ips
}
output "endpoints" {
description = "Connection endpoints for various protocols"
value = {
web_ui = "https://${try(qumulo_filesystem_azure.cluster.endpoint_ips[0], "pending")}"
api = "https://${try(qumulo_filesystem_azure.cluster.endpoint_ips[0], "pending")}:8000"
nfs = "${try(qumulo_filesystem_azure.cluster.endpoint_ips[0], "pending")}:/"
smb = "\\\\${try(qumulo_filesystem_azure.cluster.endpoint_ips[0], "pending")}\\share"
}
}