Linux 反向代理 HAProxy Load Balancer ACME TLS Runtime API

HAProxy 3.4 把 backend 從設定檔搬到 Runtime API:reload 那條裂縫終於補起來,順便帶進 ACME dns-01 跟證書壓縮

HAProxy 3.4 讓 add backend、add server、publish backend 全部從 Runtime API 走,不用再靠 haproxy -sf 換行程;同一版還把 healthcheck 拉成可重用 section、內建 ACME 支援 dns-01、TLS 憑證壓縮跟 QMux 一次補上。本文拆解 reload-free 的實務走法、跟現行 CI/CD 對接的方式,以及為什麼證書壓縮不是新協定卻是這一版才啟用的預設。

HAProxy 這幾年在效能榜上一直沒輸過,但操作模型有個大家心照不宣的裂縫:改一組 backend 得改 haproxy.cfghaproxy -c 驗證、然後 systemctl reload 觸發 -sf 把舊行程交棒。SO_REUSEPORT 讓兩個行程可以同時 bind、舊連線繼續由舊行程收尾,但這個交棒過程並不是零成本——config 越大、TLS session cache 越熱、connection tracking 越複雜,reload 的抖動就越明顯。HAProxy 3.4 在 2026 年 6 月釋出,把 backend、server 的建立與刪除全部搬到 Runtime API,宣告 HAProxy 正式進到 reload-free orchestration 的時代。同一版還把 healthcheck 拆成可重用 section、內建 ACME 支援 dns-01、預設啟用 TLS 憑證壓縮、以及一個叫 QMux 的實驗性 QUIC over TCP 層——對已經在跑 HAProxy 的機器來說,這一版值得認真評估升級。

reload 那條裂縫從哪裡來

haproxy -sf 的做法是:新行程啟動、bind 到同一個 port(透過 SO_REUSEPORT)、對舊行程送 SIGUSR1,舊行程停止 accept 新連線、把手上的 keep-alive 連線在下個請求邊界關掉。這個模型解決了大部分 zero-downtime reload 的問題,但沒解決兩件事:一是短暫的雙行程期間所有 stick table、session cache、connection counter 都是分裂的,backend 統計數字會出現詭異跳動;二是 config 一大起來,parser 跟 SSL context 初始化本身就要幾百毫秒到幾秒,這段時間新連線會被 kernel accept queue 收著等——量大的話後端會看到一波 P99 尖峰。

Kubernetes ingress 那邊靠 Envoy 走的 xDS 動態 config 推送早就避開這條路徑,HAProxy 在 3.4 之前的做法則是 server template:用 server-template web 1-100 0.0.0.0:80 disabled 先開一堆空 slot,跑起來後透過 Runtime API 的 set server 把 IP 填進去。這招管用但難看——空 slot 佔記憶體、slot 數量寫死在 config 裡、要新增 backend 本身還是得 reload。

另一個常被忽略的成本是 stats 監控。Prometheus 從 HAProxy exporter 拉的 counter 在每次 reload 都會被重置,Grafana 面板要嘛用 resets() 修正、要嘛接受計數斷層。跑一天內幾十次部署的環境,這個問題就會從「小麻煩」變成「監控完全失真」。

Dynamic Backends 把 backend 的生命週期完全搬走

3.4 的 Runtime API 多出一整組 backend 生命週期指令,可以透過 stats socket 或 master CLI 打進去:

1
2
3
4
5
6
> add backend api-v2 from default_be mode http
> add server api-v2/pod-01 10.0.1.10:8080 check
> add server api-v2/pod-02 10.0.1.11:8080 check
> enable server api-v2/pod-01
> enable server api-v2/pod-02
> publish backend api-v2

publish 之前這個 backend 存在但不會被 use_backenddefault_backend 選中,等於一個 staging 狀態,剛好對應 CI/CD 的 pre-warm。把整套流程套進 GitOps 的 controller,等於 HAProxy 對外提供的是一個真正意義上的 API,而不是靠檔案系統事件 + hup 訊號的黏合。

刪除也不是硬砍。3.4 引入 wait srv-removable 判斷伺服器上還有沒有 in-flight 連線:

1
2
3
4
> wait 5s srv-removable api-v2/pod-01
> del server api-v2/pod-01
> unpublish backend api-v1
> del backend api-v1

wait 這個指令會阻塞到條件成立或逾時,這對 rolling deploy 的 controller 非常重要——script 不用自己輪詢 stats。搭配 virtual map(add map [email protected] /v2 api-v2)可以把 path routing 也做成 runtime 動態,等於整條 blue/green 或 canary 都不用碰 config 檔。

要注意的是 default_backenduse_backend 引用了還沒 publish 或已 unpublish 的 backend 時,那個 rule 會被略過。3.4 加了 force-be-switch 選項可以覆寫這個行為,但預設關閉是對的——正常運作時不應該有 traffic 打到「本來要有但暫時消失」的 backend。

healthcheck 這個 section 早該有

HAProxy 一直沒有可重用的 health check 定義。同一組 HTTP check 要用在十個 backend 就得複製十次,還常常忘記同步。3.4 把 healthcheck 拉成 top-level section:

1
2
3
4
5
6
7
8
9
10
11
12
healthcheck api-http
type httpchk
http-check connect alpn h2
http-check send meth GET uri /healthz ver HTTP/2 hdr Host api.example.com
http-check expect status 200

