Ingress-nginx核心原理与实战:K8s七层负载均衡从入门到排错
发布时间:2026/10/21 12:46:28 锦皓数字建站

做K8s运维到今天如果让我说哪个组件最容易把概念搞混我一定选Ingress。Service和Pod那套逻辑不算难懂但一碰到Ingress脑子里得同时转三个对象Ingress资源、Ingress Controller还有Controller背后真正干活的nginx进程。这篇学习笔记5就把Ingress-nginx实现K8s七层负载均衡这件事从头到尾讲透讲清楚它解决什么问题、配置怎么下笔、生产环境怎么用好以及那些文档里不写的坑。直接说场景。有个项目要给十几个内部应用统一提供对外访问入口如果照着Service那套思路最省事的做法是每个Service开一个NodePort。可端口一多维护的人就疯了端口冲突、IP要暴露一堆、没法按域名转发后来彻底换成Ingress-nginx一个外部入口IP一个80/443端口几十个服务全接进来按域名、按路径分流干净利落。这篇内容适合刚把Service用熟、想知道外部流量怎么按HTTP层规则进集群的人也适合已经在用Ingress但被各种annotation整得云里雾里的朋友。我尽量把每一步为什么这么做讲清楚能直接抄作业。1. 先从四层Service的瓶颈说起为什么非得上七层1.1 NodePort和LoadBalancer用起来有多别扭开始接触K8s的人最先接触到的外部访问方式就是NodePort。它逻辑很简单把Service的某个端口映射到每个节点的同一端口上外部请求到达节点端口后kube-proxy再把流量转发到后端Pod。听起来没问题用的次数一多问题全冒出来了。第一是端口范围及其有限默认只有30000到32767这一段总共不到三千个端口看着不少可企业内部服务一多几十上百个应用排队申请端口冲突管理就能让人头大。第二是每个服务开一个端口外部调用方要记一长串“IP加端口”的映射关系域名基本没法用。第三是NodePort的转发是四层的只认IP和端口不认Host和URL想按域名做路由分流门都没有。LoadBalancer类型稍微好一点它由云厂商的负载均衡器对接K8s Service给服务分配一个独立的公网入口IP但成本跟着上去了。每个Service一个LB实例费用翻倍不说配置改动还要等云厂商那边同步。裸机环境或内网环境想搭LoadBalancer还得额外部署起LoadBalancer的实现组件比如MetalLB又是一层维护成本。说到底四层方案解决的只是“能不能访问”的问题企业真正需要的是“一个入口、多个服务、按规则分流”这个需求四层解决不了。1.2 七层和四层的本质区别四层负载均衡拿到的是IP和端口它做决策的依据是“这个包该转发到哪台机器”。七层负载均衡拿到的是完整的HTTP请求它可以看URL路径、域名Host、请求头Header、Cookie甚至请求体内容。用生活里的例子说四层LBS相当于酒店前台客人说“我要找302房间”不管是谁只要有房号就能带你过去。七层Ingress则是总机转接你说“找财务部张三”它会按分机表转给对应的人。前者认编号后者认规则。这个概念一旦建立起来Ingress的价值就清楚了。它把负载均衡从IP端口层面提升到了HTTP路由层面。同样的入口IP和端口根据Host是app1.example.com还是app2.example.com或者根据路径是/api还是/web把请求转发给不同的Service最终到不同的Pod。这就是七层负载均衡最核心的场景。1.3 Service并没有被替代而是分工变了很多初学者以为有了Ingress就可以不用Service了这是理解上比较大的误区。Ingress转发规则里写的后端明确指向某个ServiceService做的是把流量继续转发到Pod同时完成负载均衡。Ingress不直接连Pod。在七层架构下Service承担的职责更专注于集群内部的服务发现和负载均衡外部流量入口交给Ingress两者的分工是Ingress负责“入口分流”Service负责“内部转发”。理解了这个依赖关系后面看Ingress配置就不会犯“后端地址写Pod IP”这种错误了。2. Ingress和Ingress-nginx到底怎么配合2.1 先分清Ingress资源和Ingress Controller这是学习Ingress时最容易卡住的点。很多教程说“部署Ingress”其实说的不是同一件事。K8s里有两个东西都叫Ingress相关。Ingress资源是一个API对象它是用户写的一份规则清单YAML文件里声明了“哪个域名的什么路径转到哪个Service”它本身没有任何执行能力。Ingress Controller是一个常驻运行的Pod它一直在监听API Server里Ingress资源的变化拿到这份规则清单之后把规则翻译成nginx的配置然后让真正的nginx去干活。用最直白的话说Ingress资源是“需求文档”Ingress Controller是“执行团队”nginx是“具体干活的工人”。你只写需求文档没人执行集群没反应你只跑Controller没文档它也没事可干。两者必须配合。我们说的Ingress-nginx就是官方维护的Controller实现之一它本质是一个带着nginx的Pod里面跑了一个控制循环监控Ingress、Service、Endpoint三类资源的变化动态生成nginx.conf一旦规则有变化就会重载nginx。2.2 Controller内部到底做了什么把Controller的启动流程拆开看能理解很多现象。它启动后会先做一次全量同步把集群里所有Ingress都拉到本地生成一份nginx.conf基础配置然后通过认证信息访问K8s API建立长连接监听后续变化。这里有几个细节点值得关注。Controller默认监听宿主机或Service的80和443端口HTTP和HTTPS请求都由它接收。它生成的nginx配置里每个Ingress的Host会对应一个server块每个path会对应一个location块。默认后端default-backend负责响应那些没有匹配到任何规则的请求。如果你进到Controller的Pod里执行kubectl exec进入容器能看到nginx.conf文件静态查看当前生效的规则。这也是排查问题时最高效的手段之一后面我会专门讲。2.3 一个请求从外部打到Pod的完整链路把链路走一遍印象会非常深刻。假设外部客户端访问http://app.example.com/api/user。第一步DNS把域名解析到Ingress Controller所在的节点IP或负载均衡器IP。第二步HTTP请求到达Controller的80端口nginx根据请求的Host头找到对应的server块再根据URL路径/api匹配到location规则。第三步规则里写着转发到某个Service的某个端口nginx通过Service的ClusterIP和端口把请求发过去。第四步Service经过kube-proxy维护的规则把请求转发给后端Pod。第五步Pod把响应原路返回。整条链路里Controller充当了七层入口Service做了四层内部转发各司其职。有一个容易忽略的点如果Service的负载均衡策略是默认的round-robinnginx在转发时自己也会做一次负载均衡实际请求的分发是两层叠加的别把流量分布看成一个单一环节去分析。3. 部署Ingress-nginx的实战操作3.1 版本选择和前置条件部署前第一件事是确认Ingress-nginx版本与K8s集群版本的兼容性。版本选错轻则功能异常重则Controller启动不了。建议直接查官方版本的对应表简单说K8s 1.27以前的集群Controller v1.7或v1.8都能用1.28以上尽量选v1.9或更新版本。我用的是Controller v1.8.1配K8s 1.26一路顺畅。集群方面有几个前置要求。Ingress Controller需要读取集群内资源所以要保证API Server可访问RBAC配置正常。Controller默认不指定namespace可以单独建一个nginx-ingress命名空间来隔离资源后面管理起来清爽很多。3.2 部署方式对比部署Ingress-nginx主流两条路直接apply官方YAML或者用Helm装。官方提供的deploy.yaml适合快速上手里面把Deployment、Service、ConfigMap、RBAC等一次性都建好一条命令搞定。Helm方式适合需要大规模定制参数的情况通过values.yaml控制副本数、镜像仓库、资源限制等。我自己的习惯是测试环境直接apply官方YAML生产环境用Helm因为生产环境往往要改好几个关键参数比如副本数、Pod调度策略、资源request和limit等写进values里统一管理更可维护。部署完成之后验证手段是kubectl get pods -n ingress-nginx。如果Controller的Pod是Running状态再看日志看到类似“successfully loaded configuration”的提示说明Controller已经正常启动并同步了配置。这里提醒一下Controller启动后可能要花十几秒到几十秒做首次全量同步别刚启动就看两眼没动静就慌稍微等一下。3.3 部署后立刻要做的三件事第一件事是确认Controller的Service类型。如果是裸机集群默认生成的Service可能是LoadBalancer类型但没有云厂商支持时它的EXTERNAL-IP会一直是pending状态。这时需要改成NodePort或者用hostNetwork模式让Controller直接复用宿主机网络端口。用hostNetwork时注意端口占用宿主机80、443如果有其他服务先占住Controller会起不来。第二件事是配置IngressClass。IngressClass是K8s用来指定使用哪个Controller的资源。一个集群可以同时装多个Ingress Controller比如Ingress-nginx和TraefikIngress资源里通过ingressClassName字段指定到自己要用的那个。Ingress-nginx部署时会自动创建一个名为nginx的IngressClass一般不用手动建但要在Ingress里显式声明。第三件事是检查RBAC。如果Controller报权限不足的日志大概率是RBAC没配好。官方YAML已经带了ServiceAccount、ClusterRole和ClusterRoleBinding但如果你手动裁剪过文件这部分最容易漏。Controller需要list、watch、get Ingress、Service、Endpoint等资源权限少了它什么都干不了。4. 写一个真实的Ingress配置4.1 YAML逐段拆解纸上谈兵半天不如直接上一份能跑起来的配置。下面是一个最常见的Ingress示例把app.example.com域名的所有请求转发到backend-service的8080端口。apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: production spec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: backend-service port: number: 8080拆开看每一段。apiVersion用networking.k8s.io/v1这是当前正式版本老教程里写的extensions/v1beta1早废弃了别抄旧配置。ingressClassName: nginx对应前面说的IngressClass这是指定Controller的关键字段。rules下面定义一个hostnginx会为这个域名生成server块。path的pathType有两个常用值Prefix和ExactPrefix表示路径前缀匹配Exact要求路径完全相等。这里/用Prefix所有路径都能命中。尝试访问app.example.com时如果域名解析到了Controller所在的入口IP这个Ingress就能把请求转发给backend-service。首次实验建议后端服务用echo类型镜像这样看到的响应里带着Pod名称和Host信息能非常直观地确认请求到了哪个Pod帮助自己验证转发路径对不对。4.2 pathType和rewrite-target这里最容易踩坑pathType用Prefix看似简单实际容易出岔子。比如后端Service里的接口路径都是/api开头而Ingress规则写的path是/api前端请求的是/api/user那没问题可如果后端应用自身不感知前缀只是把Ingress的路径原样转发过去就经常出现404。Ingress-nginx默认把请求路径原封不动转发给后端。比如规则path是/app请求来了/app/loginnginx转发给后端时还是/app/login。如果后端服务的接口实际只暴露/login就会打不到接口。这时要用rewrite-target注解改写路径把Ingress接收到的路径重写之后再转发。metadata: annotations: nginx.ingress.kubernetes.io/rewrite-target: /$1 spec: rules: - host: app.example.com http: paths: - path: /app/(.*) pathType: Prefix backend: service: name: backend-service port: number: 8080注解rewrite-target这里写成/$1$1对应path正则里第一个括号捕获的内容。请求/app/login时捕获到的是loginrewrite之后后端收到的是/login完美匹配。如果多个path共用rewrite可以用/$1/$2这种形式把第一段路径也捕获进来按需拼装。踩过的坑是正则写错或target写错比如写成rewrite-target: /结果所有路径都被改成/后端永远只看到根路径误伤面很大。4.3 HTTPS终结与证书配置企业内部对安全要求高对外入口往往需要启用HTTPS。Ingress-nginx做HTTPS终结的做法是TLS握手在Controller这一层完成Controller把HTTPS解密后再用HTTP转发给后端Service。这样后端Pod不用处理证书证书管理集中在入口处。配置方式分两步。第一步创建TLS Secret里面包含证书和私钥。第二步在Ingress里声明tls字段引用这个Secret。apiVersion: v1 kind: Secret metadata: name: app-tls namespace: production type: kubernetes.io/tls data: tls.crt: base64编码的证书内容 tls.key: base64编码的私钥内容spec: tls: - hosts: - app.example.com secretName: app-tls rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: backend-service port: number: 8080证书管理在稍大规模的环境里一般是接入cert-manager实现自动签发和续期这个可以单独一个主题说。至少要注意TLS Secret是和Ingress同namespace的跨namespace引用做不到。证书私钥泄露风险高尽量别提交到Git仓库里用外部Secret方案管理更好。5. 生产环境必须掌握的配置技巧5.1 annotations速查Ingress-nginx的底座是nginx但在K8s里调整行为不是直接去改nginx.conf而是通过Ingress资源上的annotations。下面这张表是我平时最常用的一组建议收藏。annotation作用示例值使用场景nginx.ingress.kubernetes.io/rewrite-target重写转发后的路径/$1路径带前缀时去掉前缀nginx.ingress.kubernetes.io/ssl-redirect是否强制跳转HTTPStrue全站HTTPS时开启nginx.ingress.kubernetes.io/proxy-body-size允许的请求体大小8m后端有上传文件需求nginx.ingress.kubernetes.io/proxy-connect-timeout连接后端超时时间30调整后端响应慢时的等待时间nginx.ingress.kubernetes.io/limit-rps单IP每秒请求数限制10给接口做简单限流nginx.ingress.kubernetes.io/enable-cors是否开启跨域true前后端分离场景用annotation有一个原则宁可在Ingress级别配置也别去改Controller的ConfigMap。Ingress级别的配置只影响当前这条规则粒度更细排查时不用担心影响到其他服务。在实际配置时如果发现某个annotation没生效先别急着怀疑语法去检查Controller版本对它的支持情况。有些annotation名字在不同版本里改过有的老版本只认nginx.ingress.kubernetes.io开头更新版本会兼容新前缀但官方文档里只列新的。5.2 上传大小和超时参数的调整细节生产环境特别常见的一个需求是允许大文件上传。默认配置里Ingress-nginx对请求体大小限制是1m接口一旦收到稍大一点的POST请求就直接返回413。处理方法就是在Ingress上加上proxy-body-size注解。annotations: nginx.ingress.kubernetes.io/proxy-body-size: 50m但要提醒的是这个50m还受后端Service对应Pod的实际处理能力影响。如果Pod里的服务本身没有提高限制Nginx放行了也会被后端拒绝。所以调完Ingress之后后端服务的参数也要一起调。超时参数的调整思路类似。如果后端服务内部有耗时很长的操作比如报表导出、批量任务容易出现网关返回504的情况。这时把proxy-read-timeout调大比如3600秒能让长时间请求不被nginx中断。调整之后最好实际压测验证好几百秒的超时时间如果配多了等于让所有慢请求都挂在后端反而拖垮资源。5.3 灰度发布和会话保持的进阶玩法Ingress-nginx在灰度发布上提供了一套可用的组合方案。它可以通过nginx.ingress.kubernetes.io/canary注解来标记某个Ingress为金丝雀规则配合canary-weight按流量比例放量或者canary-header按请求头定向分发。举个具体场景集群里已有v1版本的Service现在要上线v2。把v2的Service接到一个新的Ingressannotations: nginx.ingress.kubernetes.io/canary: true nginx.ingress.kubernetes.io/canary-weight: 10这样v2这条规则只接收10%的流量验证没问题之后把百分比调到50最后完全切换。这个能力在功能迭代频繁的团队里非常实用比直接在Service层面改selector要简单。会话保持方面如果后端是有状态应用登录态存在本地Session里同一用户后续请求必须打到同一Pod可以在Ingress上设置nginx.ingress.kubernetes.io/affinity: cookie并指定会话cookie名称。配置之后Controller会通过Set-Cookie控制后续请求的粘性路由用户请求始终落在同一个后端Pod上。如果没有这个需求就别开会话保持会削弱负载均衡的效果。6. 常见问题与排查实录6.1 访问返回404第一反应查default-backend几乎每个新手第一次配Ingress都遇到过404。404的含义是没有匹配到任何后端。排查路径我先建议自问三个问题域名是否真的解析到了Controller入口IPIngress里的host配置和访问的域名是否完全一致path是否匹配这三个里只要有任何一个对不上大概率就是404。如果以上都对再看default-backend。Ingress-nginx默认的default-backend是一个返回404的空服务Controller启动时会创建它。当请求的Host没有匹配到任何Ingress规则或者路径全部没命中时nginx就会把请求转到default-backend并返回404。所以看到404基本就是“有请求过来但规则没接住”。排查时用命令kubectl logs到Controller的Pod里看访问日志能看到请求接收的记录。如果日志里连请求都没有说明请求压根没打到Controller这时候要查入口链路比如Service类型、hostNetwork、DNS解析路线完全不同。6.2 配置改了但感觉没生效Ingress资源更新之后Controller需要时间感知变化并重载nginx配置。正常场景下这个时间是秒级的但如果你操作的Ingress存在多处冲突更新就可能不会正常生效。常见的冲突包括同一个域名host在多个Ingress里出现尤其在不同namespace下Controller可能随机选一个规则做匹配。Inress-nginx对相同host的规则合并逻辑有一定设定遇到跨namespace同名host时行为可能不符合预期。这种场景最好收敛成一条Ingress来管理必要时把rules拆开而不是在多个Ingress之间分散。也可以直接到Controller的Pod里看nginx.conf检查对应server块的内容是否符合预期。如果nginx.conf里已经生成了新配置但访问结果还是旧的样子那要检查浏览器缓存、客户端DNS缓存或者中间是否存在其他负载均衡器缓存了旧响应。6.3 Websocket长连接总是断连Websocket这类长连接应用通过Ingress-nginx接入时最常见的症状就是连接建立后很快断开。原因是nginx默认的read timeout是60秒Websocket长期不发消息时连接被回收了。处理方法是在Ingress上调整超时时间annotations: nginx.ingress.kubernetes.io/proxy-read-timeout: 3600 nginx.ingress.kubernetes.io/proxy-send-timeout: 3600把读写超时都拉长到一小时大部分业务场景就够用了。如果后端还依赖的是HTTP/2那还要检查Controller是否启用了HTTP/2支持不过一般Websocket场景走HTTP/1.1就行。6.4 排查命令这几条救过命最后把排查命令整理成一套固定动作。先看Controller状态kubectl get pods -n ingress-nginx再看Controller日志尾部kubectl logs -f deployment/ingress-nginx-controller -n ingress-nginx接下来进Pod查Nginx实时配置kubectl exec -it -n ingress-nginx -- cat /etc/nginx/nginx.conf再核对Ingress、Service、Endpoint三个对象kubectl get ingress -A、kubectl get svc -A、kubectl get endpoints -A。最后从集群外发起请求curl -v加上-H参数指定Host做验证。这套流程走完大部分问题都能定位到具体环节。我自己在实际排查中还有一个小习惯把curl的响应头和时间一起输出比如curl -v --max-time 5能看到连的是哪个IP、哪个端口响应有没有重定向耗时多少信息量比单纯看状态码大得多。说实话第一次用Ingress-nginx时我也被Controller和Ingress资源的区别搞懵过后来完全是因为动手配了一个真实域名把请求从浏览器一路追到后端Pod日志里才彻底想通这个链路。K8s里很多组件光看文档越看越绕上手跑一遍问题就清楚了。这份笔记如果能让刚开始接触七层负载均衡的人少走一点弯路我就没白记录了。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。