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

968 lines
No EOL
25 KiB
Markdown

# 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: <membre>-infra-<type>-<env>-<instance>
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