Skip to main content
GenioCT

Bicep vs Terraform, waarom wij standaard voor Terraform kiezen (en wanneer Bicep wint)

Door GenioCT | | 7 min leestijd
Azure Terraform Bicep DevOps

In dit artikel

Bicep is Azure-native en strak geïntegreerd, terwijl Terraform multi-cloud bereik en cross-resource-group deployments biedt.

De meeste vergelijkingsartikels zetten features naast elkaar en vertellen je om “de juiste tool voor de job te kiezen.” Dit is dat artikel niet.

Wij draaien al meer dan een jaar zowel Bicep als Terraform voor dezelfde enterprise klant, op hetzelfde Azure-platform, tegen dezelfde subscriptions. Geen proof of concept, geen blogdemo: productie landing zones in Bicep, productinfrastructuur in Terraform, allebei op dezelfde subscriptions. In januari 2024 hebben we een interne IaC workshop gehouden waarbij het volledige platformteam beide oplossingen naast elkaar doornam, sterktes en zwaktes live vergeleek, en tot conclusies kwam die ons verrast hebben. (De menselijke kant van die workshop werd een apart artikel over het omgaan met technische meningsverschillen.)

Hier is wat we er echt van vinden.

Onze standaard is Terraform. Dit is waarom.

Terraform is niet perfect. We komen nog bij de problemen. Maar als we een nieuw infrastructuurproject starten, grijpen we naar Terraform, en dit zijn de redenen.

Data Blocks veranderen alles

De meeste vergelijkingsartikels vermelden dit niet eens. Terraform’s data blocks laten je bestaande infrastructuur uitlezen als eersteklas objecten in je code:

data "azurerm_resource_group" "rg" {
  for_each = toset(local.rg_names)
  name     = each.value
}

resource "azurerm_virtual_network" "this" {
  for_each            = local.vnets
  resource_group_name = each.value.resource_group

  # Inherit tags from the resource group automatically
  tags = merge(
    data.azurerm_resource_group.rg[each.value.resource_group].tags,
    try(each.value.tags, {}),
    local.tags
  )
}

Je kan een Key Vault opzoeken, access policies uitlezen, een subnet ID ophalen uit een andere subscription, tags van een resource group pullen, allemaal zonder iets te hardcoden. In Bicep gebruik je het existing keyword voor een deel hiervan, maar dat is beperkt tot de huidige deployment en komt niet in de buurt van de flexibiliteit van Terraform data sources over providers en subscriptions heen.

Tijdens onze workshop noemde een van de teamleden data blocks “een groot, groot voordeel” van Terraform. Na een jaar dagelijks met beide tools te werken, is die mening alleen maar sterker geworden.

Je zit niet vast aan een Resource Group

Bicep deployments zijn scoped. Je kiest een deployment scope (resource group, subscription, management group) en alles in die deployment richt zich op die scope. In de praktijk targeten de meeste Bicep deployments een enkele resource group.

Terraform heeft die beperking niet. Een enkele Terraform stack kan resource groups aanmaken, resources deployen naar meerdere resource groups, cross-subscription peering opzetten en DNS zones configureren, allemaal in één plan. Dit doet ertoe zodra je infrastructuur resource group grenzen overschrijdt, en dat gebeurt sneller dan je denkt.

Met Bicep heb je voor resources in twee verschillende resource groups twee deployments nodig, en moet je de dependency ordering zelf beheren via je pipeline. Met Terraform druk je de dependency uit in code en terraform apply regelt de rest.

Het ecosystem doet ertoe, zelfs op een enkele cloud

Ja, Terraform werkt over AWS, GCP en Azure heen. Maar zelfs als je 100% Azure draait, is de provider catalog belangrijk. Wij beheren DNS op Cloudflare, monitoring in Datadog, CI/CD resources in Azure DevOps en Kubernetes manifests, allemaal vanuit Terraform. Bicep kan hier niets mee.

Waar Bicep echt wint

Terraform is onze standaard, maar Bicep heeft zijn plek op hetzelfde platform verdiend. Het afwijzen zou oneerlijk zijn.

Configuration as Code met JsonNet

Onze Bicep landing zones gebruiken JsonNet als configuratielaag erbovenop. Eén configbestand per resource type, één bestand voor alle omgevingen. Wil je een testomgeving toevoegen? Voeg een entry toe aan de config, niet een nieuw bestand. Het Bicep-team dupliceert niets.

In Terraform moesten we aanvankelijk aparte configbestanden per omgeving aanmaken. Dit is een echte zwakte: voor zes omgevingen zijn dat zes bestanden met grotendeels identieke inhoud. Terragrunt lost een deel hiervan op, en onze YAML-driven aanpak heeft het verder verminderd, maar Bicep met JsonNet had dit vanaf dag één opgelost.

Lege configbestanden zijn een feature (en een risico)

Een patroon dat ons in de Bicep-oplossing geïmponeerd heeft: sommige resources deployen met een leeg configuratiebestand. De resource group stack heeft bijvoorbeeld nul parameters nodig omdat alle defaults afgeleid worden van gemeenschappelijke parameters (subscription, naamconventie, tags). Je commit een leeg bestand en krijgt een correct geconfigureerde resource group.

Elegant, maar het verbergt de intentie. Als je naar een leeg configbestand kijkt en de code erachter niet kent, heb je geen idee wat je gaat krijgen. Er is geen zichtbare single source of truth in de config. De kennis zit in de Bicep modules. Voor het team dat het geschreven heeft, is dit prima. Voor het team dat het erft, is het een documentatiegat.

Day-zero Azure resource support

