土法炼钢 · 系统与基础设施

【HAProxy 数据面】SSL/TLS 路径:终结与透传、握手落点与会话缓存

文章导航

分类入口
networkproxy
标签入口
#haproxy#tls#ssl#termination#passthrough#session-cache#3.4.3

目录

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 … sslmode tcp|httptune.ssl.cachesizetune.ssl.force-private-cacheStarter 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 tcpbind 不做 ssl 只转发 TCP 字节;握手在上游;HAProxy 不能看 HTTP
TCP 上终结再转发 mode tcp + bind … ssl(或不在 HTTP 模式处理) 本进程终结 TLS,后端可能是明文 TCP 或再加密(视 server 侧 ssl

Configuration Manualmodetcp 是全双工转发、不做 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 段,因此:

  1. 握手 CPU 落在 accept 到该 FD 的线程,不会自动被「空闲线程池」偷走;
  2. 握手完成前,该 FD 上还没有可多路复用的 HTTP/2 流世界;失败则连接在数据面选 backend 之前结束;
  3. 同一线程上堆积大量全握手,会直接抬高该线程上其它会话的延迟——这是线程亲和的代价,不是神秘的 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):

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 路径、检查、粘滞的交界

相对 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)

维护者 / 官方博客(B)

站内

实验台账


八、小结

  1. 先区分终结与透传bind ssl 与否、mode http/tcp 共同决定 HAProxy 能否看见 HTTP。
  2. 握手 CPU 与状态落在接受该连接的线程/FD 上下文;透传则把成本留给上游。
  3. tune.ssl.cachesize 用预分配块换更少全握手;满缓存驱逐与多进程私有缓存都会把成本打回 CPU。
  4. 证书选择与 ALPN 发生在握手期;Host ACL 不能事后改写已选证书;上下游双侧 TLS 有两套会话成本。
  5. 证书运维与密码套件清单不在本篇;热改与 reload 边界见后续 Runtime / seamless reload 篇。

系列目录 · 上一篇:Health-check 引擎 · 下一篇:Runtime API

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .