Martin Rylko
  • Služby
  • Blog
  • O mně
  • Kontakt
  • Spolupráce
Martin Rylko

Senior Cloud Architect & DevOps Engineer. Specializace na Microsoft Azure, IaC, Cloud Security a AI.

Navigace

  • Služby
  • Blog
  • O mně
  • Kontakt

Spolupráce

Hledáte zkušeného architekta pro Váš Azure projekt? Ozvěte se.

rylko@cloudmasters.cz

© 2026 Martin Rylko. Všechna práva vyhrazena.

Buduji v cloudu. Nasazuji přes Azure Static Web Apps.

Domů/Blog/Terraform Azure moduly: Privátní registr a testování
Všechny článkyRead in English

Terraform Azure moduly: Privátní registr a testování

15. 8. 2025 5 min
#Terraform#Azure#IaC#DevOps#Testing

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 test umožňuje psát .tftest.hcl soubory 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-scale je 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.gz

Konzumenti 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ů):

MetrikaPřed modulyPo modulech
VNet konfigurace8 unikátních kopií1 modul, 8 konzumentů
Čas na přidání flow logů do všech VNetů~3 dny2 hodiny
Drift mezi prostředímiPravidelný (týdenní změny v portálu)Vzácný (pipeline vynucuje stav)
Síťový setup nového projektu4+ 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.tf a testovacím souborem je už lepší než 200 řádků inline Terraformu.
  • Používejte terraform test pro 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.0 zabraň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.

Tagy:#Terraform#Azure#IaC#DevOps#Testing
LinkedInX / Twitter

O autorovi

Martin Rylko

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.

Email LinkedInCelý profil

Nejcastejsi dotazy

Mám použít Azure Container Registry nebo Terraform Cloud jako privátní registr modulů?▾
Azure Container Registry (ACR) funguje dobře pro Azure-centrické týmy -- podporuje OCI artefakty nativně, integruje se s Entra ID RBAC a stojí asi $5/měsíc v Basic tieru. Terraform Cloud privátní registr je lepší, pokud už používáte TFC pro state management a chcete vestavěnou dokumentaci modulů a prohlížení verzí. Pro většinu čistě Azure firem je ACR jednodušší a levnější.
Jak testovat Terraform moduly před publikací?▾
Použijte třívrstevný přístup: terraform validate a terraform fmt pro kontrolu syntaxe (vteřiny), terraform plan s mock soubory proměnných pro logickou validaci (minuty), a terraform test (nativní od v1.7) nebo Terratest pro integrační testy, které nasadí reálné zdroje a ověří chování (10-20 minut). Integrační testy spouštějte v dedikované sandbox subskripci s automatickým čištěním.
Je OpenTofu kompatibilní s mými stávajícími Terraform moduly?▾
OpenTofu 1.6+ je kompatibilní se syntaxí Terraform 1.5.x modulů a formátem state. Většina modulů funguje beze změn. Divergence začíná u funkcí přidaných po forku -- OpenTofu má state encryption a odlišné chování provider lock. Pokud zvažujete OpenTofu, otestujte svou sadu modulů proti oběma runtimem před přechodem.
Jak řešit breaking changes při aktualizaci sdíleného modulu?▾
Striktně dodržujte sémantické verzování: breaking changes dostanou major version bump (v1.x na v2.0). Novou verzi publikujte vedle staré, aby konzumenti mohli migrovat vlastním tempem. Dokumentujte migrační návod v README modulu. V CI/CD připínejte přesné verze modulů a použijte Dependabot nebo Renovate pro vytváření PR, když je dostupná nová verze.

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í.

Číst

Bicep 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.

Číst

Kubernetes 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