# 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