Déployer les diagnostic settings sur vos ressources Azure via un module Terraform

Déployer les diagnostic settings sur vos ressources Azure via un module Terraform

Voici un résumé de ma réflexion jusqu'à la construction finale d'un module Terraform pour couvrir ce besoin.

Après différents échanges avec des amis travaillant sur ce sujet Azure, je me suis penché dessus pour réaliser une solution la plus générique possible pour éviter les actions d'identifications manuelles.

Introduction

La surveillance et la gestion des ressources dans Microsoft Azure sont essentielles pour garantir la performance, la disponibilité et la sécurité de vos applications et services cloud. Une façon efficace d'y parvenir est d'utiliser les paramètres de diagnostic, qui collectent et envoient des données de journal et de métriques à Azure Monitor. Cela permet une analyse approfondie de l'état de vos ressources. Dans cette présentation, nous explorerons comment déployer facilement ces paramètres de diagnostic sur vos ressources Azure en utilisant le puissant outil d'automatisation, Terraform.

Déploiement des ressources Azure via Terraform

Terraform et Azure

Terraform est une plateforme d'infrastructure as code (IaC) qui vous permet de décrire et de provisionner des ressources cloud de manière reproductible.
Azure, quant à lui, est l'un des principaux fournisseurs de services cloud offrant une vaste gamme de solutions.

Le déploiement de paramètres de diagnostic via un module Terraform offre plusieurs avantages. Il permet une gestion cohérente et reproductible de la surveillance de vos ressources Azure, facilite la collaboration au sein de votre équipe grâce à un code IaC partageable, assure une configuration précise et automatisée des paramètres de diagnostic. Voyons maintenant comment écrire ce module Terraform.

Création d'un SPN de déploiement

Pour le déploiement des ressources Azure, créez un SPN avec le rôle RBAC "Contributor" sur une souscription.
Pour réaliser cette action, vous pouvez utilisez ce script Azure CLI :

roleName="Contributor"
subscriptionID=$(az account show --query id -o tsv)
# Verify the ID of the active subscription
echo "Using subscription ID $subscriptionID"

echo "Creating SP for RBAC with name $servicePrincipalName, with role $roleName and in scopes /subscriptions/$subscriptionID"
az ad sp create-for-rbac --name $servicePrincipalName --role $roleName --scopes /subscriptions/$subscriptionID

Récupérez les informations fournies à l'éxécution de ces commandes et créer un script de connexion en PowerShell tel que l'exemple suivant en mettant les à jour les valeurs :

$env:ARM_CLIENT_ID = "2b0f3f7e-df91-415c-a5f7-a572d0626c02"
$env:ARM_CLIENT_SECRET = ".YTK68K{(.mm|4q#^.%ew%`@gJG4o*"
$env:ARM_TENANT_ID = "a67cb066-c327-43e3-ac4b-b632dc44130c"
$env:ARM_SUBSCRIPTION_ID = "5dcaca59-2e7d-4648-adcd-661a0cca1bd3"

Les informations présentes dans cet exemple sont des valeurs aléatoires et ne vous permettrons pas de vous connectez sur ma souscription. 😁

Ce script vous permettra de définir les variables d'environnements nécessaires pour l'éxécution de vos scripts Terraform.

💡
Pour plus de détails, voici un lien vers la documentation officielle: Configuring the Service Principal in Terraform

Création de l'Infrastructure as Code

Prenons un exemple assez simple d'application avec un site web et un storage qui pourrait être décrit simplement en terraform via ce script que vous pouvez appeler "main.tf" :

resource "azurerm_resource_group" "rg" {
  name     = "example-diagnostics"
  location = "West Europe"
}

resource "azurerm_storage_account" "sa" {
  name                     = "aludevpocdiagnostics"
  resource_group_name      = azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  account_tier             = "Standard"
  account_replication_type = "GRS"

  tags = {
    environment = "dev"
  }
}

resource "azurerm_service_plan" "plan" {
  name                = "linux_plan"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  os_type             = "Linux"
  sku_name            = "F1"
}

resource "azurerm_linux_web_app" "linux_web_app" {
  name                = "alupoclinuxwebappdiag"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_service_plan.plan.location
  service_plan_id     = azurerm_service_plan.plan.id

  site_config {
    always_on         = false
    use_32_bit_worker = true
  }
}

resource "azurerm_log_analytics_workspace" "monitor" {
  name                = "log-analytics-workspace"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  sku                 = "PerGB2018"
}

Vous pouvez y ajouter un fichier "config.tf" :

provider "azurerm" {
  // More information on the authentication methods supported by
  // the AzureRM Provider can be found here:
  // https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
  skip_provider_registration = true
  features {
    // https://github.com/hashicorp/terraform-provider-azurerm/issues/8968
    key_vault {
      purge_soft_delete_on_destroy = false
    }
  }
}

terraform {
  required_version = "1.5.5"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "=3.71.0"
    }
  }

  backend "local" {}
}

Déploiement des ressources

  1. Exécuter le script PowerShell contenant la définition des variables d'environnement créé dans l'étape précédente.

  2. Déploier l'IaC Terraform en utilisant dans l'ordre les commandes suivantes:

