Gateway API 上线第 1 天就 503:Ingress 与新路由的 3 个隐性冲突
场景:集群已有 Ingress 规则在跑,接入 Gateway API 后部分路由 503 路径:GatewayClass → Gateway → HTTPRoute → 控制器兼容性 版本:K8s v1.25, Gateway API v1.0 (gateway.networking.k8s.io/v1)
上篇讲了 Istio DestRule 一个 label 拼错引发的 503 血案——拼错一个字母,灰度 20% 流量全断。
这篇我们换个层面:不是路由规则写错,是新老两套 API 同时存在时,控制器层面的兼容性问题。
【坐标】Gateway 配好了,Ingress 反而挂了
我们团队在半年前决定引入 Gateway API——新标准,社区在推,未来趋势。
架构师拍板:新服务用 Gateway API,旧 Ingress 不动。
听起来没问题。但上线第 1 天就出事了:
之前正常跑的接口,几个 503,几个 200。
同一个域名下,/api/v1/user 返回 200,/api/v1/order 返回 503。
你说这是 Gateway API 的问题?但 Ingress 没动过。 你说这是后端的问题?但 curl Service ClusterIP 是通的。
这就是 Gateway API 最隐蔽的坑:CRD 装了、YAML 写了、kubectl apply 没报错——但流量就是不走。
原因很简单:Gateway API 不是 Ingress 的升级版。它引入了全新的三层资源模型,每一层都自带验证逻辑。验证不通过,流量直接进黑洞。
这篇就是要拆清楚这 3 个隐性冲突:
| # | 冲突 | 症状 | 根因 |
|---|---|---|---|
| 1 | 控制器不匹配 | Gateway Accepted=False | GatewayClass 的 controllerName 与实际控制器不符 |
| 2 | 跨命名空间被拒 | HTTPRoute Accepted=False | Gateway 的 allowedRoutes 限制太严 |
| 3 | 新旧路由竞争 | 部分路径 503、部分 200 | Ingress 和 Gateway API 被同一控制器竞争处理 |
出事现场
那天下班前,新服务上线。团队成员第一反应:查 Pod 和 Service。
$ kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
app-svc-7d8f9c87b4-2kq5t 1/1 Running 0 12d
app-svc-7d8f9c87b4-m9x3p 1/1 Running 0 12d
$ kubectl get svc app-svc
NAME TYPE CLUSTER-IP PORT(S)
app-svc ClusterIP 10.96.0.100 8080/TCP
$ curl 10.96.0.100:8080/healthz
{"status":"ok"}
Pod 正常、Service 正常、后端 200。那问题出在哪?
$ kubectl describe httproute app-route

