ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

海量超大文件稳定上传 S3 实战:Nginx 反向代理 + 容器化部署(28011 端口转发)

海量超大文件稳定上传 S3 实战:Nginx 反向代理 + 容器化部署(28011 端口转发) 摘要在某数据上云项目中需要用 rclone 把约 20TB 数据分片上传到对象存储S3/MinIO。直连对象存储会遇到请求体过大被拒、大文件传输中途被掐断、代理落盘占满磁盘等问题。本文完整记录如何用官方 nginx:latest 镜像搭建一个「大文件友好」的反向代理入口给出 nginx.conf、Dockerfile、docker-compose.yml 等全部配置给出可照抄的部署与验证指令并复盘一次真实的「28011 端口访问不到」排障过程根因反向代理把流量转发给了自己形成死循环。说明为保护隐私本文中的项目名称、IP 地址等均已脱敏替换为示例值如 192.168.1.100、10.0.0.10 等实际使用时请替换为你自己的环境参数。一、需求与目标上传端rclone支持断点续传、异常重试、无人值守源数据规模约 几十个TB单文件很大。入口上传端统一连接 http://192.168.1.100:28011。Nginx部署在 192.168.1.100容器化运行容器内监听 28011。后端真正的 S3 入口默认走 云端 DNAT 10.0.0.10:9910。要求稳定、高效、支持大文件分片、7×24 无人值守、出问题可快速定位。二、整体架构与链路最终链路自左向右上传端 rclone│ HTTP(S3 分片 PUT / GET)▼192.168.1.100:28011 ← nginx 容器本文主角反向代理 大文件调优│ proxy_pass▼10.0.0.10:9910 ← 云端 DNAT│▼10.0.0.12:9910 ← 应用服务器云端侧 nginx│▼10.0.0.11:9989 ← 云端 SNAT│▼10.0.0.20:8000 ← 对象存储S3关键点nginx 的 upstream转发目标必须指向「真正的后端」绝不能指向 nginx 自己的监听地址否则会形成自环。这一点是后文排障的核心。三、为什么要加 Nginx大文件上传的痛点与调优直接把海量大文件推到对象存储常见的坑请求体过大默认 client_max_body_size 只有 1M大分片直接被 413 拒绝。传输中途被掐断默认超时 60s大文件传一半就断。代理落盘默认会把请求体先缓冲到临时文件20TB 级数据会把磁盘打满。响应缓冲大对象下载延迟高、内存占用大。连接复用差每个请求新建连接吞吐上不去。对应的 nginx 调优项指令取值作用client_max_body_size0不限制请求体大小避免大分片被 413 拒绝proxy_request_bufferingoff边收边转发不在 nginx 磁盘落临时文件proxy_bufferingoff关闭响应缓冲降低大对象下载延迟proxy_max_temp_file_size0彻底禁用临时文件proxy_connect/send/read_timeout30s / 3600s / 3600s大文件传输耗时长避免被掐断upstream keepalive HTTP/1.1 Connection 开启与后端复用长连接提升吞吐chunked_transfer_encodingon支持未知长度的流式上传proxy_set_header Host $http_host透传S3 SigV4 签名把 Host 计入签名必须原样透传gzipoff二进制数据无需压缩省 CPUlisten [::]:28011默认注释IPv6 未启用时会导致 nginx 启动失败见排障章节四、nginx.conf 完整配置说明nginx 部署在 192.168.1.100 并监听 28011因此 upstream 指向真正的后端 10.0.0.10:9910。# # nginx.conf · S3 上传链路 28011 入口部署在 192.168.1.100# -----------------------------------------------------------------------------# 部署位置 : 192.168.1.100 Docker 容器容器内监听 28011# 上传端 : rclone 连接 http://192.168.1.100:28011# 转发目标 : 真正的后端 S3 入口默认 云端 DNAT 10.0.0.10:9910# 基础镜像 : nginx:latest (本文件被拷贝到容器 /etc/nginx/nginx.conf)# 使用方式 : docker build -t s3-forward:latest .# docker run -d --name s3-forward -p 28011:28011 \# --restart unless-stopped s3-forward:latest# -----------------------------------------------------------------------------# 调优说明面向 20T 级大文件 / S3 分片上传:# * client_max_body_size 0 —— 不限制请求体大小大分片 PUT 必需# * proxy_request_buffering off —— 边收边转发磁盘不落地内存占用低# * proxy_buffering off —— 关闭响应缓冲降低大对象下载延迟# * 超时统一放宽到 3600s —— 避免大文件上传中途被 nginx 掐断# * upstream keepalive —— 与后端复用长连接提升吞吐# -----------------------------------------------------------------------------# 最关键的一条“28011 访问不到”的根因:# nginx 就部署在 192.168.1.100 上、监听 28011。# 所以 upstream 绝对不能写 192.168.1.100:28011 —— 那是它自己的监听口# 等于「自己转发给自己」请求进来 → nginx 又连自己 → 再进来 → 无限递归# 很快耗尽 worker_connections表现为 28011 一直挂起 / 超时 / 502。# 容器用 -p 28011:28011 时还会经 docker-proxy 再绕一圈同样是死循环。# user nginx;worker_processes auto;worker_rlimit_nofile 65535;error_log /var/log/nginx/error.log warn;pid /var/run/nginx.pid;events {worker_connections 16384;multi_accept on;use epoll;}http {include /etc/nginx/mime.types;default_type application/octet-stream;log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for upstream$upstream_addr upstream_status$upstream_status request_time$request_time upstream_time$upstream_response_time;access_log /var/log/nginx/access.log main;# ---- 基础传输优化 ----sendfile on;tcp_nopush on;tcp_nodelay on;reset_timedout_connection on;keepalive_timeout 300s;keepalive_requests 100000;server_tokens off;# 转发二进制数据无需压缩gzip off;# 大文件场景不写临时文件、不限制缓冲client_body_buffer_size 1m;client_header_buffer_size 4k;large_client_header_buffers 4 16k;# ---------------------------------------------------------------------# 上游真正的后端三选一取消对应 server 行的注释即可切换# 不要填 192.168.1.100:28011nginx 自身监听口 → 死循环# ---------------------------------------------------------------------upstream forward_target {server 10.0.0.10:9910 max_fails3 fail_timeout10s; # 云端 DNAT默认启用# server 10.0.0.11:9989 max_fails3 fail_timeout10s; # 云端 SNAT → 对象存储 10.0.0.20:8000# server 10.0.0.20:8000 max_fails3 fail_timeout10s; # 对象存储 S3 直连keepalive 256;keepalive_timeout 300s;keepalive_requests 100000;}# ---------------------------------------------------------------------# 服务端监听 28011即上传端要连的入口# ---------------------------------------------------------------------server {listen 28011;# listen [::]:28011; # 需要 IPv6 时放开主机 ipv6.disable1 时必须保持注释server_name _;client_max_body_size 0; # 0 不限制client_body_timeout 3600s;client_header_timeout 120s;send_timeout 3600s;chunked_transfer_encoding on;# ---- 主转发规则 ----location / {proxy_pass http://forward_target;# HTTP/1.1 清空 Connection 头才能命中 upstream keepaliveproxy_http_version 1.1;proxy_set_header Connection ;# 保留客户端原始 HostS3 SigV4 签名依赖 Host 头必须透传 $http_hostproxy_set_header Host $http_host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header X-Forwarded-Host $http_host;proxy_set_header X-Forwarded-Port $server_port;# 大文件直通不在 nginx 侧落盘 / 缓冲proxy_request_buffering off;proxy_buffering off;proxy_max_temp_file_size 0;proxy_connect_timeout 30s;proxy_send_timeout 3600s;proxy_read_timeout 3600s;proxy_next_upstream error timeout http_502 http_503 http_504;proxy_next_upstream_tries 2;proxy_next_upstream_timeout 30s;proxy_redirect off;proxy_intercept_errors off;# 支持断点续传 / Range 请求S3 GET 大对象常用proxy_set_header Range $http_range;# 分片上传中 100-continue 行为proxy_set_header Expect ;}# ---- 本地健康检查不经过上游仅探测 nginx 自身存活----location /nginx-health {access_log off;default_type text/plain;return 200 ok\n;}# ---- 上游连通性探测会真实访问 10.0.0.10:9910----location /nginx-upstream-health {access_log off;proxy_pass http://forward_target/;proxy_connect_timeout 3s;proxy_read_timeout 5s;proxy_set_header Host $http_host;}}}五、容器化文件5.1 DockerfileFROM nginx:latestLABEL org.opencontainers.image.titles3-nginx-forward \org.opencontainers.image.descriptionnginx reverse proxy: 192.168.1.100:28011 - 10.0.0.10:9910 \org.opencontainers.image.version1.0.2# 时区 健康检查依赖兼容 debian/alpine离线环境容错RUN set -eux; \if [ -f /usr/share/zoneinfo/Asia/Shanghai ]; then \ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime || true; \fi; \echo Asia/Shanghai /etc/timezone || true; \if command -v curl /dev/null 21; then \echo curl already present; \elif command -v apt-get /dev/null 21; then \apt-get update || true; \apt-get install -y --no-install-recommends tzdata curl ca-certificates || true; \rm -rf /var/lib/apt/lists/* || true; \elif command -v apk /dev/null 21; then \apk add --no-cache tzdata curl ca-certificates || true; \fi; \command -v curl /dev/null 21ENV TZAsia/Shanghai \LANGC.UTF-8# 用自定义配置整体替换默认配置并删除默认站点避免 80 端口冲突ARG NGINX_CONFnginx.confARG NGINX_PORT28011COPY ${NGINX_CONF} /etc/nginx/nginx.confRUN rm -f /etc/nginx/conf.d/default.conf \ nginx -t -c /etc/nginx/nginx.confENV NGINX_PORT${NGINX_PORT}EXPOSE ${NGINX_PORT}EXPOSE 9988# 只探测 nginx 自身不代表 28011 对外可达也不代表 upstream 可达HEALTHCHECK --interval30s --timeout5s --start-period10s --retries3 \CMD curl -fsS http://127.0.0.1:${NGINX_PORT}/nginx-health || exit 1STOPSIGNAL SIGQUITCMD [nginx, -g, daemon off;]5.2 docker-compose.ymlservices:nginx-forward-28011:build:context: .dockerfile: Dockerfileargs:NGINX_CONF: ${NGINX_CONF:-nginx.conf}NGINX_PORT: ${CONTAINER_PORT:-28011}image: ${IMAGE_NAME:-s3-forward:latest}container_name: ${CONTAINER_NAME:-s3-forward}restart: unless-stoppedports:- ${HOST_PORT:-28011}:${CONTAINER_PORT:-28011}# 9988 静态站点仅云端链路需要默认不映射避免端口占用导致容器起不来# - ${HTML_PORT:-9988}:9988environment:TZ: Asia/Shanghaihealthcheck:test: [CMD-SHELL, curl -fsS http://127.0.0.1:${CONTAINER_PORT:-28011}/nginx-health || exit 1]interval: 30stimeout: 5sretries: 3start_period: 10sulimits:nofile:soft: 65535hard: 65535logging:driver: json-fileoptions:max-size: 50mmax-file: 55.3 .dockerignore构建上下文最小化*!Dockerfile!nginx.conf!nginx.conf_govnetwork5.4 一键构建启动脚本build_and_run.sh 节选#!/usr/bin/env bashset -euo pipefailIMAGE${IMAGE:-s3-forward:latest}NAME${NAME:-s3-forward}HOST_PORT${HOST_PORT:-28011}docker build -t ${IMAGE} .docker ps -a --filter name^/${NAME}$ --format {{.ID}} | grep -q . docker rm -f ${NAME} || truedocker run -d --name ${NAME} \--restart unless-stopped \-p ${HOST_PORT}:28011 \-e TZAsia/Shanghai \--log-opt max-size50m --log-opt max-file5 \${IMAGE}sleep 3docker ps --filter name^/${NAME}$ --format table {{.Names}}\t{{.Status}}\t{{.Ports}}curl -fsS http://127.0.0.1:${HOST_PORT}/nginx-health echo 本地健康检查通过六、部署实操在 192.168.1.100 上6.1 前提检查docker --version # 有输出即可没有就先装 Docker Enginess -lntp | grep 28011 # 必须无输出端口未被占用nc -vz 10.0.0.10 9910 # 必须通后端可达否则起来也会 502/504注意开发机到目标机的网络如果本身不通就不能直接从开发机部署要在一台能访问目标机的跳板机上操作或先打通网络。6.2 传输文件# 在能访问目标机的机器上执行scp -r nginx转发 user192.168.1.100:/opt/s3-forward# 或tar czf nginx转发.tgz nginx转发 scp nginx转发.tgz user192.168.1.100:/opt/6.3 构建并启动cd /opt/s3-forward/nginx转发# 方式一docker compose推荐docker compose up -d --build# 方式二docker 命令docker build -t s3-forward:latest .docker run -d --name s3-forward \--restart unless-stopped \-p 28011:28011 \-e TZAsia/Shanghai \--log-opt max-size50m --log-opt max-file5 \s3-forward:latest6.4 放行防火墙# firewalld麒麟 / CentOS / RHELfirewall-cmd --add-port28011/tcp --permanent firewall-cmd --reloadfirewall-cmd --list-ports# 或 iptablesiptables -I INPUT -p tcp --dport 28011 -j ACCEPT七、验证与冒烟测试# 1) 容器与端口映射docker ps # PORTS 应有 0.0.0.0:28011-28011/tcp# 2) nginx 自身不依赖后端curl -i http://127.0.0.1:28011/nginx-health # 期望 200 ok# 3) 后端链路会真实访问 10.0.0.10:9910curl -v http://127.0.0.1:28011/nginx-upstream-health # 期望 200502/504 后端不通# 4) 从上传端所在机器验证curl -i http://192.168.1.100:28011/nginx-health# 5) 容器健康状态docker inspect --format {{.State.Health.Status}} s3-forward全部通过后上传端 rclone 用 endpoint http://192.168.1.100:28011 即可。八、踩坑记录28011 端口访问不到8.1 根因一反向代理把流量转发给了自己自环这是本次最关键的问题。nginx 部署在 192.168.1.100 上、监听 28011而 upstream 也写成了 192.168.1.100:28011于是请求进来(28011) → nginx 转发到 192.168.1.100:28011 → 又打到 nginx 自己 → 再转发 → …无限递归现象TCP 能连上但请求永远挂起/超时worker_connections16384很快被耗尽看起来就是「28011 访问不到」。容器用 -p 28011:28011 时还会经 docker-proxy 再绕一圈同样是死循环。修复upstream 必须指向真正的后端本例为 10.0.0.10:9910绝不能是 nginx 自己的监听地址。8.2 根因二listen [::]:28011 撞上 IPv6 被禁用的主机在 ipv6.disable1 的麒麟 / RHEL 主机或未启用 IPv6 的容器内核上listen [::]:28011; 会让 nginx 启动阶段直接失败并退出socket() [::]:28011 failed (97: Address family not supported by protocol)结果就是 28011 无人监听、容器反复重启。修复注释掉 listen [::]:28011;仅当确认启用 IPv6 时才放开。8.3 根因三没有做端口映射docker run 必须带 -p 28011:28011否则宿主机上根本没有 28011。用 docker ps 看 PORTS 列是否有 0.0.0.0:28011-28011/tcp 即可确认。8.4 根因四宿主机防火墙 / 安全组没放行本机 127.0.0.1:28011 通、别的机器不通通常是防火墙没放行 28011/tcp。8.5 根因五上游不可达502/504被误当成「端口不通」/nginx-health 返回 200 说明 nginx 正常但 / 或 /nginx-upstream-health 报 502/504那是后端不可达跟端口无关。用 /nginx-health 与 /nginx-upstream-health 两个地址即可区分是「端口层」还是「上游层」。8.6 根因六健康检查误导容器的 HEALTHCHECK 只探 /nginx-healthnginx 自身显示 healthy 并不代表 28011 对外可达也不代表后端可达。别被这个绿灯迷惑。8.7 排查速查表现象最可能原因处理请求挂起/超时日志里请求互相嵌套upstream 指向自己自环把 upstream 改为真正的后端日志 socket() [::]:28011 failed (97: ...)容器反复重启listen [::]:28011 撞上 IPv6 禁用注释 listen [::]:28011docker ps 里 PORTS 没有 0.0.0.0:28011-28011/tcp没做端口映射启动命令加 -p 28011:28011容器起不来日志 bind() Address already in use宿主机 28011 已被占用ss -lntp | grep 28011 找出占用者并处理本机 127.0.0.1:28011 通、外部不通防火墙/安全组没放行firewall-cmd --add-port28011/tcp --permanent/nginx-health 200 但 / 报 502/504不是端口问题是上游不可达检查 upstream 指向的后端是否可达容器 healthy 但外部访问不到健康检查只探 nginx 自身按上面几层继续排查8.8 五步定位法# 第 1 步28011 到底有没有进程在监听ss -lntp | grep 28011 # 无输出 nginx 没起来 / 没监听# 第 2 步本机自测不依赖上游、不依赖外网curl -i http://127.0.0.1:28011/nginx-health# 第 3 步容器与日志docker ps -a ; docker logs --tail 100 s3-forwarddocker exec s3-forward nginx -T # 打印容器里真正生效的配置# 第 4 步宿主机防火墙firewall-cmd --list-ports# 第 5 步上游连通性curl -v http://127.0.0.1:28011/nginx-upstream-health九、切换上游链路nginx.conf 的 upstream forward_target 里已列出三条取消对应 server 行的注释即可切换同时注释掉当前启用行。注意三条都不能是 nginx 自己的监听地址自环。链路目标云端 DNAT默认启用10.0.0.10:9910应用服务器 10.0.0.12:9910云端 SNAT10.0.0.11:9989对象存储 10.0.0.20:8000对象存储 S3 直连10.0.0.20:8000内网若能直达不确定用哪条在目标机上分别 nc -vz IP PORT 试哪个通就用哪个。改完执行 docker compose up -d --build。十、运维常用命令docker logs -f s3-forward # 实时日志docker exec s3-forward nginx -T # 打印生效的完整配置docker exec s3-forward nginx -s reload # 热加载配置已挂载时docker compose down # 停止并移除docker inspect --format {{.State.Health.Status}} s3-forward十一、总结用 nginx 做 S3 入口重点在大文件调优不限制 body、关缓冲、关临时文件、放宽超时、开长连接。反向代理的 upstream 必须是「真正的后端」绝不能指向自己的监听地址否则就是自环死循环。listen [::]:port 在 IPv6 被禁用的主机上会让 nginx 起不来默认只写 IPv4 更稳。排障要分层进程/监听 → 端口映射 → 防火墙 → 上游连通性/nginx-health 与 /nginx-upstream-health 能快速区分层次。容器 HEALTHCHECK 只代表 nginx 存活不代表对外可达别被它误导。
返回列表