Azure Resource Manager - ARM

En introduktion till Infrastrucure-as-Code - IaC

I den här posten tänkte jag presentera en väldigt hands-on lösning som en introduktion till att hantera sin infrastruktur och resurser i Azure på samma sätt som man gör med sin applikationskod.

Källkoden för dom flesta systemen lagras, versioneras och byggs/distribueras förhoppningsvis på ett automatiserat och repetitivt sätt. Detsamma kan man göra med resurser och infrastruktur i molnet, om man anammar begreppet Infrastructure-as-Code, IaC.

För att hantera resurserna i Azure kan man nyttja något som man kallar för ARM Templates, Azure Resource Manager Templates. Hela den här posten bygger på att man definierar och beskriver resurserna i json-filer och sedan distribueras resurserna antingen med hjälp av Powershell (ps1) eller från en pipeline i Azure DevOps.

Bra resurser rörande ARM Templates finns här:

Infrastrukturen för dagen

Den tänkta floran av resurser i Azure som vi ska styra med kod är tänkt att bygga upp följande lilla exempel systemarkitektur:

Infrastruktur

Notera den dimmade Storage-delen, den är inte med i koden för dagen.

Innan vi kollar på respektive resurs så kan det vara på sin plats att kort introducera hur en Azure-resurs får sin bestämda plats i molnet, vilken omgivning finns?

En Azure-resurs omgivning

Strukturen för en resurs i Azure ser ut enligt nedan:

Resurshierarki

Resurserna som vi ska skapa här kommer att ha sitt avstamp från nivån 'Subscriptions' och nedåt. Att ta avstampet på den nivån gör att vi så fort som möjligt kommer till en nivå där vi kan deklarera/konfigurera resurserna i ARM templates json-filer.

Nivån 'Resource groups' innebär att man logiskt kan gruppera sina resurser. Gruppen kommer att ha ett namn och en lokalitet, location, som vi kommer att sätta till 'North Europe'.

'Resources'-nivån är där dom verkliga resurserna ligger. Där kommer vi då att skapa:

  • En Web App Service
  • En Function App (notera att Function Appen tvingar fram även en Storage-resurs för, men det är INTE den Storage som syns nertonad i infrastruktur-bilden ovan)
  • En Service Bus

Låt oss dra igång och titta på hur man kan automatisera det här då.

Pre-reqs

För att underlätta kodningen av mallarna rekommenderas VS Code med extension Azure Resource Manager (ARM) Tools for Visual Studio Code installerad. Med denna setup får man riktigt bra stöd i form av snippets och auto-completions för en mängd resurser i Azure.

Underförstått är att man har tillgång till ett konto i Azure där man har rättigheter att skapa, redigera och plocka bort resurser.

Labben

All källkod som presenteras nedan återfinns här https://github.com/HeadlightAB/ARM-Lab.

I GitHub-repot finns även en feature-branch där sk. parameterfiler används https://github.com/HeadlightAB/ARM-Lab/tree/feature/parameter-file. Mer info finns här https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/parameter-files.

Prenumeration/subscription

Resurserna kommer att drivas fram med hjälp av ett PowerShell-script, som vi enligt tidigare kommer låta få sitt avstamp på prenumerationsnivån enligt bilden ovan. Det första man behöver göra är att försäkra sig om att man har PowerShell-modulerna i paketet 'Az PowerShell modules' installerat. Man kontrollerar detta genom att starta PowerShell och skriver 'connect-az' och trycker [TAB]. Man ska då få förslaget av PowerShell det kompletta kommandot:

> Connect-AzAccount

Tryck [ENTER] och ange inloggningsuppgifterna för den önskade Azure-prenumerationen.

Nu har du kopplat upp den aktuella PowerShell-instansen med Azure och är redo att gå vidare nedåt i hierarkin.

Variabler

Vi börjar med att deklarera några variabler i den aktuella PowerShell-sessionen:

