Basic Azure Qumulo Cluster Example¶
This example demonstrates the minimal configuration required to deploy a Qumulo cluster on Azure.
Prerequisites¶
- Azure subscription with Contributor permissions
- Existing virtual network and subnet, sized for one address per node, one for the provisioner VM, one per floating IP, and the 5 Azure reserves in every subnet
- Terraform >= 1.0
- Enabling
deletion_protectionadditionally requiresMicrosoft.Authorization/locks/*(carried by Owner, User Access Administrator, or a custom role; Contributor alone cannot manage management locks) - Setting
floating_ip_countadditionally requiresMicrosoft.Network/virtualNetworks/CheckIPAddressAvailability/actionon the virtual network, used to find free subnet addresses (Contributor and Network Contributor carry it; a narrow cross-resource-group grant of onlysubnets/readplussubnets/join/actiondoes not)
Subnet Service Endpoints Required
Your Virtual Network subnet must have the Microsoft.KeyVault and Microsoft.Storage service endpoints enabled before deploying a cluster. To verify, go to the Azure Portal, navigate to Virtual networks > your VNet > Settings > Service endpoints, and confirm both endpoints are added for the target subnet. Deployment will fail without these service endpoints.
Usage¶
-
Edit
main.tfwith your values (look for<-- Replacecomments in the Full Configuration below) -
Initialize and deploy:
Configuration¶
This example creates:
- A 3-node Qumulo cluster
- Standard_L8s_v3 VM instances
- HOT storage tier
- Basic network security configuration
Node Count¶
Valid node counts are:
- 1 (single node for testing)
- 3-24 (cluster mode)
Note: 2-node clusters are not supported because they cannot form a majority quorum. 4-node clusters are supported only in single-zone deployments (one availability zone, or a zoneless region).
Outputs¶
After deployment, you'll get:
cluster_name: Name of the Qumulo clustercluster_uuid: UUID of the Qumulo clusterdeployment_unique_name: Unique deployment identifierendpoint_ips: Client-facing IPs. Floating IPs if configured, otherwise primary IPs.primary_ips: Per-node primary IPs. Use these directly when no floating IPs are configured, or for per-node access.endpoints: Connection endpoints for various protocols
Migrating from azure-terraform-cnq¶
If migrating from the old Terraform module:
- No nested blocks: all fields are flat in the provider
- Different field names: network settings use
subnet_id(full Azure resource ID); there is nopersistent_storageblock - Removed fields:
floating_ips_per_nodeis gone; setfloating_ip_countand the provider allocates the addresses - Naming: the module's
deployment_namecarries over as the provider'sdeployment_name(lowercase prefix for Azure resource names); pair it withcluster_name(the qfsd name shown in the Qumulo UI), or setnameto use one value for both
Use terraform import to bring an existing cluster under provider management without redeploying. See the Import Guide for details.
Troubleshooting¶
- Node count: Valid values are 1, or 3-24 (2 is not supported; 4 requires a single availability zone)
- AZ distribution: Node count should ideally be divisible by the number of AZs
- VM type changes: Triggers cluster replacement (handled automatically)
- VM types: Only L-series (storage-optimized) VMs are supported
- Zoneless regions: Omit
availability_zonesfor regions that don't support zones - Storage replication: Use
LRSfor zoneless regions;ZRSrequires zone support - Debug logs: Run
TF_LOG=DEBUG terraform applyfor detailed output
Full Configuration¶
# Example: Basic Azure Qumulo Cluster
# This example shows the minimal configuration for a Qumulo cluster on Azure.
# The resource group is automatically created if it doesn't exist.
# Edit the values below directly, then run: terraform init -upgrade && terraform apply
terraform {
required_version = ">= 1.0"
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
# 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
# # Customer-managed Key Vault resource ID.
# # When provided, the provider uses this vault instead of creating one.
# # Conflicts with disable_keyvault_public_network_access and private_link_keyvault_dns_zone_id.
# # Immutable after creation. You own vault lifecycle, network, and access policies.
# # The vault must permit access from the cluster subnet - the nodes read their
# # storage SAS tokens from it. See "Customer-Managed Key Vault" in azure-production.md.
# # Format: /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.KeyVault/vaults/{name}
# key_vault_id = "/subscriptions/xxx/resourceGroups/xxx/providers/Microsoft.KeyVault/vaults/xxx"
# 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 = "qumulo"
# Prefix for the Azure resources this cluster creates (2-15 lowercase chars).
deployment_name = "qumulo"
# # Custom naming templates for Azure resources.
# # vm_name must contain exactly one {node_id} placeholder.
# # storage_account must contain exactly one {index} or {index:N} placeholder.
# # {index} is the unpadded index. {index:N} zero-pads the index to width N, so
# # {index:2} yields "01", "02", ... ({index:02} is equivalent -- the leading 0 is redundant).
# # Changing either forces replacement of the affected resource.
# naming = [{
# vm_name = "myapp-{node_id}"
# storage_account = "myappstor{index:02}"
# }]
# # Network management mode.
# # "host_managed" (default) for clusters created by this provider.
# # "qumulo_managed" only for clusters originally created by azure-terraform-cnq.
# # Immutable after creation. Mixing modes across nodes is prohibited.
# networking_mode = "host_managed"
# # 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
# # Resource group containing persistent storage accounts and KeyVault.
# # Required when migrating from the legacy azure-terraform-cnq module, where persistent
# # storage lived in a separate resource group. Defaults to resource_group_name when omitted.
# persistent_storage_resource_group = "rg-qumulo-persistent"
# # 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-basic" # <-- 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 = "Development"
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 "cluster_uuid" {
description = "UUID of the Qumulo cluster"
value = qumulo_filesystem_azure.cluster.cluster_uuid
}
output "deployment_unique_name" {
description = "Unique deployment identifier"
value = qumulo_filesystem_azure.cluster.deployment_unique_name
}
output "endpoint_ips" {
description = "Client-facing IPs. Floating IPs if configured, otherwise primary IPs."
value = qumulo_filesystem_azure.cluster.endpoint_ips
}
output "primary_ips" {
description = "Per-node primary IPs. Use these directly when no floating IPs are configured, or for per-node access."
value = qumulo_filesystem_azure.cluster.primary_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"
}
}