Na tomto blogu jsem před pár měsíci popisoval obvyklé dilema všech nadšenců pro automatizaci v Azure. Mám použít nativní ARM šablony nebo Terraform, nástroj třetí strany? Oba světy mám moc rád, s oběma dosáhnete téhož a oba mají zásadní výhody oproti druhému. Teď ale přišla změna - open source projekt Bicep a ten si zachovává zásadní výhody nativního ARM, ale přibližuje jej stylu a pohodlnosti Terraformu. Zatím je v preview, ale vypadá opravdu dobře. Jak to mění moje minulé srovnání? Jak Bicep funguje a co dalšího má v plánu?
Pro detaily se prosím vraťte k mému předchozímu článku, ale pojďme si základní vlastnosti shrnout.
Proč ARM?
Proč Terraform?
Pak jsou samozřejmě vlastnosti, které mají společné:
Nejdřív název. Azure Resource Manager, zkratka ARM - anglicky ruka. Symbol přiložení ruky k dílu, symbol namakanosti atd. je biceps … takže tak.
Co na ARMu bolí nejvíc? Podle mě určitě ten JSON a ne, náhrada za YAML nepomůže. Je to jistě otázka osobního vkusu, ale jestli vás vypeče chybějící složená závorka nebo chybějící mezera je asi vcelku jedno. Koneckonců jsou to jen datové formáty a my do toho potřebujeme dostat nějakou funkčnost. Extrémem by bylo použití standardního programovacího jazyka. To tým Bicep zvažoval (ostatně Brendan Burns, který má kromě role otce Kubernetes na starosti i ARM a další věci v Azure má tohle rád), ale pro neprogramátory by to vytvářelo bariéru. Proto jde Bicep cestou DSL a to je dost podobné Terraformu. Pokud jste přecijen banda vývojářů, která má na starost i celý provoz a bez Javascriptu nebo C# si neuvaříte ani kafe, koukněte na Pulumi.
Klíčová myšlenka Bicep je ale v tom, že jde o transparentní abstrakci. Z Bicep šablony uděláte ARM šablonu a naopak z ARM šablony můžete vytvořit Bicep! Představte si to jako obousměrný kompilátor. To je zásadní rozhodnutí a má obrovské výhody:
Nicméně pozor - Bicep není v konečném režimu, dost možná dozná ještě nějakých důležitých změn, takže pro produkční nasazení zatím nepoužívejte. Nic vám sice nerozbije, ale možná budete muset šablony občas předělat, jak půjde vývoj dopředu, než se dosáhne stabilní verze. Ale je určitě ideální čas se s ním seznámit a poskytnout zpětnou vazbu týmu na GitHubu.
Začněme třeba jednoduchou definicí IP adresy v Bicep.
resource vmIp 'Microsoft.Network/publicIPAddresses@2019-11-01' = {
name: 'prod-vm-ip'
location: resourceGroup().location
properties: {
publicIPAllocationMethod: 'Static'
}
}
Spustíme bicep, který nám vygeneruje ARM šablonu (samozřejmě v budoucích verzích očekávám víc možností, třeba že to rovnou nasadí apod.).
bicep build main.bicep
Výsledkem je tato ARM šablona.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"functions": [],
"resources": [
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2019-11-01",
"name": "prod-vm-ip",
"location": "[resourceGroup().location]",
"properties": {
"publicIPAllocationMethod": "Static"
}
}
]
}
Montování řetězců nebo přidávání parametrů je velmi pohodlné.
param prefix string = 'prod'
resource vmIp 'Microsoft.Network/publicIPAddresses@2019-11-01' = {
name: '${prefix}-vm-ip'
location: resourceGroup().location
properties: {
publicIPAllocationMethod: 'Static'
}
}
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"prefix": {
"type": "string",
"defaultValue": "prod"
}
},
"functions": [],
"resources": [
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2019-11-01",
"name": "[format('{0}-vm-ip', parameters('prefix'))]",
"location": "[resourceGroup().location]",
"properties": {
"publicIPAllocationMethod": "Static"
}
}
]
}
Myslím, že určitě přehlednější.
V Bicep můžete samozřejmě používat stejné funkce jako v ARM, protože jde o transparentní řešení. Pojďme například smontovat řetězec s DNS jménem obsahujícím unikátní generovanou část řetězce.
param prefix string = 'prod'
resource vmIp 'Microsoft.Network/publicIPAddresses@2019-11-01' = {
name: '${prefix}-vm-ip'
location: resourceGroup().location
properties: {
publicIPAllocationMethod: 'Static'
dnsSettings: {
domainNameLabel: '${prefix}-ip-${uniqueString(resourceGroup().id)}'
}
}
}
Výsledkem je tohle:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"prefix": {
"type": "string",
"defaultValue": "prod"
}
},
"functions": [],
"resources": [
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2019-11-01",
"name": "[format('{0}-vm-ip', parameters('prefix'))]",
"location": "[resourceGroup().location]",
"properties": {
"publicIPAllocationMethod": "Static",
"dnsSettings": {
"domainNameLabel": "[format('{0}-ip-{1}', parameters('prefix'), uniqueString(resourceGroup().id))]"
}
}
}
]
}
Reference na atribut objektu je najednou opravdu snadná. Podívejme se třeba jak do výstupu dám IP adresu.
param prefix string = 'prod'
resource vmIp 'Microsoft.Network/publicIPAddresses@2019-11-01' = {
name: '${prefix}-vm-ip'
location: resourceGroup().location
properties: {
publicIPAllocationMethod: 'Static'
dnsSettings: {
domainNameLabel: '${prefix}-ip-${uniqueString(resourceGroup().id)}'
}
}
}
output ip string = vmIp.properties.ipAddress
Což se přeloží jako:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"prefix": {
"type": "string",
"defaultValue": "prod"
}
},
"functions": [],
"resources": [
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2019-11-01",
"name": "[format('{0}-vm-ip', parameters('prefix'))]",
"location": "[resourceGroup().location]",
"properties": {
"publicIPAllocationMethod": "Static",
"dnsSettings": {
"domainNameLabel": "[format('{0}-ip-{1}', parameters('prefix'), uniqueString(resourceGroup().id))]"
}
}
}
],
"outputs": {
"ip": {
"type": "string",
"value": "[reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-vm-ip', parameters('prefix')))).ipAddress]"
}
}
}
S vizualizací, syntaxí a doplňováním pomáhá plugin do Visual Studio Code.
Na závěr si zkusíme tři zdroje - VNET se subnety, síťovou kartu a k ní přidruženou public IP. Mezi těmito zdroji jsou dependence - NIC nelze vytvářet, dokud neexistuje VNET a public adresa. Tím, že mezi objekty se referencujeme jménem Bicep objektu, tak dokáže Bicep dependsOn vygenerovat sám. Kromě přidání dalších zdrojů jsem z location udělal proměnnou.
param prefix string = 'prod'
var location = resourceGroup().location
// Network
resource vnet 'Microsoft.Network/virtualnetworks@2015-05-01-preview' = {
name: 'mynet'
location: location
properties: {
addressSpace: {
addressPrefixes:[
'10.0.0.0/16'
]
}
subnets: [
{
name: 'subnet1'
properties: {
addressPrefix: '10.0.0.0/24'
}
}
{
name: 'subnet2'
properties: {
addressPrefix: '10.0.1.0/24'
}
}
]
}
}
// NIC
resource vmNic 'Microsoft.Network/networkInterfaces@2020-06-01' = {
name: '${prefix}-vm-nic'
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
subnet: {
id: '${vnet.id}/subnets/apps'
}
privateIPAllocationMethod: 'Dynamic'
publicIPAddress: {
id: vmIp.id
}
}
}
]
}
}
// Public IP
resource vmIp 'Microsoft.Network/publicIPAddresses@2019-11-01' = {
name: '${prefix}-vm-ip'
location: location
properties: {
publicIPAllocationMethod: 'Static'
dnsSettings: {
domainNameLabel: '${prefix}-ip-${uniqueString(resourceGroup().id)}'
}
}
}
output ip string = vmIp.properties.ipAddress
Podle mě dobře čitelné a s možností komentářů. Výsledný ARM vypadá takhle:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"prefix": {
"type": "string",
"defaultValue": "prod"
}
},
"functions": [],
"variables": {
"location": "[resourceGroup().location]"
},
"resources": [
{
"type": "Microsoft.Network/virtualnetworks",
"apiVersion": "2015-05-01-preview",
"name": "mynet",
"location": "[variables('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"10.0.0.0/16"
]
},
"subnets": [
{
"name": "subnet1",
"properties": {
"addressPrefix": "10.0.0.0/24"
}
},
{
"name": "subnet2",
"properties": {
"addressPrefix": "10.0.1.0/24"
}
}
]
}
},
{
"type": "Microsoft.Network/networkInterfaces",
"apiVersion": "2020-06-01",
"name": "[format('{0}-vm-nic', parameters('prefix'))]",
"location": "[variables('location')]",
"properties": {
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"subnet": {
"id": "[format('{0}/subnets/apps', resourceId('Microsoft.Network/virtualnetworks', 'mynet'))]"
},
"privateIPAllocationMethod": "Dynamic",
"publicIPAddress": {
"id": "[resourceId('Microsoft.Network/publicIPAddresses', format('{0}-vm-ip', parameters('prefix')))]"
}
}
}
]
},
"dependsOn": [
"[resourceId('Microsoft.Network/publicIPAddresses', format('{0}-vm-ip', parameters('prefix')))]",
"[resourceId('Microsoft.Network/virtualnetworks', 'mynet')]"
]
},
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2019-11-01",
"name": "[format('{0}-vm-ip', parameters('prefix'))]",
"location": "[variables('location')]",
"properties": {
"publicIPAllocationMethod": "Static",
"dnsSettings": {
"domainNameLabel": "[format('{0}-ip-{1}', parameters('prefix'), uniqueString(resourceGroup().id))]"
}
}
}
],
"outputs": {
"ip": {
"type": "string",
"value": "[reference(resourceId('Microsoft.Network/publicIPAddresses', format('{0}-vm-ip', parameters('prefix')))).ipAddress]"
}
}
}
Odstraním teď komentáře a zkusím zjistit komplexitu obou souborů:
Díky jednoduchým závorkám není nutné escapovat vnořený JSON, což se celkem často hodí.
var json = '{"mojePoleObjektu":[{"klic1":"hodnota1"},{"klic2":"hodnota2"}]}'
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"functions": [],
"variables": {
"json": "{\"mojePoleObjektu\":[{\"klic1\":\"hodnota1\"},{\"klic2\":\"hodnota2\"}]}"
},
"resources": []
}
Jak se pozná transparentnost Bicepu? Jak bicep binárka tak VScode plugin vám řeknou, když něco neodpovídá aktuální specifikaci, tedy že atribut neexistuje nebo api verze je neplatná apod. Nicméně jsou to pouze varování. Pokud jste třeba v private preview, definice není k dispozici nebo tak něco, nic vám nebrání Bicep použít (to je zásadní rozdíl od Terraformu, kde dokud podpora není vytvořena, máte smůlu). Přesuňme se do roku 2022, který třeba přinese nový atribut “nesmysl”.
resource vnet 'Microsoft.Network/virtualnetworks@2022-05-01-preview' = {
name: 'mynet'
location: location
properties: {
nesmysl: 'ahoj'
addressSpace: {
addressPrefixes:[
'10.0.0.0/16'
]
}
}
}
Bicep sice varuje, ale ARM, pokud na tom trváte, vytvoří (ten samozřejmě nebude fungovat). Díky tomu vás nic neomezuje, nemusíte čekat, až někdo podporu dodělá.
{
"type": "Microsoft.Network/virtualnetworks",
"apiVersion": "2022-05-01-preview",
"name": "mynet",
"location": "[variables('location')]",
"properties": {
"nesmysl": "ahoj",
"addressSpace": {
"addressPrefixes": [
"10.0.0.0/16"
]
},
}
}
Jsou ještě oblasti, kde finální rozhodnutí nepadlo a je to tak skvělá příležitost pro vás zapojit se do diskuse. Pro mě jsou tam určitě dvě dost zajímavá témata:
Sečteno podtrženo - z hlavních nevýhod ARMu Bicep krásně odstraňuje syntaktickou složitost, nekompromisnost JSONu, nutnost pořád řešit dependence nebo složitěji modularizovat přes linkované šablony. Přesto má stále všechny výhody ARMu. Myslím, že je to dost zásadní projekt a očekávám, že během pár měsíců se zastabilizuje. Terraform si určitě ponechává svou unikátnost v podpoře dalších cloudů a velmi propracovaný troubleshooting a testování v rámci CI/CD. Rozhodnutí je na vás, ale pokud je automatizace vaše téma, určitě Bicep vyzkoušejte a podílejte se na jeho vývoji třeba názorem v diskusi na GitHubu.