wget 在 CI 里遇到 503 直接退出码 8:重试语义与脚本判断
项目地址:GNU Wget 官网 https://www.gnu.org/software/wget/ ,手册 https://www.gnu.org/software/wget/manual/wget.html 。
发布流水线里用 wget 拉发布包,上游 CDN 偶发 503。日志里找不到一行 "Retrying.",进程直接以退出码 8 结束。同一台机器手动执行一条带 -c 的命令又能下成功。看起来像是 -t 没生效,实际是 -t 根本没管到 503。
默认重试覆盖的是传输层失败
-t / --tries 默认 20 次;0 或 inf 表示无限重试。但「重试」的范围比想象中窄:connection refused、404 这类致命错误默认直接放弃;服务器返回的错误响应(HTTP 4xx/5xx)默认不进入重试循环,直接以退出码 8 结束。也就是说 -t 20 管的是连接中途断掉、读超时这类传输层失败,不管 503。
重试间隔也不是固定值。--waitretry 默认 10 秒,实际是线性退避:同一文件第一次失败后等 1 秒,第二次失败后等 2 秒,逐步递增,上限为 --waitretry 的值。--random-wait 让间隔在 0.5–1.5 倍 --wait 之间浮动。
超时参数容易被忽略。--timeout 会同时设置 --dns-timeout / --connect-timeout / --read-timeout;默认只有 read timeout 是 900 秒,DNS 与 connect 没有 wget 层超时(走系统库);--timeout=0 表示禁用超时。DNS 或路由异常时,脚本可能长时间没有任何输出地挂着。
把 503 拉进重试循环:--retry-on-http-error
1.19.1 起支持 --retry-on-http-error=code[,code,...](作者是提交补丁的 Tim Rühsen,对应 11 年的 feature request)。它把指定的 3 位状态码视为非致命瞬时错误,例如 503、429。这类重试仍受 --tries 与 --waitretry 的常规限制。
手册明确写着:该选项只用于特殊场景、一般不推荐,因为它可能在上游正主动降载时强行重试。取舍就在这里:503 有时是边缘节点的短暂抖动,重试能救回来;有时是上游在限流,重试只会加大压力。CI 里下载的是自己的发布包、上游可控,用这个选项比在外面包一层 while 循环更省事;对着第三方公共接口就不要默认打开。
验证方法很直接,httpbin 的这个 URL 固定返回 503:
wget --retry-on-http-error=429,503,504 http://httpbin.org/status/503
输出里能看到 Retrying. 与 (try: 2) 计数。
另外两个把「致命」降级为「瞬时」的开关:--retry-connrefused 把 connection refused 当瞬时错误重试,用于服务器会短暂消失的镜像站;--retry-on-host-error 把 "Temporary failure in name resolution" 这类主机错误当瞬时错误。
退出码决定脚本怎么走
手册 EXIT STATUS:
- 0 无问题
- 1 一般错误
- 2 解析错误(命令行、
.wgetrc、.netrc) - 3 文件 I/O 错误
- 4 网络失败
- 5 SSL 证书校验失败
- 6 用户名/密码认证失败
- 7 协议错误
- 8 服务器返回错误响应
除 0 和 1 外,同时出现多种错误时,编号小的优先。
1.12 之前的版本退出码不可靠:递归下载几乎总是返回 0,非递归只返回最后一次尝试的状态。老发行版上的脚本不能只看 $?。
脚本写法
显式给 -t、--waitretry、--timeout,避免 DNS/路由异常时长时间无输出挂住(默认 read timeout 900 秒)。判断 $? 决定重试:4/5/6/8 语义不同;5 先查证书链与系统时间,不要直接 --no-check-certificate。wget 的进度与错误都写 stderr,脚本里用 -q 或重定向,避免污染日志。
-c 断点续传依赖服务端支持 Range:服务端忽略 Range 返回 200 时 wget 会从头下;用 --server-response -c 确认返回 206。
-c 与 -O 一起用时,wget 靠本地文件大小推断续传起点,改名后容易判断失误,最坏会截断已有内容;要续传就让 wget 用服务端给的文件名,下完再 mv。
--tries=0 只对可重试的传输层错误无限重试,404 这类默认不重试;真正危险的是开了 --retry-on-http-error 之后上游持续 5xx,进程会长时间不退出,必须由外层 timeout 或队列兜住。
wget 自身没有互斥:cron 里重叠运行会互相写半成品文件,需要 flock 或锁文件。下载后做 sha256sum -c 校验。
一个组合示例:
#!/usr/bin/env bash
set -uo pipefail
url=https://example.com/releases/app.tar.gz
dir=/srv/releases
cd "$dir" || exit 1
flock -n /var/lock/app-download.lock \
wget -q --tries=5 --waitretry=10 --timeout=30 \
--retry-on-http-error=429,503,504 \
-c "$url"
rc=$?
case $rc in
0) sha256sum -c SHA256SUMS ;;
4) echo "network failure" >&2 ;;
5) echo "certificate failure" >&2 ;;
6) echo "auth failure" >&2 ;;
8) echo "server returned error response" >&2 ;;
*) echo "wget exit $rc" >&2 ;;
esac