De nos jours, l'automatisation des tâches et la création d'infrastructures immuables sont des aspects incontournables. C'est dans cette optique que l'utilisation d'outils comme Terraform devient particulièrement intéressante pour la création de conteneurs LXC sur un hôte Proxmox.

L'idée sous-jacente à cette approche est de déployer des conteneurs pour isoler et héberger différents services, tout en évitant l'usage habituel de Docker ou de Podman. Outre l'aspect sécuritaire qu'offre cette méthode, elle vise également à explorer et à exploiter pleinement les outils disponibles.

Ce document part du principe que vous maîtrisez déjà Terraform, que vous disposez des droits d'administration sur Proxmox et que vous êtes capable d'effectuer des manipulations sans contraintes majeures.

Plutôt que de réinventer (encore) la roue, je vous suggère l'article de Quentin, "au final, qu'est-ce qu'un conteneur ?", pour découvrir ce qu'est un conteneur Linux.

L'environnement de travail pour cet article est le suivant :

  • un serveur Proxmox 8.1.x
  • un conteneur LXC où j'installerai à l'intérieur Node Exporter avec ansible

Préparation de Proxmox

Pour simplifier les opérations dans vos environnements de test, vous pouvez utiliser le compte root@pam créé lors de l'installation de Proxmox. Ce compte possède tous les privilèges nécessaires pour effectuer des actions sur la plateforme. Cependant, pour une gestion plus sécurisée, la création d'un token pour le compte root est conseillée.

Par la suite, je vais créer un utilisateur nommé "terrabot" dans le realm "pve". Ce compte aura un rôle spécifique, doté des droits nécessaires pour la création et la gestion des conteneurs, des machines virtuelles et des disques virtuels.

Création d'un rôle

La première étape est la création d'un rôle. Ce rôle disposera des droits nécessaires pour gérer les machines et leur stockage. Voici donc tous les rôles nécessaires :

  "Datastore.Allocate",
  "Datastore.AllocateSpace",
  "Datastore.AllocateTemplate",
  "Datastore.Audit",
  "Pool.Allocate",
  "Pool.Audit",
  "SDN.Allocate",
  "SDN.Audit",
  "SDN.Use",
  "Sys.Audit",
  "Sys.Console",
  "VM.Allocate",
  "VM.Audit",
  "VM.Backup",
  "VM.Clone",
  "VM.Config.CDROM",
  "VM.Config.CPU",
  "VM.Config.Cloudinit",
  "VM.Config.Disk",
  "VM.Config.HWType",
  "VM.Config.Memory",
  "VM.Config.Network",
  "VM.Config.Options",
  "VM.Console",
  "VM.Migrate",
  "VM.Monitor",
  "VM.PowerMgmt",
  "VM.Snapshot",
  "VM.Snapshot.Rollback"

Non, je n'ai pas utilisé Terraform pour créer ce rôle, puisque Terraform a besoin de ce rôle avant de créer des ressources... Terraform-ception.

Création d'un utilisateur

Maintenant, créons l'utilisateur "terrabot" ; cet utilisateur sera utilisé exclusivement pour Terraform. Dirigez-vous dans l'onglet "Permissions", puis sur "Users", et cliquez sur le bouton "Create". Créez un nouvel utilisateur dans le realm "pve", ajoutez des informations selon votre besoin et validez les changements.

