HAProxy

Dépôt non versionné

Contrairement à tous les autres dépôts IaC d'Indio (nsx, proxy, aws-s3, etc., tous sous indio/iac/* sur GitLab), /root/haproxy ne contient aucun .git — ni local, ni distant (git status échoue avec « n'est un dépôt git »). Il n'existe donc aucun historique de commits ni aucune sauvegarde GitLab pour ce code : seule la copie locale sous /root/haproxy fait foi.

Rôle

Cluster HAProxy à 3 nœuds avec IP virtuelle flottante (keepalived), servant de façade unique multi-services pour plusieurs briques internes de l'infrastructure : un frontend/backend distinct par service, sur des ports différents de la même VIP. Au 2026-07-17, 8 services sont exposés : le dépôt Nexus, HashiCorp Vault, l'API Kubernetes, et 5 registries Docker à port dédié.

Architecture

Structure du dépôt

haproxy/
├── terraform/                     infra : 3 VM Rocky 9.6 + génération de l'inventaire Ansible
│   ├── main.tf                     data sources vSphere/NetBox, vsphere_virtual_machine.hap (for_each), local_file inventory.ini
│   ├── variables.tf                nodes (3× INDSERV00x), vm_cpu/vm_ram, vip, ansible_user...
│   ├── outputs.tf                  vms, vip, ansible_inventory_path
│   └── templates/inventory.ini.tftpl
└── ansible/                       applicatif : HAProxy + keepalived
    ├── site.yml                    rôles common, keepalived, haproxy
    ├── ansible.cfg                 vault_password_file = /root/.hap_vault_pass (hors dépôt)
    ├── group_vars/haproxy.yml      haproxy_services (8 entrées), keepalived_*
    └── roles/{common,keepalived,haproxy}

Nœuds et VIP

Rôle Hôte IP Priorité keepalived
VIP (keepalived) 10.100.2.130
MASTER INDSERV002 10.100.2.131 110
BACKUP INDSERV003 10.100.2.132 100
BACKUP INDSERV004 10.100.2.133 90

VRRP virtual_router_id = 51, interface détectée automatiquement (ansible_default_ipv4.interface). Segment seg-service (10.100.2.128/26, zone admin — voir NSX).

Services exposés (un frontend/backend par entrée de haproxy_services)

Service Port frontend Mode SSL (HAProxy) Backend(s)
repo 443 http oui Nexus 10.100.2.178:8081
vault 8200 tcp non (passthrough) 3× Vault :8200
k8s-api 6443 tcp non (passthrough) 3× control-plane k8s :6443
docker-proxy 8083 http oui Nexus 10.100.2.178:8083
k8s-proxy 8084 http oui Nexus 10.100.2.178:8084
ghcr-proxy 8085 http oui Nexus 10.100.2.178:8085
quay-proxy 8086 http oui Nexus 10.100.2.178:8086
docker-internal 8087 http oui Nexus 10.100.2.178:8087

Deux logiques distinctes cohabitent :

  • Terminaison SSL (repo + 5 services Docker) : HAProxy déchiffre le TLS avec le certificat roles/haproxy/files/repo.pem (cert+clé concaténés, déployé en /etc/haproxy/certs/repo.pem, mode 0640, groupe haproxy) puis parle HTTP en clair au backend Nexus ; un en-tête X-Forwarded-Proto: https informe le backend de l'origine HTTPS. Les 5 services Docker vérifient le backend via GET /v2/ en attendant un 401 (un httpchk générique renvoie 400 sur un connecteur Docker Nexus).
  • TCP passthrough (vault, k8s-api) : chaque backend termine son propre TLS. Pour vault, le health-check (GET /v1/sys/health, expect status 200) ne réussit que sur le leader actif du cluster Vault — les standby renvoient un autre code, donc un seul des 3 nœuds apparaît « up » en fonctionnement normal. Pour k8s-api, simple vérification TCP : les 3 apiserver sont actifs simultanément (pas de notion de leader HTTP), pas besoin de health-check applicatif.

Page de statistiques HAProxy sur le port 8404 (tous les nœuds).

flowchart TD
    CLIENTS["Flotte admin/SOC + proxies DMZ<br/>(règles dmz-to-repo / soc-to-repo côté NSX)"] --> VIP

    VIP(["VIP keepalived 10.100.2.130<br/>VRRP 51"])

    subgraph NODES[" seg-service 10.100.2.128/26 — 1 seul nœud actif porte la VIP "]
        N1["INDSERV002 · .131<br/>prio 110"]
        N2["INDSERV003 · .132<br/>prio 100"]
        N3["INDSERV004 · .133<br/>prio 90"]
        N1 -.failover.-> N2 -.failover.-> N3
    end

    VIP === N1

    N1 --> FE["8 frontends HAProxy (config identique sur les 3 nœuds)<br/>443 repo · 8200 vault · 6443 k8s-api<br/>8083-8087 registries Docker · 8404 stats"]

    FE -->|"443, SSL termination"| NEXUS1["Nexus 10.100.2.178:8081"]
    FE -->|"8083-8087, SSL termination"| NEXUS2["Nexus 10.100.2.178:8083-8087"]
    FE -->|"8200, TCP passthrough"| VAULT["Vault ×3 — 10.100.2.146-148:8200<br/>(seul le leader répond 200 au health-check)"]
    FE -->|"6443, TCP passthrough"| K8S["k8s control-plane ×3 — 10.100.2.149-151:6443"]

Provisioning Terraform

