Health-check
决定 server 是否在集合内;TLS
决定字节在哪一层变成「可解析的请求」。把「配了
ssl crt」当成唯一故事会漏掉透传农场:握手根本不在
HAProxy 进程里发生。本文钉终结 vs 透传、握手与线程/FD
的关系,以及会话缓存的高层含义——不是证书签发、续期、OCSP
运维手册。
本文是「HAProxy / 数据面代理内核」系列第 10 篇(共 14 篇)。→ 系列目录
篇目 核心内容 第 9 篇 · Health-check 探活 第 10 篇 · SSL/TLS 终结 / 透传 / 会话 第 11 篇 · Runtime API 热改边界 上一篇:Health-check 引擎 · 下一篇:Runtime API
版本锚定:HAProxy 3.4.3 Configuration Manual
bind … ssl、mode tcp|http、tune.ssl.cachesize、tune.ssl.force-private-cache;Starter Guide 中与 SSL 农场 / stickiness 相关叙述。源码 tag v3.4.3。无伪造openssl s_client输出。
本篇不展开:密码套件清单、HSTS 头菜谱、客户端证书 PKI 设计、QUIC/H3 证书细节(仅作边界提示)。证书文件如何摆放见 network/56。
一、两条路径:终结 vs 透传
| 模式 | 典型配置意图 | HAProxy 看见什么 |
|---|---|---|
| TLS 终结 | bind … ssl crt …,常配
mode http |
握手在本进程完成;之后是明文 HTTP,可做 ACL/HTX/改写 |
| TLS 透传 | mode tcp,bind 不做
ssl |
只转发 TCP 字节;握手在上游;HAProxy 不能看 HTTP |
| TCP 上终结再转发 | mode tcp + bind … ssl(或不在
HTTP 模式处理) |
本进程终结 TLS,后端可能是明文 TCP 或再加密(视 server
侧 ssl) |
Configuration Manual 对
mode:tcp 是全双工转发、不做 L7
检查,适合 SSL、SSH、SMTP 等;http
才深入分析请求并启用 L7 过滤与交换。「SSL
农场」并不自动等于终结:很多 SSL 农场用
mode tcp 做透传或用 L4 调度,此时 content
switching 只能基于 TCP 层样本(或 PROXY 头等),不能基于
Host 头——除非先终结。
flowchart TD
client["client"] --> bind{"bind has ssl?"}
bind -->|"no, mode tcp"| pass["byte relay passthrough"]
pass --> up_tls["upstream terminates TLS"]
bind -->|"yes"| hs["TLS handshake in HAProxy"]
hs --> mode{"mode http?"}
mode -->|"yes"| htx["HTX / ACL / HTTP LB"]
mode -->|"no"| tcp_clear["TCP relay of cleartext"]
htx --> be["backend"]
tcp_clear --> be
对照 Envoy listener / TLS inspector:Envoy 用 filter chain 匹配 SNI 等再选链;HAProxy 用 bind/crt-list/SNI 选择证书,再用 mode 决定是否进入 HTTP 语义。两者都要先回答「谁终结」。
二、握手落在哪:相对线程与 FD
多线程模型下(见维护者 Multithreading in HAProxy 与系列第 2 篇):与某一会话相关的实体钉在接受该连接的线程上,该会话的处理串行化。TLS 握手作为该连接建立路径上的重 CPU 段,因此:
- 握手 CPU 落在 accept 到该 FD 的线程,不会自动被「空闲线程池」偷走;
- 握手完成前,该 FD 上还没有可多路复用的 HTTP/2 流世界;失败则连接在数据面选 backend 之前结束;
- 同一线程上堆积大量全握手,会直接抬高该线程上其它会话的延迟——这是线程亲和的代价,不是神秘的 SSL 全局锁传说所能概括。
FD 层面:每个并发握手/连接占用 socket FD 与 SSL
状态对象;maxconn /
maxsslconn(若配置)与
tune.ssl.cachesize
预分配的会话缓存块,都会进入内存与 FD 规划。手册对 frontend
maxconn 给出的数量级直觉(每连接约数十 KB
量级缓冲相关消耗)在 TLS 终结场景下往往更紧,因为还叠加 SSL
结构。
透传路径:HAProxy 侧没有该连接的 TLS 状态机,握手 RTT 与证书校验成本在上游;LB 只能做 L4 决策(或 PROXY 协议注入后的有限元数据)。stickiness 若依赖 SSL ID,Starter Guide 将其列为可直接访问的粘滞元素之一——那是终结或能看见 SSL ID 的路径上的话题;纯透传看不见明文 ID,除非在别处学习。
从失败模式看:终结路径上握手失败通常在选 backend
之前结束,客户端看到的是 TLS 告警而非 HTTP
503;透传路径上「握手失败」发生在上游,HAProxy
可能只看到连接复位或超时,日志字段落在 TCP 层。排障时先确认
bind 是否 ssl,再决定该查证书/SNI
还是上游进程——相位判断错了会整段排查空转。
三、会话缓存的高层含义
tune.ssl.cachesize(Configuration
Manual):
- 设置全局 SSL
会话缓存的块数;一块大约能放下无对端证书的编码会话(文档给出来自
shctx_init相关计算的约 200 字节量级说明); - 带对端证书的会话可能占多块;
- 默认值可在构建时强制,否则默认 20000;
- 缓存满时驱逐最空闲项;更大的缓存降低驱逐,从而减少昂贵的完整握手;
- 启动时预分配;设为 0 关闭会话缓存。
tune.ssl.force-private-cache:禁用进程间共享会话缓存。文档写明:通常不该用,否则客户端打到随机进程会迫使多次协商;仅在无法使用任何
SSL 缓存同步手段的操作系统上才可能需要,并建议在 SSL
层前加一层基于哈希的负载均衡。
对数据面的含义(高层,不编造加速比):
| 现象 | 机制解读 |
|---|---|
| 缓存命中复用会话 | 省略或缩短完整握手路径,CPU/RTT 下降 |
| 缓存过小 + 高并发新客户端 | 频繁驱逐 → 全握手比例上升 → 终结线程 CPU 升高 |
| 多进程 + 强制私有缓存 | 会话粘在进程上失败 → 重复握手;现代应用应优先
nbthread 共享地址空间 |
| 关闭缓存(0) | 每个新连接倾向完整握手;仅特殊排障或极端策略 |
会话缓存优化的是重复客户端路径;无法消除首次访问或票据不可用时的全握手。容量规划应同时看:并发新连接速率、会话存活时间、以及
cachesize 预分配常驻内存。把「开了
ssl」当成固定每连接成本,而不区分全握手与恢复,会高估或低估错误的那一侧。
TLS 1.3 与票据/会话行为还受绑定选项、库版本影响;本篇不进入密码学细节,只要求容量规划时把 cachesize 预分配 算进常驻内存,而不是假定「SSL 很便宜」。
上游方向(server 侧
ssl)另有会话/校验语义(如
ssl-server-verify);那是「HAProxy
当客户端」的握手,同样落在处理该 server
连接的线程上下文中,并影响连接池复用键(SNI 等,见第 7 篇
http-reuse 属性列表)。
四、与 HTTP 路径、检查、粘滞的交界
- 终结 +
mode http:握手后进入 HTX/ACL(第 5–6 篇);可按 Host/path 内容交换。SNI 与 Host 不一致是常见配置问题,属 L7 语义,不是「TLS 坏了」。 - 透传 + stick-table:键只能来自 TCP/IP 或外置元数据;不能从加密 HTTP cookie 学习,除非旁路。
- 健康检查:
http-check connect … ssl sni …或ssl-hello-chk在检查引擎里单独握手(第 9 篇),与客户连接的会话缓存命中无关——不要用客户侧缓存命中率解释检查失败。 - Runtime / reload:证书与 bind 的热更新能力有边界,留给 第 11–12 篇;本篇只强调:reload 窗口中新旧进程各自的会话缓存不自动是同一块内存,除非文档所述的共享/继承机制适用。
相对 Envoy:二者都把重握手视为 worker/线程局部成本;Envoy 侧还有 SDS 热加载证书的控制面故事,HAProxy 侧更多是文件/crt-list + Runtime 有限面——选型时比的是控制面,不是「会不会 TLS」。
五、证书选择与 ALPN:仍在数据面,但不是运维手册
bind … ssl crt … 可指向单个 PEM 或
crt-list(多证书/SNI 映射)。握手时按客户端
SNI
等选择证书上下文——这发生在终结路径的握手阶段,早于
HTTP ACL。SNI
缺失或与证书集不匹配时,行为取决于默认证书与强制策略;结果是握手失败或落到默认证,而不是「稍后用
Host 头再选一次证书」。把内容交换用的 Host ACL
误当成选证机制,是相位错误。
ALPN(bind/server 的
alpn)影响协议协商结果,从而影响后续是否走 H2
mux、连接复用键、乃至检查指令里的 alpn
参数。它仍是握手期决策:协商失败或协商到非预期协议时,L7
规则看到的世界已经不同。QUIC/H3 的 bind 形态(如文档示例中的
quic4@… ssl crt …)把传输与 TLS
进一步绑在同一条听路上,soft-stop/迁移语义与纯 TCP FD
不同——系列将其留在开放问题,不在本篇假装已与 TCP
终结同构。
server 侧启用 ssl 时,HAProxy
作为客户端再握手;ssl-server-verify
等决定是否校验证书。双重加密(客户端→HAProxy→上游均 TLS)让
CPU
与会话缓存出现两侧:下游缓存命中不等于上游免握手。池复用键包含
SNI 表达式(第 7 篇),上游证书/SNI
变更会拆碎池命中率——表现像「复用突然变少」,根因可能在 TLS
身份,而不是 http-reuse 模式改错。
六、谱系、争论与开放问题
谱系:反向代理上的 TLS
终结把「证书与密码套件运营」从应用前移到边缘;会话复用/票据是为降低握手放大的标准工程(RFC
8446 等定义现代握手,具体会话/PSK 行为依库)。HAProxy
用进程内(可选跨进程同步)块状会话缓存,把「记得住多少会话」暴露成
tune.ssl.cachesize 这种显式旋钮。
争论:边缘终结 vs 端到端透传。终结换取 L7 策略与可观测,代价是边缘成为信任与 CPU 中心;透传保留端到端机密性,代价是 LB 变「笨」且排障信号变少。二者都是合法架构,取决于威胁模型与是否需要 ACL。
开放问题:QUIC/H3 在 HAProxy 上的成熟度与 soft-stop/连接迁移语义,使「FD + TCP 会话缓存」直觉部分失效(系列 index / 第 14 篇开放问题)。硬件 offload / kTLS 与用户态终结的分工,亦不在本篇范围,但会影响「握手落在哪颗 CPU」的答案。
七、参考资料
规范 / 官方文档(A)
- HAProxy 3.4.3 Configuration
Manual:
mode、bind的ssl/crt、tune.ssl.cachesize、tune.ssl.force-private-cache、server 侧ssl相关选项。 - HAProxy 3.4.3 Starter Guide:SSL 农场与 stickiness 中可访问 SSL ID 等叙述。
- RFC 8446(TLS 1.3)——握手/会话概念背景;实现以 HAProxy 所链 OpenSSL(或构建所选库)为准。
维护者 / 官方博客(B)
- HAProxy Technologies,Multithreading in HAProxy(会话实体线程亲和)。
站内
- 第 2 篇 · 第 5 篇 · 第 7 篇 · 第 9 篇 · 第 11 篇 · 系列目录
- Envoy main/worker/TLS · Envoy listener / filter chain
- network/56
实验台账
- 本篇无握手耗时实测;不引用未核对的「开启缓存加速 X%」。
八、小结
- 先区分终结与透传:
bind ssl与否、mode http/tcp共同决定 HAProxy 能否看见 HTTP。 - 握手 CPU 与状态落在接受该连接的线程/FD 上下文;透传则把成本留给上游。
tune.ssl.cachesize用预分配块换更少全握手;满缓存驱逐与多进程私有缓存都会把成本打回 CPU。- 证书选择与 ALPN 发生在握手期;Host ACL 不能事后改写已选证书;上下游双侧 TLS 有两套会话成本。
- 证书运维与密码套件清单不在本篇;热改与 reload 边界见后续 Runtime / seamless reload 篇。
→ 系列目录 · 上一篇:Health-check 引擎 · 下一篇:Runtime API
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【HAProxy 数据面】ACL / 规则引擎:求值顺序、副作用与 stick-table 耦合
把 ACL 与 http-request / use_backend 当作数据面机制:说明连接期到 L7 的求值顺序、动作副作用如何改写后续条件,以及与 stick-table 限流/标记的耦合点;不写 ACL 关键字百科。
【HAProxy 数据面】Backend / server / LB:算法、队列与连接复用边界
把 balance 算法、server maxconn/队列与 http-reuse 当作数据面机制:说明无持久化信息时如何选主、队列满与无 server 时如何失败,并轻对照 Envoy cluster/连接池边界。
【HAProxy 数据面】Stick-table:键类型、容量、跨线程可见性与 peers/reload
说明 stick-table 的键类型、size/expire/store、nbthread 共享内存与 nbproc 不共享的边界,以及 peers 复制与 reload 继承;覆盖表满等容量失败模式。
【HAProxy 数据面】Health-check 引擎:探活调度、httpchk/tcp-check 与 agent-check 边界
区分健康检查引擎与数据面请求调度:说明默认 TCP 探活、httpchk/tcp-check 规则集、agent-check 的独立控制面语义,以及与 soft-stop/排空的交互提示;不写伪造检查日志。