25 KiB
Templates Ansible & Terraform - Nomenclature v2.0
L'Alliance Boréale
Version: 1.0
Date: 21 octobre 2025
Licence: AGPL-3.0
################################################################################
PARTIE 1: TEMPLATES ANSIBLE
################################################################################
==============================================================================
1. Inventaire Dynamique Proxmox
==============================================================================
inventory/proxmox.yml
plugin: community.general.proxmox url: https://proxmox.example.com:8006 user: ansible@pve password: !vault | $ANSIBLE_VAULT;1.1;AES256 ... validate_certs: no
Grouper automatiquement par tags
compose: ansible_host: proxmox_ipconfig0.ip | regex_replace('/.*', '')
keyed_groups:
Grouper par couche (infrastructure)
- prefix: layer key: proxmox_tags | select('match', '^layer-[1-4]$') | first | default('unknown')
Grouper par tenant
- prefix: tenant key: proxmox_tags | select('match', '^tenant-t[0-9]{3}$') | first | default('none')
Grouper par environnement
- prefix: env key: proxmox_tags | select('match', '^(prod|stg|dev|test)$') | first | default('unknown')
Grouper par catégorie
- prefix: category key: proxmox_tags | select('match', '^(infrastructure|tenant)$') | first | default('unknown')
==============================================================================
2. Playbook de Création VM Infrastructure
==============================================================================
playbooks/create-infra-vm.yml
-
name: Créer VM Infrastructure selon nomenclature v2.0 hosts: localhost gather_facts: no
vars_prompt:
-
name: vm_layer prompt: "Couche (1-4)" private: no
-
name: vm_type prompt: "Type service (ex: 00=DNS, 10=VPN, 20=Git)" private: no
-
name: vm_instance prompt: "Instance (01-99)" private: no
-
name: vm_service_name prompt: "Nom du service (ex: dns-master, ansible-ctrl)" private: no
-
name: vm_environment prompt: "Environnement (prod/stg/dev/test)" private: no default: "prod"
vars: member_id: "{{ lookup('env', 'MEMBER_ID') | default('czp', true) }}"
Calcul VMID
vmid: "{{ '%s%02d' | format(tenant_id, vm_instance|int) }}"
Construction nom VM
vm_name: "{{ member_id }}-t{{ tenant_id }}-{{ vm_service_type }}-{{ vm_environment }}-{{ '%02d' | format(vm_instance|int) }}"
Allocation IP (via script ou manuel)
vm_ip: "{{ lookup('pipe', 'allocate-ip.sh ' + tenant_id) | regex_search('\d+\.\d+\.\d+\.\d+') }}"
DNS
vm_dns: "{{ vm_service_type }}.t{{ tenant_id }}.{{ member_id }}.alliance-boreale.ca"
tasks:
-
name: Afficher plan de création debug: msg: - "VMID: {{ vmid }}" - "Nom: {{ vm_name }}" - "IP: {{ vm_ip }}/23" - "DNS: {{ vm_dns }}" - "Tenant: {{ tenant_id }}"
-
name: Confirmer création pause: prompt: "Créer cette VM? (Ctrl+C pour annuler)"
-
name: Créer VM dans Proxmox community.general.proxmox_kvm: api_host: "{{ proxmox_host }}" api_user: "{{ proxmox_user }}" api_password: "{{ proxmox_password }}" node: "{{ proxmox_node }}"
vmid: "{{ vmid }}" name: "{{ vm_name }}"
clone: "{{ vm_template | default('debian-12-template') }}" full: yes
cores: "{{ vm_cores | default(2) }}" memory: "{{ vm_memory | default(2048) }}"
net: net0: "virtio,bridge=vmbr0,tag=10"
ipconfig: ipconfig0: "ip={{ vm_ip }}/23,gw=10.0.0.1"
tags: - tenant - "tenant-t{{ tenant_id }}" - "{{ vm_environment }}" - "service-{{ vm_service_type }}"
state: present
-
name: Enregistrer IP dans IPAM ansible.builtin.lineinfile: path: "/etc/alliance-boreale/ipam.txt" line: "{{ tenant_id }} {{ vm_ip }} {{ ansible_date_time.date }} {{ vmid }}" create: yes
-
name: Créer entrée DNS ansible.builtin.lineinfile: path: "/var/lib/alliance-boreale/dns-records.zone" line: "{{ vm_dns }}. IN A {{ vm_ip }}" delegate_to: "{{ dns_master_host }}"
-
name: Démarrer la VM community.general.proxmox_kvm: api_host: "{{ proxmox_host }}" api_user: "{{ proxmox_user }}" api_password: "{{ proxmox_password }}" vmid: "{{ vmid }}" state: started
-
==============================================================================
4. Role Ansible - Conformité Nomenclature
==============================================================================
roles/nomenclature_compliance/tasks/main.yml
-
name: Vérifier que le VMID est conforme ansible.builtin.assert: that: - inventory_hostname_short | regex_search('^[a-z]+-((infra)|(t[0-9]{3}))-') fail_msg: "Nom VM non conforme au standard v2.0" success_msg: "Nom VM conforme"
-
name: Extraire les informations de nomenclature ansible.builtin.set_fact: vm_member: "{{ inventory_hostname_short | regex_replace('^([a-z]+)-.', '\1') }}" vm_category: "{{ 'infrastructure' if 'infra' in inventory_hostname_short else 'tenant' }}" vm_tenant_id: "{{ inventory_hostname_short | regex_replace('.-(t[0-9]{3})-.*', '\1') if 'infra' not in inventory_hostname_short else 'N/A' }}"
-
name: Afficher informations extraites debug: msg: - "Membre: {{ vm_member }}" - "Catégorie: {{ vm_category }}" - "Tenant: {{ vm_tenant_id }}"
-
name: Vérifier cohérence IP ansible.builtin.assert: that: - ansible_default_ipv4.address | regex_search('^10\.0\.') fail_msg: "IP hors de la plage 10.0.0.0/8" success_msg: "IP dans la plage correcte"
-
name: Appliquer tags de conformité ansible.builtin.set_fact: nomenclature_compliant: true nomenclature_version: "v2.0" nomenclature_validated_date: "{{ ansible_date_time.iso8601 }}"
==============================================================================
5. Playbook d'Audit Ansible
==============================================================================
playbooks/audit-nomenclature-ansible.yml
-
name: Audit de conformité Nomenclature v2.0 via Ansible hosts: all gather_facts: yes
tasks:
- name: Vérifier nom VM ansible.builtin.set_fact: name_compliant: "{{ inventory_hostname_short | regex_search('^[a-z]+-(infra|t[0-9]{3})-[a-z0-9-]+-[a-z]+-[0-9]{2}
vars: member_id: "{{ lookup('env', 'MEMBER_ID') | default('czp', true) }}"
Calcul VMID
vmid: "{{ '0%s%02d%02d' | format(vm_layer, vm_type|int, vm_instance|int) }}"
Construction nom VM
vm_name: "{{ member_id }}-infra-{{ vm_service_name }}-{{ vm_environment }}-{{ '%02d' | format(vm_instance|int) }}"
Calcul IP selon couche
vm_ip: >- {{ ('10.0.0.' if vm_layer == '1' else '10.0.1.' if vm_layer == '4' else '10.0.2.' if vm_layer == '2' else '10.0.3.') + (vm_type|int * 10 + vm_instance|int)|string }}
DNS
vm_dns: "{{ vm_service_name }}.infra.{{ member_id }}.alliance-boreale.ca"
tasks:
-
name: Afficher plan de création debug: msg: - "VMID: {{ vmid }}" - "Nom: {{ vm_name }}" - "IP: {{ vm_ip }}/24" - "DNS: {{ vm_dns }}"
-
name: Confirmer création pause: prompt: "Créer cette VM? (Ctrl+C pour annuler, Entrée pour continuer)"
-
name: Créer VM dans Proxmox community.general.proxmox_kvm: api_host: "{{ proxmox_host }}" api_user: "{{ proxmox_user }}" api_password: "{{ proxmox_password }}" node: "{{ proxmox_node }}"
vmid: "{{ vmid }}" name: "{{ vm_name }}"
clone: "debian-12-template" full: yes
cores: 2 memory: 2048
net: net0: "virtio,bridge=vmbr0,tag={{ '2' if vm_layer == '2' else '1' }}"
ipconfig: ipconfig0: "ip={{ vm_ip }}/24,gw=10.0.0.1"
tags: - infrastructure - "layer-{{ vm_layer }}" - "{{ vm_environment }}" - "service-{{ vm_service_name }}"
state: present register: vm_created
-
name: Démarrer la VM community.general.proxmox_kvm: api_host: "{{ proxmox_host }}" api_user: "{{ proxmox_user }}" api_password: "{{ proxmox_password }}" vmid: "{{ vmid }}" state: started
-
name: Créer entrée DNS ansible.builtin.lineinfile: path: "/var/lib/alliance-boreale/dns-records.zone" line: "{{ vm_dns }}. IN A {{ vm_ip }}" create: yes delegate_to: "{{ dns_master_host }}"
-
name: Ajouter au monitoring ansible.builtin.template: src: templates/icinga2-host.conf.j2 dest: "/etc/icinga2/conf.d/hosts/{{ vm_name }}.conf" delegate_to: "{{ monitoring_host }}" notify: Reload Icinga2
==============================================================================
3. Playbook de Création VM Tenant
==============================================================================
playbooks/create-tenant-vm.yml
-
name: Créer VM Tenant selon nomenclature v2.0 hosts: localhost gather_facts: no
vars_prompt:
-
name: tenant_id prompt: "Tenant ID (001-999)" private: no
-
name: vm_instance prompt: "Instance (01-99)" private: no
-
name: vm_service_type prompt: "Type (web/api/db/cache/worker)" private: no
-
name: vm_environment prompt: "Environnement (prod/stg/dev) is not none }}"
-
name: Vérifier IP dans plage correcte ansible.builtin.set_fact: ip_compliant: "{{ ansible_default_ipv4.address | regex_search('^10\.0\.') is not none }}"
-
name: Extraire VMID si possible ansible.builtin.shell: | qm list | awk -v host="{{ inventory_hostname_short }}" '$2 == host {print $1}' register: vmid_check delegate_to: "{{ proxmox_node }}" changed_when: false failed_when: false
-
name: Valider VMID ansible.builtin.command: validate-vmid.sh {{ vmid_check.stdout }} register: vmid_validation delegate_to: localhost changed_when: false failed_when: false when: vmid_check.stdout != ""
-
name: Générer rapport ansible.builtin.set_fact: compliance_report: hostname: "{{ inventory_hostname }}" name_compliant: "{{ name_compliant }}" ip_compliant: "{{ ip_compliant }}" vmid: "{{ vmid_check.stdout | default('N/A') }}" vmid_compliant: "{{ vmid_validation.rc == 0 if vmid_check.stdout != '' else false }}" overall_compliant: "{{ name_compliant and ip_compliant and (vmid_validation.rc == 0 if vmid_check.stdout != '' else false) }}"
-
name: Afficher résultat ansible.builtin.debug: var: compliance_report
-
name: Sauvegarder rapport ansible.builtin.copy: content: "{{ compliance_report | to_nice_json }}" dest: "/var/lib/alliance-boreale/compliance/{{ inventory_hostname }}.json" delegate_to: localhost
-
################################################################################
PARTIE 2: TEMPLATES TERRAFORM
################################################################################
==============================================================================
6. Module Terraform - VM Infrastructure
==============================================================================
modules/infra-vm/main.tf
terraform { required_providers { proxmox = { source = "telmate/proxmox" version = "~> 2.9" } } }
variable "member_id" { description = "ID du membre (ex: czp, nul, tli)" type = string }
variable "layer" { description = "Couche (1-4)" type = number validation { condition = var.layer >= 1 && var.layer <= 4 error_message = "La couche doit être entre 1 et 4." } }
variable "service_type" { description = "Type de service (00-99)" type = number }
variable "instance" { description = "Numéro d'instance (01-99)" type = number }
variable "service_name" { description = "Nom du service (ex: dns-master, ansible-ctrl)" type = string }
variable "environment" { description = "Environnement (prod/stg/dev/test)" type = string default = "prod" }
variable "cores" { description = "Nombre de CPU cores" type = number default = 2 }
variable "memory" { description = "RAM en MB" type = number default = 2048 }
variable "disk_size" { description = "Taille disque en GB" type = string default = "20G" }
Calculs locaux
locals {
VMID: 0CTTII
vmid = format("0%d%02d%02d", var.layer, var.service_type, var.instance)
Nom VM: -infra---
vm_name = format("%s-infra-%s-%s-%02d", var.member_id, var.service_name, var.environment, var.instance )
IP selon couche
ip_base = var.layer == 1 ? "10.0.0" : ( var.layer == 2 ? "10.0.2" : ( var.layer == 3 ? "10.0.3" : "10.0.1"))
vm_ip = format("%s.%d", local.ip_base, var.service_type * 10 + var.instance)
DNS
vm_dns = format("%s.infra.%s.alliance-boreale.ca", var.service_name, var.member_id ) }
resource "proxmox_vm_qemu" "infra_vm" { name = local.vm_name vmid = local.vmid target_node = var.proxmox_node
clone = "debian-12-template" full_clone = true
cores = var.cores memory = var.memory
network { model = "virtio" bridge = "vmbr0" tag = var.layer == 2 ? 2 : 1 }
disk { type = "scsi" storage = "local-lvm" size = var.disk_size }
ipconfig0 = "ip=${local.vm_ip}/24,gw=10.0.0.1"
tags = join(";", [ "infrastructure", "layer-${var.layer}", var.environment, "service-${var.service_name}", "nomenclature-v2" ])
lifecycle { ignore_changes = [ network, ] } }
output "vmid" { value = local.vmid }
output "vm_name" { value = local.vm_name }
output "vm_ip" { value = local.vm_ip }
output "vm_dns" { value = local.vm_dns }
==============================================================================
7. Module Terraform - VM Tenant
==============================================================================
modules/tenant-vm/main.tf
terraform { required_providers { proxmox = { source = "telmate/proxmox" version = "~> 2.9" } } }
variable "member_id" { description = "ID du membre" type = string }
variable "tenant_id" { description = "Tenant ID (001-999)" type = string validation { condition = can(regex("^[0-9]{3}$", var.tenant_id)) error_message = "Tenant ID doit être au format NNN (ex: 001)." } }
variable "instance" { description = "Numéro d'instance (01-99)" type = number }
variable "service_type" { description = "Type de service (web/api/db/cache/worker)" type = string }
variable "environment" { description = "Environnement" type = string default = "prod" }
variable "ip_address" { description = "Adresse IP (optionnel, auto-alloué si vide)" type = string default = "" }
variable "cores" { type = number default = 2 }
variable "memory" { type = number default = 2048 }
locals {
VMID: TTTII
vmid = format("%s%02d", var.tenant_id, var.instance)
Nom VM
vm_name = format("%s-t%s-%s-%s-%02d", var.member_id, var.tenant_id, var.service_type, var.environment, var.instance )
IP (utiliser celle fournie ou calculer)
vm_ip = var.ip_address != "" ? var.ip_address : format("10.0.10.%d", (tonumber(var.tenant_id) - 1) * 10 + var.instance )
DNS
vm_dns = format("%s.t%s.%s.alliance-boreale.ca", var.service_type, var.tenant_id, var.member_id ) }
resource "proxmox_vm_qemu" "tenant_vm" { name = local.vm_name vmid = local.vmid target_node = var.proxmox_node
clone = var.vm_template full_clone = true
cores = var.cores memory = var.memory
network { model = "virtio" bridge = "vmbr0" tag = 10 }
disk { type = "scsi" storage = "local-lvm" size = var.disk_size }
ipconfig0 = "ip=${local.vm_ip}/23,gw=10.0.0.1"
tags = join(";", [ "tenant", "tenant-t${var.tenant_id}", var.environment, "service-${var.service_type}", "nomenclature-v2" ]) }
Enregistrer dans IPAM
resource "null_resource" "register_ipam" { provisioner "local-exec" { command = "echo '${var.tenant_id} ${local.vm_ip} ${timestamp()} ${local.vmid}' >> /etc/alliance-boreale/ipam.txt" }
depends_on = [proxmox_vm_qemu.tenant_vm] }
output "vmid" { value = local.vmid }
output "vm_name" { value = local.vm_name }
output "vm_ip" { value = local.vm_ip }
output "vm_dns" { value = local.vm_dns }
==============================================================================
8. Exemple d'Utilisation Terraform
==============================================================================
main.tf - Exemple de déploiement complet
terraform { required_version = ">= 1.0"
required_providers { proxmox = { source = "telmate/proxmox" version = "~> 2.9" } } }
provider "proxmox" { pm_api_url = var.proxmox_api_url pm_user = var.proxmox_user pm_password = var.proxmox_password pm_tls_insecure = true }
variable "proxmox_api_url" {} variable "proxmox_user" {} variable "proxmox_password" {} variable "proxmox_node" { default = "pve1" } variable "member_id" { default = "czp" }
Infrastructure minimale Bronze
module "dns_master" { source = "./modules/infra-vm"
member_id = var.member_id layer = 2 service_type = 0 # DNS instance = 1 service_name = "dns-master"
cores = 2 memory = 4096 }
module "ansible_controller" { source = "./modules/infra-vm"
member_id = var.member_id layer = 4 service_type = 0 # Ansible instance = 1 service_name = "ansible-ctrl" }
module "backup_server" { source = "./modules/infra-vm"
member_id = var.member_id layer = 3 service_type = 30 # Backup instance = 1 service_name = "pbs-backup"
cores = 4 memory = 8192 disk_size = "500G" }
Premier tenant - Stack complète
module "tenant001_web" { source = "./modules/tenant-vm"
member_id = var.member_id tenant_id = "001" instance = 1 service_type = "web" }
module "tenant001_api" { source = "./modules/tenant-vm"
member_id = var.member_id tenant_id = "001" instance = 11 service_type = "api-fastapi" }
module "tenant001_db" { source = "./modules/tenant-vm"
member_id = var.member_id tenant_id = "001" instance = 21 service_type = "db-postgres"
cores = 4 memory = 8192 }
Outputs
output "infrastructure" { value = { dns_master = { vmid = module.dns_master.vmid name = module.dns_master.vm_name ip = module.dns_master.vm_ip dns = module.dns_master.vm_dns } ansible = { vmid = module.ansible_controller.vmid name = module.ansible_controller.vm_name ip = module.ansible_controller.vm_ip dns = module.ansible_controller.vm_dns } } }
output "tenant_001" { value = { web = module.tenant001_web api = module.tenant001_api db = module.tenant001_db } }
==============================================================================
9. Variables Terraform
==============================================================================
variables.tf
variable "member_id" { description = "ID du membre de L'Alliance Boréale" type = string
validation { condition = can(regex("^[a-z]{2,4}$", var.member_id)) error_message = "member_id doit être 2-4 lettres minuscules." } }
variable "proxmox_node" { description = "Nœud Proxmox cible" type = string default = "pve1" }
variable "dns_domain" { description = "Domaine DNS de L'Alliance" type = string default = "alliance-boreale.ca" }
variable "vm_template" { description = "Template VM par défaut" type = string default = "debian-12-template" }
==============================================================================
10. Makefile pour Automatisation
==============================================================================
Makefile
.PHONY: help audit migrate plan apply destroy
help: @echo "Commandes disponibles:" @echo " make audit - Audit de conformité" @echo " make plan - Plan Terraform" @echo " make apply - Appliquer Terraform" @echo " make destroy - Détruire infrastructure Terraform" @echo " make ansible - Exécuter playbooks Ansible"
audit: @echo "=== Audit Nomenclature v2.0 ===" audit-nomenclature.sh ansible-playbook playbooks/audit-nomenclature-ansible.yml
plan: terraform plan -out=tfplan
apply: terraform apply tfplan
destroy: terraform destroy
ansible: ansible-playbook playbooks/configure-all.yml -i inventory/proxmox.yml
==============================================================================
FIN DES TEMPLATES
==============================================================================
vars: member_id: "{{ lookup('env', 'MEMBER_ID') | default('czp', true) }}"
# Calcul VMID
vmid: "{{ '0%s%02d%02d' | format(vm_layer, vm_type|int, vm_instance|int) }}"
# Construction nom VM
vm_name: "{{ member_id }}-infra-{{ vm_service_name }}-{{ vm_environment }}-{{ '%02d' | format(vm_instance|int) }}"
# Calcul IP selon couche
vm_ip: >-
{{
('10.0.0.' if vm_layer == '1' else
'10.0.1.' if vm_layer == '4' else
'10.0.2.' if vm_layer == '2' else
'10.0.3.') + (vm_type|int * 10 + vm_instance|int)|string
}}
# DNS
vm_dns: "{{ vm_service_name }}.infra.{{ member_id }}.alliance-boreale.ca"
tasks: - name: Afficher plan de création debug: msg: - "VMID: {{ vmid }}" - "Nom: {{ vm_name }}" - "IP: {{ vm_ip }}/24" - "DNS: {{ vm_dns }}"
- name: Confirmer création
pause:
prompt: "Créer cette VM? (Ctrl+C pour annuler, Entrée pour continuer)"
- name: Créer VM dans Proxmox
community.general.proxmox_kvm:
api_host: "{{ proxmox_host }}"
api_user: "{{ proxmox_user }}"
api_password: "{{ proxmox_password }}"
node: "{{ proxmox_node }}"
vmid: "{{ vmid }}"
name: "{{ vm_name }}"
clone: "debian-12-template"
full: yes
cores: 2
memory: 2048
net:
net0: "virtio,bridge=vmbr0,tag={{ '2' if vm_layer == '2' else '1' }}"
ipconfig:
ipconfig0: "ip={{ vm_ip }}/24,gw=10.0.0.1"
tags:
- infrastructure
- "layer-{{ vm_layer }}"
- "{{ vm_environment }}"
- "service-{{ vm_service_name }}"
state: present
register: vm_created
- name: Démarrer la VM
community.general.proxmox_kvm:
api_host: "{{ proxmox_host }}"
api_user: "{{ proxmox_user }}"
api_password: "{{ proxmox_password }}"
vmid: "{{ vmid }}"
state: started
- name: Créer entrée DNS
ansible.builtin.lineinfile:
path: "/var/lib/alliance-boreale/dns-records.zone"
line: "{{ vm_dns }}. IN A {{ vm_ip }}"
create: yes
delegate_to: "{{ dns_master_host }}"
- name: Ajouter au monitoring
ansible.builtin.template:
src: templates/icinga2-host.conf.j2
dest: "/etc/icinga2/conf.d/hosts/{{ vm_name }}.conf"
delegate_to: "{{ monitoring_host }}"
notify: Reload Icinga2
==============================================================================
3. Playbook de Création VM Tenant
==============================================================================
playbooks/create-tenant-vm.yml
-
name: Créer VM Tenant selon nomenclature v2.0 hosts: localhost gather_facts: no
vars_prompt:
-
name: tenant_id prompt: "Tenant ID (001-999)" private: no
-
name: vm_instance prompt: "Instance (01-99)" private: no
-
name: vm_service_type prompt: "Type (web/api/db/cache/worker)" private: no
-
name: vm_environment prompt: "Environnement (prod/stg/dev
-