Des environnements de développement locaux avec Terraform + LXD

Des environnements de développement locaux avec Terraform + LXD

Vous appréciez notre travail......nous recrutons !

Ne ratez pas nos articles sur l'open source, le big data et les systèmes distribués, fréquence faible d’un email tous les deux mois.

En tant qu’architecte de solutions Big Data et InfraOps, j’ai besoin d’environnements de développement pour installer et tester des logiciels. Ils doivent être configurables, flexibles et performants. En travaillant avec des systèmes distribués, les setups les plus adaptées à ce cas d’utilisation sont des clusters virtualisés locaux de plusieurs instances Linux.

Depuis quelques années, j’utilise Vagrant de HashiCorp pour gérer des instances libvirt/KVM. Cela fonctionne assez bien, mais j’ai récemment expérimenté une autre option qui fonctionne mieux pour moi : LXD pour gérer les instances et Terraform (un autre outil HashiCorp) pour piloter LXD. Dans cet article, j’explique quels en sont les avantages et comment mettre en place un tel environnement.

Glossaire

Vagrant et Terraform

Vagrant permet aux utilisateurs de créer et de configurer des environnements de développement légers, reproductibles et portables. Il est principalement utilisé pour provisionner des machines virtuelles localement.

Terraform est un outil d’Infrastructure as Code largement utilisé qui permet de provisionner des ressources sur pratiquement n’importe quel cloud. Il prend en charge de nombreux fournisseurs, du cloud public (AWS, Azure, GCP) à l’infrastructure privée auto-hébergée (OpenStack, Kubernetes et LXD bien sûr). Avec Terraform, les équipes InfraOps appliquent les meilleures pratiques GitOps pour gérer leur infrastructure.

Virtualisation/conteneurisation Linux

Voici une revue rapide des différents outils (et acronymes) utilisés dans cet article qui composent le vaste écosystème de virtualisation/conteneurisation Linux :

  • Outils de machines virtuelles :

    • QEMU (Quick EMUlator) : un outil d’émulation et de virtualisation qui fait office d’hyperviseur de VM, à la place de Virtualbox par exemple.
    • KVM (Kernel Virtual Machine) : un module du noyau Linux qui exploite la virtualisation matérielle, notamment les fonctionnalités spéciales du processeur spécialement conçues pour la virtualisation (par exemple, Intel VT).
    • libvirt : une API de virtualisation qui supporte plusieurs hyperviseurs de VM et simplifie la gestion des VM depuis n’importe quel langage de programmation.
  • Outils de conteneurs Linux (voir LXD : la pièce manquante pour en savoir plus sur les conteneurs Linux) :

  • LXC (LinuX Containers) : une interface pour créer et gérer des conteneurs système ou applicatif

  • LXD (LinuX container Daemon) : gestionnaire de VM et de conteneurs système qui offre une expérience utilisateur unifiée autour de systèmes Linux complets. Il fonctionne au-dessus de LXC (pour les conteneurs) et de QEMU (pour les machines virtuelles) pour gérer les machines hébergées par un hôte individuel ou par un cluster d’hôtes fédérés.

  • LXC (the other one) : la CLI pour LXD

Utiliser Vagrant pour gérer des VMs KVM est réalisé via le provider vagrant-libvirt. Voir Machines KVM pour Vagrant sur Archlinux pour savoir comment configurer libvirt/KVM avec Vagrant.

Pourquoi Terraform ?

LXD se pilote en CLI avec la command lxc pour gérer ses ressources (conteneurs et VM, réseaux, pools de stockage, profils d’instance). Étant un outil en ligne de commande, il n’est par nature pas compatible avec Git.

Heureusement, il existe un fournisseur Terraform pour gérer LXD : terraform-provider-lxd. Cela permet de versionner la configuration de l’infrastructure LXD parallèlement au code d’une application.

Remarque : Un autre outil pour piloter LXD pourrait être Juju de Canonical, mais il semble un peu plus complexe à apprendre.

Pourquoi Terraform + LXD ? Avantage par rapport à Vagrant + libvirt/KVM

Redimensionnement en live des instances

Les conteneurs Linux sont plus flexibles que des VMs, ce qui permet de redimensionner les instances sans redémarrage. C’est une fonctionnalité très pratique.

Outillage unifié du développement à la production

LXD peut être installé sur plusieurs hôtes pour créer un cluster qui peut être utilisé comme couche de base d’un cloud auto-hébergé. Le couple Terraform + LXD permet ainsi de gérer des environnements locaux, d’intégration et de production. Cela facilite considérablement le test et le déploiement des configurations d’infrastructure.

Prise en charge de LXD dans Ansible

Pour installer et configurer des logiciels sur des instances locales, j’utilise souvent Ansible. Il existe plusieurs connection plugins disponibles dans Ansible pour se connecter aux hôtes cibles, le principal étant ssh.