Le sous-dossier terraform/ ne déploie que l'infrastructure (3 VM) — aucune configuration applicative.

  • Data sources vSphere (datacenter, cluster, datastore, réseau seg-service, VM template) + netbox_ip_addresses (résolution d'IP par nom DNS).
  • resource "vsphere_virtual_machine" "hap" en for_each sur var.nodes — une validation Terraform impose exactement 3 nœuds.
  • Génère automatiquement ../ansible/inventory.ini via local_file + templatefile(inventory.ini.tftpl) : l'état MASTER/BACKUP et la priorité keepalived sont calculés depuis l'ordre de la liste (index 0 = MASTER prio 110, puis -10 par nœud suivant).
  • Variables clés (variables.tf) : template_name (défaut tmpl-rocky96-hardened), nodes (défaut INDSERV002/003/004), vm_cpu=2, vm_ram=4096, vm_domain=infra.indio, vm_gateway=10.100.2.190, vip=10.100.2.130, ansible_user=indio-adm.
  • Providers : hashicorp/vsphere >= 2.6.0, e-breuninger/netbox >= 5.0.0, < 6.0.0, hashicorp/local >= 2.4.0. Terraform >= 1.5.0.
  • Sorties (outputs.tf) : vms (nom → IP), vip, ansible_inventory_path.

README partiellement daté

README.md décrit encore l'état d'origine du dépôt : hostnames haproxy-01/02/03 (renommés INDSERV002/003/004 depuis, cf. Convention de nommage) et un unique service (le dépôt Nexus, via une variable haproxy_backends qui n'existe plus). Le modèle réel actuel est multi-services (haproxy_services, 8 entrées) — cette page reflète le contenu réel de group_vars/haproxy.yml au 2026-07-17, pas le README.

Configuration Ansible

site.yml applique 3 rôles sur le groupe haproxy : common, keepalived, haproxy.

  • common : net.ipv4.ip_nonlocal_bind=1 (permet au nœud passif de bind la VIP avant de la porter), booléen SELinux haproxy_connect_any, ouverture firewalld d'un port par service (haproxy_services) + le port stats + une règle riche autorisant le protocole VRRP.
  • keepalived : installe keepalived, déploie keepalived.conf (état/priorité/interface venant de l'inventaire généré par Terraform), redémarre le service au changement.
  • haproxy : installe haproxy, déploie le certificat repo.pem, déploie haproxy.cfg (template généré depuis haproxy_services, validé par haproxy -c -f %s avant application), démarre/active le service.

Variables (group_vars/haproxy.yml) : keepalived_vrid=51, keepalived_auth_pass (voir point d'attention), haproxy_stats_port=8404, haproxy_ssl_cert_src/haproxy_ssl_cert, et la liste complète haproxy_services détaillée en Architecture.

ansible.cfg référence un vault_password_file = /root/.hap_vault_pass (hors dépôt) pour le déchiffrement automatique de futurs secrets ansible-vault. requirements.yml installe la collection ansible.posix (modules sysctl, seboolean, firewalld).

Procédure de déploiement

cd terraform
cp terraform.tfvars.example terraform.tfvars   # vCenter + placement + IP
terraform init
terraform plan
terraform apply          # crée les 3 VM et écrit ../ansible/inventory.ini

cd ../ansible
ansible-galaxy collection install -r requirements.yml
ansible-playbook site.yml

Prérequis : template tmpl-rocky96-hardened présent dans vCenter, accès SSH indio-adm (sudo), règles DFW autorisant VRRP entre les 3 nœuds et le flux VIP → backends (voir NSX).

Pour ajouter un service : ajouter une entrée à haproxy_services dans group_vars/haproxy.yml puis rejouer site.yml (le port frontend est automatiquement ouvert dans firewalld par la boucle du rôle common) — la règle DFW correspondante côté NSX doit être ajoutée séparément si le backend n'est pas déjà couvert.

Points d'attention

  • Absence totale de dépôt git (voir encadré en tête de page) : aucune traçabilité de version, aucune sauvegarde distante. À considérer en priorité si une politique de versionnage systématique est décidée pour ce projet.
  • keepalived_auth_pass est actuellement une valeur en clair dans group_vars/haproxy.yml (gérée séparément, non reproduite ici) — le fichier lui-même porte la note « à déplacer dans un ansible-vault en prod », non encore appliquée.
  • Le certificat repo.pem (cert + clé concaténés) est un fichier déployé tel quel depuis roles/haproxy/files/ : son contenu n'a pas été lu ni reproduit dans cette page (règle de sécurité du projet), à traiter avec la même rigueur qu'un secret.
  • Cette VIP HAProxy (10.100.2.130) est indépendante des VIP keepalived du proxy Squid (10.100.10.4) et du proxy DNS (10.100.10.12) — voir Proxy Squid & DNS. HAProxy ne relaie ni Squid ni le DNS proxy : ce sont des clusters HA distincts avec leur propre mécanisme VRRP.
  • Le health-check Vault (GET /v1/sys/health, expect status 200) ne cible que le leader : ne pas s'étonner qu'un seul des 3 backends vault-* apparaisse « up » dans la page de stats en fonctionnement normal — c'est le comportement attendu, pas une panne.
  • Ajouter un backend Nexus/registry nécessite de vérifier le bon connecteur côté Nexus selon le format (le piège httpchk générique → 400 sur un connecteur Docker déjà rencontré est résolu par GET /v2/ + expect status 401, déjà en place pour les 5 services Docker existants — à reproduire pour tout nouveau registry).