Par la suite, ajoutez un mot de passe complexe à cet utilisateur (vous ne l'utiliserez probablement pas, mais ajoutez quand même un mot de passe) et validez ces changements.

Création d'un token pour l'utilisateur

Avant-dernière étape, créons un token pour cet utilisateur fraichement créé. Toujours dans l'onglet "Permissions", cliquez sur le lien "API Token" et cliquez sur le bouton "Add". Saisissez l'utilisateur et le nom du token. Ce nom est important, vous en aurez besoin plus loin dans le code Terraform. Ici, j'ai appelé ce token "tf". J'ai décoché la case "Privileges separations" et n'ai pas mis de date d'expiration pour le token. Dans des environnements de production, il est fortement conseillé de renouveler les tokens régulièrement.

Attribution du rôle

Nous sommes au virage final. Attribuons désormais le rôle à notre utilisateur et plus particulièrement au token. Retournez une dernière fois dans l'onglet "Permissions" et sélectionnez "API User token". Choisissez l'utilisateur créé (ici Terrabot) et sélectionnez le rôle "Terraform". Validez les changements et

Le code Terraform complet

Enfin, voici le code complet que j'utilise pour créer des conteneurs sur Proxmox avec Terraform. Le code est assez complet, j'aime bien avoir le maximum d'informations et d'attributs pour préciser au maximum mes ressources.

Fichier "provider.tf" :

### provider.tf
terraform {
  required_providers {
    proxmox = {
      source  = "bpg/proxmox"
      version = "0.41.0"
    }
  }
}

provider "proxmox" {
  endpoint  = var.pve_host_address
  api_token = var.pve_api_token
  insecure  = true
  ssh {
    agent    = true
    username = var.pve_api_user
  }
  tmp_dir = var.tmp_dir
}

Fichier "variables.tf" :

### variables.tf
variable "ct_bridge" {
  type = string
}
variable "ct_datastore_storage_location" {
  type = string
}
variable "ct_datastore_template_location" {
  type = string
}
variable "ct_disk_size" {
  type    = string
  default = "20"
}
variable "ct_nic_rate_limit" {
  type = number
}
variable "ct_memory" {
  type = number
}
variable "ct_source_file_path" {
  type = string
}
variable "dns_domain" {
  type = string
}
variable "dns_server" {
  type = string
}
variable "gateway" {
  type = string
}
variable "os_type" {
  type = string
}
variable "pve_api_token" {
  type = string
}
variable "pve_api_user" {
  type = string
}
variable "pve_host_address" {
  type = string
}
variable "tmp_dir" {
  type = string
}

Fichier "variables.auto.tfvars", c'est ce fichier qu'il faut modifier pour que vous puissiez avoir un conteneur correspondant à votre besoin :

### variables.auto.tfvars
ct_bridge                      = "vmbr0"
ct_datastore_storage_location  = "local"
ct_datastore_template_location = "local"
ct_disk_size                   = "20"
ct_nic_rate_limit              = 10
ct_memory                      = 128
ct_source_file_path            = "http://download.proxmox.com/images/system/debian-12-standard_12.2-1_amd64.tar.zst"
dns_domain                     = "domain.net"
dns_server                     = "192.168.1.1"
gateway                        = "192.168.1.1"
os_type                        = "debian"
pve_api_token                  = "terrabot@pve!tf=change-my-token"
pve_api_user                   = "terrabot"
pve_host_address               = "https://192.168.1.2:8006"
tmp_dir                        = "/var/tmp"

Fichier "container.tf" :

### container.tf
# see https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password.html. It will download "hashicorp/random" provider
resource "random_password" "container_root_password" {
  length           = 24
  override_special = "_%@"
  special          = true
}

output "container_root_password" {
  value     = random_password.container_root_password.result
  sensitive = true
}

# location of containers templates
resource "proxmox_virtual_environment_file" "debian_container_template" {
  content_type = "vztmpl"
  datastore_id = var.ct_datastore_template_location
  node_name    = "pve1"

  source_file {
    path = var.ct_source_file_path
  }
}

resource "proxmox_virtual_environment_container" "debian_container" {
  description   = "Managed by Terraform"
  node_name     = "pve1"
  start_on_boot = true
  tags          = ["linux", "infra"]
  unprivileged  = true
  vm_id         = 241001

  cpu {
    architecture = "amd64"
    cores        = 1
  }

  disk {
    datastore_id = var.ct_datastore_storage_location
    size         = var.ct_disk_size
  }

  memory {
    dedicated = var.ct_memory
    swap      = 0
  }

  operating_system {
    template_file_id = proxmox_virtual_environment_file.debian_container_template.id
    type             = var.os_type
  }

  initialization {
    hostname = "ct-example"

    dns {
      domain = var.dns_domain
      server = var.dns_server
    }

    ip_config {
      ipv4 {
        address = "192.168.1.3/24"
        gateway = var.gateway
      }
    }
    user_account {
      keys     = ["put-your-ssh-pubkey-here"]
      password = random_password.container_root_password.result
    }
  }
  network_interface {
    name       = var.ct_bridge
    rate_limit = var.ct_nic_rate_limit
  }

  features {
    nesting = true
    fuse    = false
  }
}

Dans ce fichier, des valeurs sont à modifier, comme le nom de la machine Proxmox (node_name), l'adresse IPv4 du conteneur (address), la clé publique SSH (keys), le nom du conteneur (hostname).

Pour faire évoluer ce code, il peut être intéressant de créer des boucles et des conditions...

Partager l'article