Dans cet article je vais utiliser le provider terraform bpg/promox pour accéder à l’api de proxmox.
Egalement je présenterai un code terraform modulaire pour créer autant de VM que l’on souhaite sans dupliquer du code.
https://www.trfore.com/posts/using-terraform-to-create-proxmox-templates
https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_vm
Le provider utilisé https://registry.terraform.io/providers/bpg/proxmox/latest/docs
Accès à l’api Terraform sur le cluster
Supposons que vous ayiez un cluster proxmox de 3 nœuds que vous avez nommé noeud1, noeud2 et noeud3.
Il suffit d’accéder à l’api d’un seul noeud pour pouvoir créer des VM sur tout le cluster.
Donc sur un des noeuds, prenons le noeud1, nous allons configurer un utilisateur pour l’API proxmox.
pveum role add TerraformUser -privs "Datastore.AllocateSpace Datastore.Audit Pool.Allocate Sys.Audit Sys.Console Sys.Modify VM.Allocate VM.Audit VM.Clone VM.Config.CDROM VM.Config.Cloudinit VM.Config.CPU VM.Config.Disk VM.Config.HWType VM.Config.Memory VM.Config.Network VM.Config.Options VM.Migrate VM.Monitor VM.PowerMgmt SDN.Use"
pveum user add terraform@pam
# On donne l'accès en modification au dossier racine /
# pour par exemple permettre le téléchargement d'une image cloud ou d'un iso
# qui seront automatiquement déposés dans /var/lib/vz/...
pveum aclmod / -user terraform@pam -role TerraformUser
# génération d'une API key dont il faudra noter le full_tokenid et la value
pveum user token add terraform@pam token -privsep 0
Exemple d'api key générée :
┌──────────────┬──────────────────────────────────────┐
│ key │ value │
╞══════════════╪══════════════════════════════════════╡
│ full-tokenid │ terraform@pam!token │
├──────────────┼──────────────────────────────────────┤
│ info │ {"privsep":"0"} │
├──────────────┼──────────────────────────────────────┤
│ value │ 782a7700-4010-4802-8f4d-820f1b226850 │
└──────────────┴──────────────────────────────────────┘
Provider bpg : Accès ssh à proxmox pour les actions non supportées par l’api proxmox
Certaines actions ne sont pas supportées par l’api proxmox comme uploader certains types de ressource, importer des disques de VM dans certaines situations, … Du coup il est nécessaire de configurer un accès ssh sur le noeud1 que nous utilisons pour terraform.
Donc toujours connecté au noeud proxmox :
# create user 'terraform'
root@noaud1:~# adduser --home /home/terraform --shell /bin/bash terraform
usermod -aG sudo terraform
# create a sudoers file for terraform user
root@noaud1:~# visudo -f /etc/sudoers.d/terraform
Dans le fichier /etc/sudoers.d/terraform :
terraform ALL=(root) NOPASSWD: /sbin/pvesm
terraform ALL=(root) NOPASSWD: /sbin/qm
terraform ALL=(root) NOPASSWD: /usr/bin/tee /var/lib/vz/*
On se place sur le serveur terraform et on génère une paire de clef privée/publique ssh sans passphrase (on peut mettre une passphrase mais il faudra alors utiliser un agent ssh au niveau du serveur terraform) :
ssh-keygen -o -t ed25519 -f ~/.ssh/terraform_user_proxmox
# On affiche le contenu de la clef publique générée afin de l'ajouter dans les clef autorisées sur le serveur proxmox
cat ~/.ssh/terraform_id_ed25519.pub
#
La clef publique qui sera affichée est du style ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPQYTV18SN+39z9W99SzaJoc8VncoLyjhLulVH+pkkZ2
On copie cette clef publique que l’on met dans le fichier /home/terraform/.ssh/authorized_keys, ainsi notre serveur terraform qui possède la clef privée pourra s’authentifier sur le noeud1.
# sur le serveur noeud1, en supposant que vous êtes connectés en root
root@noaud1:~# su - terraform
root@noaud1:~# mkdir .ssh
root@noaud1:~# echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPQYTV18SN+39z9W99SzaJoc8VncoLyjhLulVH+pkkZ2" > .ssh/authorized_keys
root@noaud1:~# chmod 700 .ssh
root@noaud1:~# chmod 600 .ssh/authorized_keys
Code terraform modulaire pour créer des VM
Dans l’exemple qui suit nous allons créer des VM Linux.
Nous créons un Module Terraform « child » afin d’utiliser ce module pour créer de multiples VM avec de multiples attributs.
Création du module child
Nous créons un dossier modules/compute_instance_proxmox avec 3 fichiers dedans : main.tf, outputs.tf et variables.tf
Dans proxmox >= 9 on peut télécharger des images cloud avec le content_type = « import », mais dans les versions plus anciennes de proxmox je n’ai pas trouvé d’autre moyen que de créer un template proxmox à partir d’une image cloud (comme détaillé plus bas).
main.tf
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
}
}
}
# Manages files upload using PVE download-url API.
# Ce qui suit n'est pas supporté dans Proxmox VE 8.2 =>
resource "proxmox_virtual_environment_download_file" "image_linux" {
node_name = var.module_node_name
content_type = "import" # The file content type. Must be iso or import for VM images or vztmpl for LXC images.
# # The content type 'import' is not supported by the Proxmox VE version 8.2.2
datastore_id = "local"
file_name = "Rocky-9-GenericCloud-Base.latest.x86_64.img"
url = "https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2"
checksum = "15d81d3434b298142b2fdd8fb54aef2662684db5c082cc191c3c79762ed6360c"
checksum_algorithm = "sha256"
overwrite = false
lifecycle {
prevent_destroy = true
}
}
# https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_vm
###############################
# MACHINE VIRTUELLE PROXMOX #
###############################
resource "proxmox_virtual_environment_vm" "vm" {
name = var.module_vm_name
node_name = var.module_node_name
tags = var.module_tags
#vm_id = var.module_vm_id # ID de la VM ==> optionnel
agent {
# read 'Qemu guest agent' section, change to true only when ready
enabled = true
}
# CPU & RAM
cpu {
cores = var.module_cpu_cores
type = "x86-64-v2-AES" # recommended for modern CPUs
}
memory {
dedicated = var.module_memory # (Optional) The dedicated memory in megabytes (defaults to 512).
floating = var.module_memory # (Optional) The floating memory in megabytes.
# The default is 0, which disables "ballooning device" for the VM.
# Please note that Proxmox has ballooning enabled by default. To enable it, set floating to the same value as dedicated.
}
clone {
vm_id = 9000
}
# => Ce qui suit ne peut marcher que pour proxmox version >= 8.4 (compatible image cloud)
disk {
datastore_id = "local-zfs" # The identifier for the datastore to create the disk in
file_format = "qcow2" # The file format
import_from = proxmox_virtual_environment_download_file.image_linux.id
# The file ID for a disk image to import into VM.
# The image must be of import content type.
interface = "scsi0" # pour windows cela nécessite le driver VirtIO
size = var.module_boot_disk_size_in_gbs # The disk size in gigabytes
}
#bios = "ovmf" # accepte les valeurs ovmf et seabios (defaults to seabios).
#efi_disk {
# datastore_id = "local-zfs"
#}
# boot_order = ["scsi0"] # ==> est indiqué dans le template
initialization {
datastore_id = "local-zfs"
#file_format = "qcow2"
ip_config {
ipv4 {
address = "dhcp"
}
}
#user_data_file_id = proxmox_virtual_environment_file.cloud_user_config.id
# Pour une version plus sécurisée, il faudrait ne pas autoriser une connexion root par password
user_account {
username = "root"
password = var.module_root_password_linux
}
}
# Réseau
network_device {
bridge = "vmbr0"
}
}
variables.tf
variable "module_vm_name" {
type = string
}
variable "module_node_name" {
type = string
description = "Nom du nœud Proxmox."
}
variable "module_memory" {
type = number
}
variable "module_cpu_cores" {
type = number
}
variable "module_boot_disk_size_in_gbs" {
type = number
}
variable "module_tags" {
type = list(string)
default = []
nullable = true
}
variable "module_root_password_linux" {
description = "Password root des machines linux"
type = string
sensitive = true # do not display in CLI
}
Dans outputs.tf on placera les valeurs de sorties souhaitées
Création du module root (c’est ici qu’on crée les multiples VM)
Au même niveau que le dossier modules, on crée un dossier proxmox qui contient 3 fichiers : provider.tf, variables.tf et vm.tf
provider.tf
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
}
}
}
provider "proxmox" {
# L’API de n’importe quel nœud expose aussi les endpoints du cluster. Tous les nœuds du cluster lisent /etc/pve/ qui est le filesystem distribué du cluster.
endpoint = "https://noeud1:8006/api2/json"
api_token = "${var.pve_token_id}=${var.pve_token_secret}"
insecure = true # true car le certificat est auto signé sur elektra et non vérifié par une autorité de vertification
ssh {
agent = true
username = "terraform"
private_key = "~/.ssh/terraform_user_proxmox"
}
}
variables.tf
variable "pve_token_id" {
description = "PVE Token ID pour l'API proxmox"
type = string
sensitive = true # do not display in CLI
}
variable "pve_token_secret" {
description = "PVE Token secret pour l'API proxmox"
type = string
sensitive = true # do not display in CLI
}
variable "root_password_linux" {
description = "Password root des machines linux"
type = string
sensitive = true # do not display in CLI
}
vm.tf
module "compute_instance_proxmox" {
source = "../modules/compute_instance_proxmox"
for_each = {
compute1 = {
node_name = "noeud1"
vname = "lxClientTest01-Created-by-Terraform"
vtags = ["LINUX"]
memory_in_mbs = 8000
cpu = 1
disk_size_in_gbs = 20
}
compute2 = {
node_name = "noeud1"
vname = "lxClientTest02-Created-by-Terraform"
vtags = ["LINUX","MYTAG"] # on met ce qu'on veut dans les tags
memory_in_mbs = 12000
cpu = 2
disk_size_in_gbs = 40
}
}
module_vm_name = each.value.vname
module_tags = each.value.vtags
module_node_name = each.value.node_name
module_cpu_cores = each.value.cpu
module_memory = each.value.memory_in_mbs
module_boot_disk_size_in_gbs = each.value.disk_size_in_gbs
module_root_password_linux = var.root_password_linux
}
Proxmox version < 8.4
Sur proxmox 8.2.2 je n’ai pas pu utiliser le systèm de téléchargement d’image pour télécharger une image cloud, car le type content_type= »import » n’était pas supporté dans cette version, il ne l’est qu’à partir de la version 8.4.
J’ai donc essayé un contournement en suivant le tutoriel donné en source au début de cet article mais j’ai eu l’erreur suivante « 1: received an HTTP 400 response – Reason: Parameter verification failed. (scsi0: local:iso/Rocky-9-GenericCloud-Base.latest.x86_64.img has wrong type ‘iso’ – not an image) »
Par conséquent pour la version 8.2.2 de proxmox (ou versions inférieurs), j’ai trouvé une autre façon de faire, qui n’est plus full terraform : créer tout d’abord un template RockyLinux à partir de l’image cloud RockyLinux et cloner dans terraform cet image.
root@noaud1:~# cd /var/lib/vz/images
root@noaud1:~# wget https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2
root@noaud1:~# qm importdisk 9000 Rocky-9-GenericCloud-Base.latest.x86_64.qcow2 local-zfs
root@noaud1:~# qm set 9000 --scsihw virtio-scsi-pci --scsi0 local-zfs:vm-9000-disk-0
root@noaud1:~# qm set 9000 --ide2 local-zfs:cloudinit
root@noaud1:~# qm set 9000 --boot order=scsi0
root@noaud1:~# qm set 9000 --serial0 socket --vga serial0
root@noaud1:~# qm template 9000
Et ainsi le code du child module change un peu dans le fichier main.tf, vous noterez la présence de
clone {
vm_id = 9000
}
dans main.tf
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
}
}
}
# https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_vm
###############################
# MACHINE VIRTUELLE PROXMOX #
###############################
resource "proxmox_virtual_environment_vm" "vm" {
name = var.module_vm_name
node_name = var.module_node_name
tags = var.module_tags
#vm_id = var.module_vm_id # ID de la VM ==> optionnel
agent {
# read 'Qemu guest agent' section, change to true only when ready
enabled = true
}
# CPU & RAM
cpu {
cores = var.module_cpu_cores
type = "x86-64-v2-AES" # recommended for modern CPUs
}
memory {
dedicated = var.module_memory # (Optional) The dedicated memory in megabytes (defaults to 512).
floating = var.module_memory # (Optional) The floating memory in megabytes.
# The default is 0, which disables "ballooning device" for the VM.
# Please note that Proxmox has ballooning enabled by default. To enable it, set floating to the same value as dedicated.
}
clone {
vm_id = 9000
}
initialization {
datastore_id = "local-zfs"
#file_format = "qcow2"
ip_config {
ipv4 {
address = "dhcp"
}
}
#user_data_file_id = proxmox_virtual_environment_file.cloud_user_config.id
user_account {
username = "root"
password = var.module_root_password_linux
}
}
# Réseau
network_device {
bridge = "vmbr0"
}
}
Laisser un commentaire