Condition 解读
HTTPRoute 的状态段有两组 condition:Accepted 和 ResolvedRefs。
- Accepted:你的 HTTPRoute 是否被 Gateway 接受。
- ResolvedRefs:后端引用能否解析成功。
这个例子里 ResolvedRefs: False, Reason: InvalidKind——"你引用的后端,我解析不了"。
Message 写的是 Service "app-svc" not found。大多数人看到这里去查 Service——存在。然后就困惑了:Service 明明在,为什么说 not found?
这就是 Gateway API 和 Ingress 的第一个差异:Ingress 的 backend 引用是直接的:service.name: app-svc, service.port: 8080,controller 拿到直接转发。
Gateway API 的 backendRef 多了一层 Group 检查:默认 group 是 core,kind 是 Service。如果你没写 Group 或写错了,ResolvedRefs 就失败。
Ingress — Kubernetes 入口流量的传统管理方式,通过 Ingress 资源定义路由规则,Ingress Controller 负责实现。 Gateway API — 标准化的新一代 K8s 网络 API,通过 GatewayClass → Gateway → HTTPRoute 三层模型管理入口流量,意图替代 Ingress。
排查 Gateway API 问题不是在查 Service 和 Pod——是在查控制器到资源的 condition 链。断在哪一层,一眼就能看到。
【分层】Gateway API 四层排查全景
Ingress 的问题是平面化的,Gateway API 的问题是分层化的。
Ingress 出问题:Ingress → Service → Pod,三层跳着查都行。 Gateway API 出问题:必须从 GatewayClass 开始,逐层往下查。跳一层就可能漏根因。
Layer 1: GatewayClass — 控制器注册了吗
结论前置:GatewayClass 没注册或 controllerName 不匹配,后面全白搭。
GatewayClass — 集群级别的网关类别定义,指定由哪个控制器实现来处理 Gateway 资源。
Ingress 没有这个抽象——Ingress 直接由 Ingress Controller 通过 annotation 或 kubernetes.io/ingress.class 接管。
$ kubectl get gatewayclass
NAME CONTROLLER ACCEPTED AGE
nginx k8s.io/ingress-nginx True 180d
istio istio.io/gateway-controller True 90d
集群里可能同时有 nginx 和 istio 两个 GatewayClass,每个代表一个控制器。
关键在 controllerName:
$ kubectl get gatewayclass nginx -o yaml | grep controllerName
controllerName: k8s.io/ingress-nginx
C2: controllerName 是 GatewayClass 与控制器之间的绑定契约。k8s.io/ingress-nginx 的意思是:"我是由 ingress-nginx 控制器管理的"。
如果集群里跑的是 Contour 但 GatewayClass 指向 ingress-nginx,Gateway 永远处于 Accepted=False——scheduler 找不到匹配的控制器来处理这个 Gateway。
隐性冲突 #1:GatewayClass 的 controllerName 与实际控制器不匹配。
常见场景:装了 Istio 的集群,istio 自动注册了 istio.io/gateway-controller。你从网上复制了一个 GatewayClass 示例,里面写着 k8s.io/ingress-nginx。kubectl apply 成功了(CRD 接受任何合法的 controllerName),但 istio 控制器看到 controllerName 不匹配自己,直接忽略。
Gateway 永远不会被 reconcile。
更隐蔽的情况:你装了 ingress-nginx,它默认注册了 k8s.io/ingress-nginx。但你的 YAML 里写的是 nginx-gateway——拼写差一个字母,Accepted=False。kubectl 不会报错,因为 controllerName 只是一个字符串,K8s 不做语义校验。

Layer 2: Gateway — 监听器就绪了吗
结论前置:Gateway 创建了不代表能监听端口。allowedRoutes 和监听器配置都可能让 HTTPRoute 绑不上。
Gateway — 流量入口点,定义监听端口、协议和允许接入的路由范围。
Ingress 没有独立 Gateway 资源——它直接指定 host 和 backend。而 Gateway 是先声明"我监听 80 端口",再由 HTTPRoute 来绑定"我的规则挂在这个 Gateway 下"。
$ kubectl describe gateway shared-gateway
...
Spec:
Gateway Class Name: nginx
Listeners:
Name: http
Port: 80
Protocol: HTTP
Allowed Routes:
Namespaces:
From: Same
Status:
Listeners:
Conditions:
Reason: Ready
Status: True
Type: Ready
关键字段 AllowedRoutes.Namespaces.From: Same——这个 Gateway 只接受同命名空间的 HTTPRoute。
隐性冲突 #2:Gateway 的 allowedRoutes 限制太严,HTTPRoute 跨命名空间引用失败。
举个例子:Gateway 在 infra 命名空间,HTTPRoute 在 default。如果 From: Same,HTTPRoute 的 Accepted 条件就是 False。Message 写"Route not allowed by Gateway's allowedRoutes"——它不会直接说"跨命名空间了",只说"不被允许"。你必须在 Gateway 的 listeners.conditions 里才能找到根因。
C3: AllowedRoutes — 控制哪些命名空间的 HTTPRoute 可以绑定到此 Gateway。默认 Same(仅同命名空间),改为 All 或 Selector 可放开限制。
Allowed Routes:
Namespaces:
From: Selector
Selector:
Match Labels:
gateway-allowed: "true"
Ingress 没有这个限制——Ingress 可以引用任何命名空间的 Service。但从安全角度看,Gateway API 的设计意图是对的:入口点(Gateway)应该控制谁可以接入。
还有另一种冲突:Gateway 配了端口 80,但集群里已经有 Ingress Controller 在监听 80。如果它们被同一个控制器管理(比如都是 ingress-nginx),监听器会竞争同一个端口。这个在下篇展开。
Ingress vs Gateway API 的对比