terraform init --upgrade
terrfaform apply --auto-approve

Attendez la fin de l'éxécution:

Puis ouvez le portail Azure et rendez-vous sur votre souscription cible dans lequel vous devez avoir ces 4 ressources Azure:

Prenez par exemple l'application web (App Service) et rendez-vous sur le paramétre des diagnostic settings:

Comme vous pouvez le voir, pour l'instant rien n'est configuré pour l'instant et nous pouvons identifier les listes des Logs et Metrics associés à ce service:

  • HTTP logs

  • App Service Console Logs

  • App Service Application Logs

  • Access Audit Logs

  • IPSecurity Audit logs

  • App Service Platform logs

  • AllMetrics

Voyons maintenant ensemble dans le prochain chapitre comment configurer ses diagnostic settings en Terraform.

Déploiement des diagnostic settings Azure via Terraform

Sans réflexion

Via le provider AzureRM nous pouvons définir la configuration des diagnostic settings en utilisant la ressource "azurerm_monitor_diagnostic_setting".

Si je souhaite mettre en place les diagnostic settings pour mon site web, voici ce que je devrais écrire si je le fais de façon standard:


resource "azurerm_monitor_diagnostic_setting" "web_app" {
  name                       = split("/", azurerm_log_analytics_workspace.monitor.id)[length(split("/", azurerm_log_analytics_workspace.monitor.id)) - 1]
  target_resource_id         = data.azurerm_monitor_diagnostic_categories.categories[count.index].id
  log_analytics_workspace_id = azurerm_log_analytics_workspace.monitor.id

  metric {
    category = "AllMetrics"
    enabled  = true
  }

  enabled_log {
    category = "AppServiceConsoleLogs"
  }

  enabled_log {
    category = "AppServiceEnvironmentPlatformLogs"
  }

  enabled_log {
    category = "AppServiceAuditLogs"
  }

  enabled_log {
    category = "AppServiceFileAuditLogs"
  }

  enabled_log {
    category = "AppServiceAppLogs"
  }

  enabled_log {
    category = "AppServiceIPSecAuditLogs"
  }

  enabled_log {
    category = "AppServicePlatformLogs"
  }
}

En voyant ce code on pourrait se dire d'optimiser le code en passant par une liste des catégories de logs disponibles.

Plus de réflexion

Du coup, j'ajoute une liste de l'ensemble des logs disponibles sous la forme d'une variable locale:

locals {
  web_app_logs = ["AppServiceConsoleLogs",
                  "AppServiceEnvironmentPlatformLogs",
                  "AppServiceAuditLogs",
                  "AppServiceFileAuditLogs",
                  "AppServiceAppLogs",
                  "AppServiceIPSecAuditLogs",
                  "AppServicePlatformLogs"]
}

Et je modifie mon code précédent pour boucler dessus:


resource "azurerm_monitor_diagnostic_setting" "web_app" {
  name                       = split("/", azurerm_log_analytics_workspace.monitor.id)[length(split("/", azurerm_log_analytics_workspace.monitor.id)) - 1]
  target_resource_id         = data.azurerm_monitor_diagnostic_categories.categories[count.index].id
  log_analytics_workspace_id = azurerm_log_analytics_workspace.monitor.id

  metric {
    category = "AllMetrics"
    enabled  = true
  }

  dynamic "enabled_log" {
    for_each = local.web_app_logs
    content {
      category = enabled_log.value
    }
  }
}

C'est mieux, mais maintenant que je suis à ce niveau je trouve cette solution problèmatique car:

  • Je dois identifier en amont l'ensemble des Logs et Metrics disponible sur tous mes services Azure : Travail long et fastidieux.

  • Si Microsoft fait évoluer la liste des logs ou métriques disponibles sur un de mes services Azure utilisée dans ma solution: Cela me génére une dette technique.

Optimisation du code

Suite au précédent chapitre, j'ai cherché une solution plus propre qui me permettrait de récupérer la liste des logs et métrics disponibles sur chacun de mes services mais de façon dynamique.

Et j'ai trouvé mon graal en parcourant la documentation de notre provider préféré :

En utilisant cette source de données, je vais pouvoir trouver pour ma ressource Azure l'ensemble des métriques et logs associés.

Pour cela, rien de plus simple, voici comment faire en Terraform:

data "azurerm_monitor_diagnostic_categories" "web_app" {
  resource_id = azurerm_linux_web_app.linux_web_app.id
}

resource "azurerm_monitor_diagnostic_setting" "web_app" {
  name                       = split("/", azurerm_log_analytics_workspace.monitor.id)[length(split("/", azurerm_log_analytics_workspace.monitor.id)) - 1]
  target_resource_id         = data.azurerm_monitor_diagnostic_categories.categories[count.index].id
  log_analytics_workspace_id = azurerm_log_analytics_workspace.monitor.id

  dynamic "metric" {
    for_each = data.azurerm_monitor_diagnostic_categories.web_app.metrics
    content {
      category = metric.value
      enabled  = true
    }
  }

  dynamic "enabled_log" {
    for_each = data.azurerm_monitor_diagnostic_categories.web_app.log_category_types
    content {
      category = enabled_log.value
    }
  }
}

