Rilasciare una nuova versione con zero down time

Quante volte dopo aver rilasciato un’applicazione web, un sito, un blog, un e-commerce o qualcosa di più complesso si scopre un bug, magari segnalato dagli utenti che stanno utilizzando il portale?

In questi casi talvolta si rende necessario un rapido fix e conseguente rilascio di una nuova versione.

Il punto è proprio questo: non sempre è possibile ripubblicare una nuova versione di un’applicazione web immediatamente, infatti la procedura causa sempre un down time che va da qualche secondo fino a 4-5 minuti in cui il servizio non sarà più online prima di essere nuovamente raggiungibile con la nuova versione.

Come Rilasciare nuove versioni senza down time?

La fortuna è che esiste una soluzione: si chiama Deployment Slot (tenendo in considerazione il cloud di Microsoft Azure).

Molto spesso un piccolo down non è problematico, ma ci sono alcune situazioni in cui questo può essere inaccettabile, poiché magari in quel momento ci sono moltissimi utenti che utilizzano il software.

Questo fa sì che sia necessaria l’esecuzione della pubblicazione in orario notturno o nei giorni festivi…non è il massimo, non trovi?

In particolare, invece di deployare l’applicazione direttamente su un servizio app service, andremo ad utilizzare due deployment slot (production e staging).

Quindi eseguiremo il deploy attraverso una pipeline CI/CD da Azure DevOps Pipelines sullo slot staging, successivamente, al termine del deploy, quando lo slot di staging tornerà raggiungibile online, eseguiremo lo scambio dello slot staging con quello production, questo scambierà il contenuto dei due slot in pochissimo tempo rendendo disponibile la nuova versione dell’app senza interruzioni del servizio.

Qui sopra è riportato lo schema generale dell’infrastruttura necessaria e dell’integrazione con CI-CD.

Consideriamo inoltre che tutte le operazioni necessarie (frecce nell’immagine) verranno eseguite esclusivamente attraverso la pipeline senza intervento umano sull’infrastruttura, pertanto la pubblicazione e il relativo scambio di slot sarà un’operazione totalmente automatizzata.

Infrastruttura Cloud Azure

Per creare la corretta infrastruttura utilizzeremo un comodissimo tool di IaC (infrastructure as code) ovvero Terraform, un meraviglioso strumento che permette di codificare un’intera infrastruttura come se fosse del semplice codice da eseguire per creare o ricreare il tutto.

Di seguito le istruzioni per creare l’infrastruttura su Azure con Terraform

Per prima cosa creiamo una cartella con dentro 3 file (main.tf, variables.tf e variables.tfvars) composti così:

main.tf

#region Terraform configuration
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0.0"
    }
  }
  required_version = ">= 0.14.9"
}

provider "azurerm" {
  features {}
}
#endregion

#region Resource group
resource "azurerm_resource_group" "rg" {
  name     = "rg-${var.name}"
  location = var.region

  tags = {}
}
#endregion

#region Server farms (app service plans)
resource "azurerm_service_plan" "plan" {
  name = "plan-${var.name}"
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
  os_type = var.operatingSystem
  sku_name = var.sku

  tags = {}
}
#endregion

#region App insights
resource "azurerm_application_insights" "app-insights" {
  name = "insights-${var.name}-${var.region}"
  location = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  application_type = "web"
}
#endregion

#region Webapps
resource "azurerm_windows_web_app" "webapp" {
  name = "app-${var.name}-${var.region}"
  resource_group_name = azurerm_resource_group.rg.name
  location = azurerm_resource_group.rg.location
  service_plan_id = azurerm_service_plan.plan.id
  https_only = true

  site_config {
    application_stack {
      current_stack  = try(var.stack, "dotnet")
      dotnet_version = try(var.dotnetVersion, "v6.0")
    }
  }

  app_settings = {
    APPINSIGHTS_INSTRUMENTATIONKEY = azurerm_application_insights.app-insights.instrumentation_key
  }

  tags = {}
}
#endregion

#region Deloyment slots
resource "azurerm_windows_web_app_slot" "slot" {
  name = "staging"
  app_service_id = azurerm_windows_web_app.webapp.id
  
  site_config {
    application_stack {
      current_stack  = try(var.stack, "dotnet")
      dotnet_version = try(var.dotnetVersion, "v6.0")
    }
  }

  app_settings = {
    APPINSIGHTS_INSTRUMENTATIONKEY = azurerm_application_insights.app-insights.instrumentation_key
  }

  tags = {}
}
#endregion
Terraform

