ARM jezelf met Bicep

Misschien heb je al gehoord van de nieuwe DSL (of Domain Specific Language) die is geïntroduceerd onder de naam Bicep. Deze nieuw ontwikkelde taal is een oplossing om het schrijven van ARM templates simpeler, makkelijker en sneller te maken. Wat is Bicep?

Auteur: Eduard Keilholz      |  Gepubliceerd in SDN Magazine 142

Wat is Bicep?

ARM is een afkorting voor Azure Resource Manager. De ARM is in de basis verantwoordelijk voor het uitrollen van resources binnen Azure. Je kunt de ARM aansturen door templates, de zogenaamde ARM templates. Deze templates zijn geschreven in JSON en worden geïnterpreteerd door de ARM waaruit instructies komen. Een ARM template schrijf je op een declaratieve manier. Dit betekent dat je omschrijft hoe je wil dat jouw infrastructuur eruit moet zien. Bijvoorbeeld: Ik wil een blauwe auto met 4 wielen. Als je dit template aanbiedt aan de ARM, gaat het kijken of je al een auto uitgerold hebt in de Cloud. Is het antwoord hierop nee, dan wordt jouw auto aangemaakt. Is het antwoord hierop ja, dan wordt gekeken of de eigenschappen van de auto overeenkomen met de in het template omschreven eigenschappen en naar wens aangepast wanneer nodig. Ook als je in het portal van Azure resources uitrolt, genereert het portal een ARM template en maakt een nieuwe ‘deployment’ aan.

Nu je in de kern weet hoe de ARM werkt, vraag je je misschien af waar die nieuwe Bicep taal nou voor nodig is. Allereerst wordt infrastructuur als ARM template, geschreven in JSON-formaat. Hierdoor zit je ook automatisch vast aan de JSON-standaarden en kun je dus handige functies als string interpolatie niet gebruiken. Ook heeft een ARM template vaste plekken waar je variabelen en parameters declareert, terwijl je die vaak juist graag in de buurt declareert van waar ze gebruikt worden. JSON heeft ook een hoop ceremonie, zoals de vele accolades, aanhalingstekens en soms schema definities. De tooling voor ARM templates (extensies en IntelliSense) zijn over de afgelopen jaren flink verbeterd, maar nog zeker niet zoals ze moeten zijn. Een mooie opsomming van nadelen waar een team mee aan de slag is gegaan om een DSL bovenop ARM templates te maken.

Bicep, de grootste spierbal van je ARM, is niet voor niets gekozen als naam voor dit project. Niet alleen is de taal compact en relatief eenvoudig te leren, het is ook gelukt om een heldere manier te vinden om infrastructuur te beschrijven als code (Infrastructure as Code (IaC)) en is daarmee een zeer goede aanvulling op ARM templates.

Mijn eerste Bicep

Neem als voorbeeld een Web App in Azure. Een Web App heeft een App Service Plan nodig die je kunt gebruiken om in te stellen hoe krachtig de computer moet zijn waar de website op komt te draaien, en om bijvoorbeeld in te stellen hoe de website moet schalen naarmate er meer of juist minder verkeer is. Daarna kun je de Web App zelf uitrollen. Hieronder staat een ARM template, waarmee je deze twee resources kunt uitrollen in Azure:

In dit voorbeeld zie je dat tot en met regel 5 er in feite niets gebeurt. Pas vanaf regel zes wordt een resource gedeclareerd, de serverfarms resource (dit is de oude benaming van een App Service Plan en onder die naam in de ARM blijven steken). Op regel 22, zie je een expliciete afhankelijkheid naar het App Service Plan, waarmee de ARM een instructie krijgt om te wachten met het uitrollen van de Web App, tot het App Service Plan klaar is. Dit moet ook, want ze zijn afhankelijk van elkaar. In het volgende stukje code worden exact dezelfde resources gedeclareerd, maar dan in Bicep:

Je vindt hier precies dezelfde informatie terug, als in de JSON-variant. Met het sleutelwoord ‘resource’ geef je aan dat je een resource wil declareren. De daaropvolgende naam, is een variabele naam. Dus ‘appServicePlan’ op regel 1 en ‘webApp’ op regel 9 zijn namen van variabelen die je kunt noemen zoals je wil. Wat ook meteen opvalt, is dat het aantal regels is teruggedrongen tot 15 (of 14 als we de lege regel weghalen) en toch staat er precies hetzelfde. Bicep wordt zo overzichtelijker en makkelijker te onderhouden.

