
1. 为什么需要Ingress从NodePort的麻烦说起先聊个场景。你在某云厂商的Kubernetes集群里跑了一堆微服务前端、订单、用户、支付每个服务都是独立Deployment和Service。联调阶段想给测试同学一个入口地址访问前端页面你用了最顺手的方式NodePort。测试同学问端口是多少你查了一下说“30089”。他一脸疑惑“这端口怎么这么奇怪能改成80吗”你解释半天也说不清楚。而且NodePort每个服务都占一个端口端口越开越多测试环境越来越混乱运维同学看你写的Service清单直摇头。这时候Ingress就该登场了。Ingress是Kubernetes官方定义的一套API资源核心作用就是做HTTP层七层的路由转发。它的定位非常清晰集群内部有那么多Service每个Service对应一套后端Pod但你不能要求客户端记住每个Service的IP和端口。Ingress做的事情相当于给集群入口装了一个“智能指路牌”——用户只需要访问一个统一的入口域名或IPIngress根据请求路径、域名、请求头等信息把流量分发给不同的Service再由Service转发给具体的Pod。打个比方没有Ingress的时候客户端就像到一个没有前台指引的大楼里自己一层层找房间有了Ingress相当于楼下有个前台你说找哪个部门他直接告诉你坐哪部电梯到几层。那么问题来了我们直接用NodePort不也能实现访问吗能用但很别扭。NodePort的端口范围默认是30000-32767客户端必须记住每个服务对应的随机端口而且每加一个服务就要多开一个端口安全组策略也跟着复杂。这在开发测试环境勉强能忍一旦上了生产几十上百个服务全走NodePort基本是灾难现场。另外NodePort只解决“能通”的问题解决不了“怎么转”的问题。你想按域名转发按URL前缀转发做路径重写加认证限流NodePort全干不了这些是七层网关的能力范畴也就是Ingress的看家本领。所以Ingress解决的核心痛点可以总结为三条第一统一入口一个IP或域名就能路由到所有HTTP服务第二条件路由支持按域名、按路径分发给不同后端第三扩展能力通过注解annotations和Controller实现HTTPS、重写、限流、白名单等高级策略。再说一下适合人群。这篇内容适合刚接触Kubernetes、准备把服务暴露到集群外的开发者也适合已经在用NodePort但被端口管理搞到头大的运维同学。不需要很深的网络基础只要理解Pod、Service、Deployment这几个基本概念就行后面所有原理我都会拆开讲清楚。2. Ingress和Ingress Controller一个定规则一个跑规则很多人刚接触Ingress时会走进同一个误区以为创建了一个Ingress资源流量就能自动按规则走了。实际上不是这样Ingress资源本身只是一段声明式的“愿望清单”写清楚了“我想让哪个域名的哪个路径转发到哪个Service”但没有任何进程会主动执行这个清单。真正干活的是Ingress Controller。这两者的关系我用一个日常工作场景解释Ingress就像公司门口贴的“来访登记表”上面写明哪个部门的访客该怎么走Ingress Controller则是门口那位保安他按照登记表的内容来实际指路放行。没有保安登记表写得再详细也没人执行没有登记表保安也不知道该把访客往哪儿带。从技术实现来说Ingress Controller是一个常驻集群里的控制器进程不断监听Kubernetes API Server中Ingress资源的变化。一旦你创建、修改或删除了某个Ingress对象Controller会立刻感知并把这些规则翻译成自己所在代理程序的配置。最常见的Controller实现就是基于Nginx做反向代理Controller把Ingress规则转成Nginx的server块和location块配置然后热加载Nginx配置让流量按新规则走起来。在集群里Ingress Controller通常以Deployment的方式运行一组Pod并配套一个Service对外暴露。这个Service的类型可以选择NodePort或LoadBalancer取决于你在什么环境。云平台上一般选LoadBalancer云的负载均衡器会把流量拉到Ingress Controller的Pod然后Controller内部再按Ingress规则做七层分流。Controller的选型也是一个值得细想的问题。不同控制器侧重点不同ingress-nginx社区最主流、文档最全基于Nginx实现适合大部分生产场景尤其对路径重写、HTTP/2、WebSocket支持都成熟。Traefik配置热更新体验好天生为云原生设计自带Dashboard调试比较直观中小团队用起来很舒服。HAProxy Ingress性能和并发能力极强适合极端高并发场景但配置习惯和Nginx差别较大上手门槛略高。APISIX Ingress、Kong Ingress适合需要丰富插件体系限流、熔断、鉴权的团队插件即插即用属于网关方案向Ingress延伸的产物。我自己用得最多的是ingress-nginx。不是因为其他不好而是它的生态太成熟了遇到问题搜一下基本都有答案。这篇博文的实操部分也以ingress-nginx为例展开。这里补充一个关键概念IngressClass。随着Ingress资源被广泛使用一个集群里可能同时跑了多套Controller比如Nginx一套、Traefik一套。那某个Ingress资源到底该由谁来处理IngressClass就是用来打标定归属的机制。Ingress资源通过spec.ingressClassName字段指定使用哪个ControllerController也通过启动参数或安装配置声明自己对应的IngressClass名字。默认情况下如果集群里只有一套Controlleringress-nginx安装时会创建一个名为nginx的IngressClassIngress资源不显式声明时走默认设置也一般没问题。但如果集群里Controller多了这个字段就非常重要写错了控制器根本不认你的规则。3. 动手部署一套ingress-nginx控制器理论聊完开始实操。部署ingress-nginx最靠谱的方式是直接用官方提供的清单文件指定好版本后一键应用。我先讲一个通用流程再补充几个生产环境值得注意的细节。部署前先确认你的集群版本。ingress-nginx不同版本对Kubernetes版本的支持范围不同建议先到官方文档看一眼版本兼容矩阵别装了个不兼容版本Pod一直CrashLoopBackOff还一脸懵。确认无误后执行kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.1/deploy/static/provider/cloud/deploy.yaml这里我用的是某个发布时间不算太旧的版本。实际上你应该根据自己的Kubernetes版本选择一个匹配的controller版本来替换URL。执行完这条命令后Kubernetes会自动帮我们创建好以下几个组件namespaceingress-nginx所有组件都装在这个命名空间里。Deploymentingress-nginx-controller核心控制器Pod。Serviceingress-nginx-controller类型为LoadBalancer这就是流量入口。ConfigMapingress-nginx-controller用于保存Nginx的全局配置。各种RBACServiceAccount、ClusterRole、ClusterRoleBindingController要读取Ingress和Service信息必须有权限。ValidatingWebhookConfiguration做Ingress资源校验用的写错字段会提前拦截。等所有Pod进入Running状态可以确认一下Controller已经就绪kubectl -n ingress-nginx get pods kubectl -n ingress-nginx get svc看到INGRESS-NGINX-CONTROLLER这个Service的EXTERNAL-IP字段不再是空或者 说明对外入口已经拿到了。云厂商环境下这一步可能需要等一两分钟因为要动态分配负载均衡器的IP。如果你用的是自建集群或裸金属环境没有LoadBalancer能力有两个替代方案一是把Service类型改成NodePort然后用任意一台节点的IP加对应端口访问二是在裸金属环境装MetalLB这类负载均衡器插件。我建议测试环境直接NodePort先跑起来没必要一开始就纠结LoadBalancer。有一点值得注意官方这套清单默认给Controller的Service开了HTTP和HTTPS两个端口分别映射到容器的80和443。但Controller本身并不会自动配证书HTTPS的证书还是得靠Ingress资源里配置Controller只是把443端口收到的流量按规则分发。所以你在云控制台上看到的安全组规则记得至少放行80和443两个端口否则外部根本访问不到。部署验证最快的方式是直接请求一下Controller的IP看能否拿到Nginx的默认响应页curl -I http://EXTERNAL-IP如果返回了404或者503都算正常因为现在还没有任何Ingress规则反之如果你看到连接超时或者连接被拒绝说明网络层就没通先检查安全组、防火墙或者Service运行状态。4. Ingress规则怎么写从最简单的单域名转发到高级规则假设我们现在有个订单服务叫order-service跑在default命名空间里Service端口是8080。我要做的是让用户访问order.example.com时流量能转到这个Service上。创建一个order-ingress.yamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: order-ingress spec: ingressClassName: nginx rules: - host: order.example.com http: paths: - path: / pathType: Prefix backend: service: name: order-service port: number: 8080逐行拆解一下。apiVersion是networking.k8s.io/v1这是当前稳定版老版本里的 extensions/v1beta1早已废弃别再用了。ingressClassName指定为nginx对应我们前面装好的Controller。rules数组里定义了一条规则host限定域名只有访问order.example.com这个域名的请求才会被这条规则捕获。path字段为/pathType为Prefix表示以/开头的所有路径都匹配实际就是全匹配。backend部分把匹配到的流量转发到order-service这个Service的8080端口。应用kubectl apply -f order-ingress.yaml然后看Ingress详情kubectl get ingress kubectl describe ingress order-ingressdescribe输出里有一栏叫Rules会列出来host和对应的后端Service信息。还有一栏AnnotationsController会回写一些状态信息。如果看到不出任何报错说明规则已经被Controller正常接管。现在你本地把order.example.com的域名解析到前面那个EXTERNAL-IP测试环境直接改hosts文件浏览器访问一下流量就通了。这只是最简单的情况。一个Ingress规则里完全可以同时配置多个host、多个path实现“一个入口、全部服务”的集中管理。我举个例子某公司跑了一个电商应用总共前端、订单、支付、用户四个服务想统一在shop.example.com这个域名下做路由前端走/,订单走/order支付走/pay用户走/user。apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: shop-ingress spec: ingressClassName: nginx rules: - host: shop.example.com http: paths: - path: / pathType: Prefix backend: service: name: frontend-service port: number: 80 - path: /order pathType: Prefix backend: service: name: order-service port: number: 8080 - path: /pay pathType: Prefix backend: service: name: pay-service port: number: 8080 - path: /user pathType: Prefix backend: service: name: user-service port: number: 8080这里有个常见的坑pathType。Kubernetes里有两种pathType一种是Prefix前缀匹配另一种是Exact精确匹配。直觉上大家会觉得/order这个前缀应该能匹配/order/list这种子路径逻辑上没错但如果后端服务内部自己也要处理路径路由比如order-service收到的请求路径还是/order/list那它内部的控制器可能就懵了因为实际接口路径是/list不是/order/list。这时就需要后面讲的rewrite注解来把路径重写掉。另一个容易被忽略的是Ingress的端口必须是Service的端口而不是Pod的容器端口。很多人写成Pod里的8080比如容器真正监听8080却忘了Service的targetPort可能已经改成了80结果怎么转发都是503。Ingress转发到的是ServiceService再按targetPort转给Pod这条链路要理清楚。除了域名和路径Ingress规则还可以做更多事。比如同一套服务想让内网用户走内网域名、外网用户走公网域名可以建两个Ingress对象指向同一个ServiceController本身没有限制。又比如蓝绿发布场景临时建一个Ingress把部分路径流量打到新版本Service上验证完再改回来非常灵活。5. 常用注解AnnotationsIngress规则里的隐藏武器一个Ingress规则如果只写域名和路径那和普通反向代理没什么区别。Ingress真正厉害的地方在于通过annotations声明各种Nginx级能力Controller读取后自动转成对应指令。我梳理几个高频高效的annotations。首先是rewrite-target这是做路径重写最关键的注解。假设前端页面里的API请求都直接发到根路径/api/goods而后端的商品服务只认/goods那Ingress规则可以这么写apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: goods-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: ingressClassName: nginx rules: - host: shop.example.com http: paths: - path: /api(/|$)(.*) pathType: ImplementationSpecific backend: service: name: goods-service port: number: 8080rewrite-target里的$2对应正则里第二个捕获组(.*)也就是把/api/后面的部分提取出来拼到新的URL里。这里pathType必须用ImplementationSpecific因为这段正则表达式是Nginx特有的标准的Prefix和Exact都不适用。如果你路径里只有一个固定前缀想剥离也可以简化为nginx.ingress.kubernetes.io/rewrite-target: /$2配合path写成- path: /goods(/|$)(.*)这样访问/goods/list时后端收到的是/list。我在实际项目中经常遇到这种需求网关转发路径和后端接口路径不一致时rewrite就派上了用场。接着说SSL证书。Kubernetes里证书以Secret的方式保存然后Ingress通过tls字段引用。假设你有一条order.example.com的证书文件先导入Secretskubectl create secret tls order-tls --certtls.crt --keytls.key然后在Ingress里增加spec: tls: - hosts: - order.example.com secretName: order-tls这样访问https://order.example.com时Controller会直接用这个Secret里的证书做终止。对于多域名证书也能这样处理Secret里放一张SAN证书hosts列出多个域名即可。还有几个常用的annotations值得记录限制上传大小nginx.ingress.kubernetes.io/proxy-body-size: 10m。客户端上传大文件时不设置这个默认1m上传限制会让你莫名其妙收到413。开启CORSnginx.ingress.kubernetes.io/enable-cors: true同时配合cors-allow-origin等注解解决前端跨域问题就很方便。连接超时nginx.ingress.kubernetes.io/proxy-connect-timeout: 30默认值有时候太短长轮询或慢接口容易提前断连。白名单限制nginx.ingress.kubernetes.io/whitelist-source-range: 192.168.0.0/16只允许指定网段访问管理端页面。这些注解在使用前先想清楚它会不会影响全局比如proxy-body-size改大了意味着Nginx处理请求时要分配更大缓冲区对内存有一定开销生产环境不要随便调得很离谱。我的习惯是每加一个注解先在测试环境验证行为再上生产不要图省事边改边上线。6. 请求链路详解一条请求从进入到返回的完整路径Ingress用多了以后我发现一个很有效的排查思路把整个请求路径画在脑子里。假设客户端输入http://shop.example.com/order/list整个链路是这样的客户端域名解析shop.example.com经过DNS解析指向的是Ingress Controller Service的负载均衡IP也就是EXTERNAL-IP。这里值得注意的是DNS解析到的不是Pod IP而是入口IP所以访问路径的第一步就是到负载均衡器。负载均衡器转发云的LoadBalancer把TCP流量转发到后端某个ingress-nginx-controller Pod的80端口。这里如果你用的是NodePort类型它会转发到任意一台节点的30080随机端口再由kube-proxy转发到Controller Pod。Nginx接收请求并解析Host和URLController Pod里的Nginx进程收到请求先看Host头去匹配server块再看URL路径匹配location块。这些规则都是Controller从Ingress资源同步过来生成的。规则命中后反向代理一旦location命中Nginx把请求代理到对应的Service ClusterIP。注意这里的Service只是四层负载均衡它通过iptables或IPVS规则把请求转发到后端某个Pod。业务Pod处理并返回最终Pod上的应用处理请求把响应原路返回。Nginx收到响应后如果需要还能做gzip压缩、添加响应头等操作然后返回给客户端。排查问题时就沿着这条链路一条条查你访问的域名解析对吗如果解析到别的IP流量根本进不来。负载均衡器的后端组里Controller Pod是健康的吗不健康说明Pod挂了或者就绪检查没过。请求到Controller时Host对不对如果你用IP访问而不是域名Nginx的默认server块会直接返回404因为Ingress规则是按server块命中域名的。Ingress规则的backend写得对吗Service存在吗端口对得上吗Service的后端Pod是Running状态吗readiness探测通过了吗如果Pod一直是0/1未就绪Service不会把流量转发过去。我印象最深的一次事故是某次同事在Ingress里写了一个很长的rewrite正则配了一个不存在的捕获组结果所有请求返回502。当时我们排查了足足一小时最后用curl直接打到Controller Pod开了Nginx的debug日志才定位出是rewrite把上游地址算错了。从那之后我养成一个习惯任何Ingress改动先kubectl apply再看kubectl get ingress -o yaml确认注解被Controller正常接受最后再用curl带Host头打一遍入口IP验证链路通了再切DNS。7. 常见问题和排查技巧遇到卡点先冷静分析这里整理几个我踩过或者看别人反复踩的坑每个都给你提供排查思路和解决方案。先看503 Service Unavailable。现象是请求能到Controller但后端不可用。查看Ingress规则对应的Service有没有后端Podkubectl get endpoints 如果Endpoints是空的说明Service没关联上Pod。再检查Pod的liveness和readiness是否为Ready以及Service的selector和Pod标签是否一致。我见过很多次Service的selector写错一个标签导致Endpoints列表为空Ingress转发自然就503了。这类问题一般不看Ingress本身去看Service和Pod的关系。再来看404 Not Found。如果一个Ingress里配了多个host或多个path请求没匹配上任何规则Nginx就返回404。排查时先确认你访问的Host是否和Ingress规则里host一致大小写、尾部点号都可能造成不匹配。然后确认path匹配方式pathType是Prefix还是Exact路径前缀是否一致。如果请求是/api/list而Ingress规则是path: /api/斜杠结尾Prefix模式下其实是匹配的但有些版本Nginx对斜杠的处理方式有细微差异最好用curl直接验证。还有一种情况你创建了Ingress但Controller没加载到它。检查Ingress的ingressClassName以及Controller是否正常运行。再看证书相关的坑。最常见的是配置了HTTPS但浏览器报证书无效。排查步骤确认Secret里证书内容正确kubectl get secret -o yaml看tls.crt和tls.key两个key是否齐全。确认Secret的命名空间和Ingress在同一个命名空间跨namespace引用是不行的。确认Ingress的tls配置正确绑定了正确域名证书的CN或SAN必须和访问域名匹配否则浏览器照样警告。还有一类问题是HTTPS配好后无变化检查时发现Controller压根没加载到更新。这种情况可以重启一下Controller Pod注意要滚动重启不要直接删除然后干等标准做法是kubectl -n ingress-nginx rollout restart deployment ingress-nginx-controlleringress-nginx在更新Ingress规则时默认实现了热加载但偶尔配置冲突或内存中的旧缓存没释放重启进程是最直接的兜底方案。再说rewrite规则不生效的情况。最常见原因就是pathType写成了Prefix而不是ImplementationSpecific因为rewrite需要的正则表达式只有ImplementationSpecific才能完整表达。另外rewrite-target里的捕获组编号容易搞错匹配到$1还是$2取决于你的正则有几个组建议先在测试环境用curl占位确认行为。灰度发布场景下你会遇到需要通过不同header或cookie转发的问题。比如想让带canary1请求头的流量打到新版本Service上ingress-nginx有一个canary注解方案nginx.ingress.kubernetes.io/canary: true nginx.ingress.kubernetes.io/canary-by-header: canary nginx.ingress.kubernetes.io/canary-by-header-value: 1把带canary注解的Ingress指向新版本ServiceIngress关联同一个host和path。Controller会优先匹配canary规则满足条件就走新版本不满足走原版本。这是做灰度发布特别实用的手段比改代码快得多。最后一个很容易忽略的点Ingress资源本身跨namespace的引用限制。Ingress和它引用的Service必须在同一个namespace里不能一个在default一个在production里异地引用。如果你确实有过跨namespace访问的需求比如管理端要让多个namespace的某个服务统一走一个入口有两种方案要么在目标namespace分别创建Ingress要么用ExternalName类型的Service做桥梁。不了解这点往往会在设计多团队Kubernetes架构时吃暗亏。8. 生产环境使用Ingress的几个重要建议Ingress本身不复杂但生产环境能用好它需要一些经验积累。我在多个项目里总结下来有这么几条建议。第一所有Ingress资源的定义要纳入代码仓库管理。Ingress的配置就是流量转发规则一旦改错影响面是整个业务入口。别直接在集群里随手kubectl apply -f改建议先提交代码走评审再用CI/CD发布。尤其是rewrite、证书这类容易出错的规则代码评审能挡掉很多低级失误。第二建立规范的命名方式。一个团队久了Ingress会越来越多没有规范会非常混乱。推荐的命名方式是应用名-场景比如order-service-public、admin-internal这种。同时给Ingress资源统一打上标签比如app.kubernetes.io/part-of方便后续用label筛选和管理。第三注意资源隔离和配额。Ingress Controller的Pod属于集群级别的重要组件建议把它的requests和limits设置好避免某个业务高峰期把Controller资源吃光导致全局入口故障。另外如果集群规模很大Ingress Controller的Pod建议反亲和部署多副本在不同节点上避免一个节点挂掉连累整个入口。第四证书管理要自动化。靠kubectl create secret手动导证书到期后手动续期是很痛苦的事。如果集群里已经装了cert-manager让Ingress自动签发和管理证书是更省心的方案。具体方式是在Ingress的annotations里声明好issuercert-manager会自动申请证书、创建Secret并在到期前自动续期。即使现在没有上cert-manager的条件也在Ingress资源里统一声明tls段避免事后加密改造时到处找证书。第五启用访问日志。ingress-nginx默认会输出访问日志到stdout你可以直接用日志采集工具收集起来。访问日志里包含了请求路径、状态码、响应时间、上游地址等信息排查问题的时候非常有用。更进一步可以用Prometheus采集Controller暴露的指标对入口QPS、延迟、错误率做监控告警这样流量异常时能第一时间发现。第六尽量少在Nginx全局配置里做奇怪的事情。ingress-nginx提供了ConfigMap入口改全局Nginx配置但全局配置改动影响所有Ingress属于高危操作。比如全局改了proxy-read-timeout结果某些直播类长连接服务正常了日常接口却因为等待太久影响了错误率。全局配置能不动就不动能力优先在单个Ingress的annotation层面去实现。第七理解IngressController多副本扩容的逻辑。当流量大涨单Controller Pod扛不住时直接replicas调整到多副本即可。但要注意ingress-nginx的多副本默认是每个Pod绑定一个对应的负载均衡IP云负载均衡后端加Pod流量分发由负载均衡器做。控制器之间不需要额外同步配置因为都从API Server读取同一份Ingress清单各自生成自己的Nginx配置天然一致。9. 回顾一下核心链路和踩坑心得回到最初的问题用NodePort一个服务一个端口转发还是在集群入口统一用Ingress做路由分发现在答案已经很明显了。Ingress把整个集群对外的HTTP入口收敛到了一个点集中管理、按规则分发、高效灵活。我在第一次真正把Ingress用于生产之前也走了一段弯路。早期图省事搭建环境时用NodePort直接暴露服务结果服务一多安全组规则和端口映射乱成一团。后来花了一个下午把整套流量迁到Ingress方案下把证书、rewrite、超时都配好从此访问链路清爽了太多。如果时间能倒流我会建议当时的自己从一开始就把Ingress当成默认方案NodePort只留着做临时调试。再分享一个后期排查很有用的经验。当你怀疑Ingress转发有问题时别急着改配置先用一个小技巧验证Nginx里生效的真实配置。ingress-nginx提供了一个带nginx进入Controller Pod的命令kubectl -n ingress-nginx exec -it pod/ingress-nginx-controller-xxxxx -- /bin/bash进去之后直接查看/etc/nginx/nginx.conf里面能看到Controller根据你的Ingress规则生成的真实server块和location块。如果配置里压根没有你预期的规则说明Controller没同步到这份Ingress需要去查RBAC权限、IngressClass或者Controller日志。如果配置里有规则但行为不对那问题大概率出在规则本身的细节上比如rewrite目标或者proxy-pass地址。有一次我们还遇到过更隐蔽的案例某个Ingress规则看起来完全正确后端Service也在Pod也健康但就是偶尔出现502。后来查了Controller日志才发现后端Pod因为内存限制频繁被OOM Kill重启之后还没Ready流量正好落在重启窗口里就报502了。这个问题从Ingress视角永远查不出来必须结合后端服务和Pod的监控一起看。所以说Ingress的排障一定不能只看Ingress自身整条链路上任何一环出问题症状都可能出现在入口层。希望这篇内容能帮你真正理解Ingress的定位和使用方式而不是只会复制粘贴。把原理、规则和排障思路串起来后续在Kubernetes里再遇到流量转发的问题就能自己一步步定位而不是全靠搜索引擎碰运气了。