问题现象
-
宿主机可以正常访问到接口地址并且拿到数据
-
容器内不带参数访问接口,服务器可以正常访问400错误(证明响应了)
-
容器内带参数访问接口,会不响应直到被远端返回RST包,响应为
curl: (56) OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 104。
排查过程
开始怀疑是ssl/tls交换协议存在问题,查了容器内和容器外,确实有点差异:
|
|
这个结果导致我一度怀疑是宿主机linux kernel版本太低了,容器中的库用到了什么内核新的特性导致的。
直到我拿到了http协议的地址,依然现象如故。 很明显这个时候已经剥离了ssl/tls了,更大的可能是网络传输出了问题,遂在宿主机对docker0网卡进行抓包。
正常请求抓包结果:
序号 | 时间 | 源 | 目标 | 协议 | 长度 | 信息 |
---|---|---|---|---|---|---|
1 | 0.000000 | 10.210.112.69 | 19.130.176.63 | TCP | 74 | 35806 → 8310 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM TSval=1495514762 TSecr=0 WS=128 |
2 | 0.005118 | 19.130.176.63 | 10.210.112.69 | TCP | 74 | 8310 → 35806 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM TSval=783766239 TSecr=1495514762 WS=512 |
3 | 0.005172 | 10.210.112.69 | 19.130.176.63 | TCP | 66 | 35806 → 8310 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=1495514767 TSecr=783766239 |
4 | 0.005267 | 10.210.112.69 | 19.130.176.63 | HTTP | 173 | GET /service/api/getPopulation HTTP/1.1 |
5 | 0.010290 | 19.130.176.63 | 10.210.112.69 | HTTP/JSON | 424 | HTTP/1.1 400 Bad Request , JSON (application/json) |
6 | 0.010343 | 10.210.112.69 | 19.130.176.63 | TCP | 66 | 35806 → 8310 [ACK] Seq=108 Ack=359 Win=30336 Len=0 TSval=1495514773 TSecr=783766244 |
7 | 0.010438 | 10.210.112.69 | 19.130.176.63 | TCP | 66 | 35806 → 8310 [FIN, ACK] Seq=108 Ack=359 Win=30336 Len=0 TSval=1495514773 TSecr=783766244 |
8 | 0.011468 | 19.130.176.63 | 10.210.112.69 | TCP | 66 | 8310 → 35806 [FIN, ACK] Seq=359 Ack=109 Win=29184 Len=0 TSval=783766246 TSecr=1495514773 |
9 | 0.011579 | 10.210.112.69 | 19.130.176.63 | TCP | 66 | 35806 → 8310 [ACK] Seq=109 Ack=360 Win=30336 Len=0 TSval=1495514774 TSecr=783766246 |
异常请求抓包结果:
序号 | 时间 | 源 | 目标 | 协议 | 长度 | 信息 |
---|---|---|---|---|---|---|
1 | 0.000000 | 10.210.112.69 | 19.130.176.63 | TCP | 74 | 33662 → 8310 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM TSval=1494493375 TSecr=0 WS=128 |
2 | 0.001855 | 19.130.176.63 | 10.210.112.69 | TCP | 74 | 8310 → 33662 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM TSval=782744858 TSecr=1494493375 WS=512 |
3 | 0.001940 | 10.210.112.69 | 19.130.176.63 | TCP | 66 | 33662 → 8310 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=1494493377 TSecr=782744858 |
4 | 0.002037 | 10.210.112.69 | 19.130.176.63 | HTTP | 200 | GET /service/api/getPopulation?pageNumber=1&pageSize=1000 HTTP/1.1 |
5 | 0.042302 | 19.130.176.63 | 10.210.112.69 | TCP | 66 | 8310 → 33662 [ACK] Seq=1 Ack=135 Win=30208 Len=0 TSval=782744899 TSecr=1494493377 |
34 | 60.224484 | 10.210.112.69 | 19.130.176.63 | TCP | 66 | [TCP Keep-Alive] 33662 → 8310 [ACK] Seq=134 Ack=1 Win=29312 Len=0 TSval=1494553600 TSecr=782744899 |
35 | 60.225605 | 19.130.176.63 | 10.210.112.69 | TCP | 66 | [TCP Previous segment not captured] 8310 → 33662 [ACK] Seq=15929 Ack=135 Win=30208 Len=0 TSval=782805081 TSecr=1494493377 |
72 | 120.384547 | 10.210.112.69 | 19.130.176.63 | TCP | 66 | [TCP Keep-Alive] 33662 → 8310 [ACK] Seq=134 Ack=1 Win=29312 Len=0 TSval=1494613760 TSecr=782744899 |
73 | 120.387179 | 19.130.176.63 | 10.210.112.69 | TCP | 54 | 8310 → 33662 [RST] Seq=1 Win=0 Len=0 |
其中在不带参数时我们能够明显看到,整个正常的请求和响应过程。但是在异常的部分我们能看到:
- 前3个包显示TCP三次握手正常完成。
- 第4个包是HTTP GET请求,发送成功。
- 第5个包显示服务器确认收到了请求。
- 之后出现了较长时间的空闲(约60秒)。
- 第34和35包显示客户端发送了Keep-Alive包,服务器正常响应。
- 又经过约60秒后,客户端再次发送Keep-Alive包(第72包)。
- 服务器以RST包响应(第73包),表明连接被重置。
问题分析如下:
- TCP连接建立成功,HTTP请求也发送成功。
- 服务器似乎没有及时响应HTTP请求(但是实际上服务器响应很快,所以可能发生了丢包或者其他的问题)。
- 客户端发送Keep-Alive包以维持连接。
- 最终,服务器因响应超时而选择重置连接。
很明显是第二步包传输中出问题了,因为宿主机没有这个问题,所以我们完全有理由怀疑是容器网络转换把这个问题搞出来了,那就回头来看dockerd,查遍了dockerd的日志一切正常,没有响应任何错误。(后面想起来真蠢,2层的错误dockerd当然不会知道)
又想起同一个地址不带参数和带参数会导致不同的现象,这里面最大的不同不就是响应体的大小吗?想到这里,赶快看了下宿主机eth0的配置:
|
|
我突然悟了,宿主机MTU有人改小了(1460
)。docker默认的mtu是1500,这玩意比宿主机大,当 docker0 MTU大于宿主机MTU(如 本次的1460)时,frame需要再次分片,如果传输中有不允许分片或者其他类似的标志,那就会导致网卡丢弃数据包,就会出现「小包可通,大包不通」的情况。
那就好办了,修改下/etc/docker/daemon.json
|
|
重启就解决了。
最佳实践
- 在使用容器或虚拟化技术时,应确保网络各层的MTU设置一致。
- 可以考虑使用路径MTU发现(PMTUD)技术来动态调整MTU大小。
- 在网络配置中,始终选择较小的MTU值可以避免类似问题。