$resourceGroupName = 'RG-arm-lab'
$appServicePlanName = 'ASP-arm-lab'
$armLabWebApiName = 'armlabwebapi'
$armLabServiceBusNamespace = 'armlabsb'
$functionName = 'armlabfa'

Dessa kommer att användas i kommande PowerShell-kommandon, när mallarna för resurserna ska exekveras.

Resursgrupp/Resource group

Nästa steg är att säkra upp grupperingen av dom kommande resurserna, genom att skapa eller uppdatera en existerande resursgrupp. Även denna nivå hanteras mha ett PowerShell-kommando, New-AzResourceGroup. Innan kommandot körs sätter vi variabeln $resourceGroupName som vi kommer att använda för alla kommande resurser.

> New-AzResourceGroup -Name $resourceGroupName -Location 'North Europe' -Force

Nu har vi säkrat upp att vi har en resursgrupp att lägga resurserna i. Låt oss gå vidare till nivån med dom verkliga resurserna.

Resurser/Resources

App Service Plan + webbapplikation

Man skulle kunna säga att det är nu som det roliga börjar. Låt oss först definiera hur vi vill att webbapplikationen för API:et ska sättas upp. All konfiguration ligger i en json-fil (webapi.json) enligt:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "webAppName": {
            "type": "string"
        },
        "appServicePlanName": {
            "type": "string"
        }
    },
    "functions": [],
    "variables": {},
    "resources": [
        {
            "name": "[parameters('webAppName')]",
            "type": "Microsoft.Web/sites",
            "apiVersion": "2018-11-01",
            "location": "[resourceGroup().location]",
            "properties": {
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]"
            }

        }
    ],
    "outputs": {}
}

Här finns det ett par saker att notera:

  • inkommande parametrar till mallen definieras i "parameters"-objektet. I det här fallet finns det två stycken, webAppName och appServicePlanName.
  • App Service Plan är det som bestämmer vilken omgivning webbapplikationen ska köras i, om resursen till exempel ska dela hårdvara med andra liknande resurser eller ha en egen dedikerad hårdvara. Vad man väljer påverkar såklart priset att driva webbapplikationen.

Enligt punkt två ovan behöver vi alltså välja en App Service Plan för webbapplikationen och även den definieras i en json-fil (asp.json):

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "appServicePlanName": {
            "type": "string"
        }
    },
    "functions": [],
    "variables": {},
    "resources": [
        {
            "name": "[parameters('appServicePlanName')]",
            "type": "Microsoft.Web/serverfarms",
            "apiVersion": "2018-02-01",
            "location": "[resourceGroup().location]",
            "sku": {
                "name": "F1", // Free
                "capacity": 1
            },
            "properties": {}
        }
    ],
    "outputs": {}
}

Notera speciellt objektet "sku" ovan, som beskriver vilken nivå på hårdvara man vill att resursen ska använda sig av. I det här exemplet väljer vi en gratis-version där man samsas på en och samma hårdvara med många andra resurser.

För att mallen för webbapplikationen ska fungera så måste alltså en 'App Service Plan' finnas och den mallen exekveras med fördel först. Detta ger då följande PowerShell-kommandon att köra:

> New-AzResourceGroupDeployment `
    -Name 'armlab-asp' `
    -ResourceGroupName $resourceGroupName `
    -appServicePlanName $appServicePlanName `
    -TemplateFile 'asp.json'

