资讯动态

Kong网关卡死排查:Docker网络DNS与限流插件连环故障

发布时间:2026/10/19 12:56:16 来源:尧图企业网站定制
做 API 网关排障的兄弟都懂Kong 这种基于 Nginx 加 Lua 的网关平时看着稳如老狗可一旦出问题日志里往往干干净净CPU 和内存也都正常唯独线上接口集体超时用户端体验就是“请求发出去半天不回包”。我这次遇到的“请求卡死”问题前后折腾了将近一天。表面上是限流插件配置引发的连锁反应实际上最深的坑埋在 Docker 网络里——容器名解析、DNS 缓存、IP 漂移再加上限流插件的 Redis 连接池被打满几件事凑在一起才酿成了一场面上的“全部卡死”。排查链路从应用层逐步往下钻到网络层最后还顺手把限流提示的英文文案改成了中文优化了用户体验。整个过程挺典型记录下来希望能帮到同样在 Kong 网关和 Docker 网络里踩坑的人。1. 请求卡死现象、错觉与第一步定位1.1 现象接口集体超时CPU 却低得反常先说现象。某天下午线上反馈部分接口响应时间从几十毫秒直接飙升到十几秒甚至直接超时。我第一反应是后端服务扛不住了于是立刻打开监控面板看服务端的 QPS、CPU、内存结果一切正常完全没有被打垮的迹象。再回头看网关层Kong 的 CPU 也很低内存占用稳定正常得不像话。最有意思的是Kong 的日志里没有明显的 error 或 timeout 记录——按道理请求超时一定会留下点蛛丝马迹可日志偏偏很干净。这个“干净”本身就是最大的异常。流量并没有消失而是全部堆在某个环节里“等”着。我当时猜测问题大概率出在网关和上游服务之间的某一层。因为如果请求已经到了后端后端监控里一定会看到延迟尖峰如果后端没收到请求那问题就一定出在网关路由、DNS 解析、负载均衡或者连接池上。1.2 错误排除先确认不是数据库和上游的问题排障第一步永远是缩小范围而不是上来就重启。我先后确认了三件事第一后端服务本身的健康检查和业务监控都正常第二数据库、Redis 等基础组件的连接数和慢查询也没有异常第三Kong 的管理 API8001 端口还能正常响应说明 Kong 进程本身没有死只是数据面转发出现了问题。到这里基本可以断定问题不在下游业务而是出在“网关 → 上游服务”这条链路上。接下来要做的是搞清楚请求究竟卡在哪一步。是卡在域名解析卡在建立 TCP 连接还是卡在等待上游响应这三者对应的排查手段完全不同。1.3 事态升级进容器里看真实延迟由于我们线上环境是用 Docker 跑的我决定直接进到 Kong 容器内部模拟一次真实请求看看链路到底卡在哪。# 进入 Kong 容器 docker exec -it kong bash # 手动请求上游服务观察耗时 time curl -v http://upstream-service:8080/api/health结果很有意思curl命令在解析域名阶段就卡了好几秒后面连接和请求倒是正常。也就是说真正的瓶颈在 DNS 解析上——容器内部解析上游服务名的过程变得极其缓慢。这个发现和一开始“请求卡死”的现象完全对上了。Kong 的 Nginx worker 在处理请求时如果碰到上游服务的域名需要解析就要去问 Docker 内置 DNS通常是 127.0.0.11。一旦 DNS 解析被卡住worker 就会在该请求上一直等待如果积压的请求足够多worker 的连接池被占满其他新请求就只能排队宏观表现就是网关卡死。2. 真正元凶Docker 网络里的 DNS 与容器重启的连锁反应2.1 Docker 内置 DNS 的不稳定时刻Docker 的自定义网络自带一个内嵌 DNS 服务器地址固定为 127.0.0.11。Kong 容器如果运行在自定义网络里默认就会使用这个 DNS 解析其他容器名。这本是一个很省心的机制——容器之间不用再相互指定 IP直接用服务名访问就行。但这个内置 DNS 有一个容易被忽略的特性当它解析某个服务名时如果目标容器正在启动、停止、重启或者处于不健康状态Docker 内置 DNS 有可能会返回 SERVFAIL。Nginx 遇到 SERVFAIL 时会根据 resolver 配置进行重试这在正常情况下问题不大重试一次拿到正确结果就完事了。坏就坏在“重试”这个动作上。如果目标容器反复处于异常状态DNS 解析就会反复失败每次失败都可能叠加额外的等待时间。对于长时间在线的 TCP 连接来说这种等待还能被容忍但对一个高并发的 API 网关来说积少成多的等待时间足以拖垮用户体验。2.2 容器重启后 IP 漂移Kong 还在往旧地址发流量我们这次故障还有另一个帮凶上游服务容器当天重启过一次。在 Docker 默认的 bridge 网络模式或自定义网络中容器重启后 IP 往往会发生变化。比如原来是 172.18.0.3重启后变成了 172.18.0.7。Kong 内部很多人不知道的是它在持久连接里可能存了旧 IP。如果上游服务通过域名访问Kong 首次解析域名时拿到的是 172.18.0.3它会缓存这个解析结果。Nginx 的 resolver 配置里有个valid参数可以控制 DNS 缓存的有效时间。我们当时没有设置这个参数长效缓存加上上游容器重启导致 Kong 在较长一段时间里仍然把流量转发到已经不存在的旧 IP 上。结果就是一部分请求被转发到黑洞里一直等到 TCP 超时另一部分请求重新发起 DNS 解析又撞上 Docker DNS 的不稳定期。两边叠加网关自然就“卡死”了。2.3 与限流插件 Redis 连接池的叠加效应到这里网络问题是主因但还有一根压垮骆驼的稻草——限流插件。Kong 的 rate-limiting 插件在集群模式下通常会用 Redis 作为共享存储。我们的限流配置依赖 Redis而 Redis 又是在另一个容器里跑的。当上游服务容器重启引发网络波动时Kong 到 Redis 的连接也受到了影响部分连接断开。本来这也没什么Kong 会自动重连。但注意限流插件对 Redis 的依赖是同步的——请求在放行之前必须查一次 Redis 计数。如果 Redis 连接异常、重连超时、或者连接池满了这个查询动作就会阻塞当前请求。我们的限流插件没有配置合理的超时时间导致查询 Redis 的阻塞不断累积最终把整个网关的 worker 连接池给拖垮了。所以这次故障的完整链条是上游服务容器重启IP 漂移Docker 内置 DNS 解析暂时异常Kong 解析服务名变慢Kong 旧的 DNS 缓存还把流量打到旧 IP 上引发超时重试限流插件需要访问 RedisRedis 连接也受网络波动影响请求进一步阻塞多路请求同时阻塞Kong worker 无法释放连接资源网关整体“卡死”。这里面任何单一因素都不至于让整个网关瘫痪但叠加在一起就是灾难。2.4 最小化复现两步让你现场还原故障为了验证这个推演我在测试环境做了最小化复现。步骤很简单两步就能还原# 第一步启动一个上游服务容器 docker run -d --name upstream-demo --network kong-test -p 8080:8080 my-server:latest # 第二步重启该容器让 IP 漂移 docker restart upstream-demo在重启之前先在 Kong 容器里连续请求上游地址一切正常在重启之后的瞬间立刻请求就能偶发出现 DNS 解析变慢、连接超时和限流插件报错的现象。这验证了我们的判断故障不是某一个配置错误导致的而是容器重启、DNS 缓存、IP 漂移和限流依赖四方联动造成的系统性风险。3. 修复动作网络、超时、健康检查三板斧3.1 从“默认网络”切换到自定义 Docker 网络第一件事是把所有相关服务统一放到同一个自定义 Docker 网络里并确保这个网络支持容器名解析。默认的 bridge 网络通常叫 docker0是不建议在生产环境使用的因为它不支持容器间的内置 DNS 解析也不支持容器名互通。你需要显式创建一个自定义网络# 创建自定义网络 docker network create kong-net # 启动 Kong 时指定网络 docker run -d --name kong \ --network kong-net \ -p 8000:8000 \ -p 8001:8001 \ kong:latest自定义网络的好处有三个一是自带 DNS 解析容器名可以直接当域名用二是网络隔离更清晰不属于该网络的容器无法随意访问三是支持在 Compose 文件里优雅编排。如果你用的是 Docker Compose它自动创建的那个默认网络也具备这些特性只要别手动覆盖成 bridge 就行。3.2 明确 Kong 的 resolver 与 DNS 缓存策略Kong 基于 NginxNginx 在做反向代理时如果 upstream 配置里写的是域名就必须用 resolver 来解析。在 Docker 环境下这个 resolver 指向 127.0.0.11 永远是个稳妥的选择但一定要正确配置valid参数。# Kong 容器内的 Nginx 配置片段 resolver 127.0.0.11 ipv6off valid10s;valid10s的意思是DNS 解析结果只在本地缓存 10 秒。10 秒之后如果再有请求需要解析该域名就会重新向 Docker 内置 DNS 发起查询。这样即使上游容器重启、IP 漂移最多 10 秒后就能拿到新 IP不会长期往旧地址发流量。我在这次踩坑之前并没有意识控制这个参数的重要性默认情况下 Nginx 对 DNS 结果的缓存时长可能会远超预期这是很要命的。在容器环境里IP 永远是会变的把 DNS 缓存时间调短等于让网关对网络变化更敏感。3.3 启用 Upstream 健康检查让故障节点自动摘除Kong 的 Upstream 对象支持主动健康检查和被动健康检查。主动健康检查会定期向后端发送探测请求被动健康检查则根据实际请求的成功/失败比例来判断节点状态。两者结合可以在某个上游实例异常时自动将其摘除直到它恢复。在 Kong 管理 API 里配置curl -X PATCH http://localhost:8001/upstreams/your-upstream \ -H Content-Type: application/json \ -d { healthchecks: { active: { type: http, http_path: /health, healthy: { interval: 5, successes: 1 }, unhealthy: { interval: 5, http_failures: 2, timeouts: 3 } }, passive: { type: http, healthy: { successes: 3 }, unhealthy: { http_failures: 3 } } } }这个配置的意图很明确每 5 秒主动探测一次/health接口连续 1 次成功就标记为健康连续 2 次失败就标记为不健康从负载均衡池里摘除。配合valid10s的 DNS 缓存Docker 容器重启导致 IP 漂移后网关最多在十几秒内就能完成对新节点的发现和编译这比原先动辄几分钟的超时体验强太多了。不过要注意一点健康检查也有代价。请求频率高了上游服务的访问日志会被冲得很厉害建议探测路径写一个轻量级接口不要打到业务主流程上。3.4 给限流插件 Redis 加上合理的超时与重试阈值限流插件卡请求这件事本质上不是 Redis 挂了而是 Redis 连接池资源被耗尽。Kong 的 rate-limiting 插件支持关于 Redis 的一堆配置项这些配置项平时不太引人注意但关键时刻真的能救命。我在这次修复中调整了限流插件的 Redis 配置把超时时间从默认值调低同时限制了连接池大小。这样当 Redis 出现短暂不可用时Kong 不会无限等待而是快速失败把错误暴露出来避免整个网关被拖垮。curl -X PATCH http://localhost:8001/plugins/your-rate-limit-plugin-id \ -H Content-Type: application/json \ -d { config: { policy: redis, redis_host: redis, redis_timeout: 1000, redis_ssl: false, limit_by: ip } }redis_timeout单位是毫秒我设置成 1000 毫秒意味着 Redis 连接或查询最多阻塞 1 秒超过就放弃。这样做会让一小部分流量在 Redis 异常时直接走失败流程而不是无限堆积。网关的职责是保护和分发不是陪着底层组件一起“殉葬”。4. 限流提示汉化把“API rate limit exceeded”换成用户体验友好的中文提示4.1 为什么这种细节值得改在修完上面的卡死问题后我注意到一个细节Kong 限流插件默认返回的提示是英文的。API rate limit exceeded这个提示对内部接口还好但要面对真实用户就很影响体验。用户不理解什么叫 rate limit更不知道自己是触发了频率限制只会觉得这个产品“报了个看不懂的错误”。把提示文案改成中文同时附带一句友好的说明不仅提升体验也能降低客服和运维的沟通成本。4.2 最直接的方案在 rate-limiting 插件配置里改 error_messageKong 的 rate-limiting 插件其实原生支持自定义错误消息字段名就叫error_message。如果你只是想更换一句话完全不需要写插件改配置就行。在 DB-less 模式下直接改 declarative 配置plugins: - name: rate-limiting config: minute: 60 error_code: 429 error_message: 访问过于频繁请稍后再试在传统数据库模式下用 Admin API 动态修改curl -X PATCH http://localhost:8001/plugins/PLUGIN_ID \ -H Content-Type: application/json \ -d { config: { error_message: 访问过于频繁请稍后再试, error_code: 429 } }改完之后再触发限流返回的响应就变成中文了。这里有一个值得注意的点error_code和error_message是配套的。如果你把error_code改成了 200客户端就不会把它当成错误这属于逻辑上的严谨性问题。一般情况下保持 429 状态码只在消息文案上做本地化就好。4.3 想彻底定制响应体可以考虑自定义插件或 pre-function如果只改一句话还不够比如你希望返回的 JSON 结构是前端约定好的格式包含 code、message、requestId、timestamp 等字段那就需要更灵活的手段了。我当时没有引入自定义插件而是利用了 Kong 的 serverless 函数类插件pre-function来做响应改写。思路是拦截 429 响应在 header_filter 阶段替换或重写返回内容。下面是一段参考 Lua 代码local function rewrite_rate_limit_response() local kong_status ngx.status if kong_status 429 then ngx.status 429 ngx.header[Content-Type] application/json; charsetutf-8 ngx.say({code:429,message:请求太过频繁请休息一下再来}) ngx.exit(429) end end将这段 Lua 代码挂到 pre-function 插件的header_filter阶段即可。注意还是不要用 emoji。这样你就能完全控制响应体了。不过说实话如果不是响应体结构有硬性要求用插件原生的error_message配置就够了。自定义 Lua 代码意味着又增加了一层维护成本每次 Kong 版本升级都可能影响要谨慎使用。4.4 汉化后的边界问题状态码、Content-Type、per-Service 配置汉化限流提示还有一个容易忽略的边界问题Kong 的限流插件可以作用在全局、服务、路由三个维度。如果你只改了全局插件某个服务单独覆盖了自己的限流配置那么那个服务可能还是英文提示。这时候需要逐个排查插件作用域# 查看所有 rate-limiting 插件及其作用范围 curl http://localhost:8001/plugins?namerate-limiting另外Content-Type 也要确认。默认返回可能是application/json如果你自定义了响应体别忘了设置charsetutf-8否则中文可能乱码。这个小细节不仔细看很容易踩进去。还有一个推荐做法把限流的错误响应做成结构化的 JSON比如{ code: 429, message: 请求过于频繁请稍后再试, detail: 您可以在 60 秒后重试 }这样前端只需要按约定解析结构不用关心具体文案同时也方便后续做多语言切换。5. 复盘清单Kong 网关与 Docker 网络协同使用的避坑经验5.1 每次容器重启后的必查项经过这次故障我把“容器重启”列为了高危操作。每次重启上游服务、Redis、Kong 原本相关的容器后我都会按下面的清单检查一遍容器是否拿到了新的 IPKong 容器内解析上游域名是否正常旧的 DNS 缓存是否还在生效可以进入 Kong 容器的 Nginx 缓存目录确认限流插件是否还能正常连接 Redis上游服务的健康检查接口是否响应正常这个清单看起来简单但每一条背后都有实际教训。尤其是第二和第四条分别对应了网络层和依赖层两个最常见故障源。5.2 Kong 配置自查清单这里分享几个我整理过的自查项配置项推荐值说明resolver valid10s控制 DNS 缓存时间防止 IP 漂移后继续访问旧地址upstream 健康检查开启 активной自动摘除不健康节点降低故障影响redis_timeout1000ms限流插件访问 Redis 的超时避免无限阻塞keepalive 连接数依据并发量调整避免连接池被耗尽后请求短路error_message中文文案优化用户可见的限流提示这里面很多配置不是一劳永逸的需要根据自己的流量模型进行调整。比如健康检查的频率太高会对上游产生额外压力频率太低故障感知又太慢。建议上线前做一轮压测找到适合自己的参数组合。5.3 日常监测与告警建议最后说一下监控告警方面的建议。这次故障中如果当时有更精细的监控完全可以在“请求从秒级恶化到卡死”的过程中截获线索。我建议关注以下几个指标Kong 的 Nginx 连接池使用率连接数打满往往是灾难的前兆Kong 访问日志中上游连接的耗时分布尤其是建立连接耗时和响应耗时两个维度DNS 解析耗时这个指标在标准监控里很难拿到但可以通过在 Kong 容器内运行定时的dig或nslookup脚本把解析耗时输出到日志里Redis 的连接数和阻塞事件容器重启事件每次容器重启都值得被记录和告警不是所有的重启都是无害的。如果你用的是 Prometheus 那套体系可以考虑暴露 Kong 的 exporter 指标重点盯住kong_nginx_connections_total、kong_upstream_latency这类的指标。但再好的指标也需要在故障之前设置好告警阈值别等到报警失去意义才去配置。最后的几点体会折腾完这一整轮排查我个人最大的感受是Kong 网关本身是可靠的真正不可靠的是它身边的人——复杂的 Docker 网络、不合理的超时参数、缺乏健康检查的上游服务、以及被忽略的依赖连接池。任何一个环节单独出问题Kong 都能扛住但当它们同时失控时网关就成了最显眼的受害者。如果以后再遇到类似的“卡死”问题我建议不要急着重启容器更不要直接猜是插件的问题。先做网络链路的逐段排查再顺着业务链路检查依赖。把网络这层理清了很多看似诡异的现象都能自圆其说。另外限流提示的汉化虽然只是个“小优化”但在实际业务里对用户体验的影响并不小。能通过配置解决的就不要写代码能通过一行配置改文案的就不要引入新的插件。简单、可控、不增加维护成本这才是网关运维里最值得坚持的原则。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