Până acum, în serie, am construit un pipeline care produce un artifact și îl promovează controlat prin dev, staging și producție, cu approval gates. Dar am trecut elegant peste o întrebare: de unde apar, mai exact, resursele Azure în care deployăm?
Dacă răspunsul e „le-am făcut din portal, cu mâna”, atunci ai un pipeline modern care deployează pe o infrastructură creată prin click-uri. Și acolo încep problemele.
Un App Service creat manual nu poate fi versionat, nu poate fi recreat identic și nimeni nu mai știe, peste șase luni, ce setări are și de ce.
🎯 Ce înseamnă Infrastructure as Code
Infrastructure as Code (IaC) înseamnă că îți definești infrastructura – App Service, baza de date, storage, networking – în fișiere de cod, pe care le ții în git, lângă aplicație.
Beneficiile sunt exact cele pe care le aștepți de la cod:
-
Versionare – vezi în git cine a schimbat un setting și când.
-
Reproductibilitate – recreezi întreg mediul identic, oricând, în câteva minute.
-
Review – o modificare de infrastructură trece prin pull request, ca orice alt cod.
-
Fără drift – ce e în fișiere e sursa de adevăr, nu ce a apăsat cineva prin portal acum trei luni.
💠 De ce Bicep
Pe Azure ai câteva opțiuni. ARM templates în JSON sunt puternice, dar dureroase de scris și citit. Terraform e excelent și multi-cloud, dar îți cere să gestionezi un state file separat.
Bicep e limbajul nativ Azure pentru IaC și lovește un echilibru foarte bun:
-
Sintaxă curată, declarativă – mult mai lizibilă decât JSON-ul ARM.
-
Transpilează în ARM – sub capotă tot ARM rulează, deci nu pierzi nimic din platformă.
-
Fără state file de administrat – Azure știe deja starea resurselor.
-
Idempotent – îl rulezi de zece ori, rezultatul e același.
🧱 Sintaxa esențială: un fișier main.bicep
Un fișier Bicep are patru piese de bază: parametri (param), variabile (var), resurse (resource) și rezultate (output). Iată un exemplu care creează un App Service Plan și un Web App pentru .NET 8:
@description('Numele de bază al aplicației')
param appName string
@description('Regiunea Azure')
param location string = resourceGroup().location
@allowed([ 'dev', 'staging', 'prod' ])
param environment string
var planName = '${appName}-plan-${environment}'
var webAppName = '${appName}-${environment}'
resource plan 'Microsoft.Web/serverfarms@2023-12-01' = {
name: planName
location: location
sku: {
name: environment == 'prod' ? 'P1v3' : 'B1'
}
}
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
name: webAppName
location: location
properties: {
serverFarmId: plan.id
httpsOnly: true
siteConfig: {
netFrameworkVersion: 'v8.0'
alwaysOn: environment == 'prod'
}
}
}
output webAppName string = webApp.name
output webAppUrl string = 'https://${webApp.properties.defaultHostName}'
Observă cât de natural se citește. Parametrul environment controlează diferențele între medii: în prod cerem un plan P1v3 cu alwaysOn, în rest un B1 mai ieftin. Același fișier, medii diferite – exact filosofia din partea 2, dar acum aplicată infrastructurii.
⚙️ Cum îl aplici din linia de comandă
Un deployment Bicep se aplică într-un resource group. Întâi creezi grupul, apoi rulezi deployment-ul:
az group create --name rg-myapp-prod --location westeurope
az deployment group create \
--resource-group rg-myapp-prod \
--template-file ./infra/main.bicep \
--parameters appName=myapp environment=prod
Rulează comanda a doua încă o dată. Nu se întâmplă nimic nou – resursele există deja în starea cerută. Asta e idempotența, și e motivul pentru care poți pune IaC în pipeline fără frică: aplică mereu starea dorită, nu „creează din nou”.
🔍 Vezi ce se schimbă înainte să aplici: what-if
Înainte de a aplica ceva în producție, vrei să știi exact ce se modifică. Bicep îți dă un preview:
az deployment group what-if \
--resource-group rg-myapp-prod \
--template-file ./infra/main.bicep \
--parameters appName=myapp environment=prod
what-if îți arată ce resurse ar fi create, modificate sau șterse – ca un git diff, dar pentru infrastructură. E genul de pas pe care îl pui înainte de un approval gate.
🔗 IaC în pipeline: provision înainte de deploy
În pipeline, adaugi un job care aplică Bicep-ul înainte de deploy-ul aplicației. Așa, infrastructura și aplicația se livrează împreună, din același commit:
provision-infra:
runs-on: ubuntu-latest
needs: build-test
environment:
name: production
steps:
- uses: actions/checkout@v4
- name: Azure login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy Bicep
uses: azure/arm-deploy@v2
with:
resourceGroupName: rg-myapp-prod
template: ./infra/main.bicep
parameters: appName=myapp environment=prod
Pasul azure/login îl vezi aici fără nicio parolă – doar client-id, tenant-id și subscription-id. Cum funcționează autentificarea asta fără client secret, prin OIDC, e exact subiectul părții 5. Deocamdată reține doar că e posibil și că e modul corect.
✅ Câteva obiceiuri bune
-
Un resource group per mediu (rg-myapp-dev, rg-myapp-prod) – izolare curată între medii.
-
Sparge fișierele mari în module Bicep reutilizabile (un modul pentru baza de date, unul pentru web app).
-
Ține o convenție de naming clară, derivată din appName + environment.
-
Folosește output-urile (webAppName, webAppUrl) în pașii următori din pipeline.
-
Rulează what-if înainte de producție – niciun deploy de infrastructură la orbește.
Concluzie
Cu Bicep, infrastructura nu mai e o serie de click-uri pe care nimeni nu le mai ține minte, ci cod versionat, reproductibil și trecut prin review – livrat în același pipeline cu aplicația. Un mediu întreg devine ceva ce recreezi în câteva minute, identic.
În partea 4 folosim exact această fundație pentru un obiectiv concret: deployment slots în Azure App Service, ca să facem deploy cu zero downtime – iar slotul îl putem defini tot în Bicep.