Op regel 13, vind je dezelfde afhankelijkheid terug als in het JSON-bestand, echter is deze afhankelijkheid impliciet. Ik heb in het Bicep bestand geen afhankelijkheid geschreven van de Web App, naar het App Service Plan, maar omdat de Web App waardes gebruikt uit de resource van het Service Plan (namelijk het ID), herkent Bicep dit wel als een afhankelijkheid. Je mag in Bicep echter ook een afhankelijkheid expliciet maken zoals in dit voorbeeld op regel 10:

In de het voorbeeld hierboven vind je ook meteen een nadeel van Bicep en dat is wat mij betreft het enige nadeel, dat is dat Bicep regel gevoelig is. Gevoelsmatig zou ik de array met afhankelijkheden op regel 10, zeker als er maar één afhankelijkheid is, op een enkele regel willen zetten. Dit zorgt er echter voor dat het Bicep bestand niet meer valide is en niet meer transpiled.

Transpiled zeg je? Jazeker! Bicep is zoals eerder beschreven, een DSL en compileert niet echt naar iets. Ook is het niet één op één te vertalen naar een ander formaat en daarom is de term transpilen bedacht. Een samenvoeging van ‘to translate’ en ‘to compile’. Het is belangrijk om te begrijpen dat Bicep een vervanger is van de ontwikkel methode, maar niet een vervanger van het uitrol mechanisme. De ARM accepteert nog altijd alleen maar JSON-bestanden. Bicep bestanden moeten dus omgezet worden naar JSON (dus ARM templates) voor ze uitgerold kunnen worden. Tegenwoordig kan de ARM dit zelf, deze stap kun je dus overslaan als je dat wil. Het is wel handig om te weten dat als er echter een foutmelding optreedt tijdens het uitrollen van een template, deze foutmelding refereert naar het JSON-bestand en dus niet naar het Bicep bestand. Foutmeldingen en regelnummers gaan dus allemaal over het JSON-bestand.

Is het verstandig om het transpilen handmatig te doen? In mijn optiek, ja! In professionele ontwikkelomgevingen met een goed CI/CD proces, zou ik zeggen dat het wenselijk is om ergens tijdens een build fase Bicep templates om te zetten naar ARM templates en deze JSON-bestanden te gebruiken voor het verdere verloop van je CI/CD proces. Stel je gebruikt een Azure DevOps pipeline, of een GitHub Actions workflow. Dan kun je een transpile doen van jouw bicep template waarmee je het omzet naar een ARM template. Dit template kun je dan publiceren als Artifact. In de daaropvolgende stages of jobs, kun je dit Artifact dan weer downloaden voor gebruik (het uitrollen naar een development, test, acceptatie en of productie omgeving).

Dus de grootste verschillen zitten hem in het ontwikkelen van je infrastructuur als code bestanden. De tooling voor Bicep is vele malen beter. De feedback en IntelliSense die je krijgt als je de Bicep extensie voor Visual Studio Code installeert is niet te vergelijken met de ervaring die je hebt met het schrijven van ARM templates. Het aantal fouten dat je daardoor maakt tijden het schrijven van infrastructuur als code is daardoor veel minder en het is niet ondenkbaar opeens de volledige infrastructuur voor een website de eerste keer foutloos te schrijven.

Parameters

Parameter bestanden blijven onveranderd als je gebruik maakt van Bicep. Omdat een Bicep bestand voordat het uitgerold wordt, eerst omgezet wordt naar een ARM template, worden ook parameter bestanden nog steeds in JSON geschreven. Hieronder staat een voorbeeld van een parameter bestand:

Dit parameter bestand bevat één parameter met de naam ‘queues’ en als waarde een array. Deze array bevat twee namen voor een queue. Dit parameter bestand hoort bij het volgende template:

Op regel 1 zie je de parameter gedeclareerd worden. Wanneer dit template wordt uitgerold zonder dat een waarde voor ‘queues’ gespecificeerd is, faalt het template. Tijdens het uitrollen kun je op verschillende manieren een waarde toekennen aan parameters, en een parameter bestand zoals hierboven is er één van. Meestal maak je per stap in je OTAP-straat, een parameter bestand om zo verschillen te kunnen definiëren tussen bijvoorbeeld een test- en een productieomgeving. Zo wil je misschien de SKU (hoe ‘zwaar’ de server ingericht moet zijn in termen van RAM en CPU) van een webserver in de test omgeving niet zo zwaar hebben als in de productieomgeving om kosten te besparen. Parameter bestanden zijn hierbij een uitkomst.

