1. 为什么要在 K8s 前面再挂一层 Nginx很多团队在集群里跑了一堆 ServiceClusterIP 加 kube-proxy 已经能做四层转发为什么还要在入口处再放一组 Nginx我最初也犯过嘀咕直到线上遇到几个绕不开的场景才彻底理解这套组合的价值。第一个场景是流量入口需要统一收敛。集群里可能同时跑着 Web API、WebSocket 长连接、gRPC、静态资源服务每种协议对超时、缓冲、连接复用的要求完全不同。K8s 的 Service 只负责把包送到 Pod它不关心你是 HTTP/1.1 还是 HTTP/2也不管你需不需要根据 Header 做灰度。这些七层的事得由 Nginx 来干。第二个场景是证书和域名管理。业务域名往往有几十个证书续期、SNI 分流、HTTP 强制跳 HTTPS这些如果全塞进 Ingress Controller配置会变得非常臃肿。把 Nginx 独立出来做一层边缘代理Ingress 只负责集群内路由职责就清晰了。第三个场景是跨集群、跨环境的统一入口。有些服务还在虚拟机里有些已经上了 K8s还有的跑在另一个集群。用一组 Nginx 作为统一入口后端 upstream 可以同时指向 K8s 的 NodePort、Ingress 地址甚至外部 IP迁移过程对客户端完全透明。所以标题里说的“对 K8s 集群中的 Service 做负载均衡”本质上是在集群外或边缘节点部署 Nginx把 K8s Service 暴露出来的入口作为 upstream再由 Nginx 完成七层负载、健康检查、限流和 TLS 终止。下面我把整套思路、配置和踩过的坑完整拆一遍。2. 整体架构设计与方案选型2.1 三种常见接入方式对比Nginx 要连到 K8s 里的 Service物理上有三种路径选哪种直接决定了后面的配置复杂度和稳定性。接入方式原理优点缺点适用场景NodePortService 在每个节点开一个高位端口配置简单不依赖额外组件端口范围有限节点 IP 变化需改配置小集群、测试环境IngressNginx 指向 Ingress Controller 的入口七层路由能力强支持域名分流多一层转发链路变长中大型集群、多域名ClusterIP 外部 LB通过云厂商 LB 或 MetalLB 暴露对 Nginx 透明IP 稳定依赖基础设施成本高生产环境、云上部署我个人的选择是生产环境优先用 Ingress 作为 Nginx 的 upstream测试环境用 NodePort 快速验证。原因很直接——Ingress Controller 本身已经处理了 Pod 漂移和健康检查Nginx 只需要把请求转给它不用关心后端 Pod 的 IP 变化。如果直接指 NodePort节点扩容或缩容时upstream 列表就得手动维护运维成本太高。2.2 为什么不用 Nginx Ingress 直接替代有人会问既然集群里已经有 Nginx Ingress Controller为什么还要在外面再套一层 Nginx这不是脱裤子放屁吗区别在于职责边界。Ingress Controller 的定位是“集群内流量的七层路由器”它跟 K8s API 强绑定配置通过 Ingress 资源下发。而外层 Nginx 的定位是“边缘网关”它要处理的是集群外到集群内的第一道 TLS 终止基于客户端 IP 的限流和黑名单多集群、多环境的统一域名入口静态资源缓存和压缩与 WAF、日志采集系统的对接这些能力如果全压在 Ingress Controller 上会导致它的配置极度膨胀而且每次改限流规则都要动 K8s 资源灵活性很差。外层 Nginx 用原生配置文件改完 reload 即可响应速度快得多。2.3 高可用与健康检查设计Nginx 本身不能是单点。我的做法是至少两台 Nginx 组成 Active-Passive 或 Active-Active前面再用 Keepalived 或云 LB 做 VIP 漂移。Nginx 到后端的健康检查用max_fails和fail_timeout控制同时配合主动健康检查模块商业版或nginx_upstream_check_module开源第三方模块。这里有个关键参数proxy_next_upstream。默认情况下Nginx 只在连接失败或超时时重试下一个 upstream。如果后端返回 502、503默认是不重试的。生产环境建议加上proxy_next_upstream error timeout http_502 http_503 http_504; proxy_next_upstream_tries 3; proxy_next_upstream_timeout 10s;这样当某个 Pod 刚重启还没就绪时Nginx 会自动把请求转到其他健康实例客户端几乎无感知。3. 核心配置细节与实操要点3.1 upstream 配置的坑与优化最基础的 upstream 配置长这样upstream k8s_backend { server 10.0.0.11:30080; server 10.0.0.12:30080; server 10.0.0.13:30080; }但这样写有几个问题。第一默认轮询算法不考虑后端负载如果某台节点上 Pod 少、压力小它也会分到同样多的请求。第二没有权重区分新扩容的节点和老节点一视同仁。第三没有连接复用每个请求都新建 TCP 连接在高并发下端口耗尽会非常快。优化后的配置upstream k8s_backend { least_conn; keepalive 64; keepalive_requests 1000; keepalive_timeout 60s; server 10.0.0.11:30080 weight1 max_fails3 fail_timeout10s; server 10.0.0.12:30080 weight1 max_fails3 fail_timeout10s; server 10.0.0.13:30080 weight1 max_fails3 fail_timeout10s; }least_conn把请求分给当前连接数最少的后端比轮询更适应真实负载。keepalive 64表示每个 worker 进程最多缓存 64 个空闲长连接配合proxy_http_version 1.1和proxy_set_header Connection 才能真正生效。注意keepalive参数必须配合proxy_http_version 1.1使用否则 Nginx 默认用 HTTP/1.0连接无法复用。这是最常见的配置遗漏。3.2 超时参数的精细调整超时设置是 Nginx 调优里最容易拍脑袋的地方。设短了正常慢请求被切断设长了故障 Pod 会拖垮整个链路。我的经验是分层设置proxy_connect_timeout 5s; proxy_send_timeout 60s; proxy_read_timeout 60s; send_timeout 60s;proxy_connect_timeout是 Nginx 跟后端建连的超时5 秒足够。如果 5 秒还连不上说明后端节点或 Pod 有问题快速失败比死等更合理。proxy_read_timeout是等待后端响应的超时。这个值要根据业务定普通 API 设 30 到 60 秒报表导出、文件处理这类长任务要单独用 location 覆盖设到 300 秒甚至更长。proxy_send_timeout是发送请求体的超时。如果客户端上传大文件这个值要放大否则传到一半就断了。3.3 真实 IP 透传与日志记录Nginx 作为边缘代理必须把客户端真实 IP 传给后端否则业务日志里全是 Nginx 的 IP排查问题两眼一抹黑。proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;X-Forwarded-For是追加式的如果前面还有一层 LB它会包含多级 IP。后端取真实 IP 时要取第一个非内网地址或者信任的代理层数。日志格式也要改默认的 combined 格式不记录 upstream 地址和响应时间排查性能问题很不方便log_format k8s_log $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent upstream$upstream_addr upstream_status$upstream_status request_time$request_time upstream_response_time$upstream_response_time;upstream_response_time和request_time的差值就是 Nginx 自身处理加网络传输的耗时。如果差值很大说明瓶颈在 Nginx 或网络不在后端。4. 完整实操流程与关键环节实现4.1 环境准备与 Nginx 编译参数假设我们在两台边缘节点上部署 Nginx系统是常见的 Linux 发行版。如果不用发行版自带的包而是自己编译编译参数很关键./configure \ --prefix/etc/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-http_stub_status_module \ --with-http_gzip_static_module \ --with-stream \ --with-stream_ssl_module \ --add-module/path/to/nginx_upstream_check_module--with-http_realip_module用于从X-Forwarded-For里取真实 IP--with-http_stub_status_module用于监控 Nginx 自身状态--with-stream用于四层转发比如数据库代理。nginx_upstream_check_module是第三方主动健康检查模块开源版 Nginx 没有内置主动检查这个模块能补上。4.2 主配置文件结构我把配置拆成几个文件放在/etc/nginx/conf.d/下主配置文件只保留全局设置user nginx; worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 10240; use epoll; multi_accept on; } http { include mime.types; default_type application/octet-stream; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; client_max_body_size 100m; client_body_buffer_size 128k; gzip on; gzip_min_length 1k; gzip_comp_level 5; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; include conf.d/*.conf; }worker_processes auto让 Nginx 自动跟 CPU 核数对齐。worker_connections 10240配合worker_rlimit_nofile 65535单机可以支撑几万并发连接。multi_accept on让一个 worker 一次接受多个新连接高并发下吞吐更好。4.3 针对 K8s Service 的 server 块配置假设业务域名是api.example.com后端是 K8s 里的 NodePort 30080server { listen 443 ssl http2; server_name api.example.com; ssl_certificate /etc/nginx/certs/api.example.com.crt; ssl_certificate_key /etc/nginx/certs/api.example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; access_log /var/log/nginx/api_access.log k8s_log; error_log /var/log/nginx/api_error.log warn; location / { proxy_pass http://k8s_backend; proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 5s; proxy_send_timeout 60s; proxy_read_timeout 60s; proxy_next_upstream error timeout http_502 http_503 http_504; proxy_next_upstream_tries 3; proxy_next_upstream_timeout 10s; } location /api/long-task/ { proxy_pass http://k8s_backend; proxy_read_timeout 300s; proxy_send_timeout 300s; } }ssl_session_cache shared:SSL:10m可以显著减少 TLS 握手开销10MB 大约能缓存 4 万个会话。http2开启后客户端到 Nginx 走 HTTP/2Nginx 到后端仍然走 HTTP/1.1这是最常见的组合。4.4 限流与熔断配置Nginx 原生限流用limit_req_zone和limit_conn_zonelimit_req_zone $binary_remote_addr zoneapi_limit:10m rate100r/s; limit_conn_zone $binary_remote_addr zoneconn_limit:10m; server { location /api/ { limit_req zoneapi_limit burst200 nodelay; limit_conn conn_limit 50; proxy_pass http://k8s_backend; } }rate100r/s表示每个 IP 每秒 100 个请求burst200允许突发 200 个nodelay表示突发请求立即处理而不是排队。limit_conn限制单 IP 并发连接数防止慢速攻击。注意limit_req_zone的 key 用$binary_remote_addr比$remote_addr省内存10MB 大约能存 16 万个 IP 状态。4.5 平滑 reload 与配置校验每次改完配置不要直接 restart而是先校验再 reloadnginx -t nginx -s reloadnginx -t会检查语法和证书路径。reload是平滑重启旧 worker 处理完现有请求后退出新 worker 用新配置接管客户端无感知。如果 reload 后发现问题想回滚把配置文件改回去再 reload 即可。但要注意已经建立的连接不会立即断开旧 worker 会继续服务直到连接关闭。所以回滚后部分客户端可能还在用旧配置这是正常现象。5. 常见问题与排查技巧实录5.1 502 Bad Gateway 排查思路502 是 Nginx 代理场景下最常见的错误原因通常有三类现象可能原因排查方法偶发 502后端 Pod 重启或未就绪查 K8s Pod 事件看是否有 OOM 或探针失败持续 502upstream 地址错误或端口不通telnet或curl测试 NodePort 连通性大量 502后端连接数打满查后端netstat的 TIME_WAIT 和 ESTABLISHED我遇到过一次偶发 502查了半天发现是 K8s 的 readinessProbe 配置太激进Pod 刚启动就被加入 Endpoints但应用还没初始化完。把initialDelaySeconds从 5 秒调到 15 秒后问题消失。5.2 连接复用不生效的排查配置了keepalive但后端连接数还是暴涨通常是这几个原因没有设置proxy_http_version 1.1没有设置proxy_set_header Connection 后端返回Connection: close前两个是 Nginx 侧配置第三个要看后端应用。有些框架默认关闭长连接需要在应用层开启。5.3 真实 IP 获取错误的处理如果后端拿到的X-Forwarded-For里第一个 IP 是内网地址说明前面还有一层代理没透传。这时候要用real_ip模块set_real_ip_from 10.0.0.0/8; real_ip_header X-Forwarded-For; real_ip_recursive on;set_real_ip_from声明信任的代理网段real_ip_recursive on表示从右往左找第一个非信任 IP。这样$remote_addr就会变成真实客户端 IP。5.4 性能瓶颈定位Nginx 自身性能问题通常看几个指标# 查看活跃连接数 curl http://127.0.0.1/nginx_status # 输出示例 Active connections: 1024 server accepts handled requests 100000 100000 500000 Reading: 10 Writing: 20 Waiting: 994Waiting数量大说明 keepalive 连接多正常。Reading和Writing持续很高说明 Nginx 在处理请求要关注 CPU 使用率。如果accepts和handled不相等说明有连接被丢弃要检查worker_connections是否够用。6. 进阶优化与个人经验总结6.1 动态 upstream 更新方案K8s 节点 IP 变化时手动改 Nginx 配置不现实。我的做法是用Consul Template 或 confd监听 K8s API自动生成 upstream 配置并 reload。具体流程是一个 sidecar 容器监听 K8s Endpoints 变化变化时渲染 Nginx upstream 配置文件执行nginx -t nginx -s reload这样节点扩缩容、Pod 漂移都能自动感知运维基本不用插手。6.2 日志采集与监控对接Nginx 日志用 Filebeat 或 Fluent Bit 采集送到日志平台。关键字段包括upstream_addr、upstream_status、request_time、upstream_response_time。监控告警重点看5xx 比例超过 1% 告警upstream_response_timeP99 超过 2 秒告警Nginx 活跃连接数突增 50% 告警6.3 我踩过的几个坑第一个坑是**proxy_next_upstream默认不重试 502**。有次后端一个 Pod 挂了Nginx 把请求转过去收到 502直接返回给客户端没有重试其他 Pod。加上http_502后客户端几乎无感知。第二个坑是**keepalive参数位置写错**。我一开始把keepalive写在server块里结果不生效。正确位置是upstream块内而且必须配合proxy_http_version 1.1。第三个坑是TLS 证书链不完整。有些客户端不报错有些客户端直接握手失败。用openssl s_client -connect检查证书链确保中间证书也配上了。第四个坑是**client_max_body_size默认只有 1MB**。上传大文件时直接 413查了半天才发现是 Nginx 限制。改成 100MB 后正常。这套方案我在几个不同规模的集群里都用过小到三节点测试环境大到几十节点的生产集群核心配置基本一致差别主要在 upstream 的接入方式和限流阈值。真正重要的是理解每一层在干什么而不是照抄配置。配置可以复制但排查问题的思路得自己练。
阅读完成 · 觉得有帮助?