Storage accounts are the service everyone uses and nobody designs. Then three years pass, the account holds 400 TB of blobs nobody has read since upload, all sitting in the hot tier at hot tier prices, with anonymous access enabled because a deployment script from 2021 needed it once. Let us design instead.
Redundancy, Honestly
LRS is three copies in one datacenter and loses data if that datacenter floods, acceptable only for reproducible data. ZRS spreads the three copies across availability zones and is my production default for anything regional. GRS and GZRS add an asynchronous copy in the paired region; remember the replication is async, so geo failover has a real RPO measured in minutes, and read access variants (RA-GRS, RA-GZRS) let applications read the secondary during a regional event. Pick per data class, not per habit: ZRS for app data, GZRS for the small subset that is genuinely irreplaceable, LRS for scratch and cache.
Tiers and Lifecycle: Where the Money Is
Hot, cool, cold, and archive trade storage price against access price and latency: archive is roughly a tenth of a tenth of hot per GB, but retrieval takes hours and costs real money, and cool and cold carry early deletion penalties of 30 and 90 days. The mistake is moving data too aggressively and paying retrieval on data that turned out to be active. Measure first with last access time tracking, then codify with lifecycle rules:
resource "azurerm_storage_management_policy" "this" {
storage_account_id = azurerm_storage_account.data.id
rule {
name = "tier-and-expire-logs"
enabled = true
filters {
prefix_match = ["logs/"]
blob_types = ["blockBlob"]
}
actions {
base_blob {
tier_to_cool_after_days_since_last_access_time_greater_than = 30
tier_to_archive_after_days_since_last_access_time_greater_than = 180
delete_after_days_since_last_access_time_greater_than = 730
}
snapshot {
delete_after_days_since_creation_greater_than = 90
}
}
}
}
Last access based tiering (rather than last modified) is the difference between archiving cold data and archiving data that is read daily but written once. Run the numbers on one real account and lifecycle rules typically cut its bill by half or more.
Data Lake Gen2
Enabling hierarchical namespace turns a blob account into a real filesystem: directories are first class, renames are atomic metadata operations instead of copy and delete storms, and POSIX ACLs apply per directory alongside RBAC. Every analytics engine that matters, Spark, Databricks, Synapse, Fabric, treats ADLS Gen2 as native ground truth. Design the zones up front (raw, curated, serving), apply ACLs by group at the zone and domain directory level, and use RBAC for coarse access with ACLs for fine grained. One caveat: some classic blob features have gaps with HNS enabled, so check the compatibility list before enabling it on an existing account, and remember the switch is one way.
The Security Checklist
Every account, no exceptions: public network access disabled with private endpoints per subresource, shared key access disabled so Entra RBAC is the only auth path, allow_nested_items_to_be_public false, TLS 1.2 minimum, infrastructure encryption for regulated data, soft delete for blobs and containers, and versioning plus immutability policies on anything an attacker or an accident should not be able to rewrite. Each one is a single Terraform argument, and the policy assignments from earlier this month make them tenant law instead of team folklore.
Cheers
Osama
Leave a comment