Parameters kun je decoreren met aanvullende informatie. Bijvoorbeeld met een omschrijving om meer informatie te geven wat je als waarde verwacht. Ook kun je bijvoorbeeld een lijst van toegestane waardes opgeven. Onderstaand zie je een aantal voorbeelden van decorators voor parameters:

Voorgaand template rolt een Service Bus namespace uit, en als dat klaar is, worden gelijk te queues ( gedefinieerd in het parameter bestand) aangemaakt. Wanneer je dit template uitrolt zit de service bus in Azure er als volgt uit:

Functies

Bicep kent een lijst aan ingebouwde functies die je kunt gebruiken. In de voorgaande voorbeelden zie je bijvoorbeeld de resourceGroup() functie. Met deze functie kun je informatie ophalen over de Resource Group waar je op dat moment een resource in uit aan het rollen bent, in dit voorbeeld wordt een resource in dezelfde locatie uitgerold als de Resource Group. Maar er zijn nog veel meer functies die je kunt gebruiken. Bijvoorbeeld de volgende string functies: contains(), replace(), substring(), toLower() en toUpper() zijn enkele van de string functies die Bicep kent. Het heeft geen zin om alle functies individueel te behandelen want het zijn er simpelweg te veel.

Outputs

Soms heb je informatie nodig die beschikbaar komt door het uitrollen van een resource. Bijvoorbeeld de connectiestring van een storage account. Dit soort informatie kun je als output waarde definiëren in een bestand.

Dit voorbeeld is al een stuk ingewikkelder. Eerst de parameters, deze krijgen een waarde van de aanroepende partij, behalve ‘administratorUsername’. Die krijgt een default waarde (lege string). Je ziet dat de parameter voor het wachtwoord een ‘@secure()’ decorator heeft. Dat betekent dat de inhoud gevoelig is en wanneer deze waarde bijvoorbeeld naar een log geschreven wordt, zie je sterretjes in plaats van de werkelijke waarde. Dan worden twee variabelen gedeclareerd voor het bepalen van de SQL Server naam en de administrator gebruikersnaam. Als de gebruikersnaam parameter een waarde heeft, dan wordt deze waarde gebruikt. Anders verzint het templates een default waarde (‘${defaultResourceName}-admin’). Hier zie je ook meteen een mooi voorbeeld van hoe string interpolatie werkt in Bicep. Onderaan het voorbeeld zie je dat de naam van de SQL Server, de gebruikersnaam en het wachtwoord als output variabele beschikbaar gemaakt worden.

Modules

Als je met Infrastructuur als Code (IaC) jouw Cloud infrastructuur beschrijft, merk je dat deze bestanden, ook met Bicep, toch best groot kunnen worden. Het is dan prettig als je de mogelijkheid hebt om deze infrastructuur in logische blokken op te kunnen delen zodat jouw infrastructuur eenvoudiger leesbaar temaken en te onderhouden is. Met ARM templates gebruikte je hier ‘linked templates’ voor met alle nadelen van dien. In Bicep, heet deze functionaliteit een module. Ik vind het zelf prettig om per resource type een apart Bicep bestand te maken zodat ik deze in latere projecten makkelijk kan hergebruiken. Dan maak ik een Bicep bestand waar ik deze resources in combineer (bijvoorbeeld een SQL Server en een SQL Server database), en dit bestand roep ik dan aan vanuit mijn hoofd template. Dit is niet per definitie de beste methode, maar wel een handige manier om Bicep templates klein, overzichtelijk, onderhoudbaar en herbruikbaar te maken. Het voorgaande voorbeeld van de SQL Server is hier een mooi voorbeeld van. Het volgende voorbeeld, omschrijft een SQL Server database:

Ook hier zie je weer parameters die helpen om de naam van de database te bepalen, maar op regel 14, zie je dat in deze naam ook de naam van de SQL Server nodig is om aan te geven op welke SQL Server de database gezet moet worden. Je kunt de bovenstaande voorbeelden als bestand opslaan. Ik vind het prettig om hierbij de namespaces aan te houden die Microsoft voor de resources gebruikt. Dus ‘Sql’ voor de SQL Server (regel 10 in het vorige voorbeeld) en ‘SQL/servers’ voor de database (regel 12 in bovenstaand voorbeeld). Dit zijn mappen om Bicep bestanden te organiseren. Ook hier geld dat dit niet hoeft, het is slechts mijn persoonlijke voorkeur. Mocht je dit voorbeeld willen gebruiken, maak dan de volgende bestandsstructuur aan:

  • bicep
  • Sql/servers.bicep
  • Sql/servers/databases.bicep

