alliance-boreale/docs/architecture/templates_automation.md
Dan Allaire e704e1cc39
Some checks failed
CI / yaml-lint (push) Has been cancelled
CI / ssot-export (push) Has been cancelled
CI / tests (push) Has been cancelled
CI / docs (push) Has been cancelled
normes et nomenclature mnémotechnique
2025-10-21 11:09:16 -04:00

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