Docker HTTPS TLS握手超时,MTU问题,是何方神圣作祟?

摘要:前言 最近遇到一个很头疼的问题: 在 Docker 容器里用 GoPythonNode 等发起 HTTPS POST 请求,总是卡死,报错: nethttp: TLS handshake timeout 但奇怪的是:从容器里 ping
前言 最近遇到一个很头疼的问题: 在 Docker 容器里用 Go/Python/Node 等发起 HTTPS POST 请求,总是卡死,报错: net/http: TLS handshake timeout 但奇怪的是:从容器里 ping 目标域名/IP 是通的,而且延迟也很正常 用 curl -v https://xxx 测试,也卡在 TLS 握手阶段,半天出不来结果 乍一看像网络不通,但 ping 又没问题,DNS 也解析正常,简直让人怀疑人生。 真相其实很简单:MTU 不匹配 ping 通 ≠ HTTPS 通 ping 发的是很小的 ICMP 包(通常几十字节) TLS 握手要交换证书、密钥等信息,一个 ClientHello + ServerHello + Certificate 链加起来轻松几百到一千多字节 当网络路径上存在 MTU 不一致 的时候,小包能过,大包就会被丢弃或分片失败,尤其当中间路由器把 “不允许分片” 的包直接丢掉时(这就是经典的黑洞路由),就表现为:握手超时。 我们当时的环境对比: 容器里面的网卡(eth0):MTU = 1500(Docker bridge 默认值) 宿主机的外网网卡(ens3):MTU = 1450(云厂商常见设置,比如某些香港/国内的云主机、OpenStack 等) 结果就是:容器发出去的大包,到宿主机网卡这里超出了 1450,被直接丢了 → TLS 握手永远完成不了。 怎么快速确认是不是 MTU 问题? 进容器执行下面这条命令(把 目标换成你实际访问的 IP 或域名): # 测试能否发 1500 字节的包(1472 + 28字节 ICMP/IP 头) ping -M do -s 1472 www.google.com 如果报 packet too big / Frag needed / 直接不通 → 说明路径 MTU < 1500 再逐步把 1472 往下降,比如 1420、1372、1320…… 直到能 ping 通 能通的最大值 + 28 ≈ 当前路径实际 MTU 我们测到大概在 1420 左右就能通,说明路径 MTU 很可能就是 1450 左右。 最终的解决办法(最简单有效) 编辑 Docker 的全局配置文件: sudo vim /etc/docker/daemon.json 加入(或修改成): { "mtu": 1400 } 或者更保守一点: { "mtu": 1300 } 然后重启 Docker: sudo systemctl daemon-reload sudo systemctl restart docker 注意:重启 Docker 会让正在运行的容器全部停止,需要重新 docker-compose up 或 docker run。 改完之后,新建的容器 MTU 就会自动变成 1400/1300,问题瞬间解决,HTTPS 请求秒通。 其他可选的解决方式(看场景选) 只改某个网络的 MTU(推荐 docker-compose 项目) networks: default: driver: bridge driver_opts: com.docker.network.driver.mtu: 1400 单个容器启动时指定 docker run --mtu=1400 ... 宿主机网卡改回 1500(不推荐) 云厂商很多默认就是 1450/1400,强行改回去可能会导致宿主机本身的外网访问出问题。 总结 容器 HTTPS 握手超时 + ping 通 → 十有八九是 MTU 惹的祸 尤其是用云主机、自建 K8s、OpenStack、VPN、Overlay 网络的时候,优先检查宿主机 MTU 和 Docker 默认 1500 是否匹配。 下次再遇到类似诡异网络问题,别急着怀疑代码,先比对一下 ip link show 的 MTU 值,往往能省很多时间。