Docker环境配置Nginx避免upstream主机不存在时启动崩溃
故障根因
当前配置启动失败的核心逻辑:Nginx启动加载配置阶段,会强制解析upstream块内所有server指令中写死的域名,任意一个域名解析失败就直接抛出emerg错误终止进程。你在server块中配置的resolver 127.0.0.11仅对运行时动态域名解析生效,完全不干预启动阶段的静态upstream域名校验,因此单独配置resolver无法解决启动崩溃问题。
方案1:最小改动(Nginx ≥1.19.1 适用,优先推荐)
利用upstreamserver指令的resolve参数,配合resolver配置,让Nginx不在启动阶段强制校验upstream域名,改为运行时动态解析,就算对应容器暂时没启动,也不会阻塞Nginx启动。
配置修改步骤:
- 将resolver配置放到http全局块(或确保upstream块可读取到resolver配置),添加适配Docker环境的优化参数:
resolver 127.0.0.11 valid=30s ipv6=off;
valid=30s:强制每30秒刷新一次DNS解析结果,适配Docker容器IP变化、启停的场景,无需重载Nginx即可自动感知后端地址变更ipv6=off:关闭IPv6解析,避免Docker内部DNS返回无效IPv6地址导致请求失败- 给所有upstream块内的
server条目加上resolve参数,修改后的upstream配置如下,其余配置完全不需要改动:
upstream portal_frontend_stage { server portal_frontend_stage_frontend_1:8080 max_conns=1 max_fails=0 resolve; server portal_frontend_stage_frontend_2:8080 max_conns=1 max_fails=0 resolve; } upstream portal_frontend_dev { server portal_frontend_dev_frontend_1:8080 max_conns=1 max_fails=0 resolve; server portal_frontend_dev_frontend_2:8080 max_conns=1 max_fails=0 resolve; } upstream portal_backend_dev { server portal_backend_dev_backend_1:80 max_conns=1 max_fails=0 resolve; server portal_backend_dev_backend_2:80 max_conns=1 max_fails=0 resolve; } upstream portal_backend_stage { server portal_backend_stage_backend_1:80 max_conns=1 max_fails=0 resolve; server portal_backend_stage_backend_2:80 max_conns=1 max_fails=0 resolve; }
修改后Nginx启动时不会校验upstream域名是否存在,就算某个环境的容器全停,Nginx也能正常启动,仅当请求实际路由到不存在的后端时返回502错误,完全不影响其他环境的正常访问。
方案2:全版本兼容方案(Nginx <1.19.1 旧版本适用)
如果你的Nginx版本过低不支持resolve参数,可以通过变量+动态proxy_pass的方式绕过启动阶段的域名校验——Nginx只有在变量被实际使用(即请求到来)时,才会通过resolver解析变量中的域名,启动阶段完全不做校验。
配置修改步骤:
- 删除所有静态定义的upstream块(即配置开头的四个upstream {}段)。
- 在http全局块配置resolver,参数和方案1一致:
resolver 127.0.0.11 valid=30s ipv6=off;
- 用
split_clients模块实现均匀的轮询负载均衡,替代原来的upstream分组,示例配置如下:
# frontend dev环境50%流量分流到两个后端 split_clients "${remote_addr}${request_time}" $frontend_dev_backend { 50% "portal_frontend_dev_frontend_1:8080"; 50% "portal_frontend_dev_frontend_2:8080"; } # frontend stage环境分流 split_clients "${remote_addr}${request_time}" $frontend_stage_backend { 50% "portal_frontend_stage_frontend_1:8080"; 50% "portal_frontend_stage_frontend_2:8080"; } # backend dev环境分流 split_clients "${remote_addr}${request_time}" $backend_dev_backend { 50% "portal_backend_dev_backend_1:80"; 50% "portal_backend_dev_backend_2:80"; } # backend stage环境分流 split_clients "${remote_addr}${request_time}" $backend_stage_backend { 50% "portal_backend_stage_backend_1:80"; 50% "portal_backend_stage_backend_2:80"; }
- 修改各个include文件中的
proxy_pass规则,把原来指向upstream名称的配置,替换为对应分流变量,示例:
# 原来转发到dev frontend的配置 # location /frontend/dev/ { # proxy_pass http://portal_frontend_dev; # } # 修改后 location /frontend/dev/ { proxy_pass http://$frontend_dev_backend; # 保留原有其他proxy配置,如proxy_set_header、超时配置等 }
其余环境的转发规则按相同逻辑替换为对应变量即可。
补充注意事项
- 两种方案都能实现单环境后端离线不影响Nginx全局启动,正常运行的环境服务不会被故障环境牵连。
- 可以搭配
proxy_next_upstream error timeout http_502 http_503配置,在请求某台后端失败时自动重试同组的另一台后端,降低单台容器离线对可用性的影响。 - 使用变量方式配置proxy_pass时,如果需要做路径替换,建议用
rewrite ^/prefix/(.*)$ /$1 break;配合处理,避免路径转发异常。
内容的提问来源于stack exchange,提问作者Артём Гусинцев
相关产品推荐
相关产品推荐