Avec cette solution implémenté pour mon service web app, je me dis maintenant que je peux généraliser cette implémentation pour n'importe quel service Azure en créant un module Terraform.

Voyons comment faire ça ensemble sur le prochain chapitre.

Générique via un module

Maintenant attaquons ensemble le vrai chapitre de cet article 🤓

Pour réaliser un module, commençons par ajouter l'arborescence suivante:

modules/diagnostics

Puis ajoutons les fichiers nécessaires:

modules/diagnostics/main.tf
modules/diagnostics/variables.tf

Commençons par le fichier variables.tf dans lequel nous allons définir 2 entrées :

  • targets_resource_id: Une liste des ressources ID des services Azure pour lesquels vous souhaitez configurer les diagnostics settings.

  • log_analytics_workspace_id: L'ID du workspace log analytics qui nous permettra de centraliser les logs et métriques de nos services applicatifs.

variable "targets_resource_id" {
  description = "(Required) The list of ID of an existing Resource on which to configure Diagnostic Settings. Changing this forces a new resource to be created."
}

variable "log_analytics_workspace_id" {
  description = "(Required) Specifies the ID of a Log Analytics Workspace where Diagnostics Data should be sent."
}

Et enfin éditons le fichier "main.tf" pour y ajouter l'intelligence de notre code IaC générique :

data "azurerm_monitor_diagnostic_categories" "categories" {
  count       = length(var.targets_resource_id)
  resource_id = var.targets_resource_id[count.index]
}

resource "azurerm_monitor_diagnostic_setting" "diagnostic_setting" {
  count                      = length(var.targets_resource_id)
  name                       = split("/", var.log_analytics_workspace_id)[length(split("/", var.log_analytics_workspace_id)) - 1]
  target_resource_id         = data.azurerm_monitor_diagnostic_categories.categories[count.index].id
  log_analytics_workspace_id = var.log_analytics_workspace_id

  dynamic "metric" {
    for_each = data.azurerm_monitor_diagnostic_categories.categories[count.index].metrics
    content {
      category = metric.value
      enabled  = true
    }
  }

  dynamic "enabled_log" {
    for_each = data.azurerm_monitor_diagnostic_categories.categories[count.index].log_category_types
    content {
      category = enabled_log.value
    }
  }
}

Ajoutons maintenant un nouveau fichier à notre solution "diagnostics.tf" pour faire appel à ce module :

module "sa_diag" {
  source                     = "./modules/diagnostics"
  log_analytics_workspace_id = azurerm_log_analytics_workspace.monitor.id
  targets_resource_id = [azurerm_log_analytics_workspace.monitor.id,
    azurerm_service_plan.plan.id,
    azurerm_linux_web_app.linux_web_app.id,
    azurerm_storage_account.sa.id,
    join("", [azurerm_storage_account.sa.id, "/blobServices/default"]),
    join("", [azurerm_storage_account.sa.id, "/queueServices/default"]),
    join("", [azurerm_storage_account.sa.id, "/tableServices/default"]),
    join("", [azurerm_storage_account.sa.id, "/fileServices/default"])]
}
💡
Pour la partie storage; vous pouvez identifier l'astuce pour configurer l'ensemble des options : blob, queue, table et file.

Il suffit d'ajouter à votre ressource ID du storage, les 4 endpoints suivants:

  • /blobServices/default

  • /queueServices/default

  • /tableServices/default

  • /fileServices/default

Et voilà une solution simplifiée pour un sujet un peu trop verbeux au début 🤗

Suppression des ressources déployées

Pour éviter les coûts associés à notre test n'oubliez pas de supprimer les ressources Azure en utilisant la commande suivante :

terraform destroy --auto-approve

Et attendez sagement la fin de l'exécution 🐱‍🏍

Et voilà, vous venez de gagner un peu d'argent et pas mal de connaissances abordées dans cet article 💪

Conclusion

En conclusion, nous avons appris ici que nous pouvions réaliser un module générique qui nous simplifie la vie et qui peut nous permettre de gérer l'ensemble de nos ressources sans forcément identifier et écrire de façon unitaire chaque configuration possible.

C'est à la fois un gain de temps mais aussi une réduction de la dette technique car si Microsoft met à jour ces options et propose par exemple un nouveau type de log associé à un service celui-ci pourra être automatiquement intégré au prochain déploiement de notre solution.

Pour ne pas réécrire tout cela; je vous donne bien sûr un lien vers un repository publique pour récupérer ces scripts qui pourront vous servir de base pour peut-être le réexploiter dans vos solutions 🤗

💡
L'url du précieux repository de ce module Terraform sur GitHub : https://github.com/aloizeau/TF_Module_Diagnostics

N'hésitez pas à me faire un retour ou de me poser des questions via LinkedIn ou bien directement ici en bas de la page. 👇👇👇

Liens utiles

Did you find this article valuable?

Support Antoine LOIZEAU by becoming a sponsor. Any amount is appreciated!