在 Kubernetes 中使用 Nginx 作为前端时踩到的 DNS 缓存坑(以及两种实战解决方案)
很多人在 k8s 集群里习惯这样做:
前端用一个 Nginx Deployment,当成“自建 Ingress / 前端网关”,然后在 nginx.conf 里直接用 Service 名称 去反向代理后端服务。
一开始一切正常,但当你 更新后端服务 之后,诡异的问题来了:
- Nginx 容器里
ping这个 Service 完全没问题 - 但通过 Nginx 访问接口,就是 502 / 连接不上
- 重启一下 Nginx Pod,又好了……
这其实就是:Nginx 的 DNS 缓存在 k8s 场景下坑了你。
下面就用一个真实案例,完整说明:
- 问题现象
- 背后原因(Nginx + k8s Service + DNS 的联动)
- 两套可落地的解决方案(原生 Nginx + Tengine 动态解析),附完整配置
一、问题:后端升级后,Nginx 无法再访问 Service
场景简化一下:
- 前端:一个 Nginx Pod,做反向代理
- 后端:若干微服务,通过 k8s Service 暴露,如
demo-dlp-service.huanfa - Nginx 里直接写:
1 | proxy_pass http://demo-dlp-service.huanfa:80; |
现象:
初次部署时,一切访问正常
当你 更新后端 Deployment / 重建 Service / 变更 Endpoints 后
- Nginx 容器里
ping demo-dlp-service.huanfa仍然能 ping 通 - 但浏览器访问前端页面时,接口变成 502/超时
- 重启下 Nginx Pod 又恢复正常(直到下一次后端更新)
- Nginx 容器里
这类问题常让人误以为是:
- k8s 网络有问题?
- Service 没更新?
- Pod 探针 / readiness 有坑?
实际上,非也——核心问题在于:Nginx 的 DNS 解析结果被缓存住了。
二、原因:Nginx 只在“第一次”解析域名,之后就不再更新
在传统物理机 / 虚拟机环境中,后端服务器 IP 很少变,所以 Nginx 默认的 DNS 行为一直没啥问题:
- 配置里写了
proxy_pass http://some-domain:80; - Nginx 在启动或第一次处理请求时做一次 DNS 解析
- 把解析出来的 IP 缓存在内存里,之后就一直用这个 IP
但是在 k8s 环境里,Service 对应的后端 Pod IP 是会变的:
- 滚动升级 / 重启 Pod
- HPA 扩容缩容
- 节点故障迁移
都会导致 Service 背后的 Endpoints 变化。
而如果你 只是简单地在 proxy_pass 里写了 Service 名称,又没有额外配置 DNS 相关指令,那么:
Nginx 仍然只会在“第一次”解析 Service 的域名,后面就一直用旧 IP。
这就是为什么你在 Nginx 容器里 ping 没问题(系统 DNS 是新的),
但 Nginx 自己还是连旧的 Pod(甚至已经不存在的 IP)——自然就访问失败了。
三、解决方案一:在原生 Nginx 中启用动态 DNS 解析(resolver + set)
在 nginx 1.18.0 版本中测试通过。
核心思路:
- 告诉 Nginx:DNS 服务器是谁(比如 k8s 里的
kube-dns) - 将 Service 名称赋值给一个变量
- 在
proxy_pass中使用这个变量 - 这样 Nginx 就会在访问时按
valid=…设定,周期性重新解析域名
完整配置如下(保持原样,不做任何修改):
1 |
|
关键配置说明
resolver kube-dns.kube-system.svc.cluster.local valid=5s;- 指定 Nginx 使用 k8s 的 DNS 服务(
kube-dns)进行解析 valid=5s表示解析结果最多缓存 5 秒,过期后会重新查
- 指定 Nginx 使用 k8s 的 DNS 服务(
set $endpoint_service demo-dlp-service.huanfa.svc.cluster.local;- 把 Service 的完整域名(一定要写全 FQDN)赋值给变量
$endpoint_service
- 把 Service 的完整域名(一定要写全 FQDN)赋值给变量
proxy_pass http://$endpoint_service:80;- 使用变量做
proxy_pass,配合上面的resolver,就能让 Nginx 定期重新解析域名
- 使用变量做
这种方案适用场景
- 已经在用 原生 Nginx(官方镜像等),不方便切到 Tengine
- 服务数量不算特别多,Nginx 做简单前端网关
- 希望配置简单、变更小,只是补上 DNS 动态解析能力
四、解决方案二:使用 Tengine 的 dynamic_resolve 功能
如果你愿意使用淘宝开源的 Tengine 3.1,可以直接用其内置的 dynamic_resolve 动态解析能力。
重点注意:Service 地址一定要写完整 FQDN,例如:imes-system-service.demowms.svc.cluster.local。
下面是完整配置(保持原样,不做任何修改):
1 |
|
关键点解析
resolver kube-dns.kube-system.svc.cluster.local ipv6=off;- 同样是指定使用 k8s 的 DNS
upstream sys { dynamic_resolve fallback=stale fail_timeout=15s; … }
以及其它一系列upstream:dynamic_resolve:开启动态解析fallback=stale:当解析失败时,继续使用旧的 IP,避免瞬时解析失败导致大面积 502fail_timeout=15s:失败后多长时间内视为不可用
server imes-xxx-service.demowms.svc.cluster.local:端口;- 一定要写完整的 FQDN(
*.svc.cluster.local),否则可能无法正确解析
- 一定要写完整的 FQDN(
这种方案适用场景
- 你愿意使用 Tengine 作为前端 Web 服务器
- 有大量后端服务 / 复杂路由,需要更强大的 upstream 能力
- 希望在解析出问题时有更优雅的降级(例如
fallback=stale继续使用旧 IP)
五、实践中的几个小建议
无论你选用哪种方案,建议注意以下几点:
永远使用 Service 的全限定域名(FQDN)
比如:my-service.my-namespace.svc.cluster.local
避免依赖 search domain,减少“在某些环境下突然解析失败”的坑。适当调小 DNS 缓存时间
- 原生 Nginx:
valid=5s是比较折中的设置 - Tengine:搭配
dynamic_resolve使用
时间太短会增加 DNS 压力,太长又起不到动态更新的效果,一般 5–30 秒比较合适。
- 原生 Nginx:
区分“系统 DNS 能 ping 通”和“Nginx 内部解析是否更新”
ping service成功,只说明 容器的 /etc/resolv.conf + CoreDNS 正常- Nginx 使用的是 自己的 DNS 缓存机制,必须通过 resolver / dynamic_resolve 主动接入。
升级 / 重启策略
- 即便有了动态解析,生产环境里还是建议对 Nginx 做滚动升级 / 定期 reload
- 避免因为历史配置 / 旧连接导致的边角问题
六、总结
在 k8s 中使用 Nginx 作为前端服务器时,DNS 缓存问题是一个非常容易被忽略的坑:
- 表面上看是“后端升级后,Nginx 偶尔 502 / 访问失败”
- 实际上是:Nginx 只在第一次解析域名,后面 Service 或 Pod IP 变了,它却仍然握着旧 IP 不放
本文基于实际案例,给出了两种实测可行的解决方案:
原生 Nginx:
使用resolver + set + 变量 proxy_pass,在 nginx 1.18.0 上验证通过Tengine:
使用dynamic_resolve,支持更丰富的动态解析和降级策略
如果你也在 k8s 里用 Nginx 直连 Service,建议尽快检查自己的 nginx.conf,
看看是不是还停留在“写死一个 Service 名称”的阶段——
早点补上 DNS 动态解析配置,可以少踩很多坑。




















