Skip to content

Gateway 与 Ingress

一个 Web 服务要通过域名从外部访问时,最容易想到的是给 Service 配 LoadBalancer。一个 Service 一个云负载均衡,十个 Service 就是十个 LB——成本、管理和证书配置很快就会失控。七层入口要做的事情是把域名和路径作为分发依据,让一个入口把请求路由到不同 Service,而不是每个 Service 单独暴露。

Ingress 最早承担了这个角色,把域名、路径、证书、后端 Service 写成 K8s 资源对象。但每个 Ingress 控制器的功能差异靠 annotation 表达——同一个 rewrite-target 注解,nginx ingress 和 traefik 的配置方式完全不同。换控制器意味着重写所有 Ingress 规则的注解部分。另外 Ingress 只有一个对象,平台管理员和开发者都在上面改,没有操作边界。

Gateway API 是社区对这个问题的回应。它用标准 CRD 替代注解——域名、路径、Header 匹配、权重分流都写成 HTTPRoute 里的字段,不再依赖控制器私有注解。同时拆成三个对象实现角色分离:基础设施团队管 GatewayClass 和 Gateway,开发者只写 HTTPRoute。

对象谁维护做什么
GatewayClass平台/基础设施声明用哪个控制器(Envoy Gateway、Traefik 等)
Gateway平台/基础设施声明入口实例——监听哪些端口、提供哪些协议的证书
HTTPRoute开发者/应用团队声明路由规则——域名、路径、Header、权重、后端 Service

Ingress API 仍然是稳定的,但官方已将其标为 frozen,新功能全部进入 Gateway API。社区维护的 kubernetes/ingress-nginx 已于 2026 年 3 月退役——不等于所有 NGINX 控制器失效,F5 NGINX Ingress Controller、商业 NGINX、HAProxy、Traefik、Envoy Gateway 都是独立项目。

入口链路的位置

Ingress 和 Gateway 都不是 Nginx 或 Envoy 本身。它们是 K8s 资源对象,描述"域名、路径、证书、后端 Service"这类规则。真正接收和转发请求的是控制器 Pod。

去掉入口组件时,外部请求要么直接打到 NodePort(每个 Service 一个端口),要么每个 Service 配一个云 LB——两种方式都很难统一做 TLS、限流、日志和灰度。

Gateway API 拆成三层后:

平台管理员创建 GatewayClass 和 Gateway 之后,开发者只关心 HTTPRoute——域名怎么配、请求往哪个 Service 发、权重怎么分。不需要知道 Gateway 监听在哪个端口、证书放在哪个 Secret。

Gateway API 对象模型

安装 Gateway API CRD 和控制器之后,第一步是声明用哪个控制器:

yaml
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller

GatewayClass 创建后只是声明"集群里有这个控制器可用",本身不会创建任何 Pod 或监听端口。一个集群可以有多个 GatewayClass,对应不同控制器——比如同时有 Envoy Gateway 和 Traefik。

第二步是创建 Gateway,定义入口实例和监听端口:

yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: main-gateway
  namespace: gateway-system
spec:
  gatewayClassName: envoy
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: Same   # 默认值,只允许同 namespace 的 HTTPRoute 绑定

Gateway 创建后,控制器会在集群里部署对应的数据面 Pod(Envoy Proxy 或 Traefik 实例),并开始监听声明的端口。allowedRoutes.namespaces.from 默认是 Same——只接受同 namespace 的 HTTPRoute。设成 All 后,任意 namespace 的 HTTPRoute 都可以绑定这个 Gateway,这时要配合 RBAC 限制谁能创建 HTTPRoute。

验证 Gateway 是否就绪:

bash
kubectl get gateway -A
# STATUS 列显示 Programmed=True 说明控制器已接受并下发配置

第三步是创建 HTTPRoute,声明路由规则。先从一个最简单的规则开始——单域名、单 Service、无 TLS:

yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: web
  namespace: demo
spec:
  parentRefs:
    - name: main-gateway
      namespace: gateway-system
  hostnames:
    - web.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: web
          port: 80

parentRefs 声明这个 HTTPRoute 绑定到哪个 Gateway。不写 namespace 时默认同 namespace。path.type 支持 PathPrefix(前缀匹配)、Exact(精确匹配)和 RegularExpression(正则),绝大多数场景 PathPrefix 够用。

apply 之后验证路由状态:

bash
kubectl get httproute web -n demo
kubectl get httproute web -n demo -o yaml | grep -A40 status:

从外部验证:

