Problem – HAProxy traffic imbalance during a blue‑green deployment
During a scheduled blue‑green rollout of a new service version, the production HAProxy 2.8 front‑end observed a ~40 % increase in query latency. The spike appeared only after the first batch of green servers was added to the blue_green backend and persisted until the rollout completed.
Typical log excerpts collected at the time of the incident:
[WARN] 2024-06-04 12:15:23.123 [haproxy] Server green-svc-01 is in state UP, weight 0 – no traffic will be sent
[ERR] 2024-06-04 12:16:01.456 [haproxy] balance: roundrobin – server green-svc-01 connection errors: 5/10
[WARN] 2024-06-04 12:17:08.789 [haproxy] Sticky session mismatch – stick-table entry not found for client 203.0.113.45
[INFO] 2024-06-04 12:18:30.001 [haproxy] Server green-svc-02 weight changed from 100 to 0 by runtime API
These messages match the common errors documented in community threads such as GitHub issue #2157 and the Server Fault discussion #102394.
Root Cause Analysis
Three intertwined factors caused the imbalance:
- Zero or mismatched server weights – HAProxy treats a server with weight
0as “no traffic”. In the incident, the green pool was added with the correctweight 100in the static configuration, but a runtime API call (triggered by an automated deployment script) inadvertently set the weight to0for the first green instance. According to the HAProxy 2.8 Reference – “Server State Transitions and Runtime Weight Adjustments”, any weight change to0immediately removes the server from the load‑balancing rotation. - Health‑check failures on the green pool – The green servers reported
DOWNafter a single failed health check because the health‑check interval was too aggressive (inter 2s) and the new version required a longer warm‑up (check rise 5). The official HAProxy Health Checks documentation notes that aggressive health checks can cause premature down transitions during rollouts. - Sticky‑session ACLs without proper stick‑table synchronization – The deployment used an ACL‑based split (
acl is_green path_beg /v2/) combined with astick-table type ip size 1M expire 30s. Clients that had already been bound to the blue pool continued to be routed there, while new connections were directed to the partially‑available green pool, creating an uneven distribution. This pattern is described in the HAProxy Enterprise Documentation – “Blue‑Green Deployment Patterns”.
The combination of weight drift (GitHub issue #2157) and health‑check induced fallback (Server Fault #102394) forced ~80 % of traffic onto the blue pool, saturating its CPU and queue depth, which manifested as the observed latency spike.
Investigation and Debugging Steps
1. Verify backend server states and weights
# Using the HAProxy Runtime Socket
echo "show servers state" | socat /var/run/haproxy.sock stdio
Expected output (healthy state):
# Backend: blue_green
# Server: blue-svc-01 UP weight 100 qcur 0 qmax 0 slow_start 0
# Server: green-svc-01 UP weight 100 qcur 0 qmax 0 slow_start 0
If any green server shows weight 0 or DOWN, the imbalance is confirmed.
2. Inspect HAProxy logs for weight changes and health‑check results
grep "weight changed" /var/log/haproxy.log
grep "health check" /var/log/haproxy.log | grep green
3. Capture a short packet trace to see actual distribution
tcpdump -i eth0 -nn -s 0 -c 200 'port 80 and dst host 10.0.0.10' -w /tmp/haproxy_trace.pcap
Analyze the pcap with tcpdump -r or Wireshark to verify the proportion of requests hitting each backend IP.
4. Review the deployment automation that manipulates runtime weights
# Example snippet from the CI/CD script
curl -s -X POST "http://127.0.0.1:9999/v2/services/haproxy/runtime" \
-d "action=set-weight&backend=blue_green&server=green-svc-01&weight=0"
Check for accidental execution of this block during the rollout.
5. Validate stick‑table entries for a sample client IP
echo "show table blue_green" | socat /var/run/haproxy.sock stdio | grep 203.0.113.45
Resolution – Restoring balanced traffic
Configuration changes
Before (problematic snippet):
backend blue_green
mode http
balance roundrobin
server blue-svc-01 10.1.0.11:80 weight 100 check
server blue-svc-02 10.1.0.12:80 weight 100 check
server green-svc-01 10.2.0.11:80 weight 100 check inter 2s rise 1 fall 3
server green-svc-02 10.2.0.12:80 weight 100 check inter 2s rise 1 fall 3
acl is_green path_beg /v2/
use_backend green if is_green
stick-table type ip size 1M expire 30s store http_req_rate(10s)
stick on src
After (balanced, resilient configuration):
backend blue_green
mode http
balance roundrobin
# Ensure both pools have equal weight and health‑check grace
server blue-svc-01 10.1.0.11:80 weight 100 check inter 5s rise 3 fall 3
server blue-svc-02 10.1.0.12:80 weight 100 check inter 5s rise 3 fall 3
server green-svc-01 10.2.0.11:80 weight 100 check inter 5s rise 3 fall 3
server green-svc-02 10.2.0.12:80 weight 100 check inter 5s rise 3 fall 3
# ACL‑based split with explicit weight scaling
acl is_green path_beg /v2/
use_backend green if is_green
use_backend blue if !is_green
# Stick‑table for session affinity – shared across both pools
stick-table type ip size 1M expire 30s store http_req_rate(10s)
stick on src
Key adjustments:
- Increased
interandriseto give the new version a warm‑up window (HAProxy Config Manual – Load Balancing Algorithms). - Removed any runtime weight‑set calls that could zero out a server.
- Ensured both blue and green pools are referenced in
use_backendstatements, preventing accidental fallback to a single pool. - Added comments to make the weight policy explicit for future automation.
Runtime fix (quick remediation)
# Restore green server weights to 100 via the runtime socket
echo "set weight blue_green/green-svc-01 100" | socat /var/run/haproxy.sock stdio
echo "set weight blue_green/green-svc-02 100" | socat /var/run/haproxy.sock stdio
# Force a health‑check re‑evaluation
echo "disable server blue_green/green-svc-01" | socat /var/run/haproxy.sock stdio
echo "enable server blue_green/green-svc-01" | socat /var/run/haproxy.sock stdio
This immediate action brought the green pool back into rotation, reducing latency within minutes.
Verification – Confirming that traffic is balanced again
1. Check server weights and health status
echo "show servers state" | socat /var/run/haproxy.sock stdio | grep green
All green servers should report UP and weight 100.
2. Observe request distribution
# HAProxy statistics page (if enabled)
curl http://haproxy-admin.local/; grep green /var/lib/haproxy/stats
Expect roughly 50 % of srv_conn for blue and green servers.
3. Measure latency post‑fix
curl -w "%{time_total}\n" -o /dev/null https://api.example.com/v2/resource
The 95th‑percentile latency should return to baseline (e.g., from 210 ms back to ~150 ms).
Prevention – Guardrails for future blue‑green rollouts
- Static weight sanity check: Add a CI validation step that parses the HAProxy config and ensures every server in a blue‑green pair has the same non‑zero weight.
- Graceful health‑check parameters: Use
inter≥ 5 s andrise≥ 3 for new versions, as recommended in the HAProxy Health Checks docs. - Runtime API audit: Restrict access to the HAProxy runtime socket and log every
set weightcall with a timestamp and CI job ID. - Stick‑table consistency: When using ACL‑based splits, configure a shared stick‑table at the
frontendlevel to avoid per‑backend affinity mismatches. - Automated traffic‑share verification: After each deployment step, run a short
curlprobe against both version paths and assert that the response counts differ by no more than 10 %.
FAQ – Common follow‑up questions
- Why did only the green pool experience weight 0 while the blue pool stayed at 100?
The deployment script executed aset-weightAPI call for every newly added server. A bug in the script used the variable${SERVER_NAME}before it was populated, resulting in a call that targeted the first green server with weight 0. - Can I use
leastconninstead ofroundrobinto avoid this issue?
leastconnbalances based on active connections, but it still respects server weights. If a server’s weight is 0, it will be excluded regardless of the algorithm. The root cause must be fixed before changing the algorithm. - How do I safely test a blue‑green split without impacting production latency?
Deploy the green pool behind a dedicatedfrontendthat mirrors production traffic on a canary IP. Use HAProxy’stcp-request content track-sc0 srcto share stick‑tables between canary and production, then gradually increase the ACL match percentage. - What monitoring alerts should I set to catch weight drift early?
Create an alert on the metrichaproxy_server_weight{backend="blue_green",server=~".*green.*"} != 100and on health‑check failure rates > 5 % for any server in the blue‑green backend. - Is there a built‑in HAProxy feature to automatically rebalance weights during a rollout?
HAProxy 2.8 introduceddynamic-weightvia the Runtime API, but it requires an external controller (e.g., Consul or a custom script) to compute and apply balanced weights. It does not replace the need for explicit health‑check tuning.
Related Topic Hub: Distributed Systems Troubleshooting Hub