Terraform Azure moduly: Privátní registr a testování
Jako konzultant vidím Terraform kód desítek firem ročně. Jeden vzorec se opakuje spolehlivě: firma začne s jedním projektem, napíše pěkný main.tf, všechno funguje. Pak přijde druhý projekt. Někdo zkopíruje VNet konfiguraci z prvního projektu, upraví pár parametrů a jede se dál. Po roce má firma šest projektů, šest kopií VNet kódu a žádné dvě nejsou totožné.
Tohle není problém disciplíny. Je to problém architektury -- chybí jedno místo pravdy s verzováním.
Effort: 3-5 dní na modularizaci existujícího Terraformu, 1 den na modul pro testování Cost: Terraform Cloud free tier (5 uživatelů) nebo ~$20/uživatel/měsíc za Teams; Azure Container Registry Basic ~$5/měsíc pro hostování modulů Prerequisites: Existující Terraform Azure projekt, Go 1.21+ pro Terratest, CI/CD pipeline (GitHub Actions nebo Azure DevOps)
Co se změnilo v roce 2025
Ekosystém Terraform modulů prošel v roce 2025 výraznými změnami:
- Terraform 1.7+ přinesl nativní testovací framework. Příkaz
terraform testumožňuje psát.tftest.hclsoubory přímo vedle modulu a spouštět aserce bez Go, bez testovacího frameworku, bez extra CI kroku. Pro validaci na úrovni plánu je to průlom. - OpenTofu 1.8 jako alternativa. Po změně licence na BSL se OpenTofu vyvinul v produkčně použitelný fork. Kompatibilita modulů je téměř 100% -- většina týmů může vyměnit binárku a moduly fungují beze změn. Rozhodnutí o migraci ale nemá smysl dělat pod tlakem.
- Azure Verified Modules (AVM) se staly oficiálně doporučeným vzorem pro Terraform moduly cílené na Azure. Starší modul
terraform-azurerm-caf-enterprise-scaleje v extended support a bude archivován v srpnu 2026. - Podpora OCI artefaktů v Azure Container Registry. Terraform moduly lze publikovat do ACR jako OCI artefakty, čímž získáte privátní registr bez Terraform Cloud.
Proč na tom záleží
Bez centrálního registru modulů se Terraform kódbáze rozkládá specifickým způsobem. Kód nepřestane fungovat -- začne divergovat.
Projekt A vytvoří VNet modul se třemi subnety a výchozím NSG. Projekt B ho zkopíruje a přidá čtvrtý subnet pro AKS. Projekt C zkopíruje z projektu A (ne B) a přidá service endpoint pro Key Vault. Teď máte tři VNet "moduly" s různou funkcionalitou, žádný z nich není testovaný a žádný nemá číslo verze.
Klasický scénář z české praxe: bezpečnostní audit odhalí, že ne všechny VNety mají zapnuté Network Watcher flow logy. Někdo musí projít každý projekt, pochopit každou variantu a opravit je jednotlivě. V organizaci s deseti projekty se třicetiminutová změna promění v vícedenní maraton.
Verzované moduly tento problém řeší systémově. Verze 1.x vašeho VNet modulu vytváří VNet se subnety. Verze 2.0 přidává povinné flow logy. Projekty na ~> 1.0 fungují dál. Upgrade na ~> 2.0 si každý tým naplánuje sám.
Implementace: Od kopírování k modulům
Struktura modulu
Standardní struktura, kterou používáme u klientských projektů:
terraform-azurerm-vnet/
main.tf # Definice zdrojů
variables.tf # Vstupní proměnné s popisy a výchozími hodnotami
outputs.tf # Výstupní hodnoty pro konzumenty
versions.tf # Požadované verze providerů
README.md # Příklady použití
tests/
main.tftest.hcl # Nativní Terraform testy
examples/
zakladni/
main.tf # Minimální funkční příklad
kompletni/
main.tf # Všechny volby využité
VNet modul pro Azure
Modul, který skutečně používáme. Vytváří virtuální síť s konfigurovatelnými subnety, volitelným NSG a diagnostickým logováním:
# terraform-azurerm-vnet/main.tf
resource "azurerm_virtual_network" "this" {
name = var.name
location = var.location
resource_group_name = var.resource_group_name
address_space = var.address_space
dns_servers = var.dns_servers
tags = merge(var.tags, {
managed_by = "terraform"
module = "terraform-azurerm-vnet"
})
}
resource "azurerm_subnet" "this" {
for_each = var.subnets
name = each.key
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.this.name
address_prefixes = [each.value.address_prefix]
service_endpoints = lookup(each.value, "service_endpoints", [])
default_outbound_access_enabled = lookup(each.value, "default_outbound_access", true)
}
resource "azurerm_network_security_group" "this" {
for_each = {
for k, v in var.subnets : k => v if lookup(v, "create_nsg", true)
}
name = "nsg-${each.key}"
location = var.location
resource_group_name = var.resource_group_name
tags = var.tags
}
resource "azurerm_subnet_network_security_group_association" "this" {
for_each = azurerm_network_security_group.this
subnet_id = azurerm_subnet.this[each.key].id
network_security_group_id = each.value.id
}# terraform-azurerm-vnet/variables.tf
variable "name" {
type = string
description = "Název virtuální sítě"
}
variable "location" {
type = string
description = "Azure region pro virtuální síť"
default = "westeurope"
}
variable "resource_group_name" {
type = string
description = "Název resource group"
}
variable "address_space" {
type = list(string)
description = "Adresní prostor virtuální sítě"
default = ["10.0.0.0/16"]
}
variable "dns_servers" {
type = list(string)
description = "Vlastní DNS servery. Prázdný seznam použije Azure DNS"
default = []
}
variable "subnets" {
type = map(object({
address_prefix = string
service_endpoints = optional(list(string), [])
create_nsg = optional(bool, true)
default_outbound_access = optional(bool, true)
}))
description = "Mapa konfigurací subnetů"
default = {}
}
variable "tags" {
type = map(string)
description = "Tagy aplikované na všechny zdroje"
default = {}
}# terraform-azurerm-vnet/outputs.tf
output "vnet_id" {
value = azurerm_virtual_network.this.id
description = "ID virtuální sítě"
}
output "vnet_name" {
value = azurerm_virtual_network.this.name
description = "Název virtuální sítě"
}
output "subnet_ids" {
value = { for k, v in azurerm_subnet.this : k => v.id }
description = "Mapa názvů subnetů na jejich ID"
}
output "nsg_ids" {
value = { for k, v in azurerm_network_security_group.this : k => v.id }
description = "Mapa NSG názvů na jejich ID"
}Testování pomocí terraform test
Nativní testování v Terraformu 1.7+ nahrazuje většinu případů, kde dříve byl potřeba Terratest. Testy se píšou v HCL:
# terraform-azurerm-vnet/tests/main.tftest.hcl
variables {
name = "vnet-test-modul"
location = "westeurope"
resource_group_name = "rg-test-moduly"
address_space = ["10.100.0.0/16"]
subnets = {
"snet-app" = {
address_prefix = "10.100.1.0/24"
service_endpoints = ["Microsoft.KeyVault"]
}
"snet-data" = {
address_prefix = "10.100.2.0/24"
create_nsg = true
}
}
tags = {
environment = "test"
ucel = "validace-modulu"
}
}
run "vnet_ma_spravny_adresni_prostor" {
command = plan
assert {
condition = azurerm_virtual_network.this.address_space[0] == "10.100.0.0/16"
error_message = "Adresní prostor VNetu neodpovídá očekávanému CIDR"
}
}
run "subnety_maji_spravne_prefixy" {
command = plan
assert {
condition = azurerm_subnet.this["snet-app"].address_prefixes[0] == "10.100.1.0/24"
error_message = "Prefix app subnetu neodpovídá"
}
assert {
condition = azurerm_subnet.this["snet-data"].address_prefixes[0] == "10.100.2.0/24"
error_message = "Prefix data subnetu neodpovídá"
}
}
run "nsg_se_vytvori_pro_kazdy_subnet" {
command = plan
assert {
condition = length(azurerm_network_security_group.this) == 2
error_message = "Očekávána 2 NSG (jedno na subnet s create_nsg=true)"
}
}Výstup terraform test vypadá takto:
$ terraform test
tests/main.tftest.hcl... in progress
run "vnet_ma_spravny_adresni_prostor"... pass
run "subnety_maji_spravne_prefixy"... pass
run "nsg_se_vytvori_pro_kazdy_subnet"... pass
tests/main.tftest.hcl... tearing down
tests/main.tftest.hcl... pass
Success! 3 passed, 0 failed.
Žádná instalace Go. Žádný testovací harness. Jen HCL aserce přímo v repozitáři modulu.
Publikace do Azure Container Registry
Pro týmy, které nechtějí používat Terraform Cloud, nabízí Azure Container Registry hostování modulů jako OCI artefaktů:
# Vytvoření Basic-tier ACR (~$5/měsíc)
az acr create \
--resource-group rg-platform-shared \
--name acrterraformmodules \
--sku Basic
# Přihlášení do ACR
az acr login --name acrterraformmodules
# Zabalení a publikace modulu
cd terraform-azurerm-vnet
tar -czf ../terraform-azurerm-vnet-2.0.0.tar.gz .
oras push acrterraformmodules.azurecr.io/terraform/azurerm/vnet:2.0.0 \
--artifact-type application/vnd.hashicorp.terraform.module \
../terraform-azurerm-vnet-2.0.0.tar.gzKonzumenti odkazují na modul s version constraintem:
module "network" {
source = "acrterraformmodules.azurecr.io/terraform/azurerm/vnet"
version = "~> 2.0"
name = "vnet-platform-prod"
location = "westeurope"
resource_group_name = azurerm_resource_group.network.name
address_space = ["10.0.0.0/16"]
subnets = {
"snet-app-prod" = {
address_prefix = "10.0.1.0/24"
service_endpoints = ["Microsoft.KeyVault", "Microsoft.Sql"]
}
"snet-aks-prod" = {
address_prefix = "10.0.4.0/22"
}
}
}Constraint ~> 2.0 povoluje patch aktualizace (2.0.1, 2.1.0), ale blokuje breaking changes (3.0.0).
Výsledky z praxe
Nejpoučnější moment přišel při migraci z v1.x na v2.0 u jednoho klienta. Verze 2.0 našeho VNet modulu přidala povinné vytváření NSG (dříve volitelné). Tři projekty na ~> 1.0 nebyly dotčeny. Při upgradu týmy použily moved bloky, aby zabránily Terraformu v destrukci a znovuvytvoření NSG:
# V main.tf konzumujícího projektu, při migraci na v2.0
moved {
from = azurerm_network_security_group.legacy["snet-app"]
to = module.network.azurerm_network_security_group.this["snet-app"]
}Bez verzování modulů by se "oprava" aplikovala na všechny kopie současně, bez možnosti rollbacku. Takto se osm projektů upgradovalo postupně během dvou týdnů.
Metriky adopce modulů u jednoho klienta (8 projektů, 6 měsíců):
| Metrika | Před moduly | Po modulech |
|---|---|---|
| VNet konfigurace | 8 unikátních kopií | 1 modul, 8 konzumentů |
| Čas na přidání flow logů do všech VNetů | ~3 dny | 2 hodiny |
| Drift mezi prostředími | Pravidelný (týdenní změny v portálu) | Vzácný (pipeline vynucuje stav) |
| Síťový setup nového projektu | 4+ hodiny (kopírování, úpravy) | 20 minut (odkaz na modul, nastavení proměnných) |
Klíčové poznatky
- Začněte strukturou, ne funkcemi. Modul s
main.tf,variables.tf,outputs.tfa testovacím souborem je už lepší než 200 řádků inline Terraformu. - Používejte
terraform testpro nové moduly. Nativní testování pokryje unit validaci bez Go. Terratest si nechte na integrační testy, které potřebují vytvářet reálné Azure zdroje. - Verzujte všechno. I interní moduly. Constraint
~> 2.0zabraňuje překvapivým breaking changes. Semver je levné pojištění. - Zvažte Azure Verified Modules (AVM) než budete psát od nuly. Microsoft je spravuje jako referenční implementace. Pokud existuje AVM pro váš typ zdroje, začněte tam.
- OpenTofu vyhodnoťte, ale nemigrujte pod tlakem. Kompatibilita modulů je vysoká, ale ne 100%. Otestujte vaše konkrétní moduly před rozhodnutím.
Pokud hledáte základní Terraform vzory, na kterých design modulů staví -- remote state, drift detection, naming konvence -- podívejte se na náš přehled Terraform best practices pro Azure. Modulární design je přirozeným dalším krokem po zvládnutí těchto základů.
Potřebujete pomoct s modularizací vašeho Terraform kódu nebo nastavením privátního registru modulů? Naše konzultační služby zahrnují návrh modulů, testování a nastavení CI/CD pipeline pro týmy zaměřené na Azure.
O autorovi

