Contattaci

Biceps, muscoli al servizio di Azure

  • Data: 21 Marzo 2022
  • Autore: Mattia Bonanni
  • Categorie

  • Giuneco Tech

    Il nostro settore richiede uno studio continuo e una forte attenzione all’innovazione. Incentiviamo quindi dei programmi formativi annuali per tutti i componenti del team, con ore dedicate (durante l’orario di lavoro) e serate formative sia online che in presenza. Sponsorizziamo eventi, sia come partner che semplicemente come partecipanti, e scriviamo articoli su quello che abbiamo imparato per essere, a nostra volta, dei divulgatori.

    Vai alla sezione Team
  • Con il passare del tempo e le sempre più complesse architetture, oltre al passaggio a metodologie agili, sono nate nuove sfide e nuove esigenze per riuscire ad integrare nel modo più fluido e iterativo possibile tutte le attività legate alla gestione dell’infrastruttura IT e del codice applicativo.

    Per raggiungere questi nuovi obiettivi, è nato un approccio chiamato Infrastructure as Code (o IaC), ovvero la gestione e il provisioning dell’infrastruttura attraverso codice invece che attraverso processi manuali. Grazie a questo nuovo approccio è oggi possibile gestire una serie di risorse in modo consistente e riproducibile nel tempo, andando a diminuire, quanto più possibile, l’entropia generata dalla gestione manuale di un’infrastruttura.

    Microsoft, per raggiungere l’obiettivo sulla propria infrastruttura cloud, ha sviluppato nel tempo un nuovo linguaggio dominio-specifico dichiarativo: Biceps.

    Bicep, tuttavia, non è la prima soluzione ideata da Microsoft per Azure, poiché è stato preceduto dai template ARM, che non sono ovviamente un linguaggio stand-alone, ma semplicemente dei file JSON tramite i quali l’Azure Resource Manager orchestra le risorse Azure; ma andiamo in ordine.

    Una fermata obbligata; la terminologia di Azure

    Prima di proseguire, temo sia doveroso, soprattutto verso chi potrebbe non essere avvezzo alla terminologia specifica di Azure, fare un piccolo riassunto su un paio di termini che troveremo in seguito:

    Azure Resource Manager
    Anche chiamato ARM, è colui che orchestra il rilascio e la gestione dei servizi su Azure attraverso una delle tecnologie già citate, ARM Template e Bicep, oppure attraverso il portale stesso di Azure, la CLI, le API Rest e i client attraverso il proprio SDK.

    Resource Group o gruppo di risorse
    Nient’altro che un contenitore grazie al quale è possibile raggruppare risorse che si vogliono gestire come un gruppo unico. Non esistono regole specifiche sul raggruppamento.

    Resource o risorsa
    Un servizio disponibile su Azure, come una VM, web app, database e via discorrendo. I resource group sono a loro volta delle risorse.

    App Service Plan
    Una risorsa specifica di Azure necessaria per l’hosting di una Web App, Web API e/o Function App. Può essere vista, seppur in modo abbastanza improprio, come una Virtual Machine sulla quale non abbiamo reale controllo se non per il suo dimensionamento.

    Template ARM

    I template ARM sono stati, e sono, il primo approccio fondamentale di Microsoft all’IaC. Esattamente come i Biceps sono file dichiarativi ma scritti in JSON. Tuttavia, come potremo vedere nell’esempio subito sotto, questi file sono particolarmente complessi da navigare, leggere e, ancor più, creare.

    {
        "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
          "webAppName": {
            "type": "string",
            "defaultValue": "[concat('webApp-', uniqueString(resourceGroup().id))]",
            "minLength": 2,
            "metadata": {
              "description": "Web app name."
            }
          },
          "location": {
            "type": "string",
            "defaultValue": "[resourceGroup().location]",
            "metadata": {
              "description": "Location for all resources."
            }
          },
          "sku": {
            "type": "string",
            "defaultValue": "F1",
            "metadata": {
              "description": "The SKU of App Service Plan."
            }
          },
          "linuxFxVersion": {
            "type": "string",
            "defaultValue": "DOTNETCORE|3.0",
            "metadata": {
              "description": "The Runtime stack of current web app"
            }
          }
        },
        "variables": {
          "appServicePlanPortalName": "[concat('AppServicePlan-', parameters('webAppName'))]"
        },
        "resources": [
          {
            "type": "Microsoft.Web/serverfarms",
            "apiVersion": "2020-06-01",
            "name": "[variables('appServicePlanPortalName')]",
            "location": "[parameters('location')]",
            "sku": {
              "name": "[parameters('sku')]"
            },
            "kind": "linux",
            "properties": {
              "reserved": true
            }
          },
          {
            "type": "Microsoft.Web/sites",
            "apiVersion": "2020-06-01",
            "name": "[parameters('webAppName')]",
            "location": "[parameters('location')]",
      
            "dependsOn": [
              "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanPortalName'))]"
            ],
            "properties": {
              "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanPortalName'))]",
              "siteConfig": {
                "linuxFxVersion": "[parameters('linuxFxVersion')]"
              }
            }
          }
        ]
      }

    Questo è un esempio di template ARM per la creazione di un App Service Plan e di una Web App. Come si può vedere è relativamente poco intuitivo.

    Scorrendolo rapidamente possiamo trovare:

    • Lo schema utilizzato per la definizione del template stesso e la sua versione;
    • Una serie di parametri, definibili in fase di utilizzo, che verranno richiamati direttamente in fase di creazione delle risorse;
    • Le variabili;
    • Le risorse da creare;

    La creazione di questo file, seppur fattibile tramite l’aiuto di estensioni per VS Code come Azure Resource Manager (ARM) Tool, è fortemente soggetta ad errori per un semplice motivo: non esiste una vera e propria documentazione che non sia un dump di informazioni più o meno esaustive come questa per la creazione di un App Service Plan.

    Quali proprietà sono obbligatorie per fare cosa? Quali sono i possibili valori da inserire per il dimensionamento (ndr, definito attraverso lo Sku)? Per non parlare di descrizioni assolutamente tautologiche.

    Di fatto l’utilizzo dei template ARM è condizionato dall’avere una conoscenza ben approfondita di tutta l’infrastruttura Azure (che comunque male non fa) o dall’avere la possibilità di perdere una discreta mole di tempo nel carpire queste informazioni direttamente dal portale; oppure possiamo procedere esportando il template stesso da un servizio già creato e configurato, dal quale poter estrapolare un file pulito.

    {
        "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
            "sites_test_biceps_name": {
                "defaultValue": "test-biceps",
                "type": "String"
            },
            "serverfarms_aps_test_biceps_externalid": {
                "defaultValue": "/subscriptions/d1315be3-6e5f-4976-8385-7ffea263fd5b/resourceGroups/rg-test/providers/Microsoft.Web/serverfarms/aps-test-biceps",
                "type": "String"
            }
        },
        "variables": {},
        "resources": [
            {
                "type": "Microsoft.Web/sites",
                "apiVersion": "2021-03-01",
                "name": "[parameters('sites_test_biceps_name')]",
                "location": "North Europe",
                "kind": "app,linux",
                "properties": {
                    "enabled": true,
                    "hostNameSslStates": [
                        {
                            "name": "[concat(parameters('sites_test_biceps_name'), '.azurewebsites.net')]",
                            "sslState": "Disabled",
                            "hostType": "Standard"
                        },
                        {
                            "name": "[concat(parameters('sites_test_biceps_name'), '.scm.azurewebsites.net')]",
                            "sslState": "Disabled",
                            "hostType": "Repository"
                        }
                    ],
                    "serverFarmId": "[parameters('serverfarms_aps_test_biceps_externalid')]",
                    "reserved": true,
                    "isXenon": false,
                    "hyperV": false,
                    "siteConfig": {
                        "numberOfWorkers": 1,
                        "linuxFxVersion": "DOTNETCORE|6.0",
                        "acrUseManagedIdentityCreds": false,
                        "alwaysOn": true,
                        "http20Enabled": false,
                        "functionAppScaleLimit": 0,
                        "minimumElasticInstanceCount": 0
                    },
                    "scmSiteAlsoStopped": false,
                    "clientAffinityEnabled": false,
                    "clientCertEnabled": false,
                    "clientCertMode": "Required",
                    "hostNamesDisabled": false,
                    "customDomainVerificationId": "63C660897FB7204733B032F0459A0CC9CD27306CFC6FAD2F9057935A120D4694",
                    "containerSize": 0,
                    "dailyMemoryTimeQuota": 0,
                    "httpsOnly": false,
                    "redundancyMode": "None",
                    "storageAccountRequired": false,
                    "keyVaultReferenceIdentity": "SystemAssigned"
                }
            },
            {
                "type": "Microsoft.Web/sites/basicPublishingCredentialsPolicies",
                "apiVersion": "2021-03-01",
                "name": "[concat(parameters('sites_test_biceps_name'), '/ftp')]",
                "location": "North Europe",
                "dependsOn": [
                    "[resourceId('Microsoft.Web/sites', parameters('sites_test_biceps_name'))]"
                ],
                "properties": {
                    "allow": true
                }
            },
            {
                "type": "Microsoft.Web/sites/basicPublishingCredentialsPolicies",
                "apiVersion": "2021-03-01",
                "name": "[concat(parameters('sites_test_biceps_name'), '/scm')]",
                "location": "North Europe",
                "dependsOn": [
                    "[resourceId('Microsoft.Web/sites', parameters('sites_test_biceps_name'))]"
                ],
                "properties": {
                    "allow": true
                }
            },
            {
                "type": "Microsoft.Web/sites/config",
                "apiVersion": "2021-03-01",
                "name": "[concat(parameters('sites_test_biceps_name'), '/web')]",
                "location": "North Europe",
                "dependsOn": [
                    "[resourceId('Microsoft.Web/sites', parameters('sites_test_biceps_name'))]"
                ],
                "properties": {
                    "numberOfWorkers": 1,
                    "defaultDocuments": [
                        "Default.htm",
                        "Default.html",
                        "Default.asp",
                        "index.htm",
                        "index.html",
                        "iisstart.htm",
                        "default.aspx",
                        "index.php",
                        "hostingstart.html"
                    ],
                    "netFrameworkVersion": "v4.0",
                    "linuxFxVersion": "DOTNETCORE|6.0",
                    "requestTracingEnabled": false,
                    "remoteDebuggingEnabled": false,
                    "httpLoggingEnabled": false,
                    "acrUseManagedIdentityCreds": false,
                    "logsDirectorySizeLimit": 35,
                    "detailedErrorLoggingEnabled": false,
                    "publishingUsername": "$test-biceps",
                    "scmType": "None",
                    "use32BitWorkerProcess": true,
                    "webSocketsEnabled": false,
                    "alwaysOn": true,
                    "managedPipelineMode": "Integrated",
                    "virtualApplications": [
                        {
                            "virtualPath": "/",
                            "physicalPath": "site\\wwwroot",
                            "preloadEnabled": true
                        }
                    ],
                    "loadBalancing": "LeastRequests",
                    "experiments": {
                        "rampUpRules": []
                    },
                    "autoHealEnabled": false,
                    "vnetRouteAllEnabled": false,
                    "vnetPrivatePortsCount": 0,
                    "localMySqlEnabled": false,
                    "ipSecurityRestrictions": [
                        {
                            "ipAddress": "Any",
                            "action": "Allow",
                            "priority": 1,
                            "name": "Allow all",
                            "description": "Allow all access"
                        }
                    ],
                    "scmIpSecurityRestrictions": [
                        {
                            "ipAddress": "Any",
                            "action": "Allow",
                            "priority": 1,
                            "name": "Allow all",
                            "description": "Allow all access"
                        }
                    ],
                    "scmIpSecurityRestrictionsUseMain": false,
                    "http20Enabled": false,
                    "minTlsVersion": "1.2",
                    "scmMinTlsVersion": "1.0",
                    "ftpsState": "AllAllowed",
                    "preWarmedInstanceCount": 0,
                    "functionAppScaleLimit": 0,

    L’unico problema è che i file che vengono esportati dal portale sono ancora meno leggibili di quello già visto sopra, con parametri cablogrammati e talvolta con sintassi diverse.

     

    "serverfarms_aps_test_biceps_externalid": {
                "defaultValue": "/subscriptions/               /resourceGroups/rg-test/providers/Microsoft.Web/serverfarms/aps-test-biceps",
                "type": "String"
            }

    Difatti, come si può vedere, se la definizione dell’App Service Plan nel primo esempio era identificata dal nome stesso del servizio, qui è una concatenazione di parametri che non definirei esattamente human readable.

    Template Biceps

    Ed è qui, in questo marasma di illeggibilità, che Microsoft ad un certo punto ha deciso di cambiare rotta e introdurre i file Biceps.

    @description('Web app name.')
    @minLength(2)
    param webAppName string = 'webApp-${uniqueString(resourceGroup().id)}'
    
    @description('Location for all resources.')
    param location string = resourceGroup().location
    
    @description('The SKU of App Service Plan.')
    param sku string = 'F1'
    
    @description('The Runtime stack of current web app')
    param linuxFxVersion string = 'DOTNETCORE|3.0'
    
    var appServicePlanPortalName_var = 'AppServicePlan-${webAppName}'
    
    resource appServicePlanPortalName 'Microsoft.Web/[email protected]' = {
      name: appServicePlanPortalName_var
      location: location
      sku: {
        name: sku
      }
      kind: 'linux'
      properties: {
        reserved: true
      }
    }
    
    resource webAppName_resource 'Microsoft.Web/[email protected]' = {
      name: webAppName
      location: location
      properties: {
        serverFarmId: appServicePlanPortalName.id
        siteConfig: {
          linuxFxVersion: linuxFxVersion
        }
      }
    }		
    

    Già di primo acchito si può notare quanto sia infinitamente più semplice da leggere e più contenuto rispetto al file precedente. Tutto il codice superfluo è stato rimosso, i parametri sono direttamente nel file senza il bisogno di una sovrastruttura che li contenga, e possono essere facilmente decorati con una sintassi simile alle Data Annotation che chi lavora nel mondo .NET conosce.

    Esattamente come per il template ARM, la creazione può avvenire tramite estensioni per VS Code come Biceps (che in realtà è anche l’unica) che mettono a disposizione sia l’highlight syntax che un minimo di IntelliSense per facilitare la creazione del file.

    Tuttavia non tutto è rose e fiori; infatti, come qualcuno che potrebbe essere stato più attento navigando la doc Microsoft poco potrebbe aver notato, non esiste una vera e propria documentazione neanche per i Biceps. Questa è la pagina della documentazione di un servizio di tipo App Service Plan. Notate qualche somiglianza con quella linkata per i template ARM?

    La pagina è la stessa.

    Di fatto, l’utilizzo dei file Biceps è fortemente condizionato dall’avere una conoscenza ben approfondita di tutta l’infrastruttura Azure…

    …déjà vu?

    Sì, i file Biceps sono più semplici da leggere, ma il problema intrinseco che avevano i template ARM è il medesimo: senza una conoscenza pregressa – e neanche troppo superficiale – di Azure, sono praticamente irrealizzabili, se non per casistiche estremamente semplici e banali (come quelle coperte dall’IntelliSense) che in ambienti Enterprise difficilmente hanno riscontro e/o sono minimamente esaustive per quelli che potrebbero essere i requisiti di un cliente.

    Un approccio pragmatico

    Nonostante ciò, è comunque possibile approcciare i Biceps in modo tale che possano comunque espletare la loro funzionalità principale: versionare l’infrastruttura e poter rendere la sua creazione / modifica consistente nel tempo.

    Costruire un Bicep, o un template ARM, da zero non è affatto semplice, ma possiamo navigare attorno al problema.

    Abbiamo già visto che è possibile scaricare un template ARM direttamente da Azure dopo che una risorsa è stata creata e modificata per rispettare i requisiti infrastrutturali. Una volta che abbiamo il template ARM possiamo utilizzare l’Azure CLI per decompilare il file .JSON e ottenere un Bicep, attraverso il comando az bicep decompile –file template.json.

    param sites_test_biceps_name string = 'test-biceps'
    param serverfarms_aps_test_biceps_externalid string = '/subscriptions/d1315be3-6e5f-4976-8385-7ffea263fd5b/resourceGroups/rg-test/providers/Microsoft.Web/serverfarms/aps-test-biceps'
    
    resource sites_test_biceps_name_resource 'Microsoft.Web/[email protected]' = {
      name: sites_test_biceps_name
      location: 'North Europe'
      kind: 'app,linux'
      properties: {
        enabled: true
        hostNameSslStates: [
          {
            name: '${sites_test_biceps_name}.azurewebsites.net'
            sslState: 'Disabled'
            hostType: 'Standard'
          }
          {
            name: '${sites_test_biceps_name}.scm.azurewebsites.net'
            sslState: 'Disabled'
            hostType: 'Repository'
          }
        ]
        serverFarmId: serverfarms_aps_test_biceps_externalid
        reserved: true
        isXenon: false
        hyperV: false
        siteConfig: {
          numberOfWorkers: 1
          linuxFxVersion: 'DOTNETCORE|6.0'
          acrUseManagedIdentityCreds: false
          alwaysOn: true
          http20Enabled: false
          functionAppScaleLimit: 0
          minimumElasticInstanceCount: 0
        }
        scmSiteAlsoStopped: false
        clientAffinityEnabled: false
        clientCertEnabled: false
        clientCertMode: 'Required'
        hostNamesDisabled: false
        customDomainVerificationId: '63C660897FB7204733B032F0459A0CC9CD27306CFC6FAD2F9057935A120D4694'
        containerSize: 0
        dailyMemoryTimeQuota: 0
        httpsOnly: false
        redundancyMode: 'None'
        storageAccountRequired: false
        keyVaultReferenceIdentity: 'SystemAssigned'
      }
    }
    
    resource sites_test_biceps_name_ftp 'Microsoft.Web/sites/[email protected]' = {
      parent: sites_test_biceps_name_resource
      name: 'ftp'
      location: 'North Europe'
      properties: {
        allow: true
      }
    }
    
    resource sites_test_biceps_name_scm 'Microsoft.Web/sites/[email protected]' = {
      parent: sites_test_biceps_name_resource
      name: 'scm'
      location: 'North Europe'
      properties: {
        allow: true
      }
    }
    
    resource sites_test_biceps_name_web 'Microsoft.Web/sites/[email protected]' = {
      parent: sites_test_biceps_name_resource
      name: 'web'
      location: 'North Europe'
      properties: {
        numberOfWorkers: 1
        defaultDocuments: [
          'Default.htm'
          'Default.html'
          'Default.asp'
          'index.htm'
          'index.html'
          'iisstart.htm'
          'default.aspx'
          'index.php'
          'hostingstart.html'
        ]
        netFrameworkVersion: 'v4.0'
        linuxFxVersion: 'DOTNETCORE|6.0'
        requestTracingEnabled: false
        remoteDebuggingEnabled: false
        httpLoggingEnabled: false
        acrUseManagedIdentityCreds: false
        logsDirectorySizeLimit: 35
        detailedErrorLoggingEnabled: false
        publishingUsername: '$test-biceps'
        scmType: 'None'
        use32BitWorkerProcess: true
        webSocketsEnabled: false
        alwaysOn: true
        managedPipelineMode: 'Integrated'
        virtualApplications: [
          {
            virtualPath: '/'
            physicalPath: 'site\\wwwroot'
            preloadEnabled: true
          }
        ]
        loadBalancing: 'LeastRequests'
        experiments: {
          rampUpRules: []
        }
        autoHealEnabled: false
        vnetRouteAllEnabled: false

    È vero, il Bicep decompilato non è tanto meglio, in quanto a leggibilità, rispetto al template ARM dal quale è stato creato, ma con un po’ di lavoro e trial and error è possibile arrivare ad una soluzione accettabile, che riesca a farci comodamente raggiungere l’obiettivo.

    Potrebbe esser peggio… potrebbero esserci delle sovrastrutture

    Potrebbe sembrare, leggendo fino a qui, che i Bicep non siano esattamente la miglior soluzione possibile per avere una IaC esaustiva in ambito Azure, e probabilmente è così. Sia Ansible che Terraform possono essere utilizzati, e sono strumenti sicuramente più completi di Bicep.

    Ma qui probabilmente entra in gioco il vantaggio di Bicep (e per estensione dei template ARM): non servono ulteriori sovrastrutture e/o tecnologie, solo la conoscenza di Azure. Certo, la mancanza di una documentazione centrale e strutturata è un bel problema, ma d’altra parte la conoscenza della piattaforma Cloud che utilizzeremo, da un certo punto di vista, si può considerare un requisito intrinseco.

    Si può migliorare, ma chi visse sperando…

    Ad oggi sembra ancora un linguaggio – anche se la critica credo si possa estendere a tutto l’ecosistema che ruota attorno ai Bicep – lontano dall’essere maturo, seppur tranquillamente utilizzabile con qualche aggiustamento qua e là.

    I Bicep sono sicuramente uno strumento molto utile all’interno dell’ecosistema di Azure, ma ad oggi potrebbe non valere la pena andare oltre ai template ARM scaricabili dopo la creazione di una risorsa con qualche parametro (come il nome della risorsa stessa o poco altro) modificato per allinearlo all’eventuale nuovo ambiente di distribuzione.

    Non resta che sperare nell’espansione e nella centralizzazione della documentazione, unita magari all’ampliamento dell’estensione per VS Code, che vada oltre all’highlight e l’IntelliSense, affinché venga ridotto al minimo il lavoro extra da dover fare per avere un file completo e funzionante.