背景:

上文提到刚搭建了docker-compose 部署了harbor 镜像仓库v2.12.4,镜像最终存储到远端S3上,但是本地 docker push上传镜像却在push完之后一直卡住了。。。
# docker push harbor.domain.net/goharbor/harbor-exporter:v2.12.4
The push refers to repository [harbor.domain.net/goharbor/harbor-exporter]
82ad754be5b8: Pushing [==================================================>] 4.096kB
5f70bf18a086: Pushing 1.024kB
41f53763f274: Pushing 1.536kB
3690ac561010: Pushing [==================================================>] 27MB
167b50cbc9a5: Pushing [==================================================>] 4.145MB
544b14d8a201: Waiting
4724408ab3be: Waiting
d6d0586ce802: Waiting
排查:
现象:docker login以及harbor 界面使用是没问题的,只是push镜像有问题。
1. 排查前先理清镜像上传的流程:
docker client
↓
nginx(proxy)
↓
core(harbor-core)
↓
registry(harbor-registry)
↓
S3 backend (对象存储)
2. 分析问题
异常第一反应就是看日志,所以,想看下core以及registry是否有异常,通过docker logs看即可,但是看log没有什么异常信息。
因为push并无任何报错,client也不提示任何信息,harbor相关服务也没有错误日志,所以怀疑是网络问题,而卡住所以怀疑是MTU问题。
我们知道,TCP和MTU一个工作在三层一个是二层的,所以TCP不知道MTU的问题,它只知道包发出去了但是没有ACK回来,按照TCP的规范,TCP会指数退避重试,每次超时重传间隔会越来越长,只要连接还在,应用层就不会报错,TCP没有告诉应用层连接不可用,所以应用层只能"挂着"。
可以在宿主机抓包,能够抓到docker0给容器了需要分包的icmp报文
# tcpdump -nnee -i any icmp and 'icmp[0]=3 and icmp[1]=4'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
17:19:26.356337 Out 02:42:bd:a8:90:1c ethertype IPv4 (0x0800), length 592: 172.18.0.1 > 172.18.0.8: ICMP 10.118.60.33 unreachable - need to frag (mtu 1450), length 556
17:19:26.356348 Out 02:42:bd:a8:90:1c ethertype IPv4 (0x0800), length 592: 172.18.0.1 > 172.18.0.8: ICMP 10.118.60.33 unreachable - need to frag (mtu 1450), length 556
17:20:21.948816 Out 02:42:bd:a8:90:1c ethertype IPv4 (0x0800), length 592: 172.18.0.1 > 172.18.0.7: ICMP 10.224.144.23 unreachable - need to frag (mtu 1450), length 556
所以问题的原因是在上传镜像大blob时,因为容器MTU 1500并且有DF(don't fragment) 不能分片标志位,而docker0 MTU 1450,导致包发不出去,同时tcp一直等待,虽然下游返回了需要分片,可能和go语言自身net/http包对TLS的处理有BUG吧,在后续MTU变小后即使收到了ICMP也并会调整当前连接,所以导致一直卡住。
3. 解决问题
将容器的网卡mtu改成和宿主机eth0一样的mtu,因为宿主机是虚机,虚机mtu是1450,所以改容器的mtu。
更改harbor的docker-compose.yml
networks:
harbor:
driver: bridge
driver_opts:
com.docker.network.driver.mtu: 1450
长期来说,docker网络的mtu要与宿主机保持一致。
#/etc/docker/daemon.json
{ "mtu": 1450 }