一个后端容器挂了,竟然把整个 Nginx 全给搞宕机了
报错信息非常直接:host not found in upstream "importer"。
为什么会发生这种“连坐”现象
很多人觉得 Nginx 不起作用是因为 DNS 缓存,其实更底层的原因是 Nginx 的配置解析机制。如果你在配置文件里直接写死 Hostname,像这样:
location /importer/ {
proxy_pass http://importer:8001/;
}Nginx 在解析配置文件(Parse time)的那一刻,会立即去尝试解析 importer 这个域名。如果这时候那个容器刚好没启动,或者 Docker 网络还没准备好,Nginx 就会认为这个配置是非法的,直接拒绝启动。这就造成了极高的故障风险:只要有一个非核心服务没准备好,你的整个入口网关就彻底瘫痪了。
避坑指南:三行代码实现动态解析
要解决这个问题,核心思路是把域名解析推迟到请求阶段(Request time)。你只需要把地址赋值给一个变量,并显式指定一个 resolver。
实操配置如下:
# 使用 Docker 内置的 DNS 服务器
resolver 127.0.0.11 valid=10s ipv6=off;
resolver_timeout 5s;
# 将 upstream 地址存入变量
set $svc_importer http://importer:8001;
set $svc_api http://api:8000;
location /importer/ {
proxy_pass $svc_importer;
}这么做之后,即使 importer 容器挂了,Nginx 也能正常启动。请求过来时,它发现后端连不上,只会针对该路径返回 502,而不会影响其他业务。
这里有几个实战细节要注意:
127.0.0.11:这是 Docker 内部默认的 DNS 地址,在自定义网络里非常稳。ipv6=off:这个一定要加!如果不关掉,Nginx 会去查 AAAA 记录,如果你的服务只支持 IPv4,连接会报一些莫名其妙的错误,排查起来能让你怀疑人生。valid=10s:设置一个合理的缓存时间,既能保证容器重启后能自动恢复,又不会因为频繁查 DNS 损耗性能。
别高兴太早,这招有两个隐藏“副作用”
我当时改完配置觉得稳了,结果上线后发现两个大坑,大家一定要避开:
第一个坑:路径剥离失效(Trailing Slash 问题)
当你用变量 proxy_pass $svc_importer 时,Nginx 不再具备自动剥离路径的能力。
如果你以前写 proxy_pass http://importer:8001/;(注意末尾有斜杠),请求 /importer/api 会变成 /api。
但用了变量后,请求依然会带着前缀变成 /importer/api 发给后端,导致后端直接报 404。
解决方案: 必须配合 rewrite 指令手动处理路径:
location /importer/ {
rewrite ^/importer/(.*)$ /$1 break;
proxy_pass $svc_importer;
}第二个坑:rewrite 后的 if 失效
如果你在 rewrite 之后写了 if ($request_method = 'OPTIONS') 来处理 CORS 跨域,你会发现它完全不生效。这是因为 rewrite 和 if 都属于 rewrite 模块,它们在执行顺序上会有冲突。如果你在做复杂的 API 网关,记得把跨域逻辑提前,或者改用更稳健的配置方式。
这次踩坑也提醒我们,在做高可用架构时,一定要把“依赖项”和“核心链路”在配置层面彻底解耦。