map ssl preread protocol端口复用
nginx的口复stream模块里藏着个冷门但极其实用的指令——preread。它能在不解析完整HTTP请求的口复情况下,仅凭前几个字节就判断出流量该往哪走。口复配合SSL握手时的口复SNI字段,就能实现一个端口同时承载HTTPS、口复SSH、口复甚至任意TCP协议。口复
具体做法不复杂。口复stream块里定义个preread区域,口复用map指令匹配$ssl_preread_server_name变量。口复这个变量在TLS握手阶段就被nginx捕获,口复根本不需要等完整请求到达。口复比如我拿443端口做实验,口复同一个IP上跑着三个服务:blog.example.com走Web,口复git.example.com走SSH,口复还有个内部监控系统走自定义协议。传统方案得开三个端口,现在全挤在443上,防火墙规则瞬间清爽。
map的写法跟http块里几乎一样。关键在配置顺序——preread必须在ssl_handshake之前执行,否则拿不到SNI。踩过坑的人都知道,stream块里的timeout设置比http苛刻得多,握手超时默认30秒,实际生产环境建议压到5秒内。别问我怎么知道的,凌晨三点被报警吵醒的滋味不好受。
有个细节容易被忽略:map的fallback值必须显式声明。我见过有人没写default,结果未匹配的流量全被丢到第一个upstream,排查半天才发现是空值作祟。另外,preread buffer大小默认8k,够用但别贪心调大,内存碎片会教做人。
实际部署时,我习惯把map结果转发到不同upstream组。比如把SSH流量导到本机22端口的unix socket,Web流量走HTTP/2集群。有次手滑把default设成了回环地址,结果所有未知SNI的请求都在打转,CPU直接飙到80%。这功能越灵活越要小心,建议在map里加条日志指令,把未匹配的SNI记下来,比事后翻抓包省事多了。
安全性方面倒不用太担心。preread只读不写,不参与TLS解密,密钥材料完全碰不到。真正的加密流量在握手完成后由后端服务处理,nginx只是个聪明的交通警察。不过得提醒一句,非HTTPS流量伪装成SNI的情况偶尔发生,最好在upstream层再加层校验,别把宝全押在端口复用上。
这套方案最爽的地方是运维体验。以前要改服务端口得动防火墙、改监控、通知所有客户端,现在只要调map映射,reload一下nginx就完事。上周刚把测试环境的Redis从6379挪到443,开发同事完全无感知——他们只改了连接字符串里的端口号,剩下的交给preread搞定。
本文地址:http://www.79wt.cn/news/1202c0699873.html
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。