在构建需要搬运大型镜像、数据集或备份文件的系统时,网络不稳定往往让传输任务前功尽弃。将断点续传逻辑运行在Docker容器内,可以利用容器的隔离性与挂载卷的持久化能力,让任务在中断后从精确字节位置恢复,而不必重新来过。

为什么用Docker承载断点续传任务
传统做法是在宿主机写 Shell 或 Python 脚本做分片下载,但这类脚本通常依赖特定版本的 curl、wget 或语言库。当运维人员切换机器或更新系统后,环境差异会导致脚本行为变化。Docker 把传输工具、依赖与配置全部打包进镜像,无论在哪台主机运行,行为都一致。
另一个关键点是状态隔离。断点续传必须记录“已经传了多少”。如果记录文件写在容器内部文件系统,容器一删就丢。正确方式是通过 -v 参数把宿主目录挂进容器,让进度文件落在挂载卷。这样即使容器被强制删除,再次启动新容器挂载同一卷,就能读回偏移量继续工作。
基础实现:用curl与挂载卷做续传
下面示例用一个简单容器,通过 curl 的 -C - 参数自动从进度文件继续下载,并将临时文件与偏移记录放在挂载卷。
# 宿主机执行:启动容器并挂载数据卷
docker run --rm
-v /data/transfer:/work
alpine:3.19
sh -c '
cd /work
URL="https://ipipp.com/bigfile.bin"
# curl -C - 表示自动从已存在文件大小处续传
curl -C - -o bigfile.bin "$URL"
echo "done"
'
上面的命令中,/data/transfer 是宿主机目录,容器内的 /work 与之对应。若 bigfile.bin 已存在且不完整,curl 会请求服务端从对应字节返回数据。多数支持 Range 的 HTTP 服务都能配合此方式。
不过纯命令行在复杂网络下仍可能失败。更稳妥的做法是写一个小程序,显式管理偏移量并捕获终止信号,保证容器被停掉前把进度落盘。
用Python在容器内管理偏移量
以下代码展示一个最小可用的续传逻辑:它把已下载字节数写在 progress.txt,每次启动先读再请求 Range,并在收到 SIGTERM 时保存进度。
import os
import signal
import sys
import requests
WORK_DIR = "/work"
FILE_NAME = "bigfile.bin"
PROGRESS = "progress.txt"
URL = "https://ipipp.com/bigfile.bin"
path = os.path.join(WORK_DIR, FILE_NAME)
prog = os.path.join(WORK_DIR, PROGRESS)
offset = 0
if os.path.exists(prog):
with open(prog, "r") as f:
offset = int(f.read().strip() or 0)
def save_progress(signum, frame):
with open(prog, "w") as f:
f.write(str(offset))
sys.exit(0)
signal.signal(signal.SIGTERM, save_progress)
headers = {"Range": "bytes=%d-" % offset}
r = requests.get(URL, headers=headers, stream=True)
mode = "ab" if offset else "wb"
with open(path, mode) as fp:
for chunk in r.iter_content(8192):
if chunk:
fp.write(chunk)
offset += len(chunk)
with open(prog, "w") as f:
f.write(str(offset))
print("transfer finished")
这段代码在每次写入数据块后都更新 progress.txt,虽然频繁写盘有轻微开销,但能最大限度防止进度丢失。生产环境可改为每 N 兆或每数秒落盘一次。
注意 signal.signal 只捕获 SIGTERM。Docker 停止容器默认发 SIGTERM,等待十秒再发 SIGKILL。所以必须在 SIGTERM 处理函数里完成进度保存,否则强杀后最后一段未记录。
容器编排与资源限制
在 docker run 时加上 --memory 与 --cpu-quota 可避免传输任务吃光资源。例如限制内存 256M、CPU 核心一半:
docker run -d --name resume_job --memory=256m --cpu-quota=50000 -v /data/transfer:/work my_transfer_image:1.0
使用 -d 让任务后台运行,配合 docker stop resume_job 会触发前面说的 SIGTERM。若用 Kubernetes,可将挂载卷换成 PersistentVolume,并通过 Jobs 对象控制重试,失败容器重建后仍能读回原卷中的偏移文件。
权限方面,挂载卷内文件默认属主是宿主机 UID。若容器以非 root 用户跑,需在 Dockerfile 里建同名 UID 或修改卷权限,否则写 progress.txt 会报 PermissionError。
常见误区与排查
有人误以为只要容器不删,内部文件就安全。其实节点重启或 docker system prune 都可能清掉未挂载的层。只有显式挂载的卷或命名卷才可靠。另外,部分对象存储对 Range 请求返回 200 而非 206,会导致 curl 从头下,此时要在代码里检查状态码与 Content-Range,避免误覆盖。
还有一个坑是符号链接与稀疏文件。若宿主机文件系统支持稀疏文件,已分配偏移但没写数据的部分在容器看可能大小正常却读不到字节,建议在续传前用 os.path.getsize 与实际统计结合判断,必要时 truncate 到真实长度再续。
| 方案 | 状态保存位置 | 重启恢复 | 环境一致性 |
|---|---|---|---|
| 宿主机脚本 | 本地文件 | 依赖脚本留存 | 弱 |
| Docker+挂载卷 | 宿主卷 | 强 | 强 |
| 纯容器内部存储 | 容器层 | 弱 | 强 |
综上,Docker 在断点续传中的价值不只是“能跑”,而是把易变的环境与必须持久的状态分开。只要挂载卷用对、信号捕得到、偏移量写得勤,哪怕集群节点轮番重启,大文件也能安稳续完。