Lors du provisionnement des instances LXC, nous pouvons utiliser le plugin ssh standard mais aussi un plugin LXC natif : lxc (qui utilise la bibliothèque Python LXC) ou lxd (qui utilise la CLI LXC). Ceci est bénéfique pour deux raisons :

  • Pour la sécurité car nous n’avons pas besoin de démarrer un serveur OpenSSH et d’ouvrir le port SSH sur nos instances
  • Pour plus de simplicité car nous n’avons pas à gérer les clés SSH pour Ansible

Aperçu des changements de configuration

L’une des principales fonctionnalités de Terraform est la possibilité de prévisualiser les modifications qu’une commande appliquerait. Cela évite les déploiements de configuration indésirables et les erreurs de commande.

Exemple avec le redimensionnement d’un profil d’instance LXD :

$ terraform plan
...
Terraform will perform the following actions:

  # lxd_profile.tdp_profiles["tdp_edge"] will be updated in-place
  ~ resource "lxd_profile" "tdp_profiles" {
      ~ config = {
          ~ "limits.cpu"    = "1" -> "2"
          ~ "limits.memory" = "1GiB" -> "2GiB"
        }
        id     = "tdp_edge"
        name   = "tdp_edge"
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Lisibilité et modularité de la configuration

Le langage Terraform est déclaratif. Il décrit un objectif visé plutôt que les étapes pour atteindre cet objectif. En tant que tel, il est plus lisible que le langage Ruby utilisé dans les fichiers Vagrant. De plus, comme Terraform analyse tous les fichiers du répertoire actuel et permet de définir des modules avec des inputs et des outputs, nous pouvons très facilement diviser la configuration pour augmenter la maintenabilité.

# Using multiple Terraform config files:
$ ls -1 | grep -P '.tf(vars)?$'
local.auto.tfvars
main.tf
outputs.tf
provider.tf
terraform.tfvars
variables.tf

Gain de performance

L’utilisation de Terraform + LXD accélère les opérations quotidiennes dans les environnements de développement locaux, ce qui est toujours agréable.

Voici un benchmark des performances lors de l’exploitation d’un cluster de développement local avec les spécifications suivantes :

  • OS hôte : Ubuntu 20.04
  • Nombre d’instances guest : 7
  • Ressources allouées : 24GiB de RAM et 24 vCPUs
MétriqueVagrant + libvirt/KVMTerraform + LXDGain de performance
Création du cluster (sec)56.5511.1x faster
Démarrage du cluster (sec)36.566x faster
Arrêt du cluster (sec)4613.53.4x faster
Destruction du cluster (sec)9172x slower

Mise en place d’un environnement Terraform + LXD minimal

Essayons maintenant de configurer un environnement Terraform + LXD minimal.

Prérequis

Votre ordinateur à besoin de :

  • LXD (voir Installation)
  • Terraform >= 0.13 (voir Install Terraform)
  • Linux cgroup v2 (pour faire tourner des conteneurs Linux récents comme Rocky 8)
  • 5 GB de RAM disponible

Créez également un répertoire à partir duquel travailler :

mkdir terraform-lxd-xs
cd terraform-lxd-xs

Linux cgroup v2

Pour vérifier si votre système utilise cgroup v2, exécutez :

stat -fc %T /sys/fs/cgroup
# cgroup2fs => cgroup v2
# tmpfs     => cgroup v1

Les distributions récentes utilisent cgroup v2 par défaut (consultez la liste ici) mais la fonctionnalité est disponible sur tous les hôtes qui exécutent un noyau Linux >= 5.2 (par exemple Ubuntu 20.04). Pour l’activer, consultez Enabling cgroup v2.

Le provider Terraform

Nous utiliserons le provider Terraform terraform-lxd/lxd pour gérer nos ressources LXD.

Créez provider.tf :

terraform {
  required_providers {
    lxd = {
      source  = "terraform-lxd/lxd"
      version = "1.7.1"
    }
  }
}

provider "lxd" {
  generate_client_certificates = true
  accept_remote_certificate    = true
}

Définition des variables

Il est recommandé de permettre aux utilisateurs de configurer l’environnement Terraform via des input variables. Nous forçons l’exactitude des variables en déclarant leurs types attendus.

Créez variables.tf :

variable "xs_storage_pool" {
  type = object({
    name   = string
    source = string
  })
}

variable "xs_network" {
  type = object({
    ipv4 = object({
      address = string
    })
  })
}

variable "xs_profiles" {
  type = list(object({
    name = string
    limits = object({
      cpu    = number
      memory = string
    })
  }))
}

variable "xs_image" {
  type    = string
  default = "images:rocky/8"
}

variable "xs_containers" {
  type = list(object({
    name    = string
    profile = string
    ip      = string
  }))
}

Les variables suivantes sont définies :

  • xs_storage_pool : la storage pool LXD qui stocke les disques des conteneurs
  • xs_network : le réseau IPv4 LXD utilisé par les conteneurs pour communique au sein d’un réseau partagé
  • xs_profiles : les profiles LXD créés pour nos conteneurs. Les profiles permettent la définition d’un ensemble de propriétés qui peuvent être appliqué à n’importe quel conteneur.
  • xs_image : l’image LXD. Cela définie principallement quel OS les conteneurs utilisent.
  • xs_containers : Les instances LXD à créer.

Main

Le fichier Terraform main définit toutes les ressources configurées par les variables. Ce fichier n’est pas modifié très souvent par les développeurs après sa première implémentation pour le projet.

Créez main.tf :

# Storage pools
resource "lxd_storage_pool" "xs_storage_pool" {
  name = var.xs_storage_pool.name
  driver = "dir"
  config = {
    source = "${path.cwd}/${path.module}/${var.xs_storage_pool.source}"
  }
}

# Networks
resource "lxd_network" "xs_network" {
  name = "xsbr0"

  config = {
    "ipv4.address" = var.xs_network.ipv4.address
    "ipv4.nat"     = "true"
    "ipv6.address" = "none"
  }
}

# Profiles
resource "lxd_profile" "xs_profiles" {
  depends_on = [
    lxd_storage_pool.xs_storage_pool
  ]

  for_each = {
    for index, profile in var.xs_profiles :
    profile.name => profile.limits
  }

  name = each.key

  config = {
    "boot.autostart" = false
    "limits.cpu"    = each.value.cpu
    "limits.memory" = each.value.memory
  }

  device {
    type = "disk"
    name = "root"

    properties = {
      pool = var.xs_storage_pool.name
      path = "/"
    }
  }
}

# Containers
resource "lxd_container" "xs_containers" {
  depends_on = [
    lxd_network.xs_network,
    lxd_profile.xs_profiles
  ]

  for_each = {
    for index, container in var.xs_containers :
    container.name => container
  }

  name  = each.key
  image = var.xs_image
  profiles = [
    each.value.profile
  ]

  device {
    name = "eth0"
    type = "nic"
    properties = {
      network        = lxd_network.xs_network.name
      "ipv4.address" = "${each.value.ip}"
    }
  }
}

Les ressources suivantes sont créées par Terraform :

  • lxd_network.xs_network : le réseau pour toutes nos instances
  • lxd_profile.xs_profiles : plusieurs profils pouvant être définis par l’utilisateur
  • lxd_container.xs_containers : les définitions des instances (y compris l’application du profil et l’attachement du périphérique réseau)

Fichier de variables

Enfin, nous fournissons à Terraform les variables propres à notre environnement. Nous utilisons l’extension auto.tfvars pour charger automatiquement les variables lors de l’exécution de terraform.

Créez local.auto.tfvars :

xs_storage_pool = {
  name = "xs_storage_pool"
  source = "lxd-xs-pool"
}

xs_network = {
  ipv4 = { address = "192.168.42.1/24" }
}

xs_profiles = [
  {
    name = "xs_master"
    limits = {
      cpu    = 1
      memory = "1GiB"
    }
  },
  {
    name = "xs_worker"
    limits = {
      cpu    = 2
      memory = "2GiB"
    }
  }
]

xs_image = "images:rockylinux/8"

xs_containers = [
  {
    name    = "xs-master-01"
    profile = "xs_master"
    ip      = "192.168.42.11"
  },
  {
    name    = "xs-master-02"
    profile = "xs_master"
    ip      = "192.168.42.12"
  },
  {
    name    = "xs-worker-01"
    profile = "xs_worker"
    ip      = "192.168.42.21"
  },
  {
    name    = "xs-worker-02"
    profile = "xs_worker"
    ip      = "192.168.42.22"
  },
  {
    name    = "xs-worker-03"
    profile = "xs_worker"
    ip      = "192.168.42.23"
  }
]

Provisionnement de l’environment

Nous avons maintenant tous les fichiers nécessaires pour provisionner notre environnement :

# Install the provider
terraform init

# Create the directory for the storage pool
mkdir lxd-xs-pool

# Preview and apply the resource changes
terraform apply

Une fois les ressources créées, nous pouvons vérifier que tout fonctionne correctement :

# Check resources existance in LXD
lxc network list
lxc profile list
lxc list

# Connect to an instance
lxc shell xs-master-01

Et voilà !

Note, pour détruire l’environment : terraform destroy

Exemple plus avancé

Vous pouvez jeter un œil à tdp-lxd pour une configuration plus avancée avec :

  • Plus de profils
  • Du templating de fichier (pour un inventaire Ansible)
  • La définition des outputs

Conclusion

La combinaison de Terraform et LXD apporte une nouvelle façon de gérer les environnements de développement locaux qui présente plusieurs avantages par rapport aux concurrents (à savoir Vagrant). Si vous utilisez souvent ce type d’environnement, je vous suggère de l’essayer !

Partagez cet article

Canada - Maroc - France

Nous sommes une équipe passionnée par l'Open Source, le Big Data et les technologies associées telles que le Cloud, le Data Engineering, la Data Science le DevOps…

Nous fournissons à nos clients un savoir faire reconnu sur la manière d'utiliser les technologies pour convertir leurs cas d'usage en projets exploités en production, sur la façon de réduire les coûts et d'accélérer les livraisons de nouvelles fonctionnalités.

Si vous appréciez la qualité de nos publications, nous vous invitons à nous contacter en vue de coopérer ensemble.

Support Ukrain