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.
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
Exécuter le script PowerShell contenant la définition des variables d'environnement créé dans l'étape précédente.
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"])]
}
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 🤗
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. 👇👇👇