Ingress 是单层模型:Ingress → Service → Pod。Ingress Controller 看到 Ingress 资源,直接解析 backend,转发到 Service。
Gateway API 是三层模型:GatewayClass → Gateway → HTTPRoute → backendRef → Service → Pod。每一层都加了验证:
- GatewayClass 检查 controllerName → 决定 Gateway 能否被 reconcile
- Gateway 检查 allowedRoutes 和监听器配置 → 决定 HTTPRoute 能否绑定
- HTTPRoute 检查 backendRefs(Group/Kind/Name) → 决定流量能否到达后端
多出来的这叫"控制器鉴权层"。Ingress 是"配了就生效"——只要 Ingress Controller 在运行就行。Gateway API 是"鉴权通过才生效"——Gateway 检查 parentRefs 指向谁、allowedRoutes 是否允许、监听器端口是否冲突。全过才接受路由。
Layer 3: HTTPRoute — 后端引用解析了吗
结论前置:Accepted=True 只说明 Gateway 收下了你,不等于流量能到后端。ResolvedRefs=True 才是后端可达的信号。
$ kubectl describe httproute app-route
...
Spec:
Parent Refs:
Name: shared-gateway
Rules:
Matches:
- Path:
Type: PathPrefix
Value: /
Backend Refs:
Group: core
Kind: Service
Name: app-svc
Port: 8080
Status:
Parents:
Conditions:
Last Transition Time: 2026-06-15T10:35:00Z
Message: The Route is accepted
Reason: Accepted
Status: True
Type: Accepted
---
Message: Resolved all backends
Reason: ResolvedRefs
Status: True
Type: ResolvedRefs
HTTPRoute 的两个 condition 要一起看:
| Condition | True 含义 | False 含义 |
|---|---|---|
| Accepted | Gateway 接受此路由 | allowedRoutes 或监听器问题 |
| ResolvedRefs | 后端引用全部解析成功 | Group/Name 有误或 Service 不存在 |
回到开头的场景:ResolvedRefs: InvalidKind。原因是 backendRef 的 Group 写错了。
HTTPRoute 的默认后端 Group 是 core。但如果写成 apps(常见错误——Deployment 的 API group),ResolvedRefs 就失败。或者如果引用了一个自定义资源(比如 VirtualService),但 Group 没注册,同样失败。
底层原理:Gateway API 的 condition 链为什么是这样设计的?
因为 Gateway API 采用了Reconciler 链架构:每个资源有自己的 controller/reconciler,独立运行。 - GatewayClass 的 reconciler 检查 controllerName 是否有匹配的控制器。没有 → Accepted=False。 - Gateway 的 reconciler 检查监听器配置 + 匹配的 GatewayClass 是否就绪。不通过 → Ready=False。 - HTTPRoute 的 reconciler 检查 parentRefs 指向的 Gateway 是否允许。不通过 → Accepted=False。再检查 backendRefs 是否能解析到真实端点。不通过 → ResolvedRefs=False。
设计意图很清晰:每个 reconciler 只对自己这一层负责,通过 condition 向下游传递状态。 这样修改 Gateway 的 allowedRoutes 时,不需要改 HTTPRoute 的代码。关注点分离。
排查顺序表
| 顺序 | 层 | 检查目标 | 关键命令 | 关键字段 |
|---|---|---|---|---|
| 1 | GatewayClass | 控制器注册 | kubectl get gatewayclass |
controllerName, Accepted |
| 2 | Gateway | 监听器状态 | kubectl describe gateway <name> |
listeners.conditions, allowedRoutes |
| 3 | HTTPRoute | 路由解析 | kubectl describe httproute <name> |
Accepted, ResolvedRefs |
| 4 | Ingress | 冲突检测 | kubectl get ingress -A |
host, port 是否重叠 |
Layer 4: Ingress — 管理员冲突检测
结论前置:Ingress 和 Gateway API 被同一控制器管理时,路由规则可能互相覆盖。
$ kubectl get ingress -A
NAMESPACE NAME CLASS HOSTS ADDRESS PORTS AGE
default app-ingress nginx app.opencao.cn 10.0.0.1 80 60d
这个 Ingress 存在了 60 天,规则指向同一个 Service。现在新建了一个 Gateway,也监听 80 端口,绑定了同一域名的 HTTPRoute。
两个资源被同一控制器处理时,路由合并逻辑是什么?
不同控制器行为不同:
-
ingress-nginx:Ingress 和 Gateway API 可以共存。路由规则的优先级取决于控制器的内部实现。某些版本中 HTTPRoute 的规则会覆盖 Ingress 的相同路径——如果 controllerName 匹配。这就是"几个 503、几个 200"的根因:同一域名下,Ingress 和 HTTPRoute 的路径规则在控制器内部合并,发生了覆盖。
-
istio:通过
PILOT_ENABLE_GATEWAY_API=true环境变量启用 Gateway API 支持。没设这个变量的话,Gateway 资源创建了但不会被 istiod 处理,HTTPRoute 的 Accepted 永远是 False。 -
Contour:显式通过
--gateway-api标志启用。默认不处理 Gateway API 资源。
隐性冲突 #3:Ingress 和 Gateway API 被同一控制器竞争处理。
你以为是两套独立系统,但在控制器眼里,它们是同一张路由表的不同入口。
避免方法:用 spec.ingressClassName 或 spec.gatewayClassName 做显式隔离——不同类名可以分离路由表。