Martin Rylko
Senior Cloud Architect & DevOps Engineer
Více než 14 let v IT – od on-premises datacenter a Hyper-V clusteringu po cloudovou infrastrukturu v Microsoft Azure. Specializuji se na Landing Zones, IaC automatizaci, Kubernetes a bezpečnostní compliance.
Nejcastejsi dotazy
Mám použít Azure Container Registry nebo Terraform Cloud jako privátní registr modulů?▾
Jak testovat Terraform moduly před publikací?▾
Je OpenTofu kompatibilní s mými stávajícími Terraform moduly?▾
Jak řešit breaking changes při aktualizaci sdíleného modulu?▾
Mohlo by vás zajímat
Terraform Azure: Best practices pro produkci
Terraform Azure best practices pro produkční projekty. Remote state locking, modulární struktura, drift detection, naming konvence a testování.
ČístBicep CI/CD: GitHub Actions pipeline pro Azure
Produkční deployment pipeline pro Bicep v GitHub Actions. What-if náhledy, schvalování prostředí, OIDC autentizace a rollback strategie.
ČístKubernetes AKS: Produkční checklist pro architekty
Kubernetes AKS produkční checklist pro architekty. Azure CNI síťování, Workload Identity, RBAC, autoscaling, monitoring a DR strategie.
Číst