> New-AzResourceGroupDeployment `
    -Name 'armlab-webapi' `
    -ResourceGroupName $resourceGroupName `
    -webAppName $armLabWebApiName `
    -appServicePlanName $appServicePlanName `
    -TemplateFile 'webapi.json'

Service bus

Näst på kö i uppsättningen är en service bus. Tanken med bussen är att den ska ta emot meddelanden från webbapplikationen/api:et ovan och låta den kommande funtionsappen prenumerera på dessa meddelanden, via topics och subscriptions.

Definitionen för bussen följer samma strukturella mall som ovan resurser (sb.json):

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "serviceBusNamespace": {
            "type": "string"
        }
    },
    "functions": [],
    "variables": {},
    "resources": [
    {
        "name": "[parameters('serviceBusNamespace')]",
        "type": "Microsoft.ServiceBus/namespaces",
        "apiVersion": "2017-04-01",
        "location": "[resourceGroup().location]",
        "sku": {
            "name": "Standard" // For topic support
        },
        "properties": {}
    }
    ],
    "outputs": {}
}

Notera att den service bus som definieras ovan har en "sku" satt till "Standard". Den här servicenivån är lite intressantare att laborera med då man bland annat får topic-stöd på bussen.

Function App

Den här funktionsappen ska konsumera/prenumerera på meddelanden på bussen skapad ovan. Värt att notera med en Function App är att den, förutom själva funktionsvärden, även kräver ett storage-konto. Detta konto definieras med fördel i samma json-fil som "sin tillhörande" function app. Denna mall kommer då att innehålla två resurser och ser ut enligt nedan (fa.json):

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "functionName": {
            "type": "string"
        },
        "appServicePlanName": {
            "type": "string"
        }
    },
    "functions": [],
    "variables": {
      "storageAccountName": "[concat('storageaccount', parameters('functionName'))]"
    },
    "resources": [
        {
            "name": "[variables('storageAccountName')]",
            "type": "Microsoft.Storage/storageAccounts",
            "apiVersion": "2019-06-01",
            "location": "[resourceGroup().location]",
            "kind": "Storage", // Cheapest storage for the function app
            "sku": {
                "name": "Standard_LRS"
            }
        },
        {
            "name": "[parameters('functionName')]",
            "type": "Microsoft.Web/sites",
            "apiVersion": "2018-11-01",
            "location": "[resourceGroup().location]",
            "kind": "functionapp",
            "dependsOn": [
                "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
            ],
            "properties": {
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]",
                "siteConfig": {
                    "appSettings": [
                        {
                            "name": "AzureWebJobsDashboard",
                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountName'),'2019-06-01').keys[0].value)]"
                        },
                        {
                            "name": "AzureWebJobsStorage",
                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountName'),'2019-06-01').keys[0].value)]"
                        },
                        {
                            "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountName'),'2019-06-01').keys[0].value)]"
                        },
                        {
                            "name": "WEBSITE_CONTENTSHARE",
                            "value": "[toLower(parameters('functionName'))]"
                        },
                        {
                            "name": "FUNCTIONS_EXTENSION_VERSION",
                            "value": "~2"
                        },
                        {
                            "name": "FUNCTIONS_WORKER_RUNTIME",
                            "value": "dotnet"
                        }
                    ]
                }
            }
        }
    ],
    "outputs": {}
}

Notera:

  • Storage-kontot är det billigaste möjliga för funktionsappen att fungera, "Standard_LRS".
  • Funktionsappen är i grunden en webbapplikation, "type": "Microsoft.Web/sites".
  • Funktionsappen har ett beroende till storage-kontot via "dependsOn"-objektet.
  • Funktionsappen får sina behörigheter till storage-kontot via connectionstring och 'listKeys(...)'.
  • Funktionsappen i exemplet kör dotnet-kod.

Powershell

Det kompletta powershell-scriptet för att skapa upp resurserna beskrivna ovan ser ut enligt nedan (arm-lab.ps1):

# Connect with Azure Account
#$credential = Get-Credential
#Connect-AzAccount -Credential $credential

##########################################################################################
# Parameters for the ARM templates
Write-Host "Setting parameters..." -ForegroundColor Yellow

$resourceGroupName = 'RG-arm-lab'
$appServicePlanName = 'ASP-arm-lab'
$armLabWebApiName = 'armlabwebapi'
$armLabServiceBusNamespace = 'armlabsb'
$functionName = 'armlabfa'

Write-Host "...done (Setting parameters)`n`n" -ForegroundColor Green

##########################################################################################
# Create/ensure resource group
Write-Host "Creating/ensuring resourcegroup..." -ForegroundColor Yellow

New-AzResourceGroup -Name $resourceGroupName -Location 'North Europe' -Force

Write-Host "...done (Creating/ensuring resourcegroup)`n`n" -ForegroundColor Green

##########################################################################################
# App Service Plan
Write-Host "App Service Plan..." -ForegroundColor Yellow

New-AzResourceGroupDeployment `  
    -Name 'armlab-asp' `
    -ResourceGroupName $resourceGroupName `
    -appServicePlanName $appServicePlanName `
    -TemplateFile 'asp.json'

Write-Host "...done (App Service Plan)`n`n" -ForegroundColor Green

##########################################################################################
# App service (web api)
Write-Host "App service (web api)..." -ForegroundColor Yellow

New-AzResourceGroupDeployment `  
    -Name 'armlab-webapi' `
    -ResourceGroupName $resourceGroupName `
    -webAppName $armLabWebApiName `
    -appServicePlanName $appServicePlanName `
    -TemplateFile 'webapi.json'

