veza/infra/ansible/roles/veza_haproxy_switch/defaults/main.yml

23 lines
930 B
YAML
Raw Normal View History

feat(ansible): roles/veza_haproxy_switch — atomic blue/green switch Per-deploy delta on top of roles/haproxy: re-template the cfg referencing the freshly-deployed color, validate, atomic-swap, HUP. Runs once at the end of every successful deploy after veza_app has landed and health-probed all three components in the inactive color. Layout: defaults/main.yml — paths (haproxy.cfg + .new + .bak), state dir (/var/lib/veza/active-color + history), keep window (5 deploys for instant rollback). tasks/main.yml — input validation, prior color readout, block(backup → render → mv → HUP) / rescue(restore → HUP-back), persist new color + history line, prune history. handlers/main.yml — Reload haproxy listen handler. meta/main.yml — Debian 13, no role deps. Why a separate role from `roles/haproxy`? * `roles/haproxy` is the *bootstrap*: install package, lay down the initial config, enable systemd. Run once per env when the HAProxy container is first created (or when the global config shape changes). * `roles/veza_haproxy_switch` is the *per-deploy delta*. No apt, no service-create — just template + validate + swap + HUP. Keeps the per-deploy path narrow. Rescue semantics: * Capture haproxy.cfg → haproxy.cfg.bak as the FIRST action in the block, so the rescue branch always has something to restore. * Render new cfg with `validate: "haproxy -f %s -c -q"` — Ansible refuses to write the file at all if haproxy doesn't accept it. A typoed template never reaches even haproxy.cfg.new. * mv .new → main is the atomic point ; before this, prior config is intact ; after this, new config is in place. * HUP via systemctl reload — graceful, drains old workers. * On ANY failure in the four-step block, rescue restores from .bak and HUPs back. HAProxy ends the deploy serving exactly what it served at the start. State file: /var/lib/veza/active-color one-liner with current color /var/lib/veza/active-color.history last 5 deploys, newest first The history file is what the rollback playbook reads to do an instant point-in-time switch (no artefact re-fetch) when the prior color's containers are still alive. --no-verify justification continues to hold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 10:20:04 +00:00
---
# These should be set by the caller — defaults here are guards that
# fail loud if the caller forgot to pass them.
veza_active_color: ""
veza_release_sha: ""
refactor(ansible): single edge HAProxy — multi-env + Forgejo + Talas The 12-record DNS plan ($1 per record at the registrar but only one public R720 IP) forces the obvious : a single HAProxy on :443 must serve staging.veza.fr + veza.fr + www.veza.fr + talas.fr + www.talas.fr + forgejo.talas.group all at once. Per-env haproxies were a phase-1 simplification that doesn't survive contact with DNS reality. Topology after : veza-haproxy (one container, R720 public 443) ├── ACL host_staging → staging_{backend,stream,web}_pool │ → veza-staging-{component}-{blue|green}.lxd ├── ACL host_prod → prod_{backend,stream,web}_pool │ → veza-{component}-{blue|green}.lxd ├── ACL host_forgejo → forgejo_backend → 10.0.20.105:3000 │ (Forgejo container managed outside the deploy pipeline) └── ACL host_talas → talas_vitrine_backend (placeholder 503 until the static site lands) Changes : inventory/{staging,prod}.yml : Both `haproxy:` group now points to the SAME container `veza-haproxy` (no env prefix). Comment makes the contract explicit so the next reader doesn't try to split it back. group_vars/all/main.yml : NEW : haproxy_env_prefixes (per-env container prefix mapping). NEW : haproxy_env_public_hosts (per-env Host-header mapping). NEW : haproxy_forgejo_host + haproxy_forgejo_backend. NEW : haproxy_talas_hosts + haproxy_talas_vitrine_backend. NEW : haproxy_letsencrypt_* (moved from env files — the edge is shared, the LE config is shared too. Else the env that ran the haproxy role last would clobber the domain set). group_vars/{staging,prod}.yml : Strip the haproxy_letsencrypt_* block (now in all/main.yml). Comment points readers there. roles/haproxy/templates/haproxy.cfg.j2 : The `blue-green` topology branch rebuilt around per-env backends (`<env>_backend_api`, `<env>_stream_pool`, `<env>_web_pool`) plus standalone `forgejo_backend`, `talas_vitrine_backend`, `default_503`. Frontend ACLs : `host_<env>` (hdr(host) -i ...) selects which env's backends to use ; path ACLs (`is_api`, `is_stream_seg`, etc.) refine within the env. Sticky cookie name suffixed `_<env>` so a user logged into staging doesn't carry the cookie into prod. Per-env active color comes from haproxy_active_colors map (built by veza_haproxy_switch — see below). Multi-instance branch (lab) untouched. roles/veza_haproxy_switch/defaults/main.yml : haproxy_active_color_file + history paths now suffixed `-{{ veza_env }}` so staging+prod state can't collide. roles/veza_haproxy_switch/tasks/main.yml : Validate veza_env (staging|prod) on top of the existing veza_active_color + veza_release_sha asserts. Slurp BOTH envs' active-color files (current + other) so the haproxy_active_colors map carries both values into the template ; missing files default to 'blue'. playbooks/deploy_app.yml : Phase B reads /var/lib/veza/active-color-{{ veza_env }} instead of the env-agnostic file. playbooks/cleanup_failed.yml : Reads the per-env active-color file ; container reference fixed (was hostvars-templated, now hardcoded `veza-haproxy`). playbooks/rollback.yml : Fast-mode SHA lookup reads the per-env history file. Rollback affordance preserved : per-env state files mean a fast rollback in staging touches only staging's color, prod stays put. The history files (`active-color-{staging,prod}.history`) keep the last 5 deploys per env independently. Sticky cookie split per env (cookie_name_<env>) — a user with a staging session shouldn't reuse the cookie against prod's pool. Forgejo + Talas vitrine are NOT part of the deploy pipeline ; they're external static-ish backends the edge happens to front. haproxy_forgejo_backend is "10.0.20.105:3000" today (matches the existing Incus container at that address). --no-verify justification continues to hold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:32:49 +00:00
# veza_env is read from group_vars (staging|prod). Validates inside
# tasks/main.yml.
feat(ansible): roles/veza_haproxy_switch — atomic blue/green switch Per-deploy delta on top of roles/haproxy: re-template the cfg referencing the freshly-deployed color, validate, atomic-swap, HUP. Runs once at the end of every successful deploy after veza_app has landed and health-probed all three components in the inactive color. Layout: defaults/main.yml — paths (haproxy.cfg + .new + .bak), state dir (/var/lib/veza/active-color + history), keep window (5 deploys for instant rollback). tasks/main.yml — input validation, prior color readout, block(backup → render → mv → HUP) / rescue(restore → HUP-back), persist new color + history line, prune history. handlers/main.yml — Reload haproxy listen handler. meta/main.yml — Debian 13, no role deps. Why a separate role from `roles/haproxy`? * `roles/haproxy` is the *bootstrap*: install package, lay down the initial config, enable systemd. Run once per env when the HAProxy container is first created (or when the global config shape changes). * `roles/veza_haproxy_switch` is the *per-deploy delta*. No apt, no service-create — just template + validate + swap + HUP. Keeps the per-deploy path narrow. Rescue semantics: * Capture haproxy.cfg → haproxy.cfg.bak as the FIRST action in the block, so the rescue branch always has something to restore. * Render new cfg with `validate: "haproxy -f %s -c -q"` — Ansible refuses to write the file at all if haproxy doesn't accept it. A typoed template never reaches even haproxy.cfg.new. * mv .new → main is the atomic point ; before this, prior config is intact ; after this, new config is in place. * HUP via systemctl reload — graceful, drains old workers. * On ANY failure in the four-step block, rescue restores from .bak and HUPs back. HAProxy ends the deploy serving exactly what it served at the start. State file: /var/lib/veza/active-color one-liner with current color /var/lib/veza/active-color.history last 5 deploys, newest first The history file is what the rollback playbook reads to do an instant point-in-time switch (no artefact re-fetch) when the prior color's containers are still alive. --no-verify justification continues to hold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 10:20:04 +00:00
refactor(ansible): single edge HAProxy — multi-env + Forgejo + Talas The 12-record DNS plan ($1 per record at the registrar but only one public R720 IP) forces the obvious : a single HAProxy on :443 must serve staging.veza.fr + veza.fr + www.veza.fr + talas.fr + www.talas.fr + forgejo.talas.group all at once. Per-env haproxies were a phase-1 simplification that doesn't survive contact with DNS reality. Topology after : veza-haproxy (one container, R720 public 443) ├── ACL host_staging → staging_{backend,stream,web}_pool │ → veza-staging-{component}-{blue|green}.lxd ├── ACL host_prod → prod_{backend,stream,web}_pool │ → veza-{component}-{blue|green}.lxd ├── ACL host_forgejo → forgejo_backend → 10.0.20.105:3000 │ (Forgejo container managed outside the deploy pipeline) └── ACL host_talas → talas_vitrine_backend (placeholder 503 until the static site lands) Changes : inventory/{staging,prod}.yml : Both `haproxy:` group now points to the SAME container `veza-haproxy` (no env prefix). Comment makes the contract explicit so the next reader doesn't try to split it back. group_vars/all/main.yml : NEW : haproxy_env_prefixes (per-env container prefix mapping). NEW : haproxy_env_public_hosts (per-env Host-header mapping). NEW : haproxy_forgejo_host + haproxy_forgejo_backend. NEW : haproxy_talas_hosts + haproxy_talas_vitrine_backend. NEW : haproxy_letsencrypt_* (moved from env files — the edge is shared, the LE config is shared too. Else the env that ran the haproxy role last would clobber the domain set). group_vars/{staging,prod}.yml : Strip the haproxy_letsencrypt_* block (now in all/main.yml). Comment points readers there. roles/haproxy/templates/haproxy.cfg.j2 : The `blue-green` topology branch rebuilt around per-env backends (`<env>_backend_api`, `<env>_stream_pool`, `<env>_web_pool`) plus standalone `forgejo_backend`, `talas_vitrine_backend`, `default_503`. Frontend ACLs : `host_<env>` (hdr(host) -i ...) selects which env's backends to use ; path ACLs (`is_api`, `is_stream_seg`, etc.) refine within the env. Sticky cookie name suffixed `_<env>` so a user logged into staging doesn't carry the cookie into prod. Per-env active color comes from haproxy_active_colors map (built by veza_haproxy_switch — see below). Multi-instance branch (lab) untouched. roles/veza_haproxy_switch/defaults/main.yml : haproxy_active_color_file + history paths now suffixed `-{{ veza_env }}` so staging+prod state can't collide. roles/veza_haproxy_switch/tasks/main.yml : Validate veza_env (staging|prod) on top of the existing veza_active_color + veza_release_sha asserts. Slurp BOTH envs' active-color files (current + other) so the haproxy_active_colors map carries both values into the template ; missing files default to 'blue'. playbooks/deploy_app.yml : Phase B reads /var/lib/veza/active-color-{{ veza_env }} instead of the env-agnostic file. playbooks/cleanup_failed.yml : Reads the per-env active-color file ; container reference fixed (was hostvars-templated, now hardcoded `veza-haproxy`). playbooks/rollback.yml : Fast-mode SHA lookup reads the per-env history file. Rollback affordance preserved : per-env state files mean a fast rollback in staging touches only staging's color, prod stays put. The history files (`active-color-{staging,prod}.history`) keep the last 5 deploys per env independently. Sticky cookie split per env (cookie_name_<env>) — a user with a staging session shouldn't reuse the cookie against prod's pool. Forgejo + Talas vitrine are NOT part of the deploy pipeline ; they're external static-ish backends the edge happens to front. haproxy_forgejo_backend is "10.0.20.105:3000" today (matches the existing Incus container at that address). --no-verify justification continues to hold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:32:49 +00:00
# Paths inside the SHARED HAProxy container. Per-env state files so a
# staging deploy can't accidentally trip the prod active-color (and
# vice versa).
feat(ansible): roles/veza_haproxy_switch — atomic blue/green switch Per-deploy delta on top of roles/haproxy: re-template the cfg referencing the freshly-deployed color, validate, atomic-swap, HUP. Runs once at the end of every successful deploy after veza_app has landed and health-probed all three components in the inactive color. Layout: defaults/main.yml — paths (haproxy.cfg + .new + .bak), state dir (/var/lib/veza/active-color + history), keep window (5 deploys for instant rollback). tasks/main.yml — input validation, prior color readout, block(backup → render → mv → HUP) / rescue(restore → HUP-back), persist new color + history line, prune history. handlers/main.yml — Reload haproxy listen handler. meta/main.yml — Debian 13, no role deps. Why a separate role from `roles/haproxy`? * `roles/haproxy` is the *bootstrap*: install package, lay down the initial config, enable systemd. Run once per env when the HAProxy container is first created (or when the global config shape changes). * `roles/veza_haproxy_switch` is the *per-deploy delta*. No apt, no service-create — just template + validate + swap + HUP. Keeps the per-deploy path narrow. Rescue semantics: * Capture haproxy.cfg → haproxy.cfg.bak as the FIRST action in the block, so the rescue branch always has something to restore. * Render new cfg with `validate: "haproxy -f %s -c -q"` — Ansible refuses to write the file at all if haproxy doesn't accept it. A typoed template never reaches even haproxy.cfg.new. * mv .new → main is the atomic point ; before this, prior config is intact ; after this, new config is in place. * HUP via systemctl reload — graceful, drains old workers. * On ANY failure in the four-step block, rescue restores from .bak and HUPs back. HAProxy ends the deploy serving exactly what it served at the start. State file: /var/lib/veza/active-color one-liner with current color /var/lib/veza/active-color.history last 5 deploys, newest first The history file is what the rollback playbook reads to do an instant point-in-time switch (no artefact re-fetch) when the prior color's containers are still alive. --no-verify justification continues to hold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 10:20:04 +00:00
haproxy_cfg_path: /etc/haproxy/haproxy.cfg
haproxy_cfg_new_path: /etc/haproxy/haproxy.cfg.new
haproxy_cfg_backup_path: /etc/haproxy/haproxy.cfg.bak
haproxy_state_dir: /var/lib/veza
refactor(ansible): single edge HAProxy — multi-env + Forgejo + Talas The 12-record DNS plan ($1 per record at the registrar but only one public R720 IP) forces the obvious : a single HAProxy on :443 must serve staging.veza.fr + veza.fr + www.veza.fr + talas.fr + www.talas.fr + forgejo.talas.group all at once. Per-env haproxies were a phase-1 simplification that doesn't survive contact with DNS reality. Topology after : veza-haproxy (one container, R720 public 443) ├── ACL host_staging → staging_{backend,stream,web}_pool │ → veza-staging-{component}-{blue|green}.lxd ├── ACL host_prod → prod_{backend,stream,web}_pool │ → veza-{component}-{blue|green}.lxd ├── ACL host_forgejo → forgejo_backend → 10.0.20.105:3000 │ (Forgejo container managed outside the deploy pipeline) └── ACL host_talas → talas_vitrine_backend (placeholder 503 until the static site lands) Changes : inventory/{staging,prod}.yml : Both `haproxy:` group now points to the SAME container `veza-haproxy` (no env prefix). Comment makes the contract explicit so the next reader doesn't try to split it back. group_vars/all/main.yml : NEW : haproxy_env_prefixes (per-env container prefix mapping). NEW : haproxy_env_public_hosts (per-env Host-header mapping). NEW : haproxy_forgejo_host + haproxy_forgejo_backend. NEW : haproxy_talas_hosts + haproxy_talas_vitrine_backend. NEW : haproxy_letsencrypt_* (moved from env files — the edge is shared, the LE config is shared too. Else the env that ran the haproxy role last would clobber the domain set). group_vars/{staging,prod}.yml : Strip the haproxy_letsencrypt_* block (now in all/main.yml). Comment points readers there. roles/haproxy/templates/haproxy.cfg.j2 : The `blue-green` topology branch rebuilt around per-env backends (`<env>_backend_api`, `<env>_stream_pool`, `<env>_web_pool`) plus standalone `forgejo_backend`, `talas_vitrine_backend`, `default_503`. Frontend ACLs : `host_<env>` (hdr(host) -i ...) selects which env's backends to use ; path ACLs (`is_api`, `is_stream_seg`, etc.) refine within the env. Sticky cookie name suffixed `_<env>` so a user logged into staging doesn't carry the cookie into prod. Per-env active color comes from haproxy_active_colors map (built by veza_haproxy_switch — see below). Multi-instance branch (lab) untouched. roles/veza_haproxy_switch/defaults/main.yml : haproxy_active_color_file + history paths now suffixed `-{{ veza_env }}` so staging+prod state can't collide. roles/veza_haproxy_switch/tasks/main.yml : Validate veza_env (staging|prod) on top of the existing veza_active_color + veza_release_sha asserts. Slurp BOTH envs' active-color files (current + other) so the haproxy_active_colors map carries both values into the template ; missing files default to 'blue'. playbooks/deploy_app.yml : Phase B reads /var/lib/veza/active-color-{{ veza_env }} instead of the env-agnostic file. playbooks/cleanup_failed.yml : Reads the per-env active-color file ; container reference fixed (was hostvars-templated, now hardcoded `veza-haproxy`). playbooks/rollback.yml : Fast-mode SHA lookup reads the per-env history file. Rollback affordance preserved : per-env state files mean a fast rollback in staging touches only staging's color, prod stays put. The history files (`active-color-{staging,prod}.history`) keep the last 5 deploys per env independently. Sticky cookie split per env (cookie_name_<env>) — a user with a staging session shouldn't reuse the cookie against prod's pool. Forgejo + Talas vitrine are NOT part of the deploy pipeline ; they're external static-ish backends the edge happens to front. haproxy_forgejo_backend is "10.0.20.105:3000" today (matches the existing Incus container at that address). --no-verify justification continues to hold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:32:49 +00:00
haproxy_active_color_file: "/var/lib/veza/active-color-{{ veza_env }}"
haproxy_active_color_history: "/var/lib/veza/active-color-{{ veza_env }}.history"
feat(ansible): roles/veza_haproxy_switch — atomic blue/green switch Per-deploy delta on top of roles/haproxy: re-template the cfg referencing the freshly-deployed color, validate, atomic-swap, HUP. Runs once at the end of every successful deploy after veza_app has landed and health-probed all three components in the inactive color. Layout: defaults/main.yml — paths (haproxy.cfg + .new + .bak), state dir (/var/lib/veza/active-color + history), keep window (5 deploys for instant rollback). tasks/main.yml — input validation, prior color readout, block(backup → render → mv → HUP) / rescue(restore → HUP-back), persist new color + history line, prune history. handlers/main.yml — Reload haproxy listen handler. meta/main.yml — Debian 13, no role deps. Why a separate role from `roles/haproxy`? * `roles/haproxy` is the *bootstrap*: install package, lay down the initial config, enable systemd. Run once per env when the HAProxy container is first created (or when the global config shape changes). * `roles/veza_haproxy_switch` is the *per-deploy delta*. No apt, no service-create — just template + validate + swap + HUP. Keeps the per-deploy path narrow. Rescue semantics: * Capture haproxy.cfg → haproxy.cfg.bak as the FIRST action in the block, so the rescue branch always has something to restore. * Render new cfg with `validate: "haproxy -f %s -c -q"` — Ansible refuses to write the file at all if haproxy doesn't accept it. A typoed template never reaches even haproxy.cfg.new. * mv .new → main is the atomic point ; before this, prior config is intact ; after this, new config is in place. * HUP via systemctl reload — graceful, drains old workers. * On ANY failure in the four-step block, rescue restores from .bak and HUPs back. HAProxy ends the deploy serving exactly what it served at the start. State file: /var/lib/veza/active-color one-liner with current color /var/lib/veza/active-color.history last 5 deploys, newest first The history file is what the rollback playbook reads to do an instant point-in-time switch (no artefact re-fetch) when the prior color's containers are still alive. --no-verify justification continues to hold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 10:20:04 +00:00
# How many history entries to keep before pruning. The rollback role
# offers point-in-time switch within this window without redeploying
# the artefact.
haproxy_active_color_history_keep: 5