Bicep compileert naar ARM templates. Wanneer Microsoft op dinsdag een nieuwe API-versie uitbrengt, kan Bicep die op dinsdag gebruiken. De AzureRM provider van Terraform heeft weken of maanden nodig om bij te benen. Voor teams die cutting-edge Azure services adopteren, is deze vertraging reëel.

Je kan dit omzeilen met de azapi provider, maar dan schrijf je ruwe ARM-achtige resource definitions in Terraform, wat het hele doel van de provider abstractie tenietdoet. Wij hebben azapi gebruikt voor Azure Functions Flex Consumption omdat de AzureRM provider het simpelweg nog niet ondersteunde. Het werkt, maar prettig is het niet.

Stabiliteit voor Landing Zones

Onze Bicep landing zones hebben implementatie, testing en productie doorlopen. Ze deployen meerdere landing zones succesvol en zijn al meer dan een jaar stabiel. Wanneer iets werkt en bewezen is, is “laten we het herschrijven in Terraform” geen overtuigend argument. We hebben exact die discussie gehad in de workshop, en het antwoord was duidelijk: houd Bicep voor de platformlaag.

Slecht geschreven Terraform is erger dan goede Bicep

Laten we direct zijn. Terraform geeft je meer kracht, maar kracht zonder discipline creëert een puinhoop die moeilijker te debuggen is dan wat Bicep ook maar kan produceren.

In onze setup is het moeilijkste deel de locals.tf transformatielaag, waar YAML-configuratie omgezet wordt naar de platte maps die Terraform’s for_each verwacht. Het vereist het platslaan van geneste structuren, het mergen van defaults, het verzamelen van resource group namen voor data lookups. Zoals een teamlid het verwoordde tijdens de workshop: “je moet wat masseren om alle verschillende configbestanden klaar te maken voor inname in Terraform.”

# This is where Terraform complexity lives
locals {
  subnets = {
    for s in flatten([
      for v in values(local.vnets) : [
        for sn in try(v.subnets, []) : merge(sn, {
          vnet_name      = v.name
          resource_group = v.resource_group
        })
      ]
    ]) : "${s.vnet_name}/${s.name}" => s
  }
}

Als je team dit goed schrijft, is het krachtig en onderhoudbaar. Als dat niet het geval is, krijg je geneste for expressions die niemand kan lezen, state files die onmogelijk te ontwarren zijn, en plan output waar je 20 minuten naar moet turen om te begrijpen. Op dat punt was een schone Bicep module met verstandige defaults de betere keuze geweest.

Terraform beloont goed engineering en bestraft slecht engineering strenger dan Bicep dat doet. Bicep’s strakkere scope en eenvoudiger deployment model werken als vangrails. Terraform heeft geen vangrails, tenzij je ze zelf bouwt.

Het dual-track model dat echt werkt

Na de workshop was het team het eens over een model dat we sindsdien succesvol draaien:

Bicep beheert de platformlaag: landing zones, resource groups, basisnetwerking, policy assignments. Deze worden eenmalig gedeployed (of heel zelden gewijzigd), zijn scoped naar resource groups, en Bicep’s stabiliteit en JsonNet-configuratiemodel werken hier perfect.

De productlaag gebruikt Terraform: applicatie-infrastructuur, productspecifieke netwerking, WAF policies, Application Gateways. Alles wat frequent gedeployed en gewijzigd wordt over omgevingen heen, waar cross-resource-group scope en data blocks hun waarde bewijzen.

Bicep beheert de fundering, Terraform bouwt erop verder. De pipeline draait eerst Bicep, dan Terraform. Geen interferentie, schone grens.

“Kies één tool” is de verkeerde insteek. Platforminfrastructuur en productinfrastructuur hebben verschillende wijzigingsfrequenties, scope-vereisten en team-eigenaarschapsmodellen. Een enkele tool voor beide gebruiken dwingt één kant om tegen de defaults van de tool te vechten.

Wat we je zouden vertellen bij een koffie

Als je ons zou dwingen om één tool te kiezen voor alles, zou het Terraform zijn. Data blocks, cross-scope deployments, honderden providers en de plan/apply workflow versterken elkaar over tijd. Hoe meer infrastructuur je beheert, hoe meer Terraform’s bereik zich terugverdient.

Maar als je team klein is, je infrastructuur puur Azure is en je relatief statische omgevingen deployt, is Bicep eenvoudiger en ga je sneller leveren. Introduceer geen Terraform-complexiteit omwille van de “industriestandaard.” Een goed gestructureerde Bicep codebase met JsonNet gaat je beter dienen dan een slecht gestructureerde Terraform codebase met configbestanden overal verspreid.

Vergeet “Bicep of Terraform?” Vraag in plaats daarvan: “heeft mijn team de discipline om goede Terraform te schrijven?” Zo ja, Terraform. Als je het niet zeker weet, begin met Bicep en voeg Terraform toe wanneer je tegen de muren aanloopt, want die ga je tegenkomen. Resource group scope, cross-subscription dependencies, non-Azure resources: dit zijn de momenten waarop Bicep geen antwoorden meer heeft en Terraform zijn complexiteit verdient.

Ons YAML-driven Terraform catalog artikel beschrijft de productlaag-patronen in detail. Voor officiële documentatie: Terraform on Azure best practices, Bicep vs ARM templates, AzureRM Provider.

Klaar om uw infrastructuur te automatiseren?

Van Infrastructure as Code tot CI/CD-pipelines, wij helpen teams sneller te leveren met vertrouwen en minder handmatig werk.

Typisch traject: 2-6 weken afhankelijk van de scope.
Bespreek uw automatiseringsdoelen
Deel dit artikel

Begin met een Platform Health Check

Niet zeker waar te beginnen? Een snelle architectuurreview geeft u een helder beeld. Vrijblijvend.