backend api-primary
server p1 10.0.1.20:8443 check healthcheck api-http
server p2 10.0.1.21:8443 check healthcheck api-http

backend api-shadow
server s1 10.0.2.20:8443 check healthcheck api-http

看起來只是語法糖,實際上把 health check 從「backend 屬性」升級成獨立資源,同一個 backend 裡不同 server 也能各自套不同的 healthcheck,這對混合部署(一組 HTTP/2 pod、一組 HTTP/1.1 legacy)的過渡期特別有用。健康檢查支援的協定也不只 HTTP——TCP、SMTP、Redis 檢查都能定義成 section 共用,這在跑 mail relay 或 Redis sentinel-less 拓撲時省下大量複製貼上。

Runtime API 也認得這些新 section,show healthcheck 可以列出目前所有已註冊的健康檢查定義,方便 controller 在 push config 前先確認 dependency 已就位。

TLS 憑證壓縮不是新協定,是這一版才啟用預設

RFC 8879 定義的 TLS Certificate Compression 是 2020 年就標準化的東西,Chrome、Firefox 早就支援。HAProxy 3.4 之前要靠 patch OpenSSL 或改 config 打開,3.4 加了 tune.ssl.certificate-compression 全域指令,預設值 auto 會跟隨底層 TLS library 的設定。

證書壓縮的實務效益取決於憑證鏈長度。單張 Let’s Encrypt 憑證加 intermediate 大約 3–4 KB,壓縮後省下 1–1.5 KB。看起來不多,但這是 TLS handshake 第一個 flight 就得傳的資料,直接影響 initial congestion window 能不能一次塞完;在高延遲鏈路(跨洋、行動網路)上省下的 RTT 是實打實的。跑 QUIC 就更明顯,QUIC 的 CRYPTO frame 塞在 Initial packet 裡,MTU 一小就得多一次 flight。

3.4 預設走 auto 是務實的選擇——大部分場景直接受惠,少數需要精準控制 handshake 大小的(例如做低階網路除錯)可以顯式關掉。

ACME 內建 dns-01 讓 wildcard 不用再多養一支 client

HAProxy 3.2 開始塞進內建 ACME client,但只支援 http-01 challenge。這代表 wildcard 憑證還是得靠外部 acme.sh、certbot 之類的工具,然後把檔案掛回 HAProxy 的 SSL crt list。3.4 補上 dns-01 challenge,配合新的 challenge-ready 指令告訴 HAProxy 怎麼確認 TXT record 已經全球生效:

1
2
3
4
5
6
7
acme letsencrypt
directory https://acme-v02.api.letsencrypt.org/directory
account-key /etc/haproxy/acme/account.key
challenge dns-01
challenge-ready dns
dns-delay 30s
dns-timeout 5m

challenge-ready dns 的做法是 HAProxy 自己查權威 nameserver 直到看到 TXT record,避免了「DNS provider API 回報成功但實際 propagation 還沒完成」的競態。這比大部分 shell script 寫的 sleep 60 檢查邏輯可靠得多。

搭配另一個新引數 ips,SAN 欄位可以直接放 IP 位址(前提是 Let’s Encrypt 或其他 CA 支援 IP SAN,目前仍是有限支援),這對純用 IP 對外的 API gateway 是個小福音。

QMux 是給特殊網路環境的實驗品,多數人不用管

3.4 引入的 QMux 常被誤會成「HAProxy 支援 QUIC 了」,但 HAProxy 早就支援 QUIC(前後端都有),QMux 是把 QUIC 的 stream 多工層抽出來,讓它可以跑在 TCP 之上而不是 UDP。

用途很窄:某些企業防火牆或雲環境把 UDP 全擋(QUIC 走 UDP 443),這時 QMux 讓 HTTP/3 語意(多工、header compression、0-RTT resumption)可以退回到 TCP 傳輸。實驗性標記還在,需要 expose-experimental-directives 打開,而且必須顯式指定 alpn h3。除非明確知道自己在跟一個封鎖 UDP 的環境打交道,這個功能可以暫時忽略。

該不該升級

3.4 是 LTS,支援期到 2028 年 Q2。從 3.2 或 3.3 升上來的動機夠強:Dynamic Backends 直接改變 orchestration 模型,healthcheck section 消掉一大堆重複設定,ACME dns-01 讓內建 ACME 真的能取代 acme.sh。從 2.x 系列跳的話得先看 deprecated 清單,compression-direction 這類舊指令要換成 filter comp-reqfilter comp-res,OpenTracing 支援已經標定在 3.5 移除、要換 OpenTelemetry。

實務上建議先在 staging 用 3.4 跑一段時間,把 Runtime API 的 backend lifecycle 接進現有 deployment pipeline——這是 3.4 最有價值的地方,但也是需要重新設計運維流程的地方。不接 API 只當一般 reload 用的話,升級收益就剩證書壓縮跟 ACME dns-01 這兩塊。

想在臺灣機房部署 HAProxy 3.4 或其他反向代理/負載平衡架構的團隊,NCSE Network 的 VPS 產品線提供 Intel Gold CPU 加 NVMe SSD 的臺灣是方電訊機房節點,適合作為前端 LB 與內部服務池的落腳點;有 IP Transit 或多線路整合需求也可以直接聯繫規劃。

需要穩定的雲端主機?

NCSE Network 提供企業級 VPS,7 天免費試用,臺灣是方電訊機房,99% SLA 保證。

查看 VPS 方案 →