incus-contrib/.claude/rules/haproxy-lb.md

5.9 KiB

paths
incusos/deploy-haproxy
incusos/helpers/aether-browser
notes/haproxy-guide.md

HAProxy Load Balancing (Aether-Managed)

Overview

  • Aether manages HAProxy deployment: image builds, HA pair, VIP management, L7 service configuration, health monitoring.
  • Lab cluster: oc-lab-cluster (ID: 52), OVN network: net-prod (10.10.10.0/24).
  • VIP range: 192.168.103.200-210 (from UPLINK).
  • deploy-haproxy script: --deploy, --status, --cleanup, --doctor.

Use Playwright for Aether session auth, NOT curl

HAProxy management endpoints use session-authenticated routes with CSRF protection. curl cannot reliably handle this — use Playwright instead.

  • Playwright MCP: .mcp.json configures @playwright/mcp@latest. When loaded, use browser_navigate, browser_click, browser_fill tools.
  • Helper: incusos/helpers/aether-browser for standalone automation.
  • Login page: GET / (root, NOT /login). Form action: POST /login.
  • CSRF token: hidden <input name="csrf_token"> in HTML form, NOT cookie.

Key UI element IDs (for Playwright automation)

Element ID Purpose
Cluster dropdown #clusterSelect Select cluster to manage
Deploy mode #deployMode OVN or Keepalived
OVN network #deployNetwork Select network (populates IP dropdowns)
LB VIP select #deployLBVIPSelect Choose VIP from UPLINK range
HP01 IP select #deployHP01IPSelect Choose HAProxy 01 IP
HP02 IP select #deployHP02IPSelect Choose HAProxy 02 IP
CPU limit #deployCPU Default: 2
Memory limit #deployMemory Default: 1 GB
Service VIP #serviceVIP VIP by ID (e.g., value="41")
Service mode #serviceMode tcp, http, https-passthrough, https-termination
Service port #servicePort Listen port
Service balance #serviceBalance roundrobin, leastconn, source, first
Health check #serviceHealthCheck Checkbox, default: checked
Backend name input[name="backend_name_N"] Nth backend name
Backend IP input[name="backend_ip_N"] Nth backend IP
Backend port input[name="backend_port_N"] Nth backend port

Key JS functions: showEditServiceModal(serviceId), showServiceHealthStats(serviceId, name), reloadHAProxyConfig(), showAddServiceModal(), showAddVIPModalWithVIPs().

Key API routes (session-authenticated, use via Playwright page.evaluate)

Route Method Purpose
/haproxy/infrastructure/{cluster_id} GET Deployment state (JSON)
/haproxy/infrastructure/deploy POST Deploy infrastructure
/haproxy/service POST Create service
/haproxy/service/{id} PUT Update service
/haproxy/service/{id} DELETE Delete service
/haproxy/reload/{cluster_id} POST Reload HAProxy config
/haproxy/infrastructure/{cluster_id} DELETE Remove infrastructure

API field names (validated 2026-02-24)

Deploy infrastructure (POST /haproxy/infrastructure/deploy): cluster_id (int), ovn_network, lb_vip (NOT vip), haproxy_01_ip, haproxy_02_ip, cpu_limit (string), memory_limit (string, e.g. "1GB")

Create service (POST /haproxy/service): vip_id (int, NOT VIP address), name, description, hostname, listen_port (int), mode, balance_method, health_check_enabled (bool), session_persistence (bool), backends array with objects: { name, target_ip, target_port (int), weight (int) }

Container naming

ffsdn-haproxy-{clusterID}-01 and ffsdn-haproxy-{clusterID}-02 For cluster 52: ffsdn-haproxy-52-01, ffsdn-haproxy-52-02.

Lab IP assignments (live, validated 2026-02-24)

Component IP
VIP 192.168.103.200
HAProxy 01 10.10.10.50 (oc-node-01)
HAProxy 02 10.10.10.51 (oc-node-02)
nginx-lb-01 10.10.10.60
nginx-lb-02 10.10.10.61
nginx-lb-03 10.10.10.62

Validated gotchas

  • OVN LB not created by Aether: Aether deploys HAProxy containers and ACLs but does NOT create the OVN load balancer (incus network load-balancer). You must create it manually after deployment. See guide Step 3.
  • HTTP mode health checks: Aether generates option httpchk (sends OPTIONS /). Nginx returns 405 → backends marked DOWN. Use TCP mode for nginx backends, or configure nginx to accept OPTIONS.
  • HP-01 may not auto-start: After deploy, ffsdn-haproxy-52-01 was sometimes found Stopped. Check both containers post-deploy. Not always reproducible — both started on the 2026-02-24 re-validation.
  • OVN VIP external access: Aether ACLs only allow ingress from source: VIP_IP. External traffic preserves original client IP → rejected. Fix: add broad ingress rule to each HAProxy ACL: incus network acl rule add <remote>:<acl> ingress action=allow protocol=tcp destination_port=80,443 Must re-apply after infrastructure redeploy.
  • Internal VIP not reachable: The manually-created OVN LB handles external-to-internal traffic but does NOT hairpin for internal-to-VIP requests from the same logical switch. Use HAProxy localhost for internal tests.
  • OVN LB has no health checking: When one HAProxy instance is stopped, the OVN LB continues routing ~50% of connections to the dead backend, causing timeouts. Failover is not instant. Consider Keepalived mode for production deployments requiring true active/standby failover.
  • Static IPs on OVN: Use ipv4.address device key on the NIC device, not systemd-networkd: incus launch ... -d eth0,ipv4.address=10.10.10.60.

Key differences from deploy-awx

  • No VM creation: HAProxy uses Aether-managed containers, not a standalone VM.
  • Playwright auth: Uses browser automation, not JWT or basic auth.
  • No --heal action: HAProxy is fully Aether-managed. Recovery = --cleanup + --deploy.
  • Backends via incus CLI: Test nginx backends created directly with ipv4.address device key.