【路径】🔍 下次 Gateway API 不通,先跑这个组合
核心命令链

每条命令的判读标准:
| Step | 命令 | 绿灯 | 红灯 |
|---|---|---|---|
| 1 | kubectl get gatewayclass |
Accepted=True | 无 GatewayClass 或 Accepted=False |
| 2 | kubectl describe gateway <name> |
listeners.conditions.Ready=True | 有 Detached 或 Ready=False |
| 3 | kubectl describe httproute <name> |
Accepted=True 且 ResolvedRefs=True | 任一为 False |
Condition 链串行原理
C2: Gateway API 的 condition 链是串行的。
GatewayClass 的 Accepted → 决定 Gateway 能否被 reconcile。 Gateway 的 Ready → 决定 HTTPRoute 的 Accepted。 HTTPRoute 的 ResolvedRefs → 决定流量是否真正到达后端。
断任何一环,503。
这意味着你不需要同时查 4 层。 从 HTTPRoute 开始看 conditions: - Accepted=False → 问题出在 Gateway 或 GatewayClass 层 - Accepted=True 但 ResolvedRefs=False → 问题出在 backendRef 引用
只用两个 kubectl describe 就能定位到断链层。少学 10 个命令,多看两个 condition。
【定位】最常误判:Gateway API 当 Ingress 配
隐性对手线
| 层级 | 思维 | 行为 | 结果 |
|---|---|---|---|
| Naive | "装个 CRD 就行,跟 Ingress YAML 一样配" | apply 完就不管了 | Gateway Accepted=False,HTTPRoute 绑不上 |
| 过渡经验 | "CRD 和 GatewayClass 都有,怎么还 503?" | 只查 Service 和 Pod | 查了半天全是绿的 |
| 正确 | "从 controllerName 开始逐层查 condition 链" | describe HTTPRoute → 看 conditions | 5 分钟找到断链层 |
Naive 的误区:认为 Gateway API 是 Ingress 的 Plus 版,安了就能用。实际上它引入了全新的三层模型,每层自带验证。
过渡经验的误区:查了 CRD 存在、GatewayClass 存在,就以为配置没问题。但 CRD 安装 ≠ 控制器运行,GatewayClass 存在 ≠ controllerName 匹配。
排查路径对比
❌ Ingress 排查思路(Naive):
1. 检查 Pod → kubectl get pods
2. 检查 Service → kubectl describe svc
3. 检查 Ingress → kubectl describe ingress
4. 重启 Ingress Controller → kubectl rollout restart
前三步全绿,第四步重启完还是 503。因为你用的是 Ingress 的排查方法,但问题出在 Gateway API 的新层。
✅ Gateway API 排查思路(正确):
1. kubectl get gatewayclass → 确认控制器名
2. kubectl describe gateway <name> → 看 listeners.conditions
3. kubectl describe httproute <name> → 看 parents.conditions
4. kubectl get ingress -A → 检查冲突
差异根源:Ingress 是"apply 即生效"。Gateway API 是"apply 只是开始"——多了一层 condition 链检查,全过才生效。
这个差异决定了整个排查思路的转变。你不能再靠"apply 了然后 curl 看通不通"的试错法——你得学会读 condition。