bash
curl -H 'Host: web.example.com' http://<gateway-ip>/

-H 'Host:' 模拟浏览器发送的 Host 头。Gateway 用 Host 头匹配 hostnames,所以在 DNS 配好之前可以用这个方式测试路由是否生效。

HTTPRoute 的 status.conditions 反映了路由是否被 Gateway 接受:

Conditions含义用户看到什么排查入口
Accepted=False路由没被 Gateway 接受请求 404 或连接拒绝kubectl describe httproute,看 parentRefs、namespace 权限、listener 是否匹配
ResolvedRefs=False后端 Service 或 Secret 解析失败请求 503kubectl get svc 确认后端存在,Secret 名称和 namespace 正确
Programmed=False控制器还没把规则下发到数据面请求可能返回旧规则或 404kubectl describe gateway 看 Gateway 自身状态

AcceptedResolvedRefsProgrammed 三者全部 True 之后,路由才算真正生效。

逐层叠加

在基础路由上叠加金丝雀——同一个域名按权重把流量分给两个 Service:

yaml
rules:
  - backendRefs:
      - name: web-v1
        port: 80
        weight: 90
      - name: web-v2
        port: 80
        weight: 10

不加 Header 匹配或 Cookie 亲和时,权重是随机分配——每次请求独立决策,不保证同一用户的连续请求落在同一版本。

再叠加 TLS——在 Gateway 的 listener 上加证书引用:

yaml
listeners:
  - name: https
    protocol: HTTPS
    port: 443
    tls:
      mode: Terminate
      certificateRefs:
        - name: example-com-tls
          kind: Secret
    allowedRoutes:
      namespaces:
        from: Same

mode: Terminate 表示 TLS 在 Gateway 终止,后端 Pod 走 HTTP。证书来自 example-com-tls 这个 Secret。Secret 必须在 Gateway 所在的 namespace。

完整的请求链路:

Ingress 的现状

项目现状
Ingress API稳定 API,无移除计划,但已 frozen
新功能进入 Gateway API
ingress-nginxkubernetes/ingress-nginx 已于 2026 年 3 月退役
老集群现有 Ingress 仍能工作,但控制器维护状态需要单独确认

从 Ingress 迁到 Gateway API

迁移前先盘点现有资源:

bash
kubectl get ingress -A
kubectl get ingress -A -o yaml | grep "nginx.ingress.kubernetes.io"

第二条命令把集群里所有 Ingress 的注解全部拉出来——注解是迁移的核心工作量,大部分路由规则本身可以直接翻译。

Ingress 路由规则到 Gateway API 的对应关系:

Ingress 字段Gateway API 对象
ingressClassNameGatewayClass / Gateway
rules.hostHTTPRoute.spec.hostnames
paths.pathHTTPRoute.rules.matches.path
backend.serviceHTTPRoute.rules.backendRefs
tls.secretNameGateway.listener.tls.certificateRefs 或控制器约定

注解比路由规则麻烦。迁移时需要逐个对照:

注解能力Gateway API 替代方式
rewrite-targetHTTPRoute filter 或控制器扩展
canary / canary-weightHTTPRoute backendRefs weight
rate limiting控制器插件、外部网关或 Service Mesh
auth / oauth2控制器扩展或外部认证服务
proxy-body-size / proxy-read-timeout控制器特定字段或 policy CRD

没有控制器能 100% 覆盖所有常用注解。选型之前把现有集群的注解全部拉出来,和候选控制器的能力做对照,比看功能列表有用。

迁移按入口链路拆分:低风险域名先迁,保留旧 Ingress 作为回退路径。先迁纯路由(域名→Service),再迁带 rewrite 的,最后迁带 TLS、鉴权和限流的。每条路由迁完后用 curl -H 'Host:' 分别验证新 Gateway 和旧 Ingress,确认行为一致。

控制器选型

控制器特点
Envoy GatewayGateway API 原生方向,社区活跃
Traefik上手轻,Gateway API 支持积极
HAProxy Ingress/Gateway传统负载均衡能力强
Cilium Gateway和 Cilium/eBPF 网络栈结合
云厂商 Gateway/LB Controller云上和 SLB/ALB/NLB 集成
F5 NGINX Ingress ControllerNGINX 生态,和社区 ingress-nginx 不同项目

选型关注点:Gateway API 覆盖了哪些版本来支持、现有注解有没有对应的替代方式、访问日志和指标的数据格式、CRD 运维复杂度、和现有 CNI 的配合、社区维护活跃程度。