variables.tfvars

name = "exampleapp" #project name
region = "westeurope" #region
dotnetVersion = "v6.0" dotnet version
stack = "dotnet" #stack
operatingSystem = "Windows" #operating system
sku = "S1" #B1, B2, B3, D1, F1, I1, I2, I3, I1v2, I2v2, I3v2, I4v2, I5v2, I6v2, P1v2, P2v2, P3v2, P0v3, P1v3, P2v3, P3v3, P1mv3, P2mv3, P3mv3, P4mv3, P5mv3, S1, S2, S3, SHARED, EP1, EP2, EP3, WS1, WS2, WS3, Y1
Terraform

variables.tf

variable "name" {
  type = string
}
variable "region" {
  type = string
}
variable "operatingSystem" {
  type = string
}
variable "dotnetVersion" {
  type = string
}
variable "stack" {
  type = string
}
variable "sku" {
  type = string
}
Terraform

Installiamo Terraform e Azure CLI, (se non è stato già fatto in precedenza), poi spostandoci con il terminale nella cartella appena creata, eseguiamo l’inizializzazione con il comando:

terraform init
Prompt

eseguiamo poi la validazione per testare la bontà del codice sopra con:

terraform validate
Prompt

l’output dovrebbe restituire un check positivo senza alcun errore, successivamente verifichiamo l’esecuzione del codice con i seguenti passaggi:

az login
Prompt

quindi effettuiamo il login su Microsoft Azure e poi eseguiamo:

terraform plan -var-file="variables.tfvars"
Prompt

se il risultato ci soddisfa possiamo eseguire il comando digitando:

terraform apply -var-file="variables.tfvars"
Prompt

e successivamente inseriamo “yes” quando richiesto, questo avvierà la creazione di quanto richiesto su Microsoft Azure e alla fine l’infrastruttura creata sarà simile a quella riportata nell’immagine sopra.

CI / CD

A questo punto manca solo l‘implementazione della pipeline di rilascio continuo.

Iniziamo creando una pipeline che permetta di compilare la nostra applicazione e rilasciare un pacchetto in un file zip (questa procedura differisce in base allo stack e alle tecnologie utilizzate).

Una volta ottenuto l’artefatto da pubblicare, creiamo una pipeline nella sezione release, utilizzando l’artefatto prodotto e creiamo 3 task, ovvero:

1. Deploy Azure App Service

Quindi selezioniamo la nostra app da Azure selezionando la connection type ARM e poi selezioniamo Deploy to Slot or App Service Environment scegliendo poi staging come slot di pubblicazione.

Questo rilascerà le nuove pubblicazioni sullo slot di staging.

2. Wait

Creiamo un task in cui fermiamo la pipeline per 5 minuti, questo darà il tempo allo slot di staging di tornare up e disponibile.

3. Swap slots

Infine creiamo un ultimo task che esegua lo scambio dello slot di produzione con quello di staging, selezioniamo quindi per la voce Source Slot il nostro slot di staging e selezionando Swap with production.

Ecco il codice YAML dei 3 task da inserire nella pipeline di rilascio:

steps:
- task: AzureRmWebAppDeployment@4
  displayName: 'Deploy Azure App Service'
  inputs:
    azureSubscription: '$(Parameters.ConnectedServiceName)'
    appType: '$(Parameters.WebAppKind)'
    WebAppName: '$(Parameters.WebAppName)'
    deployToSlotOrASE: true
    ResourceGroupName: 'rg-exampleapp-dev'
    SlotName: staging
- task: deadlydog.WaitBuildAndReleaseTask.Wait.Wait@1
  displayName: 'Wait for 5 minutes'
  inputs:
    Value: 5
    Unit: minutes
- task: AzureAppServiceManage@0
  displayName: 'Swap Slots: app-exampleapp-dev-westeurope'
  inputs:
    azureSubscription: 'Crionet Azure (4d2e08a3-17bf-4a08-9cbe-4dbe2b2dc570)'
    WebAppName: 'app-exampleapp-dev-westeurope'
    ResourceGroupName: 'rg-exampleapp-dev'
    SourceSlot: staging
YAML

Questo è tutto!

Da adesso in poi ogni pubblicazione avverrà come di consueto ma senza alcun disservizio, mantenendo sempre il servizio disponibile e senza interruzioni.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *