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

HTTPRoute 503 错误状态

Condition 解读

HTTPRoute 的状态段有两组 condition:AcceptedResolvedRefs

  • 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 不做语义校验。

Gateway API 三层资源模型

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(仅同命名空间),改为 AllSelector 可放开限制。

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 vs Gateway API 路由路径对比

Ingress 是单层模型:Ingress → Service → Pod。Ingress Controller 看到 Ingress 资源,直接解析 backend,转发到 Service。

Gateway API 是三层模型:GatewayClass → Gateway → HTTPRoute → backendRef → Service → Pod。每一层都加了验证:

  1. GatewayClass 检查 controllerName → 决定 Gateway 能否被 reconcile
  2. Gateway 检查 allowedRoutes 和监听器配置 → 决定 HTTPRoute 能否绑定
  3. 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.ingressClassNamespec.gatewayClassName 做显式隔离——不同类名可以分离路由表。

Ingress 冲突检测


【路径】🔍 下次 Gateway API 不通,先跑这个组合

核心命令链

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。

错误 vs 正确排查路径


【标点】修复 + 预防

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 配置示例

兼容 YAML 配置

4 个配置要点

  1. GatewayClass 的 controllerName 必须匹配实际运行的控制器。不确定就查控制器的标识: bash kubectl get pod -n <控制器命名空间> -o yaml | grep -i controller

  2. Gateway 的 allowedRoutes 默认 Same(同命名空间)。如果 HTTPRoute 在其他命名空间,改 AllSelector

  3. HTTPRoute 的 backendRef 确认 Group 和 Kind 正确。Group 默认 core,Kind 默认 Service。引用自定义资源要写完整 Group。

  4. 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