L’objectif de cette partie est de déployer un conteneur LXC contenant Ubuntu 24.04 LTS depuis notre machine de déploiement (qui est elle même un conteneur LXC).
Prérequis :
- Notre machine de déploiement existe et nous y avons accès en SSH
- L’outil
terraform
est accessible en ligne de commande - Nous avons un token d’API Proxmox VE nous permettant de créer et d’administrer des conteneurs LXC
- Nous avons un dossier pour notre projet (
~/homelab_julia_pve_tf_ansible
) contenant un fichier.envrc
avec notemment le token d’API Proxmox VE précédemment décrit
Génération de clés
Nous aurons besoin de nous connecter (à l’aide de SSH) depuis la machine de déploiement aux conteneurs LXC que nous allons créer. Aussi il est nécessaire de créer sur cette machine des clés.
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ ssh-keygen
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/deployer/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/deployer/.ssh/id_ed25519
Your public key has been saved in /home/deployer/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:JyfdvtDMzpfSFyYoTysLqVAIUT3accIFG6IeV3u+PRA deployer@ubuntu-deploy
The key's randomart image is:
+--[ED25519 256]--+
| ..oo+o. |
| o o*+. |
| + .oo=E |
|. +...o .. . |
| . . . oS + o |
| . =* B . o |
| . + o= *.o..|
| . . ...*..+ .|
| . .o +o . |
+----[SHA256]-----+
Créations des fichiers Terraform
Nous allons créer notre premier projet Terraform à l’aide de 3 fichiers.
main.tf
variables.tf
terraform.tfvars
L’organisation d’un projet Terraform en trois fichiers distincts - main.tf
, variables.tf
et terraform.tfvars
- reflète une pratique éprouvée d’organisation et de séparation des responsabilités dans le code infrastructure. Cette structure suit le principe de séparation des préoccupations, où main.tf contient la logique principale de l’infrastructure (définition des ressources, providers et leurs configurations), variables.tf déclare et documente toutes les variables utilisables (avec leurs types, descriptions et contraintes éventuelles), et terraform.tfvars stocke les valeurs concrètes de ces variables. Cette séparation offre plusieurs avantages : elle améliore la sécurité en permettant d’isoler les informations sensibles dans terraform.tfvars (qui peut être exclu du contrôle de version), facilite la réutilisation du code en permettant différentes configurations pour différents environnements, et augmente la maintenabilité en organisant clairement le code. Cette approche modulaire permet également une meilleure collaboration au sein des équipes, car chaque fichier a un rôle bien défini et peut être maintenu indépendamment. Elle suit les meilleures pratiques de l’Infrastructure as Code en rendant le code plus lisible, plus facile à maintenir et plus sécurisé.
Commençons par créer notre fichier main.tf
(je l’ai personnellement fait dans VS Code, après m’être connecté en SSH et après avoir installé l’extension Terraform pour VS Code)
main.tf
Code
Voici le contenu du fichier main.tf
# Configuration du provider Terraform
terraform {
required_providers {
# Utilisation du provider Proxmox de BPG
proxmox = {
source = "bpg/proxmox"
version = "0.42.0"
}
}
}
# Configuration du fournisseur Proxmox
provider "proxmox" {
# URL de l'API Proxmox
endpoint = var.proxmox_endpoint
# Token d'authentification (à définir dans les variables)
api_token = var.api_token
# Désactive la vérification SSL pour les environnements de test
insecure = true
# Configuration SSH pour l'accès root
ssh {
agent = true
username = "root"
}
}
# Définition des conteneurs LXC
resource "proxmox_virtual_environment_container" "lxc_container" {
# Nombre de conteneurs à créer
count = var.container_count
# Description pour l'identification dans Proxmox
description = "Managed by Terraform"
# Nœud Proxmox cible
node_name = var.target_node
# ID de la VM (incrémenté pour chaque conteneur)
vm_id = 241201 + count.index
# Ajout de tags au conteneur
# Les tags peuvent être utilisés pour identifier et organiser les conteneurs
# Plusieurs tags peuvent être ajoutés, séparés par des points-virgules
tags = [
"calcul-distribue", # Tag pour identifier le type d'usage
"julia-worker-${count.index + 1}", # Tag unique pour chaque worker
"terraform-managed" # Tag indiquant la gestion par Terraform
]
# Configuration initiale du conteneur
initialization {
# Nom d'hôte avec index
hostname = "${var.vm_hostname}-${count.index + 1}"
# Configuration IP
ip_config {
ipv4 {
# Attribution d'une IP statique dans la plage 192.168.1.201+
address = "192.168.1.${201 + count.index}/24"
gateway = var.gateway
}
}
# Configuration utilisateur avec clé SSH
user_account {
keys = var.ssh_public_keys
}
}
# Configuration réseau
network_interface {
name = "eth0"
bridge = "vmbr0"
}
# Système d'exploitation
operating_system {
template_file_id = var.template_file_id
type = "ubuntu"
}
# Ressources CPU
cpu {
cores = var.cores
}
# Allocation mémoire
memory {
dedicated = var.memory
}
# Configuration stockage
disk {
datastore_id = var.disk.storage
size = var.disk.size
}
# Fonctionnalités avancées
features {
nesting = true
}
# Démarrage automatique
start_on_boot = var.onboot
# Mode non privilégié pour plus de sécurité
unprivileged = true
}
Explications
Le fichier se divise en trois sections principales :
- Configuration du provider Terraform
- Configuration du fournisseur Proxmox
- Définition des ressources (conteneurs LXC)
Il permet de créer count
(=1 ici) conteneurs LXC dont les adresses IP commencent à 192.168.1.200
variables.tf
Voici le contenu de variables.tf
# Token d'API pour l'authentification Proxmox
# À définir via une variable d'environnement ou un fichier tfvars
variable "api_token" {
description = "Token pour la connexion à l'API Proxmox"
type = string
sensitive = true
}
# Endpoint de l'API Proxmox
variable "proxmox_endpoint" {
description = "URL de l'API Proxmox (exemple: https://192.168.1.7:8006/)"
type = string
validation {
condition = can(regex("^https://.*:\\d+/$", var.proxmox_endpoint))
error_message = "L'endpoint doit être une URL HTTPS valide se terminant par un port et un slash (exemple: https://192.168.1.7:8006/)"
}
}
# Nombre de conteneurs à créer
variable "container_count" {
description = "Nombre de conteneurs LXC à créer"
type = number
default = 1
validation {
condition = var.container_count >= 0 && var.container_count <= 10
error_message = "Le nombre de conteneurs doit être entre 0 et 10."
}
}
# Nœud Proxmox cible pour le déploiement
variable "target_node" {
description = "Nom du nœud Proxmox pour le déploiement"
type = string
default = "pve"
}
# Nom de base pour les conteneurs
variable "vm_hostname" {
description = "Préfixe de nom d'hôte pour les conteneurs"
type = string
default = "lxc-ubuntu"
validation {
condition = length(var.vm_hostname) > 2 && length(var.vm_hostname) <= 63
error_message = "Le nom d'hôte doit faire entre 3 et 63 caractères."
}
}
# Template du conteneur à utiliser
variable "template_file_id" {
description = "ID du template de conteneur à utiliser"
type = string
default = "local:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst"
}
# Configuration du processeur
variable "cores" {
description = "Nombre de cœurs CPU à allouer"
type = number
default = 2
validation {
condition = var.cores > 0 && var.cores <= 8
error_message = "Le nombre de cœurs doit être entre 1 et 8."
}
}
# Configuration de la mémoire
variable "memory" {
description = "Quantité de mémoire en MB"
type = number
default = 2048
validation {
condition = var.memory >= 512 && var.memory <= 16384
error_message = "La mémoire doit être entre 512 MB et 16 GB."
}
}
# Configuration du disque
variable "disk" {
description = "Configuration du stockage"
type = object({
storage = string
size = string
})
default = {
storage = "local-lvm"
size = "20" # Taille en GB sans le suffixe
}
validation {
condition = can(tonumber(var.disk.size))
error_message = "La taille du disque doit être un nombre (en GB) sans suffixe."
}
}
# Configuration du démarrage automatique
variable "onboot" {
description = "Démarrer le conteneur au boot du système"
type = bool
default = true
}
# Configuration réseau
variable "gateway" {
description = "Passerelle réseau par défaut"
type = string
default = "192.168.1.1"
validation {
condition = can(regex("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$", var.gateway))
error_message = "L'adresse de la passerelle doit être une IPv4 valide."
}
}
# Serveurs DNS
variable "dns_servers" {
description = "Liste des serveurs DNS"
type = list(string)
default = ["192.168.1.1", "1.1.1.1"]
validation {
condition = length(var.dns_servers) > 0
error_message = "Au moins un serveur DNS doit être spécifié."
}
}
# Clé SSH publique pour l'accès aux conteneurs
variable "ssh_public_keys" {
description = "Liste des clés SSH publiques pour l'accès aux conteneurs"
type = list(string)
validation {
condition = length(var.ssh_public_keys) > 0
error_message = "Au moins une clé SSH publique doit être fournie."
}
validation {
condition = alltrue([
for key in var.ssh_public_keys :
can(regex("^(ssh-rsa|ssh-ed25519|ssh-dss)", key))
])
error_message = "Toutes les clés doivent être dans un format SSH valide (ssh-rsa, ssh-ed25519, ou ssh-dss)."
}
}
terraform.tfvars
Voici enfin le contenu de terraform.tfvars
proxmox_endpoint = "https://192.168.1.6:8006/"
# Ne jamais mettre le api_token dans ce fichier
# Utiliser plutôt une variable d'environnement :
# export TF_VAR_api_token="votre_token"
# api_token = "NE_PAS_METTRE_ICI"
# Nom du nœud Proxmox cible
target_node = "opti-7010"
# Nombre de conteneurs à créer
container_count = 1
# Configuration des conteneurs
vm_hostname = "ubuntu-lxc"
# Ressources de calcul
cores = 2
memory = 2048 # 2 GB
# Configuration du stockage
disk = {
storage = "local-lvm"
size = "20" # Taille en GB sans le suffixe "G"
}
# Configuration réseau
gateway = "192.168.1.1"
dns_servers = [
"192.168.1.1", # Passerelle locale comme DNS primaire
"1.1.1.1" # Cloudflare comme DNS secondaire
]
# La clé publique SSH doit être générée au préalable
# Exemple de commande : ssh-keygen -t ed25519 -C "terraform-proxmox"
ssh_public_keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDKW96r73C2mON+Yy9Oxi8BrFiOACz11mYsZYrgrdkC2 deployer@ubuntu-deploy"
]
# Options de démarrage
onboot = true
Déploiement des conteneurs
Depuis notre machine de déploiement, nous pouvons alors lancer les commandes suivantes :
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ terraform init
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ terraform plan
# pensez à taper yes pour accepter le déploiement
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ terraform apply
Ces 3 commandes permettent respectivement :
- d’initialiser un répertoire de travail Terraform
- de créer un plan d’exécution
- d’appliquer les changements dans notre infrastructure pour atteindre l’état désiré
Nous pouvons observer sur notre hôte Proxmox VE la création du nouveau conteneur ubuntu-lxc-1
Test de connexion au conteneur nouvellement créé
Depuis notre machine de déploiement nous pouvons nous connecter au conteneur créé.
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ ssh root@192.168.1.201
The authenticity of host '192.168.1.201 (192.168.1.201)' can't be established.
ED25519 key fingerprint is SHA256:1F9S0v6CCmzemsy4SXWmn0dvdLkD/tXbS+EX7rwGZoE.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.1.201' (ED25519) to the list of known hosts.
Enter passphrase for key '/home/deployer/.ssh/id_ed25519':
Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.8.12-2-pve x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
root@ubuntu-lxc-1:~#
Destruction du conteneur
Pour détruire le conteneur créé à l’aide de Terraform et Proxmox VE, il suffit de faire :
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ terraform destroy
Création de 5 conteneurs
Notre fichier main.tf
est suffisamment flexible pour permettre la création de plusieurs conteneurs.
Il suffit simplement de changer dans terraform.tfvars
# Nombre de conteneurs à créer
container_count = 5
Nous pouvons aussi profiter de ce changement pour explorer les outputs
(sorties) de Terraform.
Il suffit de créer un fichier outputs.tf
# Sortie simple
output "container_count" {
description = "Nombre de conteneurs déployés"
value = var.container_count
}
# Sortie avec liste d'IPs
output "container_ips" {
description = "Liste des adresses IP des conteneurs"
value = [
for container in proxmox_virtual_environment_container.lxc_container :
split("/", container.initialization[0].ip_config[0].ipv4[0].address)[0]
]
}
# Sortie avec informations détaillées
output "container_details" {
description = "Détails de tous les conteneurs"
value = {
for idx, container in proxmox_virtual_environment_container.lxc_container :
container.initialization[0].hostname => {
id = container.vm_id
ip_address = split("/", container.initialization[0].ip_config[0].ipv4[0].address)[0]
cores = container.cpu[0].cores
memory = container.memory[0].dedicated
}
}
}
# Sortie sensible (masquée dans les logs)
output "sensitive_info" {
description = "Informations sensibles des conteneurs"
value = "information_sensible"
sensitive = true
}
Redéployons… tf init apply plan …
Nous pouvons voir dans PVE la création des 5 conteneurs.
Remarque : il est également possible de passer la variable container_count
via la console et modifier le nombre de conteneur déployé. Par exemple :
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ terraform apply -var 'container_count=3'
Exploitation des sorties (outputs) de Terraform
Nous pouvons également observer les sorties de Terraform via
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ terraform output
container_count = 5
container_details = {
"ubuntu-lxc-1" = {
"cores" = 2
"id" = 241201
"ip_address" = "192.168.1.201"
"memory" = 2048
}
"ubuntu-lxc-2" = {
"cores" = 2
"id" = 241202
"ip_address" = "192.168.1.202"
"memory" = 2048
}
"ubuntu-lxc-3" = {
"cores" = 2
"id" = 241203
"ip_address" = "192.168.1.203"
"memory" = 2048
}
"ubuntu-lxc-4" = {
"cores" = 2
"id" = 241204
"ip_address" = "192.168.1.204"
"memory" = 2048
}
"ubuntu-lxc-5" = {
"cores" = 2
"id" = 241205
"ip_address" = "192.168.1.205"
"memory" = 2048
}
}
container_ips = [
"192.168.1.201",
"192.168.1.202",
"192.168.1.203",
"192.168.1.204",
"192.168.1.205",
]
sensitive_info = <sensitive>
ou dans un format facilement lisible par une machine (comme le JSON)
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ terraform output -json
{
"container_count": {
"sensitive": false,
"type": "number",
"value": 5
},
"container_details": {
"sensitive": false,
"type": [
"object",
{
"ubuntu-lxc-1": [
"object",
{
"cores": "number",
"id": "number",
"ip_address": "string",
"memory": "number"
}
],
"ubuntu-lxc-2": [
"object",
{
"cores": "number",
"id": "number",
"ip_address": "string",
"memory": "number"
}
],
"ubuntu-lxc-3": [
"object",
{
"cores": "number",
"id": "number",
"ip_address": "string",
"memory": "number"
}
],
"ubuntu-lxc-4": [
"object",
{
"cores": "number",
"id": "number",
"ip_address": "string",
"memory": "number"
}
],
"ubuntu-lxc-5": [
"object",
{
"cores": "number",
"id": "number",
"ip_address": "string",
"memory": "number"
}
]
}
],
"value": {
"ubuntu-lxc-1": {
"cores": 2,
"id": 241201,
"ip_address": "192.168.1.201",
"memory": 2048
},
"ubuntu-lxc-2": {
"cores": 2,
"id": 241202,
"ip_address": "192.168.1.202",
"memory": 2048
},
"ubuntu-lxc-3": {
"cores": 2,
"id": 241203,
"ip_address": "192.168.1.203",
"memory": 2048
},
"ubuntu-lxc-4": {
"cores": 2,
"id": 241204,
"ip_address": "192.168.1.204",
"memory": 2048
},
"ubuntu-lxc-5": {
"cores": 2,
"id": 241205,
"ip_address": "192.168.1.205",
"memory": 2048
}
}
},
"container_ips": {
"sensitive": false,
"type": [
"tuple",
[
"string",
"string",
"string",
"string",
"string"
]
],
"value": [
"192.168.1.201",
"192.168.1.202",
"192.168.1.203",
"192.168.1.204",
"192.168.1.205"
]
},
"sensitive_info": {
"sensitive": true,
"type": "string",
"value": "information_sensible"
}
}
Sauvegardons cela dans un fichier, cela pourra nous servir.
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ terraform output -json > outputs.json
A l’aide d’un outil comme jq
(JSON queries) nous pouvons récupérer uniquement les adresses IP des interfaces réseau des conteneurs nouvellement créés. Il est d’abord nécessaire d’installer cet outil.
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ sudo apt install jq
On peut ensuite utiliser jq
ainsi
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ jq -r '.container_ips.value | @csv' outputs.json > servers_ip.csv
ou
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ jq -r '[.container_details.value[].ip_address] | @csv' outputs.json > servers_ip.csv
On obtient alors un fichier servers_ip.csv
contenant les adresses IP des interfaces réseau de nos 5 conteneurs nouvellement créés.
"192.168.1.201","192.168.1.202","192.168.1.203","192.168.1.204","192.168.1.205"
Test de connexion SSH à nos nouveaux conteneurs
Nous pouvons nous connecter aux différents conteneurs Essayons avec le n°241205 dont l’adresse IP est 192.168.1.205
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ ssh root@192.168.1.205
Enter passphrase for key '/home/deployer/.ssh/id_ed25519':
Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.8.12-2-pve x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
root@ubuntu-lxc-5:~#
Cela fonctionne sans soucis.
Par contre nous allons avoir un problème avec le 201
root@ubuntu-lxc-5:~# exit
logout
Connection to 192.168.1.205 closed.
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ ssh root@192.168.1.201
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ED25519 key sent by the remote host is
SHA256:7mIyi+Jnl46k4W0us8zM4gCyKZ/zvMu39KCszNmj1ck.
Please contact your system administrator.
Add correct host key in /home/deployer/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /home/deployer/.ssh/known_hosts:3
remove with:
ssh-keygen -f '/home/deployer/.ssh/known_hosts' -R '192.168.1.201'
Host key for 192.168.1.201 has changed and you have requested strict checking.
Host key verification failed.
Le message est clair… l’hôte a changé ! … oui nous avons détruit le conteneur et l’avons recréé. Il n’a plus la même empreinte.
Ici nous pouvons de manière sûre faire la commande demandé… à savoir
$ ssh-keygen -f '/home/deployer/.ssh/known_hosts' -R '192.168.1.201'
qui permet de retirer l’empreinte de 192.168.1.201 des hôtes connus.
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ ssh-keygen -f '/home/deployer/.ssh/known_hosts' -R '192.168.1.201'
# Host 192.168.1.201 found: line 1
# Host 192.168.1.201 found: line 2
# Host 192.168.1.201 found: line 3
/home/deployer/.ssh/known_hosts updated.
Original contents retained as /home/deployer/.ssh/known_hosts.old
deployer@ubuntu-deploy:~/homelab_julia_pve_tf_ansible$ ssh root@192.168.1.201
The authenticity of host '192.168.1.201 (192.168.1.201)' can't be established.
ED25519 key fingerprint is SHA256:7mIyi+Jnl46k4W0us8zM4gCyKZ/zvMu39KCszNmj1ck.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.1.201' (ED25519) to the list of known hosts.
Enter passphrase for key '/home/deployer/.ssh/id_ed25519':
Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.8.12-2-pve x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
root@ubuntu-lxc-1:~#
La connexion SSH au conteneur LXC fonctionne à nouveau.