K8s Ingress网关性能调优:从2秒到100毫秒的排查实战
接到运维告警的时候第一反应是后端应用又出问题了。可躲开那一波“2 秒左右”的慢请求我盯着监控面板看了好一阵——应用自身 P50 只有 30 毫秒P99 却在 1.8 秒到 2.3 秒之间反复横跳。所有慢请求都卡在 K8s Ingress 网关上。这个标题里的“从 2 秒到 100 毫秒”我理解成一段完整的性能救赎过程先把拖延症到底出在哪一环节找明白再动刀最后把优化成果稳定下来。如果你也在维护 K8s Ingress 网关或者被莫名其妙的偶发延迟折磨过这篇内容足够当成一份排查手册来用。下面我直接按实操顺序讲。1. 先弄清楚这 2 秒到底花在哪里而不是马上升级网关很多团队遇到网关变慢第一反应是换组件、加副本、提升节点配置。其实 2 秒这个量级往往和机器性能没有太大关系甚至和 Nginx 本身的处理能力也无关。我见过太多案例问题都出在连接建立、DNS 解析、连接复用这类“看不见的握手环节”。1.1 用一条完整链路拆出延迟来源我先从请求路径说起。一次普通的访问在 K8s 环境里大体是客户端 - 四层负载均衡 - Ingress Controller Pod - Service - 后端 Pod延迟可能分布在这条链路的多个位置。我们不能只看 Ingress 监控里的平均耗时那会让真正的问题被平均掉。我把定位延迟位置的方法拆成三步。第一步在客户端机器上直接看时间分布。使用curl的分阶段计时能先粗分出“DNS 解析”“TCP 连接”“TLS 握手”“等待响应”几段各自消耗了多少时间curl -o /dev/null -s -w \ dns:%{time_namelookup} tcp:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n \ https://gateway.example.com/health如果tcp耗时高说明客户端到网关之间的网络或负载均衡层有问题如果ttfb高但total不高说明网关到后端链路上消耗了更多时间如果tls高则要查证书链和会话复用。第二步横向对比 Ingress 访问日志里的时间字段和后端应用日志里的接收时间。Ingress Nginx 的日志中有request_time和upstream_response_time如果前者远大于后者说明网关内排队或上游连接建立环节拖了后腿。第三步必要时抓包。不是每次都抓而是当值从毫秒级跳到秒级时看是不是发生了 TCP 重传、SYN 重传或者等某个超时窗口。我那次排查中最终就是在抓包里看到一条规律性的 DNS 查询等待才锁定问题方向。1.2 Ingress 网关常见的性能卡点清单把常见的卡点整理成一张清单按出现频率排序后面排查时能快速对照卡点典型表现根源DNS 解析异常偶发 1~2 秒延迟重试后正常Nginx resolver 对 AAAA 查询处理不当或 CoreDNS 响应慢上游连接复用不足高并发下延迟飙升每次请求都新建 TCP 连接握手开销大Service 端点变化频繁发布窗口内延迟明显后端 Pod 重建后Nginx 连接池失效重新建立连接Worker 进程配置过小大量请求排队单 Worker 处理能力不足accept 锁成为瓶颈内核参数限制高并发时连接建立失败或超时本地端口耗尽、TIME_WAIT 堆积那次 2 秒问题的核心确实出在 DNS 环节但后续优化如果不把连接复用一起搞定优化完也只能从 2 秒变成 300 毫秒很难达到 100 毫秒。所以我建议你按链路逐层排查而不是只盯一个点。2. 方案选型为什么我选择保留 Ingress Nginx而不是立即换网关排查完一圈后团队里有人提议直接换网关组件认为 Nginx Ingress 不行。我理解这种情绪但“换”解决不了已经存在的配置缺陷。先做方案对比再决定是否要动大手术。2.1 三种主流网关的适用场景对比我拿 Nginx Ingress Controller、Traefik、Envoy 类网关做了对比。这里的比较不是分优劣而是看谁更匹配现有环境和团队能力。特性Nginx Ingress ControllerTraefikEnvoy 类网关配置成熟度极高文档和案例最多中等配置风格新较高偏云原生性能上限强靠配置调优中规中矩强指标丰富运维成本低社区方案多中自定义能力有限较高学习曲线陡动态配置能力依赖 Reload优秀配置热更新优秀xDS 驱动排障友好度高日志和抓包直观中较高但链路概念多对大多数以业务开发为主、没有专职网关团队的团队来说Nginx Ingress 仍然是性价比最高的选择。它能处理常见的吞吐需求而且遇到问题时网上几乎能找到所有场景的答案。2.2 基于现状的选型结论我最终选择保留 Nginx Ingress理由很简单当前问题不是“组件性能不够”而是“组件配置没调对”。在配置层面解决 DNS 解析和连接复用之后吞吐和延迟都有明显改善。换网关意味着重学配置体系、重写路由规则、重调监控告警这些成本都要分摊到每一次发布里风险远大于收益。如果你的场景是成千上万条路由且频繁动态变更或者需要精细的灰度流量拆分和熔断能力那时候再考虑 Envoy 类网关才值得。但如果只是延迟不达标我建议先在当前组件上做深度优化。网关选型从来都是“够用、能维护”优先而不是参数越新越好。3. 从 2 秒到 100 毫秒的分步实操与核心参数确认保留 Nginx Ingress 之后我开始做分步优化。整个过程大概持续了两天每一步都是有明确目的和验证方法的。下面按我执行顺序讲解并给出可以直接参考的配置。3.1 先用压测建立可信的基准线没有基准线后面所有改动都说不清是否有效。我先用压测工具打了一轮请求记录优化前的 P50、P95、P99。这里推荐使用hey或者ghz它们对高并发压测的支持比传统工具更直观且能直接输出分位延迟统计。hey -n 20000 -c 300 -m GET https://gateway.example.com/health注意压测不能只跑一次至少要取三次数据并确认结果稳定。如果第一次跑出来的延迟曲线忽高忽低说明环境里还有其它干扰因素需要先排除。我当时记录了优化前的数据请求总量 2 万、并发 300P50 在 420 毫秒左右P95 为 1.6 秒P99 达到 2.1 秒。这就是我们需要解决的目标基线。压测期间也要观察 Ingress Pod 的资源消耗。如果 CPU 已经打满那后续的参数调优意义不大得先扩容。一般 Ingress 的 CPU 使用率在压测时最好低于 70%否则容易压到 Pod 配额边缘。3.2 第一刀DNS 解析与 resolver 缓存回到那次最核心的问题。抓包时我发现网关在处理请求时会向集群 DNS 发起解析请求但解析过程异常缓慢。进一步分析后问题来自 Nginx 默认的 resolver 行为它会在解析 A 记录时同时等待 AAAA 记录返回而当前网络环境对 IPv6 支持不完整。在某些查询组合下Nginx 进入了一段较长的等待期等满一个超时窗口后才完成解析这个超时量级正好和 2 秒接近。解决办法是让 Nginx resolver 缓存结果更久并且关闭当前网络不适用的 IPv6 AAAA 查询。在 Ingress Controller 生成 Nginx 配置的模块中可以通过注入自定义片段的方式把resolver指令改成resolver 10.96.0.10 valid30s ipv6off;这里的集群 DNS 地址以你实际环境为准大多数 K8s 集群中 CoreDNS 服务的 ClusterIP 可以在kube-system命名空间下看到。valid30s表示对解析结果缓存 30 秒ipv6off则直接省去 AAAA 查询。30 秒这个值不是随便写的它保证网关不会因为缓存过久导致后端 Service 的端点变化得不到及时感知同时避免每个请求都发起重复解析。这一刀之后延迟大头立刻消除。但这里有个环节容易踩坑修改 Nginx 配置后并不一定马上生效需要确认 Ingress Controller 完成了一次配置 Reload。有些修改可以通过 Annotation 实现有些需要重启 Controller Pod 才能生效。我的建议是尽量把通用配置放在 Controller 的 ConfigMap 中而不是给每条 Ingress 单独加 Annotation。3.3 第二刀Worker 进程与连接池配置DNS 问题解决后延迟降到了一定程度但距离 100 毫秒还有距离。接下来我把优化重点放到连接复用上。Nginx 默认对上游的 keepalive 连接数量通常偏小导致大量请求都在重新建连。TCP 三次握手加 TLS 开销在并发场景下会放大成很明显的延迟。我先看 Ingress Controller ConfigMap 当前的配置然后做以下调整apiVersion: v1 kind: ConfigMap metadata: name: ingress-nginx-controller namespace: ingress-nginx data: worker-processes: auto worker-connections: 32768 keep-alive: 128 proxy-connect-timeout: 2 proxy-read-timeout: 60 proxy-send-timeout: 60 enable-multi-accept: true enable-reuse-port: true这里逐项解释。worker-processes设成auto一般够用Controller 会按节点 CPU 核数自动分配。worker-connections决定了每个 Worker 能同时处理的连接数上限调大它能缓解高并发时的排队。keep-alive是 Nginx 与上游后端之间的空闲连接池数量。常见默认值很小我调到128后复用效果立竿见影。proxy-connect-timeout控制与上游建立 TCP 连接的超时原本太大会拖慢失败场景的响应改小是合理的但不要小于 1 秒否则极端网络抖动下容易误判后端故障。enable-multi-accept让每个 Worker 可以一次从监听队列中取多个连接而不是一个接一个处理。enable-reuse-port则让多个 Worker 共享同一监听端口减少锁竞争。这两个开关对高并发场景都很实用。如果你使用的 ingress-nginx 版本较新可能在 ConfigMap 里还会看到upstream-keepalive-connections、upstream-keepalive-requests这类键名。不同版本的键名有差异改之前先看当前 Controller 版本支持的配置项别照抄旧文档。最好的确认方式是在改完后查看 Controller 日志有没有出现unknown configuration key的警告。3.4 第三刀TLS 会话复用与超时参数如果业务通过 HTTPS 访问TLS 握手是隐藏的延迟大头。客户端第一次握手免不了但后续请求完全可以通过会话复用省掉一个完整握手过程。我在 Controller ConfigMap 中补充了 TLS 相关配置data: ssl-session-cache: true ssl-session-tickets: false enable-ocsp: true ssl-protocols: TLSv1.2 TLSv1.3开启ssl-session-cache后同一个客户端再次连接时能复用会话参数不需要重新走完整密码套件协商。ssl-session-tickets我选择关闭因为 Ticket 在负载均衡多实例下需要统一密钥管理如果只在本机缓存会话复用成功率会下降反而增加开销。OCSP 装订开启后证书状态查询不再由客户端单独发起也能省掉一部分握手时间。超时参数也要一起调。我习惯把proxy-read-timeout和proxy-send-timeout设置为 60 秒避免积聚过多的慢连接占用连接池。很多团队会把这两个值设成几秒结果业务中一个稍长的数据库查询就会导致网关断连那属于为了性能牺牲正确性不推荐。3.5 第四刀Linux 内核层面收尾前面几刀解决的是应用层问题但压测并发到一定程度后我观察到 Pod 所在节点的本地端口出现过短暂不足部分连接出现Cannot assign requested address报错。这属于经典的短连接太多导致的端口表耗尽Nginx 大量向上游建连时会快速消耗本地端口。我在节点上调整了内核参数这类变更需要通过sysctl或节点初始化脚本固化net.ipv4.tcp_fin_timeout 15 net.ipv4.tcp_tw_reuse 1 net.ipv4.ip_local_port_range 2048 65000 net.core.somaxconn 32768调小tcp_fin_timeout让处于 FIN_WAIT 状态的连接更快释放开启tcp_tw_reuse让 TIME_WAIT 套接字在安全条件下复用扩大ip_local_port_range给高并发连接留更多本地端口。这些参数不是越高越好tcp_tw_reuse需要内核版本支持且要确认不会影响 NAT 环境下的连接合法性。我在云环境实测过这批调整对延迟的贡献没有前几刀大但在极端流量下能避免“毫无征兆的连接建立失败”。全部调整完后我再跑了一轮相同参数的压测P50 降到 25 毫秒P95 降到 60 毫秒P99 稳定在 98 毫秒左右。从 2 秒到 100 毫秒在数据层面达成了标题里的目标。4. 优化过程中踩过的坑与排查实录调优过程不是一路绿灯。这一节我把真正影响进度的几个坑说出来避免你重复踩。4.1 偶发延迟回弹每次发布窗口都会出现完成参数调优后系统稳定了大概半天。第二天下午延迟监控又出现几个超过 500 毫秒的点且时间恰好和一次版本发布重叠。这个问题一开始很迷惑因为参数没变后端应用也换了新版本无法确认是哪边引入的。通过对比 Ingress 访问日志和发布记录我发现问题是这样的后端 Pod 在滚动更新时旧 Pod 被销毁Nginx 收到新的 Endpoint 后需要重新建立上游连接而新 Pod 的 readiness 检查还没完全通过。这时候请求如果被分配到正在启动中的 Pod连接建立会等待表现就是偶发超时或慢请求。解决思路不是在 Nginx 层调参而是规范发布流程。我为后端服务完善了 readinessProbe确保新 Pod 真正具备处理能力后才继续滚动同时调整了滚动更新策略让maxUnavailable更保守一些。这里想强调一点网关优化从来不只是改 Nginx 配置下游应用的生命周期行为也会直接影响延迟表现。4.2 服务自身 DNS 问题带来的“假阴性”优化后我在验证某个新接入服务时又看到了 200 毫秒以上的响应排查却始终没有头绪。后来发现是服务自身代码里每次请求调用下游时都在做 DNS 解析而没有复用连接。这类慢请求从监控上看经过网关时确实消耗了时间但根子其实在应用侧。它给我的教训是要区分“网关造成的慢”和“网关暴露出来的慢”。通过访问日志中的upstream_response_time如果后端已经响应很快则问题在网关如果后端本身就慢就别让网关背锅。团队里如果有应用开发者建议在客户端 SDK 层统一开启连接池和 DNS 缓存而不是让每个项目各写各的。4.3 高并发下客户端端口耗尽还有一个坑出现在压测并发从 300 往 500 提升时压测机的端口表被打满大量请求建立连接失败。这类问题容易被误判成“网关高负载”。确认方式很简单在压测机上看ss -s如果 TIME_WAIT 数量异常高或者报错里有Cannot assign requested address那就是端口耗尽。我调整了压测机内核参数后恢复但这也说明一个问题如果线上有大规模并发客户端比如很多应用服务器同时访问网关它们也可能出现类似情况。优化网关的同时也要让调用方的连接池足够大并尽量复用长连接。很多“网关变慢”的误报实际是调用方自己把本地端口耗尽了。5. 结果沉淀与后续长期治理优化完成只是开始。如何保证这些参数在下次版本升级、配置变更、人员交接后不丢失才是长期稳定运行的难点。5.1 优化前后的量化对比我整理了一张最终对比表供团队后续复盘和汇报时直接使用。这份数据是固定压测参数、同一时间段采集的具有可对比性指标优化前优化后改善幅度P50420 ms25 ms大幅下降P951.6 s60 ms明显改善P992.1 s98 ms达到目标请求成功率99.2%99.95%提升如果你也想达到类似效果建议不要追求某个绝对值而是看同一测试条件下的改善趋势。目标是降低 P99而不是把 P50 压到最低因为 P50 的优化很容易通过设置超时来实现但那样会牺牲长尾请求体验。5.2 把配置固化为代码防止漂移我踩过一个典型坑Ingress Controller 的 ConfigMap 被团队成员手工改过几次后来升级版本时忘记了之前的参数导致延迟回退。现在我把所有相关配置统一维护在 Git 仓库里包括 ConfigMap、启动参数、内核插件配置通过类似 GitOps 的流程进行变更。每次修改都有记录升级前会对比当前生效配置和仓库配置的差异。这一点对于多人维护的集群尤其重要。网关配置漂移比应用配置漂移更隐蔽因为它不会立刻报错只会让性能缓慢劣化。5.3 用监控与告警守住这个 100ms 的红线最后一步是监控。如果只优化不监控问题可能在下一次发布或配置变更后卷土重来。我建立了分位延迟监控重视 P99 接口耗时和上游连接池指标。对于 Nginx Ingress可以关注 Controller 自身暴露的指标比如连接数、请求处理耗时分布。如果 Prometheus 中已有接入可以用类似下面的查询来做 P99 监控histogram_quantile(0.99, sum(rate(nginx_ingress_controller_nginx_process_requests_seconds_bucket[5m])) by (le) )同时也设置了告警P99 超过 200 毫秒并持续 5 分钟就触发通知避免问题只在业务投诉时才被发现。监控的价值不在于看板多漂亮而在于它能在用户感知之前暴露异常。我个人这里还有一个小习惯每次优化完会把压测命令和结果留档在真实环境中形成一套可重复的“体检流程”。下次团队里有人提出“网关好像慢了”先用同一套流程跑数据再下结论。这个习惯帮我挡掉过不少基于感觉的误判。优化这个 2 秒问题之后我对网关排障的态度有了明显改变先看清延迟分布再逐个击破不盲目换组件。K8s Ingress 网关只要配置合理完全能撑住高并发场景。把 DNS 缓存、连接复用、Worker 参数、内核参数这几刀砍到位从 2 秒到 100 毫秒并不是什么玄学而是一套可以复现的方法。