Docker 容器 MTU 不一致导致 AList 上传云盘失败的排查记录

发布于 3 小时前  0 次阅读


▎ 现象:rclone 通过 WebDAV 上传到 AList,AList 后端是某云盘存储,每次都在文件上传时 405 失败。裸机部署正常,docker 部署必现。

一、症状

rclone 日志:

2026/05/27 21:38:30 ERROR : … Failed to copy: Method Not Allowed: 405

AList 日志同步报:

PUT /dav/…/Season 1/魔法少女小圆 - S01E01.mkv | 405 | 1m0s
Post "https://vip-lixian-08.guangyapan.com/…?sequential&uploads":
read tcp 172.17.0.7:49256->47.102.69.6:443: i/o timeout

特征:

  • 失败前正好卡 60 秒
  • 失败发生在向云盘 OSS 发起 ?uploads(OSS InitiateMultipartUpload)阶段
  • 错误码是 i/o timeout,不是 connection refused
  • 裸机部署 AList 完全正常,只有 docker 部署复现

二、第一个误导:405 不是真正的错

翻 AList 源码 server/webdav/webdav.go 的 handlePut:

err = fs.PutDirectly(ctx, path.Dir(reqPath), fsStream)
if errs.IsNotFoundError(err) {
return http.StatusNotFound, err
}
// TODO(rost): Returning 405 Method Not Allowed might not be appropriate.
if err != nil {
return http.StatusMethodNotAllowed, err // ← 所有非 404 一律 405
}

注释里作者自己标了 TODO——AList WebDAV 把所有 PUT 失败(除"对象不存在"外)都包装成 405。所以 405 只是症状,真错误在底层网络层。

三、真正的根因:MTU 黑洞

关键线索

宿主机网卡:

ens18: mtu 1400
inet 192.168.88.104

docker0 默认 MTU 是 1500。容器从 docker0 出来的包按 1500 协商 MSS,到 ens18 时装不进 1400 的物理 MTU,包被静默丢弃,中间设备又不返回 ICMP "Fragmentation Needed"——这就是经典的 PMTU 黑洞。

为什么偏偏在 TLS 握手时报错

普通 HTTP 小包 < 1400,不触发。但 TLS 握手时服务端发的证书包通常 2~4 KB,必须分片为多个 TCP 段。其中至少有一个段达到链路 MTU 上限——这个包发不出去,TLS 握手卡死,TCP 读超时(默认 60s)后失败。

具体到 AList 这个场景:

  • 列表、元数据接口:小包,正常
  • OSS 分片上传初始化(InitiateMultipartUpload):响应含较大的 XML 和签名信息,踩坑
  • 已经握手成功的连接传数据:MSS 协商过了,有时也能传完

这解释了为什么有些操作正常、有些操作必失败。

为什么裸机没问题

裸机进程直接走 ens18,内核 PMTU discovery 把 ens18 自己的 MTU=1400 直接用进 TCP MSS,对端收到的 SYN 里 MSS 就是合理的。问题在 docker bridge 多了一层 NAT,PMTU 信息没有正确同步到容器命名空间。

四、解决方案

4.1 改 /etc/docker/daemon.json

{
"mtu": 1400,
"default-network-opts": {
"bridge": {
"com.docker.network.driver.mtu": "1400"
}
}
}

两个字段缺一不可:

┌───────────────────────────────────────────────────────────┬────────────────────────────────┬──────────────────────┐
│ 字段 │ 作用 │ 备注 │
├───────────────────────────────────────────────────────────┼────────────────────────────────┼──────────────────────┤
│ mtu │ 控制 docker0 默认 bridge │ 老版本 docker 也支持 │
├───────────────────────────────────────────────────────────┼────────────────────────────────┼──────────────────────┤
│ default-network-opts.bridge.com.docker.network.driver.mtu │ 控制以后新建的所有 bridge 网络 │ Docker 24+ 才支持 │
└───────────────────────────────────────────────────────────┴────────────────────────────────┴──────────────────────┘

应用:

systemctl restart docker

4.2 重建已存在的网络

default-network-opts 只对新建网络生效,已经存在的 bridge 网络(包括 compose 自动建的)还是老 MTU。


希望赤诚善良的人,能被世界温柔以待
最后更新于 2026-05-28