1. 项目背景与需求拆解
前几天同事跑来问我一个标题,说在搜索引擎里怎么都搜不到结果,标题写的是“k8s degress 暴漏服务端口”。我先愣了一下,然后反应过来,这里的“degress”八成是 Ingress 的笔误,而“暴漏”其实是“暴露”的拼音输入错误。这个需求本身非常典型:Kubernetes 集群里的 Pod 默认只能在集群内部访问,外部进不来,现在需要把某个服务端口暴露到集群外面,让用户能通过域名或者 IP 直接访问。而 Ingress 恰好就是 Kubernetes 里负责这件事的核心 API 资源。
这篇文章不准备把 Ingress 的每个字段都像文档一样过一遍,那样意义不大。我更想把几个最让人困惑的核心问题讲清楚:为什么有了 Service 还要 Ingress?Ingress Controller 和 Ingress 资源到底是什么关系?一条 Ingress 规则从写到通,流量是怎么一步步走到后端 Pod 的?然后给出一套可以直接抄作业的配置、验证流程和排错思路。
适合两类读者:一类是刚把集群搭起来、想在集群里跑一个 Web 服务并让外面能访问的新手;另一类是已经用 NodePort 凑合了一段时间、被一堆随机端口和配置碎片搞得很烦的开发者。文章里的操作命令和 YAML 写法都比较通用,本地方便复现,放到云环境里也只是多一步负载均衡器或者域名解析的配置,整体思路完全一致。
先说清楚一个最关键的坑:Ingress 本身只是一个声明式的规则对象,它不干任何转发活计。真正处理流量的是 Ingress Controller,一个运行在集群里的七层反向代理组件。你把规则写进 Ingress 资源里,Controller 会持续监听这些规则的变化,然后自动更新自己的转发配置。所以很多新手把 Ingress 建好之后发现一点反应都没有,大概率就是 Controller 根本没装,规则写了等于白写。
1.1 标题里这句话到底在说什么
“暴露服务端口”可以拆成两个动作:第一,让集群外部的流量能进入集群,这叫入口能力;第二,把进入的流量按规则准确转发到一个或多个服务上,这叫路由能力。如果你只用 NodePort,相当于给每个 Service 开一个随机的宿主机端口,外部访问的时候得自己记住“哪台机器 + 哪个端口”,服务一多,端口号就变成了一堆没有业务含义的乱码。
Ingress 的思路完全不同:它固定暴露一个统一的入口端口,通常是 80 和 443,然后根据域名、路径、请求头等条件,把请求路由到不同的 Service 上。外部用户看到的永远是干净的域名加标准端口,内部的端口怎么规划、怎么改,对外完全透明。所以标题那句话,实际就是想表达:用 Ingress 把 Kubernetes 集群里的某个服务端口开放出去,让用户通过域名访问这个服务。
1.2 为什么要用 Ingress 而不是 NodePort
NodePort 是 Kubernetes 提供的最直接的暴露方式。给 Service 分配一个 30000 到 32767 范围内的端口,集群里所有节点都会监听这个端口,外部通过“任意节点 IP 加端口号”就能访问。表面上看确实简单,但实际用起来有几个很现实的痛点。
端口段有限,一个集群内能开的 NodePort 是 2767 个,服务一多就开始捉襟见肘。这个端口没有业务含义,30001、31002 这种数字,过两周你自己都想不起来对应哪个服务,更别说团队协作时别人根本看不懂。最关键的是,NodePort 不能做基于域名的路由,两个服务想同时用 80 端口、按路径分流,NodePort 完全做不到。而你一旦在集群里跑上几十个服务,这种需求几乎是必然出现的。
LoadBalancer 模式倒是能分配公网入口,但它依赖云厂商的负载均衡器,而且是为每个 Service 单独建一个 LB,成本和资源开销都很高。Ingress 则是把所有服务收拢到一个统一入口后面,由 Controller 集中做路由、TLS 证书终结、限流、灰度等能力,天生就是为 HTTP/HTTPS 场景设计的。
1.3 Ingress 的适用场景与不适用场景
Ingress 最适合的是一层基于 HTTP/HTTPS 的路由需求:多个服务共用一个域名、按路径分发流量、需要统一管理 TLS 证书、想对某个路径做限流或者灰度。这些场景里 Ingress 无论是配置复杂度还是运维成本,都比 NodePort 和 LoadBalancer 低不少。
但 Ingress 不是万能的。如果底层协议不是 HTTP/HTTPS,比如你想暴露一个数据库的 3306 端口,或者一个消息队列的 9092 端口,传统 Ingress 资源是管不了的。这类场景要么直接用 NodePort 或者 LoadBalancer,要么用支持 TCP/UDP 透传扩展的 Controller,通过额外的端口映射配置来实现。所以一定记住:Ingress 解决的是 HTTP 路由问题,不是所有端口暴露问题的万能钥匙。先认清这一点,后面才不会配了半天发现方案根本不适用。
2. Ingress 核心概念与原理拆解
写配置之前,我建议先把 Ingress 这条链路完整拆开看一遍。流量从外部进来之后的完整路径大致是:外部客户端访问域名 → DNS 解析到 Ingress Controller 所在的入口地址 → Controller 收到请求,根据 Ingress 规则匹配 Host 和 Path → 把请求转发给后端 Service 对应的 ClusterIP 和端口 → Service 再通过 Selector 把负载均衡到后端 Pod。
这个链路每一个环节都可能出问题。“域名能通但页面 404”“有响应但永远是 502”这些现象,本质都是链路上某一段断了。排错的时候从上往下逐层验证,思路会非常清晰。我见过不少人在出问题时一头扎进 Ingress 规则里反复改,改了半天发现后端 Pod 压根没起来,这就是没有链路思维的表现。
2.1 Controller 与 Ingress 资源的关系
Ingress Controller 本质上是一个 Deployment,里面跑了一个七层反向代理进程,同时还实现了 Kubernetes 的监听逻辑。它会 watch 集群里的 Ingress、Service、Endpoint 等资源,一旦发现这些资源发生变化,就动态生成或者修改本地的转发配置。
Controller 在社区里有多种开源实现,比较常见的是以 Nginx 为核心的反向代理方案,还有 Traefik、HAProxy 等。它们实现的功能大体类似,但配置方式、注解名称、性能表现会有差异。如果你没有特别强烈的需求,选社区最常见、文档最全的那个实现上手最快。这里我多说一句:选 Controller 时不要只看功能列表,要看看你团队里已经会的技术栈是什么,毕竟后面动不动就要看它生成的配置和日志。
强调一遍:创建 Ingress 资源不代表流量就通了。Ingress 只是定义了预期状态,Controller 才是实际执行人。创建之后可以用 kubectl get ingress 看到规则,但网络是否真的通了,要看 Controller 有没有正确感知并加载这条规则。这是新手最容易忽略的一点。
2.2 Ingress 规则的核心字段解析
一个最简单的 Ingress 规则通常包含三部分:host(域名)、path(路径)、backend(后端 Service 名称和端口)。backend 写的是 Service 的端口,不是 Pod 的端口。比如你的容器里跑的是 8080,Service 的 targetPort 指向 8080,Service 对外暴露的端口是 80,那么 Ingress 里的 service.port 字段就写 80。
Controller 会把请求转发到 Service 的 ClusterIP 加端口上,再由 Service 转给后端 Pod。写法上很简单,但有一个非常常见的误区:有人直接把 Pod 端口写到 Ingress 里,最后发现流量一直指向错误端口。原因就在于 Ingress 的转发目的地永远是 Service,不是 Pod。所以写 Ingress 之前,先确认三件事:Service 存在、Selector 能匹配到 Pod、Service 端口定义正确。这三件事缺一不可,顺序也建议固定下来。
2.3 host/path/servicePort 的匹配与转发逻辑
Ingress 的路由规则默认是“Host + Path”两级匹配。Host 是访问域名,Path 是 URL 路径前缀。比如你定义 host 为 app.example.com、path 为 /,那么所有访问 app.example.com 的请求都会转发给指定 Service。如果定义了 path 为 /api 但没定义 host,那么任何域名下以 /api 开头路径的请求都会命中这条规则。
实际项目里我强烈建议每条规则都显式指定 host,不要依赖这种“全部匹配”的写法,不然以后加规则时很容易出现互相覆盖,排查起来极其痛苦。还有一个容易忽略的点:Ingress 支持配置 defaultBackend,当请求的域名和路径都没有匹配到任何规则时,Controller 会把流量转发到它。这个默认后端通常是一个自定义的 404 页面服务,也可以留空让 Controller 返回自带错误页。搭建阶段可以留空,上线前我建议配一个,至少给用户返回一个友好的提示,而不是光秃秃的错误码。
刚开始接触时,理解 path 的匹配语义也很重要。Kubernetes 里 pathType 有 Prefix 和 Exact 两种,Prefix 是前缀匹配,Exact 是完全匹配。一般用 / 加 Prefix 就能覆盖整个域名的流量,而 /status 这类精确路径用 Exact 更合适,能避免 /status2 这种请求也错误命中的情况。匹配优先级和正则规则因 Controller 实现而异,多个 path 同时匹配时通常是最长路径优先,但具体行为最好以当前 Controller 的文档为准。
2.4 IngressClass 与多控制器共存
如果你集群里只部署了一个 Ingress Controller,那通常不需要额外配置 IngressClass,使用默认控制器即可。但同一个集群里有多个控制器时,比如生产环境用一套、测试环境用另一套,就必须在 Ingress 上明确标注使用哪一个。
Kubernetes 用 IngressClass 这个资源来解决这个解耦问题:Controller 启动时会注册自己的 IngressClass,Ingress 通过 spec.ingressClassName 字段指定要使用哪个 Controller。没有标注,某些 Controller 可能会用默认策略接管所有规则,导致流量走向完全不符合预期。我见过不止一次踩坑,明明改了 A Controller 的配置,结果流量却被 B Controller 接走了,最后查下来就是 IngressClass 没指定。这个字段虽然只有一行,但生产环境里非常重要。
3. 实操落地:把服务端口暴露到集群外部
概念讲完,下面进入实战环节。我会按一套完整流程来走:环境确认 → 安装 Controller → 准备后端 Deployment 和 Service → 编写 Ingress → 验证访问。这套流程是通用的,按顺序执行即可,最后再补充一些端口、协议层面的细节。
3.1 环境准备与前置确认
开始之前,先确认三件事。第一,集群是可用状态,kubectl 能正常访问,并且你有部署资源的权限。第二,集群里已经有一个希望被外部访问的 Deployment,并为它创建好了对应的 Service。如果没有,可以先拿一个最简单的测试服务顶上来,比如一个返回固定内容的 Web 服务。第三,准备一个可供测试的域名,本地可以用 hosts 文件临时解析,也可以使用真实域名。
很多同学会问,Ingress Controller 本身要不要暴露出去?答案是必须的。Controller 必须是外部流量进入集群的第一个入口。本地方便的做法是给 Controller 的 Deployment 配一个 NodePort 类型的 Service,把 80 和 443 映射到宿主机端口;云环境里一般会直接绑定一个负载均衡器,拿到公网 IP 或者 CNAME。
这里有个细节值得注意:NodePort 的端口号虽然难看,但它只是 Controller 的承载方式,不是用户最终访问的入口。流量进入集群后,Controller 会根据 Ingress 规则按域名和路径路由,所以用户最终访问仍然是在干净的 80/443 端口上,内部怎么映射不影响使用体验。
3.2 安装 Ingress Controller
安装方式通常有两种:用 Helm 包管理器安装,或者直接应用官方提供的 YAML 清单。我个人的建议是第一次用 YAML 文件直接 apply,先熟悉一下 Controller 包含哪些资源,不要一上来就 Helm 黑盒安装。万一出了问题,你知道该去哪里看日志。
执行安装命令后,仔细看一下新增的命名空间下面都有哪些资源:Deployment、Service、ServiceAccount、ClusterRole、ClusterRoleBinding 等。这几个资源组合保证了 Controller 有权限 watch 集群里的 Ingress、Service、Endpoint 对象,并且能以 NodePort 或者 HostNetwork 方式监听端口。
安装完成后,使用 kubectl get pods -n <命名空间> -w 观察 Pod 是否处于 Running 状态。如果一直 CrashLoopBackOff,优先查看日志,常见原因有镜像拉取失败、RBAC 授权不完整、宿主机端口被占用等。这一步不要跳过,很多问题在装完几秒钟内就能暴露,尽早处理能省大量时间。
3.3 准备后端服务并编写第一条 Ingress 规则
假设你已经部署好一个后端应用,Pod 里容器监听的是 8080 端口。现在要为它创建一个 Service:
apiVersion: v1 kind: Service metadata: name: web-service spec: selector: app: web ports: - port: 80 targetPort: 8080这个 Service 对外的暴露端口是 80,后端容器的 targetPort 是 8080。接下来创建 Ingress,把 web.example.com 的流量转发到这个 Service 上:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-ingress spec: ingressClassName: nginx rules: - host: web.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-service port: number: 80注意几个版本上的细节。Ingress 的 apiVersion 在 Kubernetes 1.22 之后固定为 networking.k8s.io/v1,旧版本的 extensions/v1beta1 已经废弃,不要在新集群里照抄旧文档。pathType 用 Prefix 配合 / 表示匹配该域名下所有路径,也就是全部流量都转给这个 Service。另一个再强调一遍的细节:port.number 写的是 Service 的 80,不是 Pod 的 8080。
创建后执行 kubectl apply -f ingress.yaml,然后用 kubectl get ingress 查看规则状态。如果输出正常,说明规则已经被集群接受了。如果 Controller 没有注册 IngressClass,先删掉 ingressClassName 这一行,让默认控制器接管,等通了之后再回头处理多控制器的问题。
3.4 通过域名访问与常见验证手段
Ingress 创建完成后,剩下的是访问验证。如果是真实域名,把域名解析到 Controller Service 的外部 IP 上;如果是本地测试,在 hosts 文件里加一行“宿主机 IP web.example.com”,例如:
192.168.1.10 web.example.com然后直接访问 http://web.example.com,理论上就能看到后端服务的响应内容。如果访问不成功,不要太早怀疑 Ingress 规则,先做分段验证。第一步,确认 Service 本身通不通:进入集群里任意一个 Pod,执行 curl http://web-service:80,如果配置了域名解析的内部 DNS,这名字在这个集群内应该能直接用。第二步,确认 Controller 能不能转给 Service:执行 kubectl exec 进入 Controller Pod,然后 curl -H "Host: web.example.com" http://127.0.0.1,看能否拿到预期响应。第三步,再从外部访问域名。这个过程可以快速把问题锁定在后端 Service、Controller 路由、外部链路这三段中的某一段。
3.5 端口、协议与应用场景的细节补充
如果你的服务是数据库、消息队列、游戏服这类 TCP 或 UDP 协议的应用,传统 Ingress 资源无法直接处理。这时需要重新评估暴露方式。最简单的是 NodePort,直接把 Service 的端口暴露到宿主机;其次是使用支持 TCP/UDP 透传的 Controller 扩展,通过专门的端口映射配置把外部端口映射到集群内某个 Service 上;如果集群部署在云上,也可以为 Service 创建 LoadBalancer 类型,由云平台分配公网 IP,本质上就是接入平台自己的负载均衡器。
另外,多个服务共用一个域名时,可以按路径拆分。比如 web.example.com/ 给前台,web.example.com/admin 给管理后台。路径拆分能省不少域名,但要注意路径冲突。如果同时定义了 /api 和 /api/v1,大多数 Controller 默认按最长路径优先匹配,也就是 /api/v1 的请求会命中更精确的那条规则。这个优先级逻辑不同控制器可能略有差异,上线前最好确认清楚,避免出现“吞流量”这种隐蔽问题。
4. 常见问题与排查心得记录
这一节整理我在实际运维里碰到的高频问题。排查思路不一定适配所有环境,但方向是通用的,按照这个顺序走,大多数问题都能定位到具体环节。
4.1 502 与后端找不到的问题
502 Bad Gateway 是 Ingress 场景里最常见的报错。含义是 Controller 已经收到了请求,但转发时连不上后端 Service。原因通常集中在三个方向:后端 Pod 没在跑、Service 的 Selector 匹配不到任何 Pod、Service 端口定义和后端实际监听端口不一致。
排查路径建议固定成一套。先看后端 Pod 状态,kubectl get pods 确认有没有处于 Running 的实例;再看 Endpoint,kubectl get endpoints ,如果 ENDPOINTS 这一列是空的,说明 Selector 没有匹配到任何 Pod;最后进 Controller Pod 里 curl 一下 Service 的 ClusterIP 加端口,能通说明 Service 这一层没问题,不能通就继续查 Service 的 targetPort 和容器实际监听端口。
我遇到过一种隐蔽情况:Service 的 targetPort 写的不是数字而是字符串名称,比如 targetPort: http,但 Pod 里的容器端口并没有定义相应的名称,Endpoint 一样会为空,接口就返回 502。用数字端口可以规避这个问题,但如果是沿用别人已经配好的服务,就得把字符串名称和 Pod 里的端口定义对上,这个细节要检查到位。
4.2 配置没生效与 404 问题
404 一般说明 Controller 本身是通的,但 Ingress 规则没有命中请求。常见原因是 Ingress 里 host 和访问时请求的 Host 不一致;IngressClass 没配对,规则被另一个 Controller 接走了;path 类型和实际访问路径不一致,比如路径是 /home,规则里却用的 Exact + /。
验证方法很直接:用 curl 加 -H 参数显式指定域名,对 Controller 的入口地址发请求,再观察 Controller 日志里的匹配记录。如果日志里显示请求进来了但没有匹配到任何规则,说明 Ingress 里的 host/path 和实际请求不一致;如果日志里根本没有这条请求,说明请求压根没到 Controller 这一层,问题在前面。
还有一个需要留意的点:创建或更新 Ingress 之后,Controller 需要一点时间感知并重新加载配置。有些场景下热更新有几秒延迟是正常现象,不要一改完就慌。如果等了很久还没任何反应,就去翻 Controller 日志,看有没有解析 Ingress 失败、IngressClass 权限不足之类的告警信息。
4.3 TLS、注解与多服务共存的坑
配置 HTTPS 时,Ingress 需要在 spec.tls 里挂 TLS 证书,常见的做法是把证书和私钥导入到 Secret,再在 Ingress 里引用。最容易出问题的点是命名空间:Ingress 和 Secret 必须在同一个命名空间下,跨命名空间引用是不支持的,创建时会直接报错或者静默失败。
另外,很多 Controller 默认带着 HTTP 强制跳转 HTTPS 的配置。如果你证书还没配好,访问 http 协议也会被跳到 443,然后因为证书不对直接报错。调试阶段建议先关闭强制跳转,把证书链路彻底跑通了再打开,不然会被跳转干扰到排查重点。
多服务共存时,全局配置和注解的覆盖关系也要心里有数。Controller 的全局配置和 Ingress 注解通常是两套机制,注解的优先级一般都高于全局配置。如果你发现某个服务的超时参数怎么调都不生效,大概率是全局配置里有对应项,而注解没写对名称,被 Controller 静默忽略了。这类问题最有效的排查方式就是看 Controller 日志里实际生成出来的配置值,确认到底取的是哪个来源。
4.4 一线排查思路与速查表
我把高频问题和排查路径整理成一张速查表,方便你直接对照操作:
| 现象 | 优先排查点 | 常用命令 |
|---|---|---|
| 访问域名无响应 | Controller 入口是否可达、DNS 解析是否正确 | curl -v 域名 |
| 返回 404 | Ingress 的 host/path 与请求是否匹配、IngressClass 是否指向正确控制器 | kubectl describe ingress |
| 返回 502 | Pod 是否 Running、Endpoint 是否有 IP、Service 端口是否一致 | kubectl get endpoints |
| 502 但 Endpoint 正常 | 后端容器是否监听 0.0.0.0、targetPort 是否指错 | kubectl logs |
| 配置改了但没生效 | Controller 是否重新加载、注解名称是否写对 | kubectl logs |
| HTTPS 访问报证书错误 | Secret 与 Ingress 是否同命名空间、证书内容是否完整 | kubectl get secret -o yaml |
这张表不是万能药,但足够把“无响应、404、502、证书错误”这几大类问题快速分流。绝大多数问题卡在 Service 层或者后端应用层的概率远高于 Ingress 规则本身,所以先查 Endpoint,再看规则,这个顺序我建议固化下来。
5. 实操经验与后续扩展建议
文章写到最后,分享几个实际使用心得。第一,Ingress 规则的复杂度要克制。有人喜欢在 Ingress 里堆注解,搞出一大段配置,看起来能做很多事,其实大部分注解只是在临时绕过问题,后续维护起来特别痛苦。能用多个 Service 拆分完成的场景,不要都堆在 Ingress 层做各种花哨路由。第二,命名规范早点定下来。Ingress、Service、Deployment 的命名如果不统一,排查时你根本分不清这个规则对应的到底是哪个服务。我习惯用“应用名-层次”的命名方式,比如 web-ingress、web-service、web-deploy,一眼就能看出资源之间的关系。
第三,日志是排查这类问题最重要的依据。做 Ingress 相关调试,一定要开启 Controller 的访问日志,里面记录了每一条请求的 host、path、匹配到的 rule、转发到的 upstream,几乎能把问题定位到具体的规则上。建议在搭建阶段就配置好日志格式和输出位置,不要等到线上发现问题再手忙脚乱地开日志。
5.1 我自己踩过的几个坑
有一次给服务配 Ingress,外部访问一直 502,Pod 是 Running 状态,Endpoint 有 IP,Service 端口也对得上,折腾一下午最后发现是后端应用只监听了 IPv6 的地址,没有监听 IPv4 全地址,Controller 用 IPv4 去连接自然被拒。从那以后我就养成了习惯:需要被 Ingress 转发到的后端应用,启动参数里明确监听 0.0.0.0。虽然这不是 Kubernetes 的问题,但非常现实地卡住了 Ingress 的转发链路。
还有一次,两个环境共用一个入口域名,测试环境的 Ingress 没有指定 IngressClass,结果被生产环境的 Controller 按默认规则接走了,流量直接进错集群。那次之后我严格要求所有 Ingress 都必须显式标注 IngressClass,宁可多写一行,也不留给默认接管的机会。这种问题最麻烦的地方在于,表面看起来什么都没错,Pod 是正常的,Service 是正常的,Ingress 也显示存在,只有流量走到了错误的地方。
5.2 可以继续扩展的方向
如果基础的 HTTP 路由已经打通,下一步可以了解这几个方向。一是 TLS 证书到期前的自动续期方案,由 Controller 配合证书管理能力减少人工干预,避免证书过期这种低级事故。二是在 Ingress 层做灰度发布,比如按请求头或者按权重把一部分流量切到新版本服务上。三是端口映射的扩展玩法,在已有 Ingress 能力上增加对 TCP/UDP 服务的支持。这几条都是生产环境里很实用的能力,值得花时间仔细研究。
5.3 最后再分享一个小技巧
调试 Ingress 时,我建议先在本地起一个最小的后端服务,比如直接返回固定文本的简单 Web 服务,然后从外部 curl 入口地址时带上 -H 'Host: web.example.com' 参数,强制指定访问域名。这样能绕过本机 DNS 配置的干扰,直接验证 Ingress 规则是否真的生效。等这条链路彻底通了,再把真实服务挂上去,排查范围会小很多。这个习惯帮我省掉过大量无用功,也推荐给你试试。