Write-Host "...done (App service (web api))`n`n" -ForegroundColor Green

##########################################################################################
# Service bus
Write-Host "Service bus..." -ForegroundColor Yellow

New-AzResourceGroupDeployment `  
    -Name 'armlab-sb' `
    -ResourceGroupName $resourceGroupName `
    -serviceBusNamespace $armLabServiceBusNamespace `
    -TemplateFile "sb.json"

Write-Host "...done (Service bus)`n`n" -ForegroundColor Green

##########################################################################################
# Function app (storage account included as dependency)
Write-Host "Function app..." -ForegroundColor Yellow

New-AzResourceGroupDeployment `  
    -Name 'armlab-fa' `
    -ResourceGroupName $resourceGroupName `
    -functionName $functionName `
    -appServicePlanName $appServicePlanName `
    -TemplateFile "fa.json"

Write-Host "...done (Function app)`n`n" -ForegroundColor Green  

Notera dom översta rader som är bortkommenterade. Inloggningsuppgifterna för Azure-prenumerationen kan såklart hanteras på något annat sätt, till exempel om man väljer att deploya resurserna via Azure DevOps.

Azure DevOps

Att deploya ovanstående resurser mha Azure DevOps handlar om att återspegla dom olika delarna i Powershell-scriptet i en pipeline. Skärmdumpen nedan visar hur det kan se ut i en Build Pipeline:

En DevOps Pipeline för Azure-resurserna

Inparametrar till respektive ARM Template sätts i fältet xyz för bygg steget:
DevOps Pipeline build step för en resurs

Notera:

  • Resursgruppen skapas eller uppdateras i VARJE build step
  • Den valda mallen är json-filen
  • Override template parameters används eftersom parametrarna i den här labben INTE ligger i egna filer
  • Deployment mode är satt till Incremental, vilket gör att resurserna uppdateras endast om förändringar gjorts i mallen sedan senast den deployades

Städa resurser

Om man väljer att skapa alla resurser i en och samma resursgrupp så är städningen väldigt enkel efter genomförd labb. Antingen plockar man bort gruppen i Resource Groups i Azure Portal eller kör man följande kommando i PowerShell:

> Remove-AzResourceGroup -Name $resourceGroupName

$resourceGroupName antas vara satt till den gruppen man använt i labben.

Summering

Den här posten skrapar bara på ytan av vad som kan göras med ARM Templates, men förhoppningen är att den ska locka devops-intresserade "där ute" till att automatisera uppsättning av resurser.

Om man inte befinner sig i molnet med sin infrastruktur så kanske den trots allt lockar till att få bort den mänskliga felfaktorn i konfiguration samt att säkertställa en versionering av infrastrukturen, som i sin tur säkerställer möjligheten att återskapa den OM olyckan skulle vara framme och elda upp den. Eftersom man textuellt, i ett väldefinierat json-format, deklarerar resurserna så är uppsättningen också möjlig att granska INNAN man trycker på knappen och deployar sitt systems körtidsmiljö och omgivning.