S3 State Backend Example¶
This example demonstrates using AWS S3 for Terraform state management when deploying Qumulo clusters on Azure. Useful for AWS-centric environments managing multi-cloud infrastructure.
The backend configuration is a standard backend "s3" block:
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "qumulo/azure/prod.tfstate"
region = "us-east-1"
use_lockfile = true
encrypt = true
}
}
State locking uses S3's native conditional-write support (use_lockfile = true), which requires Terraform 1.10 or later. No DynamoDB table is needed.
Prerequisites¶
- AWS account with S3 access
- Azure subscription with Contributor permissions
- Existing virtual network and subnet
- Terraform >= 1.10
Creating the Backend Bucket¶
Before running terraform init -upgrade, create the S3 bucket:
aws s3 mb s3://my-terraform-state-bucket --region us-east-1
# Enable versioning so prior state revisions are recoverable
aws s3api put-bucket-versioning \
--bucket my-terraform-state-bucket \
--versioning-configuration Status=Enabled
Usage¶
- Edit
main.tf: - Update the
backend "s3"block with your bucket details -
Update provider and resource values (look for
<-- Replacecomments) -
Initialize and deploy:
Backend Configuration Options¶
use_lockfile: Enable native S3 state locking (recommended; Terraform 1.10+)encrypt: Enable encryption at restprofile: Use a specific AWS profilerole_arn: Assume role for cross-account access
Best Practices¶
- Enable bucket versioning for state history and recovery
- Use server-side encryption (SSE-S3 or SSE-KMS)
- Enable MFA delete for additional protection
- Restrict bucket access using IAM policies
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¶
# Example: Using S3 as Terraform State Backend
# This example shows how to configure remote state with AWS S3.
# Useful when managing Qumulo Azure clusters from an AWS-centric environment.
# 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 S3 bucket must exist before running terraform init -upgrade.
# Create it with:
# aws s3 mb s3://my-terraform-state-bucket --region us-east-1
# aws s3api put-bucket-versioning \
# --bucket my-terraform-state-bucket \
# --versioning-configuration Status=Enabled
#
# State locking uses S3's native conditional-write support (use_lockfile),
# which requires Terraform 1.10+. No DynamoDB table is needed.
terraform {
required_version = ">= 1.10"
# S3 backend configuration
backend "s3" {
bucket = "my-terraform-state-bucket" # <-- Your S3 bucket name
key = "qumulo/azure/prod.tfstate" # <-- Path within bucket
region = "us-east-1" # <-- AWS region for bucket
# Enable native S3 state locking (recommended; Terraform 1.10+)
use_lockfile = true
# Enable encryption at rest
encrypt = true
# Optional: Use a specific AWS profile
# profile = "production"
# Optional: Assume a role for cross-account access
# role_arn = "arn:aws:iam::123456789012:role/TerraformStateAccess"
}
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 on Azure (state stored in S3)
# 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 = "qumulos3"
# Prefix for the Azure resources this cluster creates (2-15 lowercase chars).
deployment_name = "qumulos3"
# # 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-AZ 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"
}
}