
1. 为什么Kubernetes里偏偏需要Ingress这一层1.1 从NodePort和LoadBalancer说起它们到底卡在哪用Kubernetes做过业务暴露的同学应该都经历过这样一段从能用到不想用的过程。最早部署个Web服务想从集群外面访问它最直接的办法就是把它发布成NodePort。操作很简单kubectl expose deployment my-app --typeNodePort --port80然后集群每个节点的IP加一个高位端口比如30008就能访问了。看着挺爽可一旦服务多起来问题就来了。首先是端口管理。NodePort的端口范围默认是30000-32767全集群所有服务共享。我见过一个集群里部署了三四十个服务全部用NodePort端口表长得像一本电话簿。每次新加一个服务都要先查一下哪个端口还没被占用而且万一有人把端口写死在了前端配置里后面根本不敢随便回收排查成本极高。其次是入口分散。每个服务一个端口意味着客户端要记得这台机器IP加这个端口是订单服务那台机器IP加另一个端口是用户服务。一旦节点扩容、缩容或者换机器IP变了所有依赖方都要跟着改。更麻烦的是如果哪一天你想在入口统一做一次限流、加一层鉴权、或者给所有接口加个访问日志你会发现根本没有一个合适的位置去干这件事因为你压根没有一个统一的入口。再到LoadBalancer。云环境下面的LoadBalancer确实解决了端口混乱的问题因为它会给每个Service分配一个独立的公网访问地址。可是一个Service一个LB这就是真金白银的成本问题了。LB资源也是稀缺的有些环境还会限制用户能创建的数量。一个集群底下挂着二十个LoadBalancer不管是从成本、配额、还是网络链路的复杂度来说都相当难看。1.2 Ingress的本质一份路由规则说明书带着这些痛点再来看Ingress你很快就会明白它为什么能成为主流方案。Ingress在Kubernetes里的定位很明确它是一种API对象专门用来描述外部流量怎么访问集群内服务的规则。说得直接一点它就是一份路由规则说明书——规定哪个域名、哪个URL路径转发到哪个Service的哪个端口。不要小看这个说明书的定位它的价值在于把路由决策从各个服务里剥离出来统一放到集群入口这一层去处理。服务端不需要关心外面怎么访问它它只需要知道有流量通过Service进来然后正常响应就行。流量进来之前的一切逻辑比如域名匹配、路径拆分、TLS终止、请求头改写全都在Ingress这层完成。举个例子假设你的集群里有三个服务前端页面、用户接口、支付接口。没有Ingress的时候你得分别记住三条访问路径。有了Ingress之后规则可以统一写成www.某项目.com/转发到前端Serviceapi.某项目.com/user转发到用户Serviceapi.某项目.com/pay转发到支付Service所有流量先汇聚到这个统一的入口上再由它按规则分发。这样无论是加新服务、调整路径、还是想统一做点什么都只需要改Ingress规则不需要去动服务本身也不需要改客户端的访问方式。这才是Ingress真正要解决的核心问题——不是暴露服务这么简单而是给集群提供一个可控、可管、可扩展的统一流量入口。2. Ingress和Ingress Controller最容易混淆的一对概念2.1 Ingress只是声明不干活我刚接触Kubernetes那会儿有一件事困惑了很久我明明创建了Ingress对象也把域名和Service对应关系都写好了怎么访问域名还是返回404后来才意识到我犯了一个很典型的错误——正确创建Ingress对象不等于流量就能被正确转发。Ingress这个API对象本质上只是一份配置声明。它描述了你想让流量怎么走但它本身不处理任何网络流量。就像你在办公室里贴了一张快递分拣流程图流程图画得再清楚没有分拣员去照着做快递依然不会自己跑到对应的工位上。这个分拣员就是Ingress Controller。你得先在集群里部署一个Ingress Controller它才会持续地读取Ingress对象的定义、把规则翻译成具体的转发配置、并真正承担流量转发的工作。Controller没装好的时候你的Ingress就只是一份躺在etcd里的配置不会产生任何实际作用。很多新手在本地环境或者自建集群里第一次实验Ingress时都会碰到这个坑——Ingress建了一大堆却发现根本没有生效原因十有八九就是没装Controller。2.2 Controller的工作管线和选型考量Ingress Controller的工作机制可以用一条流水线来理解。Controller启动后它会通过Kubernetes的API监听Ingress、Service、Endpoint、Secret等资源的变化。一旦发现Ingress新增或修改它就立刻把新的路由规则同步到自己内部——也就是把规则写入Nginx或者Traefik的配置然后重新加载配置让变更生效。整个链路可以分成三步监听变更、生成配置、重载生效。Controller的种类很多各有偏向。我实测下来比较常用的是这几类列个表格方便对比Controller核心优势适合场景需要留意的点Ingress Nginx社区最活跃、文档最全、注解丰富大多数通用场景团队有Nginx经验在高并发下注意调worker进程和连接数Traefik配置热更新体验好Go单二进制部署简单微服务架构、想快速上手高级调优细节不如Nginx生态成熟HAProxy Ingress性能强、连接数管理优秀高吞吐、长连接多的流量型业务配置模板相对复杂调试门槛稍高选型的时候不要只看某篇文章推荐什么最好结合团队的实际运维能力。如果团队平时就熟悉Nginx那用Ingress Nginx是最稳的选择因为遇到问题可以顺着Nginx的思路去排查日志格式、配置指令都有迹可循。如果只是小规模内部系统想快速跑起来Traefik的简单直接更省心。3. 一份生产级Ingress配置的拆解过程3.1 最小可用配置逐行拆解先把一份最基础但真实可用的配置放在这里后面逐行讲apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: default spec: ingressClassName: nginx rules: - host: app.某项目.com http: paths: - path: / pathType: Prefix backend: service: name: app-service port: number: 8080第一行apiVersion用的是networking.k8s.io/v1这是目前稳定版本的API。网上能搜到很多旧教程还在用extensions/v1beta1或者networking.k8s.io/v1beta1那些都已经被淘汰了集群版本稍新一点就直接不给创建。ingressClassName这一项很关键。它指定了这个Ingress由哪个Controller来处理值是nginx意思就是说找集群里叫nginx的IngressClass。如果集群里同时装了多个Controller你创建的Ingress没有指定Class很可能没有任何Controller接管它或者被多个Controller同时抢着处理造成不必要的困扰。所以只要集群里不是只装了唯一一个Controller我都建议显式写上ingressClassName。rules下面定义了真正生效的转发规则。host字段是域名匹配的入口app.某项目.com就表示所有访问这个域名的流量才会走到这里。如果想把所有域名都接进来可以不写host但生产环境一般不会这么做因为太容易发生规则冲突。paths是整个配置里最容易出错的部分。path是URL路径pathType分为Prefix和Exact两种。Prefix表示前缀匹配/会匹配所有路径Exact表示完全匹配只有路径一模一样才命中。很多人在这一步踩坑比如定义了/api想匹配/api/user结果没配pathType导致规则行为不符合预期。实际上如果你把pathType设成Exact/api就只会精确匹配/api本身/api/user根本进不来。要命中子路径必须用Prefix。backend是最终转发目的地。它指向一个具体的Service名称和端口。这里要注意一个细节Ingress转发的是Service的端口不是Pod的端口。也就是说你得保证Service的targetPort对应到Pod的真实端口否则Ingress把流量交给Service之后Service转发到Pod时会找不到正确的目标端口。3.2 生产环境里的策略组合基础配置能跑通以后生产环境通常还需要叠加一些策略。最常见的是路径重写和TLS终止。路径重写主要用在一个场景前端需要通过固定前缀来访问后端服务但后端接口本身不带这个前缀。举个例子前端统一走/user-service/...但后端实际提供的是/api/user/...这个时候就要在Ingress上做路径改写。用Ingress Nginx的话配置里加一条注解就可以nginx.ingress.kubernetes.io/rewrite-target: /$2路径写法也需要配合调整把要捕获的部分用括号包起来。这个机制一旦搞明白能把很多前后端路径对不上的问题化解在入口层不用去改业务代码。TLS终止则是把HTTPS证书放在Ingress这一层来管理。一个集群里几十个服务如果每个服务都自己处理证书光是证书续期就能把运维累死。集中到Ingress之后所有流量的SSL解密都发生在入口后端服务器只需要处理HTTP请求整体链路更清晰。配置方式是在spec里加一个tls段spec: tls: - hosts: - app.某项目.com secretName: app-tls-secret这个secretName指向一个Kubernetes SecretSecret里面包含tls.crt和tls.key两个字段。使用证书管理工具比如cert-manager的话这个Secret通常可以自动创建和续期不用手动维护。证书的配置看起来简单实际踩坑点不少我在下一节会专门展开。4. 常见坑的排查思路从404到证书过期4.1 症状一请求返回404但Ingress规则看着没问题这是我见过最多的问题。配置看起来每条都对域名写对了、Service存在、Pod也在跑可一访问就是404。排查这类问题有个固定顺序按这个顺序走可以少走很多弯路。第一步先确认Ingress是被哪个Controller接管的。运行kubectl get ingress查看输出里的CLASS列再把Ingress事件看一眼kubectl describe ingress 名称。如果你发现Events里空空如也说明Controller根本没有处理这个Ingress。这时候要查一下Controller的Pod日志看看是不是因为注解写错、资源没权限等原因没有同步成功。第二步确认Service的后端端点是否正常。有时候Service定义里selector写错了标签导致后端没有任何Pod这个情况很隐蔽因为Service依然存在Pod也活着但二者根本没有建立关联。用kubectl get endpoints看一下Endpoint列表如果显示none基本可以确定是Service的selector没匹配上任何Pod流量自然无处可去。第三步检查Controller内部的转发配置是否已经生效。以Nginx Controller为例进入Controller Pod后执行nginx -T可以把最终生效的配置都打印出来。这时候你会清楚地看到有没有生成对应的server块和location块。如果配置里没有说明Ingress没被正确同步如果有但访问还是404就要往下看是不是路径匹配的问题。404的另一个高频原因就是上一节提到的pathType。路径类型用错了规则没生效流量根本没走到你预期的Service上面。4.2 症状二HTTP请求被强制跳转成HTTPS这个坑特别容易被忽视。场景通常是你明明只配置了HTTPS但是拿HTTP访问时自动被301跳到了HTTPS甚至无限重定向。原因其实不难理解很多Ingress Controller默认会开启HTTPS强制跳转。当Ingress规则里配了TLS证书之后Controller会自动把HTTP 80端口的访问流量301到HTTPS 443端口。这在生产环境通常是合理的行为但对某些内部调试链路来说就很烦人。比如签名回调、某些老系统的Webhook它们只支持HTTP回调结果被一跳到HTTPS就验签失败了。解决思路是看Controller是否支持关闭这个跳转。以Ingress Nginx为例可以在Ingress上添加注解nginx.ingress.kubernetes.io/ssl-redirect: false这样HTTP访问就不会被强制跳转了。如果全局都不需要HTTPS跳转也可以在Controller的ConfigMap里改配置项但要谨慎这是全局生效的可能会影响其他依赖跳转的服务。另外还有个容易踩的变体后端服务自己也有重定向逻辑。比如后端收到HTTP请求后自己301跳一下结果和Ingress的跳转叠加就形成了无限跳转。排查这种问题最直接的方式是curl -v看一下响应头里的Location字段它到底指向什么地址、带了什么协议整个链路是谁在跳就一目了然了。4.3 症状三证书过期了但Secret里的内容看起来是新的证书类问题的特征是无声的故障——页面打不开或者报不安全警告但你的Secret里看起来配置挺正常的。我在实际中遇到过一种情况证书已经通过自动续期更新到Secret里了但浏览器仍然提示证书无效。后来一查原因是证书自动更新工具更新的是另一个Secret而Ingress里secretName指向的却是手写的那个Secret。也就是说Ingress引用和实际更新的对象根本不是同一个东西。这类问题最容易发生在先手动创建了一个证书Secret后来接入了自动管理工具的过渡期。排查建议是先用kubectl get secret确认实际持有证书的Secret再对比Ingress里指向的Secret看这两者是不是同一个。如果确实指向不对把Ingress改到正确的secretName上问题立即解决。还有一种情况更隐蔽Controller缓存了旧的证书内容。正常来说Secret更新后Controller会重新加载但如果你用的是非常老版本的Controller或者Secret更新方式有问题比如直接编辑Secret而不是替换Controller可能不会探测到变更。遇到这种情况最省事的办法是重启Controller Pod强制它重新读取所有配置。5. 再往上走一步限流与灰度发布的实践5.1 用注解实现接口限流流量入口集中之后很多原本需要业务代码去做的通用能力都可以放到Ingress这一层来解决限流就是一个典型例子。比如你有个对外接口突然被大量调用来不及改代码直接在Ingress配置上加一个注解就能马上生效。以Ingress Nginx为例限制每秒请求数可以这样写metadata: annotations: nginx.ingress.kubernetes.io/limit-rps: 10limit-rps表示每秒允许的请求数阈值超过的请求会直接返回503。类似的还有limit-rpm按分钟限流、limit-connections限制并发连接数。限流生效的层级是Nginx的工作进程它会把超限的请求拦在业务服务之外从而避免业务系统被瞬时洪峰打垮。这里要提醒一个容易理解错的点限流是按Nginx实例维度生效的不是全局限流。如果你的Controller副本数有3个配置的limit-rps是10实际每秒能打进来的请求可能是30而不是10。从网关层的角度来说这个特性更多是用来保护单机承受能力而不是做精确的配额控制。想要做到严格的全局配额还得借助独立的网关中间件或者专门的限流组件。实际使用限流时要观察两个指标一是超限返回的503数量二是后端服务的耗时和错误率。如果你发现503大量出现但后端其实还很健康说明阈值定得太保守了适当上调反过来如果503不多但后端的超时明显增加那阈值可能还是偏高。限流参数的调整最好结合压测数据来定别拍脑袋。5.2 金丝雀发布按比例切流不打断线上Ingress层做的另一件很有价值的事情是灰度发布也可以叫金丝雀发布。它的核心思路是新版本Pod先只接收一小部分流量让真实用户帮忙验证无误后再逐步放大比例最终完成全量切换。整个过程不需要改Service、不需要重启业务、不需要切换域名。在Ingress Nginx上实现金丝雀发布主要靠两个Ingress对象配合。第一个是正常的、生产环境的Ingress指向稳定的旧版本Service。第二个是带金丝雀注解的Ingress指向新版本Service。看一个典型的配置apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-canary-ingress annotations: nginx.ingress.kubernetes.io/canary: true nginx.ingress.kubernetes.io/canary-weight: 10 spec: ingressClassName: nginx rules: - host: app.某项目.com http: paths: - path: / pathType: Prefix backend: service: name: app-service-new port: number: 8080关键就是那两行注解。canary: true标记这是一个金丝雀规则canary-weight: 10表示有10%的流量会被路由到新版本Service。等新版本运行稳定了把权重从10调到30、50逐步验证最后把权重调到100后再把正式Ingress切到新Service上收尾干净利落。还可以用请求头来做灰度比如让指定的测试团队直接访问新版本配置方式是把canary-weight换成canary-header注解。具体业务场景不同选择也不同如果是正式全量用户验证用权重逐步放流量如果是内部测试用请求头或Cookie更精准。用Ingress做金丝雀的好处是门槛低不需要引入额外的服务网格组件对多数中小规模集群来说性价比很高。但也要坦率地说它的能力边界是请求级别的路由做不了更细的链路追踪和流量染色。如果团队的微服务规模很大需要更高级的灰度策略那还是得上服务网格那套体系这套注解方案更适合快速落地、容易回滚的场景。我个人在实际操作中还有一个习惯金丝雀期间务必盯着日志和监控只看HTTP错误码是不够的。有一次我们灰度发布时新版本的响应码率一直正常但业务自己埋点的成功率监控显示新版本在处理某类数据时始终抛异常。因为这个异常被业务代码捕获后返回了200状态码网关层完全看不出来。后来养成了习惯——灰度期间不只盯网关还要同时对比新旧版本业务层面的成功率指标。这个细节看起来小但关键时刻能避免把故障放大到全量用户。