Het voorgaande SQL Server voorbeeld, neem je over in Sql/servers.bicep en het bovenstaande SQL Database voorbeeld neem je over in Sql/servers/databases.bicep. In het volgende voorbeeld worden de twee gecombineerd waardoor een SQL Server uitgerold gaat worden, met een SQL Database erop. Deze Bicep code komt in het bestand main.bicep:

Het template hierboven maakt gebruik van de kleinere Bicep bestanden die in de voorgaande voorbeelden gemaakt zijn. Er wordt een standaard resource naam opgemaakt aan de hand van de systeemnaam (bijvoorbeeld ‘sdndemo’) gecombineerd met een omgeving naam (development, test of productie). Op regel 15 en 23 zie je dat het sleutelwoord ‘resource’ is vervangen door ‘module’ om aan te geven dat een module gebruikt wordt. Ook hier ken je een variabele naam aan toe gevolgd door het pad naar het Bicep bestand. Op regel 15 zie je dat de module voor het aanmaken van de SQL Server aangeroepen wordt en op regel 23 zie je dat de module voor het aanmaken van de SQL Database aangeroepen wordt. Op regel 27, word de naam van de SQL Server doorgegeven aan de SQL Database module, door deze uit te lezen uit de SQL Server module. Hier wordt dus gebruik gemaakt van een van de output parameters van de voorgaande module.

Het voorgaande voorbeeld van de SQL Server en database dit gecombineerd worden in één Bicep bestand is een mooi voorbeeld van herbruikbaarheid. Die lijn zou je nog verder door kunnen trekken zodat je een overzichtelijke set aan (herbruikbare) Bicep bestanden hebt.

Alternatieven

En hoe het dan zit met alternatieven zoals Terraform en Pulumi? Het antwoord zit al verstopt in de vraag. Het zijn alternatieven, met name als je (alleen) met Azure werkt. Terraform en Pulumi kunnen beide meerdere platformen aan zoals Azure, AWS en Google. Soms kunnen zelfs hosting providers als GoDaddy en zelfs Azure DevOps aangesproken worden. Als je dergelijke oplossingen al gebruikt, is er niet een goede reden om opeens naar Bicep over te gaan. Mocht je nog geen IaC invulling hebben voor jouw project(en), is het echt raadzaam hiermee te starten. Bicep is DE nieuwe oplossing toegespitst op Azure. Wil je zonder een extra andere tool te leren willen starten met IaC, dan is Bicep absoluut aan te raden. Moet je per direct overstappen als je nu al ARM templates ingericht hebt voor al jouw projecten? Nee zeker niet. Het werken met Bicep ervaar ik ontzettend prettig, vooral ten opzichte van ARM templates. Dit betekent echter niet dat je alles opeens om moet zetten. Het is wel handig om te weten dat je met de Azure CLI jouw ARM templates automatisch om kunt zetten naar Bicep.

Conclusie

De syntax, de feedback tijdens het schrijven, het onderhoud en het gemak van modules maken het schrijven van infrastructuur een stuk aangenamer dan voorheen, wanneer je ARM templates gebruikt. Het blijft echter wel zo dat Bicep altijd vertaald wordt naar een ARM template JSON-bestand, dus verwacht niet dat er opeens nieuwe mogelijkheden bijgekomen zijn. De drempel om IaC te gebruiken is nog nooit zo laag geweest, tegen alle voordelen die erbij komen kijken. Soms moet je om te starten even door de zure appel heen, dat is in dit geval echter meer dan de moeite waard! Mocht je echt alles willen weten over het automatisch uitrollen van resources in Azure, kun je overwegen het boek ‘Azure Infrastructure as Code’ te kopen dat ik samen met Henry Been en Erwin Staal geschreven heb: https://www.manning.com/books/azure-infrastructure-as-code.

Bio

Eduard Keilholz

Ik heb al meer dan twintig jaar ervaring met het ontwikkelen van software. Als Cloud Solution Architect bij 4DotNet mag ik bedrijven helpen kwaliteit stappen te maken in hun cloud oplossing. Ik vind het leuk om kennis over te dragen en om mensen te helpen. Naast mijn specialiteit, backend systemen ontwikkelen in de cloud, heb ik ook veel plezier in het maken van Angular front-end oplossingen.

4DotNet: https://4dotnet.nl
Blog: https://hexmaster.nl
Twitter: https://twitter.com/ed_dotnet
LinkedIn: https://www.linkedin.com/in/eduard-keilholz/