【标点】修复 + 预防
Check-list(每条对应一个 kubectl 命令)
上生产前跑一遍这个 check-list:
□ kubectl get crd | grep gateway.networking.k8s.io
→ 确认 CRD 已安装,版本 ≥ v1.0(v0.x 部分字段已废弃)
□ kubectl get gatewayclass -o yaml | grep controllerName
→ 确认 controllerName 匹配集群实际运行的控制器
□ kubectl describe gateway <name> | grep -A 20 "Status:"
→ 确认 Accepted=True、Ready=True
□ kubectl describe httproute <name> | grep -A 15 "Parents"
→ 确认 Accepted=True、ResolvedRefs=True
□ kubectl get ingress -A -o wide
→ 确认无 Ingress 与 Gateway 监听相同 host 和端口
□ kubectl get pods -n <gateway-ns> | grep gateway
→ 确认 Gateway 控制器 Pod 正常运行
□ kubectl describe httproute <name> | grep "Message"
→ 看 Message 的具体错误原因(InvalidKind/RefNotPermitted/NoMatchingParent)
兼容 YAML 配置示例

4 个配置要点
-
GatewayClass 的 controllerName 必须匹配实际运行的控制器。不确定就查控制器的标识:
bash kubectl get pod -n <控制器命名空间> -o yaml | grep -i controller -
Gateway 的 allowedRoutes 默认
Same(同命名空间)。如果 HTTPRoute 在其他命名空间,改All或Selector。 -
HTTPRoute 的 backendRef 确认 Group 和 Kind 正确。Group 默认
core,Kind 默认Service。引用自定义资源要写完整 Group。 -
Ingress 共存隔离 Ingress 和 Gateway API 被同一控制器管理时,用不同的 className 做显式隔离。
Gateway API 不是加上就能用的——多了一层 controller 鉴权,少配一个字段就断一条链。
排查的终点不是 503 变 200——是把排查路径写成 check-list,下次不用再踩一遍。
快速排查决策树
HTTPRoute describe 看 Parents.conditions
├─ Accepted=False
│ └─ Gateway describe 看 listeners.conditions
│ ├─ Ready=False → Gateway 问题(端口冲突/配置错误)
│ └─ 无条件 → get gatewayclass 看 Accepted
│ ├─ True → GatewayClass 到 Gateway 链断了
│ └─ False → controllerName 不匹配 → 隐性冲突 #1
└─ Accepted=True
└─ ResolvedRefs=False → backendRef 问题 → 隐性冲突 #2
├─ InvalidKind → Group/kind 不对
└─ RefNotPermitted → 跨命名空间不允许
└─ 两者皆 True → 查 Ingress 冲突 → 隐性冲突 #3
阅读路线:Gateway API 排查系列
① Gateway API 分层排查全景(本篇) ② Gateway 端口冲突——Gateway 和 Ingress 争 80/443 时谁赢? ③ 跨命名空间 HTTPRoute 引用——allowedRoutes 设错了路由直接断 ④ annotation 继承失效——ingress.kubernetes.io/rewrite-target 在 HTTPRoute 里怎么写?
下篇我们聊 Gateway 与 Ingress 的端口冲突——同一个 80 端口被两个资源监听时,控制器怎么选。
附:完整命令清单
# Gateway API CRD 版本检查
kubectl get crd | grep gateway.networking.k8s.io
# GatewayClass 检查
kubectl get gatewayclass
kubectl get gatewayclass <name> -o yaml
# Gateway 检查
kubectl get gateway -A
kubectl describe gateway <name>
# HTTPRoute 检查
kubectl get httproute -A
kubectl describe httproute <name>
# HTTPRoute 状态快速查看
kubectl describe httproute <name> | grep -A 15 "Conditions:"
# Ingress 冲突检测
kubectl get ingress -A -o wide
# 控制器 Pod 状态检查
kubectl get pods -n <controller-namespace> | grep gateway
# Gateway API 资源完整导出
kubectl get gatewayclass,gateway,httproute -A -o wide
📺 公众号「Ai拆代码的曹操」 🌟 知识星球「Ai拆代码的曹操」 🔗 个人博客:https://opencao.cn