资讯详情

资讯详情

Kubernetes Ingress实战:微服务统一入口的配置与踩坑指南

服务一多入口管理很容易先变成一团乱麻。早期我维护过一个十几个微服务的集群最痛苦的时刻不是服务本身出Bug而是同事跑来问“订单服务对外地址是什么端口多少有没有配HTTPS”我经常要翻半天资料才能凑出一个完整答案。后来把 Ingress 引入进来作为统一入口所有外部请求都走同一套域名和路由规则问题才算真正解决。这篇文章就把我的整个落地过程、配置思路和踩坑记录写出来从 Ingress 的原理讲起一直到生产环境里的进阶玩法适合正在做微服务架构、被入口管理折磨过的同学参考。1. 微服务一多入口管理先乱套1.1 没有统一入口时的混乱现场先还原一下最原始的状态。微服务刚拆分出来的那段时间每个服务都是一个独立 Deployment各自配一个 Service。为了能对外访问Service 的类型直接选 NodePort 或者 LoadBalancer让集群把端口暴露出来。问题就在这里。NodePort 模式下每个服务会在每台节点上开一个 30000 以上的随机端口比如用户服务映射到了 30080订单服务映射到了 30081商品服务映射到了 30082。前端要调用后端开发要写环境配置运维要开安全组全都盯着这张“端口对照表”过日子。服务少的时候还能忍服务一旦超过十个维护这张表本身就是一项全职工作。更麻烦的是外部调用方。他们记不住“30080 是用户服务、30081 是订单服务”这种映射关系只能把端口号写死在配置里。哪天服务重建NodePort 重新分配所有下游调用方都得跟着改配置。夜里线上报警一查是端口漂移导致调用失败这种经历有过一次就够刻骨铭心了。LoadBalancer 模式会好一点因为每个服务都能分到一个独立 IP不需要记端口。但代价是成本每个 Service 都会创建一个云负载均衡实例十几个服务就是十几个 LB每个月光基础费用就够心疼一阵子。而且 LB 分散管理证书要分别挂载监控要分别对接安全组要分别维护。1.2 从“端口思维”到“路由思维”我后来想明白了一个道理外部系统其实根本不该关心你的集群内部长什么样。他们想要的只有一个稳定的服务入口自己报上“我要调用哪个业务”的意图剩下的转发工作交给入口完成。这就像公司大楼的前台访客不需要知道财务部在第几层第几间只需要告诉前台“我找财务”前台负责把人带过去。Ingress 干的就是这件事。在 Kubernetes 的体系里Ingress 是一个独立的 API 对象专门用来描述“什么样的 HTTP 请求该被转发到哪个 Service”。你可以把不同微服务挂在同一个域名下面用路径区分业务也可以给每个业务独立子域名还可以在 Ingress 层统一挂载 TLS 证书、做限流、做灰度这些都是普通 Service 做不到的。从“按端口暴露”转向“按路由规则暴露”表面上只是配置方式的改变实际上是把“入口”这个公共资源从分散变成了集中。集中之后所有策略都能在一个地方定义、审计和治理这才符合微服务架构里“关注点分离”的原则。后面我写的所有配置和踩坑经验都建立在这个思路上。2. Ingress 到底在 K8s 里扮演什么角色2.1 两个容易混淆的角色Ingress 资源和 Ingress Controller很多新手第一次接触 Ingress 时会被两个名词绕晕一个是 Ingress 资源另一个是 Ingress Controller。这两者完全不是一回事。Ingress 资源只是一份声明式的路由配置写清楚“什么域名加什么路径转发到什么 Service”。它本身不处理任何流量就像墙上贴的一张分诊指引描述规则但不会真的去接电话。真正干活的是 Ingress Controller。它是一个常驻集群里的 Pod 集合通常基于 Nginx、Envoy 这类反向代理实现。Controller 一直在监听 Kubernetes API Server一旦发现 Ingress 资源有新增或变更就把里面的路由规则翻译成自己对应的配置片段然后热加载生效。打个比方Ingress 资源是导诊台上写好的分诊规则Controller 是分诊护士。规则写得再好没有护士去执行病人还是不知道该往哪儿走。所以在动手写 Ingress 之前你必须先确认集群里已经部署了一个 Controller否则配置写完也不会生效。2.2 一条请求的完整链路了解了分工再说一次请求从外部到 Pod 的完整路径这有助于理解后面查问题时的排查思路。外部请求先做 DNS 解析把域名解析到 Ingress Controller 暴露出来的入口 IP。这个入口 IP 通常由一个 LoadBalancer 类型的 Service 承载云厂商会给它分配公网地址。请求到达 Controller Pod 后Controller 根据请求里的 Host 域名和 URL 路径去匹配 Ingress 资源里的规则。命中以后通过 ClusterIP 转发到对应的业务 Service再由 Service 把流量分发给后面的 Pod。这里有一个关键认知Ingress 终端的不是 Service 本身而是后端 Pod 的网络流量。中间经过的 Service 更多是一种服务发现和负载均衡的抽象真正的路由匹配逻辑全部在 Controller 层完成。所以排查 404 问题时重点看 Controller 的日志和配置而不是业务 Pod这个习惯能节省大量时间。2.3 Ingress 与 LoadBalancer Service 的边界很多文章会说“Ingress 替代 LoadBalancer Service”这个说法不太准确。Ingress 不是替代关系而是站在 Service 前面的一层统一接入。对比一下就很清楚维度LoadBalancer ServiceIngress暴露方式每个 Service 独立公网 IP多个 Service 共享一个入口 IP路由能力只做四层转发支持域名、路径、请求头等七层路由证书管理每个 LB 单独挂证书集中管理可配置多证书成本开销服务多则资源多一份入口资源复用适用场景少量服务快速暴露微服务数量多、规则复杂的场景我用过一段时间 LoadBalancer 直连当时理由是“简单直观”。后来服务从五个涨到十五个云控制台上整整齐齐一排负载均衡费用账单涨得比业务量还快我就把所有流量慢慢收敛到了 Ingres 入口后面。当然某些特殊场景比如数据库的直连访问、UDP 服务确实不适合走 Ingress这种时候保留 LoadBalancer 或 NodePort 是合理的。Ingress 解决的是 HTTP 层入口统一的问题不是所有协议的银弹。3. 实操一份 Ingress 管理订单、用户和商品三个服务3.1 环境准备确认 Controller 已就绪原则和配置细节都清楚了动手之前先确认环境。我用的是最常见的 Nginx Ingress Controller如果集群里还没装可以通过 Helm一键部署也可以在官方文档里找到 YAML 清单直接应用。装完之后第一件事不是写 Ingress而是确认 Controller 状态正常kubectl get pods -n ingress-nginx kubectl get svc -n ingress-nginx正常情况下你会在ingress-nginx命名空间看到 Running 的 Controller Pod以及一个类型为 LoadBalancer 的 Service。如果 Service 的 EXTERNAL-IP 一直显示pending说明云环境没有正常分配负载均衡地址先解决这个再继续。还要检查是否有 IngressClass 资源。新版本 Kubernetes 里Ingress 资源支持通过ingressClassName字段指定使用哪个 Controller。一个集群里装多个 Controller 的场景并不少见写清楚 Class 能避免流量被不相关 Controller 抢走。3.2 写出第一份多路由 Ingress YAML我这里模拟三个微服务分别对应订单、用户、商品后端 Service 已经创建好了名字分别是 order-svc、user-svc、product-svc。我的目标是把它们统一放到api.example.com这个域名下通过路径区分业务。对应的 Ingress 配置如下apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: microservices-ingress namespace: default annotations: nginx.ingress.kubernetes.io/rewrite-target: /$1 spec: ingressClassName: nginx rules: - host: api.example.com http: paths: - path: /order(/|$)(.*) pathType: ImplementationSpecific backend: service: name: order-svc port: number: 8080 - path: /user(/|$)(.*) pathType: ImplementationSpecific backend: service: name: user-svc port: number: 8080 - path: /product(/|$)(.*) pathType: ImplementationSpecific backend: service: name: product-svc port: number: 8080这里有两个细节需要特别注意。第一rewrite-target: /$1配合正则路径写法是为了把路径里的前缀去掉再转发。订单服务的接口实际路径是/list而不是/order/list如果不做 rewriteController 会把/order/list原封不动转发给后端后端没有这个路径就会直接 404。第二pathType使用了ImplementationSpecific目的是允许我们传入 Nginx 风格的正则表达式。如果使用Prefix类型路径不能带正则写法和语义会受限。应用配置kubectl apply -f microservices-ingress.yaml验证规则是否生效kubectl get ingress microservices-ingress你会看到 HOSTS 一列是api.example.com说明规则已经被 API Server 接受了。3.3 TLS 证书挂载和 HTTPS 跳转生产环境当然不能只走 HTTP证书要提前挂在 Ingress 上。我的做法是先通过 kubectl 创建 TLS Secret然后在 Ingress 里引用它。kubectl create secret tls example-tls \ --certexample.crt \ --keyexample.key \ -n default然后修改 Ingress 配置增加 tls 段spec: tls: - hosts: - api.example.com secretName: example-tls rules: - host: api.example.com http: paths: # 同上如果想强制跳转 HTTPS加一个 annotation 就行metadata: annotations: nginx.ingress.kubernetes.io/ssl-redirect: true证书到期管理是另一个容易踩坑的点。我后来引入了 cert-manager配合 Lets Encrypt 自动签发和续期把证书过期问题彻底交给自动化处理比手动更换证书省心得多。这里建议你也尽早把自动证书方案规划进去否则证书续期这件事一定会占掉你每个季度的某一天。3.4 快速验证与失败回滚配置应用完建议用 curl 快速走一遍链路验证不要只盯着页面能不能访问curl -H Host: api.example.com http://入口IP/order/list curl -k https://api.example.com/user/info第一条命令测试 HTTP 模式下 Host 路由是否正常第二条测试 HTTPS。如果返回了后端服务的 JSON 结果说明 Controller 的转发和 TLS 挂载都成功了。万一配置有问题回滚也是一门学问。我在改 Ingress 之前会先执行kubectl get ingress microservices-ingress -o yaml ingress-backup.yaml保留一份备份。出问题时直接kubectl apply -f ingress-backup.yaml恢复到改动前的状态比临时改写一个 YAML 再去 apply 要快得多。Controller 热加载很快一般几秒内就能完成回滚业务影响面可以控制到最小。4. 实战里最容易翻车的几个点4.1 rewrite 规则与路径匹配的玄机rewrite 和路径匹配是 Ingress 里最磨人的地方很多刚开始用 Ingress 的人都在这里卡过壳。我见过一个很典型的案例后端服务的接口路径是/api/order/list但前端只认/order/list。开发在 Ingress 里写了path: /order后端一直报 404排查半天找不到原因。问题就出在路径没有 rewriteController 转发时保留的是/order/list直接怼到了后端的/api/order/list上自然匹配不上。正确的姿势是用正则捕获组把动态部分保留下来。比如nginx.ingress.kubernetes.io/rewrite-target: /api/$1配合- path: /order/(.*)这样/order/list会被重写成/api/list后端就能正确响应。需要特别注意正则表达式和重写目标之间的捕获组数量必须一一对应写错了 Controller 不会报错只会默默转发出一个错误路径。另外要注意路径匹配的类型。Prefix类型匹配的是前缀Exact精确匹配ImplementationSpecific允许我们使用 Controller 特有的正则能力。如果你需要精细控制多个相似路径的匹配顺序建议全部使用正则式写法并且把更长的路径放在更前面。Nginx 的 location 匹配有优先级首选的匹配规则会在冲突时生效这个顺序逻辑跟普通 Nginx 配置完全一致。4.2 WebSocket、SSE 和长连接超时微服务架构里 WebSocket 很常见比如聊天、消息推送、实时任务进度。Ingress 默认对 WebSocket 的支持是没问题的因为 Nginx 本身能转发 Upgrade 请求头。但有一个隐藏问题默认的proxy-read-timeout是 60 秒。如果 WebSocket 连接在 60 秒内没有任何消息Controller 就会主动断开连接客户端表现为连接频繁掉线重连。我一开始以为是后端的心跳机制有问题查了半天才发现是入口层的超时在作怪。解决方案是在 Ingress 上增加 annotationnginx.ingress.kubernetes.io/proxy-read-timeout: 3600 nginx.ingress.kubernetes.io/proxy-send-timeout: 3600Service 层可以使用类似方案。类似的问题也出现在 SSEServer-Sent Events场景需要长时间保持连接的接口都要把超时调大。不过调大超时需要权衡连接占用的资源会变多如果服务本身要求高并发建议在业务层做心跳检测而不是一味拉长超时时间。4.3 接口超时和重试带来的体验问题默认的proxy-connect-timeout和proxy-read-timeout都是 60 秒。对于大多数接口够用但总有些例外比如导出报表、批量任务提交请求时间可能超过一分钟。这种接口如果通过 Ingress 转发会在入口层直接超时客户端收到 504。我的习惯是给这类接口单独配置 Ingress 规则或者在服务内部拆分成“提交-异步查询”模式避免长连接占用入口资源。实在需要同步调用的在 Ingress 上覆盖超时参数nginx.ingress.kubernetes.io/proxy-connect-timeout: 30 nginx.ingress.kubernetes.io/proxy-read-timeout: 300还有一个容易被忽略的问题Controller 的默认重试行为。Nginx 默认会对幂等请求在 upstream 失败时重试其他后端节点这本意是提升可用性但如果后端某个接口本身处理时间很长重试反而会放大问题甚至导致数据库重复操作。我曾经遇到过批量任务接口因 Controller 重试而重复提交的情况排查到最后才发现是入口层在“帮忙”。遇到涉及写操作的接口确认重试策略是否符合业务预期必要时关闭重试。4.4 客户端真实 IP 和会话保持请求经过 Ingress 转发后后端的服务拿到的源 IP 变成了 Controller Pod 的 IP客户端真实 IP 被藏在了X-Forwarded-For头里。如果架构里还有云负载均衡会出现多处 IP 追加取错位置的问题。Nginx Ingress Controller 很早就提供了解决办法nginx.ingress.kubernetes.io/use-forwarded-headers: true这个开关让 Controller 信任来自上游的转发头。同时建议在后端服务里采取“取X-Forwarded-For最右边的第一个非信任 IP”的策略而不是直接取第一位。云负载均衡会追加 IP第一位反而不一定是客户端。这个细节在做风控、限流、审计日志时特别重要IP 取错了后面所有基于 IP 的策略都会跟着错。会话保持又是一个高频需求。默认情况下同一个客户端的多次请求会被负载均衡到不同的后端 Pod如果服务没有做会话共享用户登录状态就会“掉”。Ingress 支持基于 Cookie 的会话保持nginx.ingress.kubernetes.io/affinity: cookie nginx.ingress.kubernetes.io/session-cookie-name: route开启后Controller 会给第一次请求设置一个路由 Cookie后续请求根据 Cookie 命中同一台后端 Pod。但这里我要泼盆冷水Pod 重启后 Cookie 对应的 Pod 没了会话照样会断。生产级方案还是建议后端接入 Redis 这类外部会话存储Ingress 的粘性会话只在链路兜底不能当作唯一的会话保障。5. 生产环境下的进阶玩法5.1 用 annotation 做金丝雀发布Ingress 接管的入口层不仅仅是一个路由通道它天然适合做流量治理。我用得最多的功能就是金丝雀发布配置起来非常简单。假设用户服务目前只有一个稳定版本 v1现在要上线 v2希望先让 5% 的流量过去验证。只需要给 v2 的 Ingress 增加几个 annotationnginx.ingress.kubernetes.io/canary: true nginx.ingress.kubernetes.io/canary-weight: 5Controller 会在所有匹配到这个服务的请求里随机抽出 5% 转发给 v2 对应的后端。观察一段时间没有异常把权重改成 10%、30%、50%最后全量切换到 v2 并删除金丝雀规则。如果只想让特定用户先试用新版可以用请求头来控制nginx.ingress.kubernetes.io/canary-by-header: X-Canary nginx.ingress.kubernetes.io/canary-by-header-value: true这种方式的优点是不需要侵入业务代码发布和回滚都只是改 Ingress 注解的问题。需要注意的是Controller 热加载规则有短暂延迟调整权重后稍微等几十秒再继续操作不要连续快速变更。5.2 入口限流与基础安全没有防护的入口等于把家门敞开着。Ingress 支持做简单的限流nginx.ingress.kubernetes.io/limit-rps: 10 nginx.ingress.kubernetes.io/limit-burst: 20limit-rps限制每秒请求数limit-burst允许突发流量。这两个参数需要根据业务容量认真压测后设定设置太小会误伤正常用户设置太大又起不到保护作用。我习惯先用监控数据看一周的峰值指标再按 1.5 倍峰值设置限流阈值。安全层面还有几个基础动作值得做强制 HTTPS 跳转关闭不需要公网访问的路径敏感的服务比如管理后台不要通过公网 Ingress 暴露。如果安全要求更高可以在 Ingress 前面再加一层专门的安全组件比如 WAF 网关把 Web 攻击拦截在入口层以外。5.3 多 IngressClass 和多集群入口规划单个集群里也可以存在多个 Ingress Controller我遇到过隔离场景一批服务只需要内网访问另一批服务面向公网两者用同一个入口会有安全风险。解决方案就是在集群里部署两个 Controller分别用不同的 IngressClass 标识。公网 Ingress 资源里写上spec: ingressClassName: nginx-public内网 Ingress 资源里写上spec: ingressClassName: nginx-internal这样两个业务域天然隔离互不干扰证书、限流策略、监控也都各管各的。多集群场景下通常的做法是每个集群各部署一套 Ingress 入口然后在更上层的 DNS 或全局流量管理器统一规划域名解析让入口层本身也具备容灾能力。Inress 这个组件只管集群内的路由跨集群的流量调度要靠上层方案去协调。整个 Ingress 落地过程走下来我最大的体会是它真正的价值不是省那几个负载均衡费用而是把“入口管理”从一张散落在各个配置文件和同事脑子里的端口表变成了结构化的代码和规则。微服务越拆越碎如果入口还是靠人肉维护总有一天会出大问题。Ingress 规则写好后新服务上线只需加几行 YAML证书和策略集中治理排查问题时也能从一条完整的链路去追踪这才是微服务架构里最值得先做好的基础建设。最后分享一个排查小技巧遇到 Ingress 转发 404先不要翻后端日志直接看 Controller Pod 的访问日志确认 Host 是否被正确匹配、路由有没有命中。日志里会出现被匹配到的 Service 名称和上游地址一眼就能看出规则写错了还是后端响应错了。这个方法帮我节省过太多无用功。掌握了 Ingress 之后你会发现原本最混乱的入口层反而成了整个微服务架构里最清晰、最好治理的一环。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →