news 2026/10/21 12:46:21

Ingress-nginx核心原理与实战:K8s七层负载均衡从入门到排错

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ingress-nginx核心原理与实战:K8s七层负载均衡从入门到排错

做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转发规则里写的后端明确指向某个Service,Service做的是把流量继续转发到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-robin,nginx在转发时自己也会做一次负载均衡,实际请求的分发是两层叠加的,别把流量分布看成一个单一环节去分析。

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和Traefik,Ingress资源里通过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下面定义一个host,nginx会为这个域名生成server块。path的pathType有两个常用值,Prefix和Exact,Prefix表示路径前缀匹配,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/login,nginx转发给后端时还是/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时,捕获到的是login,rewrite之后后端收到的是/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是否强制跳转HTTPS"true"全站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接到一个新的Ingress,

annotations: 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入口IP?Ingress里的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里很多组件光看文档越看越绕,上手跑一遍问题就清楚了。这份笔记如果能让刚开始接触七层负载均衡的人少走一点弯路,我就没白记录了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 12:46:20

AI编程流水线:用Subagent工作流把写代码变成严谨工程

最近常有人问我:AI 编程工具我也在用了,为什么在别人手上那么神,在我手上就翻车?演示视频里它能一口气写几百行代码,放到真实项目里却各种离谱——改 A 坏 B、上下文越聊越乱、代码能跑但没人敢上线。我通常直说&#…

作者头像 李华
网站建设 2026/10/21 12:46:20

C++实战:27个趣味程序从编译到二次开发

简介:这份资源是面向C初学者与进阶学习者的27个经典趣味程序合集,以「学练结合」的方式帮助读者在动手实践中理解语法与编程思想。内容覆盖从Hello World变体、数据类型与变量、数组与字符串处理,到指针与内存操作、类与对象、继承多态封装等…

作者头像 李华
网站建设 2026/10/21 12:46:09

CycleGAN无配对图像翻译:循环一致性损失与Keras实现避坑指南

简介:这份资源是CycleGAN的Keras实现代码包,面向具备一定深度学习基础、希望上手无监督图像到图像转换的开发者与研究者。CycleGAN通过两个生成器与两个判别器的对抗博弈,配合循环一致性损失,在无需成对样本的条件下完成跨域风格迁…

作者头像 李华
网站建设 2026/10/21 12:45:53

改进DETR算法的齿轮表面缺陷识别方法与工程实践

齿轮表面缺陷检测这个方向,前几年大家讨论最多的是YOLO改到第几版、mAP能到多少。这两年风向变了,越来越多实验室和工厂技术团队在往DETR这类端到端检测器上转。原因也不难理解:齿轮缺陷的形状差异实在太大,一条划痕可以横跨整个齿…

作者头像 李华
网站建设 2026/10/21 12:45:49

self_operating_computer实战:多模态大模型操控电脑的完整指南

第一次接触 self_operating_computer 时,我第一反应是:“这不就是给大模型装上了一双眼睛和一只手吗?”它要做的事情其实很直白:让 AI 看着电脑屏幕,自己移动鼠标、敲击键盘,像真人一样完成你交代的任务。…

作者头像 李华
网站建设 2026/10/21 12:45:03

LDA主题模型从原理到实战:吉布斯采样与中文文本挖掘

第一次翻开《统计学习方法》的LDA章节,我第一反应是把书合上,去窗边喘口气。前面十几章虽然也劝退,但至少每章都在告诉你"我要拟合一个分类器"或者"我要估计一个序列标注",而LDA(潜在狄利克雷分配…

作者头像 李华