Kubernetes 基础体系 · 第 29/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。

Ingress 与 Gateway API:路由、TLS、Controller、策略和迁移

在 Kubernetes 中,“入口”不是一个单独的负载均衡器,而是一条由多个对象和组件共同完成的请求路径:

客户端
  │ DNS
  ▼
云负载均衡器或节点端口
  │ TCP/HTTP/TLS
  ▼
Ingress Controller 或 Gateway Controller
  │ 根据路由规则转发
  ▼
Service
  │ 根据 EndpointSlice 选择后端端点
  ▼
Pod

IngressGateway API 都描述“外部请求如何进入集群”,但它们的抽象层次不同:

  • Ingress 是一个较早的、面向 HTTP/HTTPS 的 Kubernetes API 对象。
  • Gateway API 是一组可扩展的、面向角色和协议的 API,对入口、路由和后端引用进行拆分。
  • Controller 不是 API 对象本身,而是观察这些对象并把期望状态转换成真实代理、负载均衡器或数据平面配置的控制器。
  • TLS 解决连接加密、证书选择和身份验证问题,但不等于路由本身。
  • “策略”可能指 Kubernetes NetworkPolicy,也可能指 Gateway API 实现提供的认证、限流、超时、重试和安全策略;这些概念不能混为一谈。

本文中的 Kubernetes 示例使用 networking.k8s.io/v1 Ingress API。Gateway API 是独立项目,CRD 的安装和具体 Controller 的支持矩阵由 Gateway API 项目及实现方分别发布;示例使用其稳定的核心资源模型,但部署时仍必须核对所选 Controller 的版本和支持范围。


一、先区分 API、Controller、数据平面和 Service

1. API 对象只描述期望状态

例如下面的 Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop
spec:
  ingressClassName: nginx
  rules:
    - host: shop.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: shop-api
                port:
                  number: 8080

该对象表达的是:

对发往 shop.example.com、路径以 /api 为前缀的 HTTP 请求,应转发到 shop-api Service 的 8080 端口。

它没有直接监听端口,也不会自己接收数据包。API Server 只是存储并校验对象,真正处理流量的是 Controller 管理的数据平面。

2. Controller 负责把对象变成运行配置

Controller 通常执行以下循环:

  1. 通过 Kubernetes API Watch IngressServiceEndpointSliceSecret 等对象。
  2. 根据 ingressClassNameGatewayClass 判断某个对象是否由自己负责。
  3. 校验引用的 Service、端口、TLS Secret 和路由规则。
  4. 生成 NGINX、Envoy、HAProxy、云负载均衡器或其他代理的配置。
  5. 重新加载或动态更新数据平面。
  6. 将处理结果写回对象的 status,例如地址、已接受条件和错误原因。

因此,“kubectl apply 成功”只说明 API Server 接受了对象,不说明请求已经可以访问。

3. Service 仍然是入口后的服务发现边界

Ingress 或 Gateway 通常不是直接把请求发给 Pod IP,而是发给 Service 的后端端点。典型路径是:

代理
  │ Service 的 ClusterIP、EndpointSlice 或实现内部的端点发现
  ▼
kube-proxy / eBPF 数据路径 / 代理直连
  ▼
Pod IP

Service 的 port 是代理连接的服务端口,targetPort 才是容器实际监听的端口。例如:

apiVersion: v1
kind: Service
metadata:
  name: shop-api
spec:
  selector:
    app: shop-api
  ports:
    - name: http
      port: 8080
      targetPort: 8080

如果 Service 没有匹配到 Pod,入口路由可能完全正确,但仍会得到 503、连接拒绝或超时。此时问题在 Service、EndpointSlice 或 Pod 就绪状态,而不是路由规则。

可使用以下命令区分各层问题:

kubectl get ingress shop
kubectl describe ingress shop

kubectl get svc shop-api
kubectl get endpointslice \
  -l kubernetes.io/service-name=shop-api

kubectl get pods -l app=shop-api

二、Ingress 的路由模型

1. Ingress 的三个核心字段

Ingress 的 HTTP 路由主要由以下部分组成:

  • host:HTTP Host 头或 HTTPS 握手中的 SNI 对应的主机名。
  • path:请求 URI 路径。
  • backend:目标 Service 及其端口。

一个完整例子如下:

apiVersion: v1
kind: Namespace
metadata:
  name: shop
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: shop
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
        - name: app
          image: nginx:1.27
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
  namespace: shop
spec:
  selector:
    app: frontend
  ports:
    - name: http
      port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: frontend
  namespace: shop
spec:
  ingressClassName: nginx
  rules:
    - host: www.example.test
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 80

这个示例的前置条件是:

  • 集群中已经安装并运行名为 nginx 的 Ingress Controller;
  • DNS 或本地 /etc/hostswww.example.test 指向 Controller 的外部地址;
  • frontend Pod 已经就绪;
  • Controller 支持 networking.k8s.io/v1

检查入口地址:

kubectl get ingress -n shop frontend

常见结果类似:

NAME       CLASS   HOSTS                ADDRESS        PORTS   AGE
frontend   nginx   www.example.test     203.0.113.10   80      20s

如果 ADDRESS 为空,可能是 Controller 尚未分配地址,也可能是该实现只通过其 Service 暴露地址,不会回写 Ingress 状态。

2. pathType 决定路径语义

networking.k8s.io/v1 要求显式指定 pathType。常见类型如下。

Exact

只匹配完全相同的路径:

path: /login
pathType: Exact

匹配 /login,不匹配 /login//login/user

Prefix

按路径段匹配,而不是简单字符串前缀:

path: /api
pathType: Prefix

通常匹配:

  • /api
  • /api/
  • /api/users

不匹配:

  • /apiv2
  • /api2

这是一个常见误解:Prefix 不是把路径直接交给字符串 startsWith("/api")。规范对路径段边界有定义,但 Controller 的重写、正则扩展和非标准行为仍可能改变最终效果。

ImplementationSpecific

匹配方式交给 Controller 实现决定。某些实现用它支持正则表达式或历史注解,但这会降低跨 Controller 可移植性。需要可迁移的清晰语义时,应优先使用 ExactPrefix

3. 多条规则的选择顺序

假设存在:

paths:
  - path: /
    pathType: Prefix
    backend:
      service:
        name: web
        port:
          number: 80
  - path: /api
    pathType: Prefix
    backend:
      service:
        name: api
        port:
          number: 80

请求 /api/users 同时匹配 //api。预期选择更长的匹配路径 /api,因此转发到 api Service。

若同一路径同时存在 ExactPrefix,规范定义优先选择更具体的匹配。实现仍应通过状态和实际请求验证,尤其是使用复杂路径、正则或 rewrite 注解时。

4. Host 匹配和通配符

Ingress 支持精确主机名和受限的通配符。例如:

rules:
  - host: "*.example.com"

通常可以匹配:

  • a.example.com
  • api.example.com

但不应理解为任意层级的通配符;a.b.example.com 不等同于单层 *.example.com。根域 example.com 也不会因为存在 *.example.com 自动匹配。

没有 host 的规则是默认主机规则,可匹配未被更具体 Host 规则匹配的请求。生产环境中,如果多个团队共享一个 Controller,未指定 Host 的规则可能造成意外接管流量,应谨慎使用。


三、IngressClass:谁负责处理这个 Ingress

IngressClass 用于把 Ingress 对象绑定到某类 Controller:

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx
spec:
  controller: k8s.io/ingress-nginx

Ingress 中的:

spec:
  ingressClassName: nginx

表示它应该交给名为 nginx 的 IngressClass 处理。spec.controller 是控制器标识,具体值由实现定义;它不是任意字符串,也不能仅凭名称推断 Controller 已经安装。

查看当前集群的类:

kubectl get ingressclass
kubectl describe ingressclass nginx

早期 Ingress 常见这种写法:

metadata:
  annotations:
    kubernetes.io/ingress.class: nginx

这是历史兼容方式。spec.ingressClassName 是当前 API 推荐方式,但某些旧 Controller 仍同时识别注解。若两者同时存在且值冲突,行为取决于实现,不应依赖这种配置。

默认 IngressClass 通常通过注解声明:

metadata:
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"

一个集群最好只有一个默认类。多个默认类会使未指定 ingressClassName 的对象产生歧义,某些 Controller 会拒绝处理或出现不同实现的选择结果。


四、TLS:握手、证书选择和路由是三个不同步骤

1. TLS 终止的完整路径

最常见的 HTTPS 入口是 TLS 在 Ingress Controller 或云负载均衡器处终止:

客户端
  │ ClientHello,包含 SNI=shop.example.com
  ▼
入口代理选择证书并完成 TLS 握手
  │ 解密为 HTTP
  ▼
根据 Host 和 Path 选择路由
  ▼
Service / Pod

这里有三个容易混淆的事实:

  1. TLS 证书主要用于证明主机身份和加密连接。
  2. SNI 发生在 HTTP 请求之前,用于选择证书。
  3. TLS 终止后,Controller 才能读取 HTTP Host、Path、Header 等字段进行七层路由。

Ingress TLS 示例:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: frontend-tls
  namespace: shop
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - www.example.test
      secretName: frontend-tls
  rules:
    - host: www.example.test
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend
                port:
                  number: 80

这里的 Secret 必须是同一命名空间中的 TLS Secret:

kubectl create secret tls frontend-tls \
  -n shop \
  --cert=fullchain.pem \
  --key=privkey.pem

其数据通常包含:

  • tls.crt:证书链,通常是 PEM;
  • tls.key:私钥,通常是 PEM。

hostsrules.host 应保持一致。若客户端通过 https://www.example.test 访问,SNI、证书名称和 HTTP Host 最好都指向同一主机名。

验证证书:

openssl s_client \
  -connect 203.0.113.10:443 \
  -servername www.example.test \
  -showcerts </dev/null

验证 HTTP:

curl -vk \
  --resolve www.example.test:443:203.0.113.10 \
  https://www.example.test/

--resolve 让 curl 使用指定地址,同时仍发送正确的 Host 和 SNI,适合绕过 DNS 测试入口。

2. 证书链和私钥错误

以下错误分别对应不同问题:

  • tls: private key does not match public key:证书和私钥不匹配;
  • 客户端提示 unable to get local issuer certificate:服务端没有发送完整中间证书链,或客户端不信任签发者;
  • 404 或默认后端响应:TLS 握手成功,但 Host/Path 没有匹配路由;
  • 502503:路由可能已匹配,但后端连接失败或没有可用端点;
  • 连接超时:可能是云负载均衡器、安全组、节点端口、网络策略或代理监听问题。

查看 Secret 类型:

kubectl get secret frontend-tls -n shop \
  -o jsonpath='{.type}{"\n"}'

应为:

kubernetes.io/tls

3. TLS 重定向不是 TLS 本身

从 HTTP 自动跳转 HTTPS 通常是 Controller 的实现行为或注解配置,例如某些实现支持强制 SSL 重定向。它执行的是:

HTTP 请求 → 301/308 → HTTPS 请求

这与 TLS 握手无关。若入口前面还有云负载均衡器,必须明确负载均衡器到 Controller 之间是否使用 HTTP 或 HTTPS,否则可能出现循环重定向:

客户端 HTTPS
  → LB 终止 TLS
  → LB 用 HTTP 转发
  → Controller 根据错误的协议判断“需要跳 HTTPS”
  → 客户端再次 HTTPS

解决方式依赖实现对 X-Forwarded-Proto、PROXY protocol 或云负载均衡器配置的处理,不能只修改业务 Service。

4. TLS Passthrough 与端到端加密

TLS Passthrough 表示入口不解密 TLS,而是根据 SNI 将加密连接转发给后端:

客户端 TLS
  │
  ▼
入口按 SNI 转发,不读取 HTTP
  │
  ▼
Pod 完成 TLS 握手

这种模式的直接后果是:

  • 入口无法按 HTTP Path 路由;
  • 入口无法执行基于 HTTP 的 Header 修改、认证、限流或重试;
  • 后端 Pod 必须持有证书;
  • 入口通常只能按主机名、SNI 或四层信息进行转发。

传统 Ingress API 对 Passthrough 没有统一、可移植的标准字段,通常依赖 Controller 特有配置。Gateway API 对 TLS listener 的 TerminatePassthrough 模型描述得更明确,但具体实现仍需支持该能力。

另一种模式是 TLS 重加密:

客户端 ──TLS──> 入口
入口    ──TLS──> 后端

入口解密后重新使用 TLS 连接后端。这能保护入口到后端的链路,但需要入口验证后端证书或至少配置后端 TLS 参数。相关能力在 Gateway API 中以及各 Controller 中的成熟度并不完全一致,不能因为前端使用 HTTPS 就假定后端也被加密。


五、Gateway API 为什么不仅是“新版本 Ingress”

Gateway API 将入口模型拆成多个资源,并把资源管理责任分开:

资源 主要职责
GatewayClass 声明由哪个 Controller 实现这类 Gateway
Gateway 声明监听地址、端口、协议、TLS 和允许哪些 Route
HTTPRoute 声明 HTTP 主机、路径、Header 匹配及后端
GRPCRoute 声明 gRPC 路由
TCPRouteTLSRouteUDPRoute 面向特定协议的路由,支持情况依实现和版本而异
ReferenceGrant 允许跨命名空间引用特定资源

这种拆分解决了 Ingress 常见的责任混杂问题:

  • 平台团队管理共享入口和证书;
  • 应用团队管理自己的路由;
  • Route 不必拥有整个入口的所有权;
  • Controller 可以通过状态条件报告每个连接是否被接受。

1. GatewayClass

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: example-gateway-class
spec:
  controllerName: example.net/gateway-controller

controllerName 必须与实际 Controller 约定的名称一致。这个示例中的值不是可直接运行的通用值;安装某个实现后,应使用该实现文档指定的值。

Gateway API CRD 和 Controller 不是 Kubernetes 核心组件自动提供的。仅安装 CRD 会让 API Server 能接受对象,但不会产生监听器或代理配置。

2. Gateway 描述监听器

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public
  namespace: platform
spec:
  gatewayClassName: example-gateway-class
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              tenant: public

该对象表达:

  • 创建一个名为 public 的 Gateway;
  • 使用指定的 GatewayClass;
  • 监听 HTTP 80 端口;
  • 只允许带有 tenant=public 标签的命名空间中的 Route 绑定。

Gateway 更接近“入口基础设施”的配置,而不是某个应用的全部路由。

3. HTTPRoute 描述应用路由

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: frontend
  namespace: shop
  labels:
    expose: public
spec:
  parentRefs:
    - name: public
      namespace: platform
      sectionName: http
  hostnames:
    - www.example.test
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: frontend
          port: 80

这个对象表示:

  • shop 命名空间中的团队希望把 Route 附加到 platform/public Gateway 的 http listener;
  • 只匹配 www.example.test
  • / 请求发送到同一命名空间的 frontend:80 Service。

要使它真正成立,需要同时满足两个方向的授权:

  1. Gateway 的 allowedRoutes 必须允许 shop 命名空间;
  2. HTTPRoute.parentRefs 必须正确引用 Gateway 和 listener。

状态检查:

kubectl get gateway -n platform public -o yaml
kubectl get httproute -n shop frontend -o yaml

重点查看:

status:
  parents:
    - parentRef:
        name: public
        namespace: platform
        sectionName: http
      controllerName: ...
      conditions:
        - type: Accepted
          status: "True"
        - type: ResolvedRefs
          status: "True"

Accepted=True 表示路由被父 Gateway 接受;ResolvedRefs=True 表示引用的 Service 等资源能够解析。二者任一为 False,都可能导致请求无法转发。


六、Gateway API 的跨命名空间安全边界

Gateway API 的一个重要设计是:跨命名空间引用默认不会因为“名字写对了”就自动成功。

1. Route 附加到其他命名空间的 Gateway

HTTPRoute 可以通过 parentRefs.namespace 引用其他命名空间的 Gateway,但 Gateway 必须通过 allowedRoutes 明确允许该命名空间。

例如:

spec:
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: Same

Same 表示只允许同命名空间 Route。若希望按标签允许多个命名空间,可以使用 Selector

2. Route 引用其他命名空间的 Service

backendRefs 默认引用 Route 所在命名空间的 Service。跨命名空间时需要 ReferenceGrant

apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-shop
  namespace: shared-services
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: shop
  to:
    - group: ""
      kind: Service

随后 Route 才可以引用:

backendRefs:
  - name: shared-api
    namespace: shared-services
    port: 8080

ReferenceGrant 必须创建在被引用资源所在的命名空间,即这里的 shared-services。它不是“给整个集群开权限”,而是对来源命名空间、来源资源类型和目标资源类型做显式授权。


七、Ingress 与 Gateway API 的路由表达差异

Ingress 的表达方式

Ingress 把 Host、Path 和后端放在一个对象中:

spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /v1
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 8080

优点是简单、部署广泛、工具链成熟。缺点是扩展能力大量依赖注解:

metadata:
  annotations:
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"

这种注解的问题是:

  • 只对特定 Controller 有效;
  • API Server 通常无法验证注解的语义;
  • 同一注解在不同实现中可能含义不同;
  • 迁移到另一种 Controller 时需要重新解释。

HTTPRoute 的表达方式

HTTPRoute 将匹配、过滤器和后端分开:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api
  namespace: shop
spec:
  parentRefs:
    - name: public
      namespace: platform
      sectionName: https
  hostnames:
    - api.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /v1
          headers:
            - name: X-Environment
              value: canary
      filters:
        - type: RequestHeaderModifier
          requestHeaderModifier:
            add:
              - name: X-Routed-By
                value: gateway-api
      backendRefs:
        - name: api-canary
          port: 8080
            weight: 10
        - name: api
          port: 8080
            weight: 90

这表示满足 Host、Path 和 Header 条件的请求,在两个后端之间按权重分配。其直觉是:

P(api-canary)=1010+90=0.1P(\text{api-canary}) = \frac{10}{10+90}=0.1

但这个公式表达的是逻辑权重,不保证短时间窗口内精确得到 10% 请求,也不保证按用户、连接或字节数分配。实际粒度取决于 Controller 的负载均衡算法、连接复用和流量分布。

必须注意,Gateway API 的核心过滤器有明确支持范围,但某些 Controller 可能尚未支持全部过滤器;如果不支持,Route 状态通常会报告过滤器无效或不可接受,而不是保证按预期执行。部署前应查看实现的 Gateway API conformance 和扩展能力。


八、路由冲突、优先级和状态

当多个 Route 附加到同一个 Gateway 时,Controller 必须处理冲突。例如:

  • 两个 Route 声明相同 hostname;
  • 两个 Route 匹配相同路径;
  • 一个 Route 试图使用另一个 Route 已声明的规则;
  • 一个 listener 的 allowedRoutes 不允许该 Route。

Gateway API 规定了一套规则选择和冲突处理模型,但工程上不能仅凭 YAML 文件顺序判断结果。对象创建时间、名称、精确匹配程度和实现支持都可能参与最终处理。

可靠的验证方法是:

kubectl describe httproute -n shop frontend
kubectl get httproute -n shop frontend -o yaml

关注以下条件:

  • Accepted:Route 是否被父 Gateway 接受;
  • ResolvedRefs:Service、Secret 等引用是否解析成功;
  • Programmed:Gateway 是否已经被写入数据平面配置;
  • Ready:某些实现可能提供的实现特有条件。

Gateway API 的状态不是装饰信息。它是 Controller 将“对象语义”转换为“可用数据平面”的可观察接口。一个对象存在但 Accepted=False,不能视为已经发布。


九、Controller 的状态、并发与故障路径

1. 对象变化如何传播到请求

当后端 Pod 被替换时,完整路径可能如下:

sequenceDiagram
    participant K as Kubernetes API Server
    participant C as Gateway/Ingress Controller
    participant D as Data Plane
    participant S as Service/EndpointSlice
    participant P as Client

    K->>C: Watch Service、EndpointSlice、Route、Secret
    C->>C: 重新计算路由与后端端点
    C->>D: 动态更新或重新加载配置
    D-->>C: 配置应用结果
    C->>K: 更新 status.conditions
    P->>D: 发起 HTTP/TLS 请求
    D->>S: 选择可用后端端点
    S-->>D: 返回 Pod endpoint
    D->>P: 返回响应

Controller 通常是异步控制器。API 对象变更到数据平面生效之间存在延迟。这个延迟不是固定的规范值,受 Watch 事件、配置生成、代理重载、云 API 调用和健康检查影响。

2. 并发更新不是简单的“最后一个 YAML 获胜”

多个团队或自动化系统可能同时修改同一个 Ingress、Gateway 或 Route。Kubernetes API Server 使用资源版本和更新语义防止无条件覆盖,但客户端仍可能遇到:

the object has been modified; please apply your changes to the latest version

声明式 kubectl apply、Server-Side Apply 和不同字段管理者可以降低互相覆盖,但不能解决两个团队对同一路由拥有矛盾意图的问题。Gateway API 通过拆分 Gateway 与 Route,减少了这种共享对象冲突,却不会自动消除业务域名和路径上的逻辑冲突。

3. 常见故障要按层定位

Ingress/Gateway 不被处理

现象:

  • 对象存在;
  • 没有地址;
  • Accepted=False
  • Controller 日志显示忽略对象。

检查:

kubectl get ingressclass
kubectl get gatewayclass
kubectl describe gatewayclass <name>
kubectl describe ingress <name> -n <namespace>
kubectl logs -n <controller-namespace> deploy/<controller>

常见原因:

  • ingressClassNamegatewayClassName 错误;
  • Controller 没有安装;
  • Controller 的 controllerName 不匹配;
  • 资源字段超出实现支持范围;
  • Gateway listener 不允许该 Route。

地址可用但返回 404

优先判断请求是否到达了正确入口:

curl -v \
  --resolve www.example.test:80:203.0.113.10 \
  http://www.example.test/

如果访问 IP 而不带正确 Host,Host 路由通常不会匹配。HTTPS 场景还必须发送正确 SNI:

curl -vk \
  --resolve www.example.test:443:203.0.113.10 \
  https://www.example.test/

因此“直接浏览器访问 IP 得到 404”不能证明 Ingress 配置错误。

返回 503 或 502

检查后端引用和端点:

kubectl get svc -n shop frontend -o yaml
kubectl get endpointslice \
  -n shop \
  -l kubernetes.io/service-name=frontend

常见原因:

  • Service 端口写错;
  • selector 没有匹配 Pod;
  • Pod 没有通过 readiness probe;
  • 应用只监听 127.0.0.1,没有监听 Pod IP;
  • Controller 到后端使用 HTTPS,但后端实际只提供 HTTP;
  • NetworkPolicy 阻断了 Controller 到应用的连接。

HTTPS 握手失败

检查:

kubectl describe secret frontend-tls -n shop
openssl x509 -in tls.crt -noout -subject -issuer -dates -ext subjectAltName

还要确认:

  • Secret 所在命名空间正确;
  • Gateway listener 或 Ingress 的 hostname 与证书 SAN 匹配;
  • 私钥和证书匹配;
  • 入口的 443 端口确实暴露;
  • 云负载均衡器是否在入口处终止了 TLS。

十、策略:NetworkPolicy、入口策略和应用策略不是一层东西

1. NetworkPolicy 是 L3/L4 访问控制

Kubernetes NetworkPolicy 主要描述 Pod 之间的网络流量允许关系,例如:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-gateway
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
      ports:
        - protocol: TCP
          port: 80

它表达的是:

允许来自带有指定命名空间标签的 Pod、目标端口为 TCP 80 的流量。

它不表达:

  • 只允许 GET /api
  • 根据 JWT 用户授权;
  • 每个客户端每秒最多 100 个请求;
  • 对 5xx 自动重试;
  • 根据 HTTP Header 选择 canary。

而且 NetworkPolicy 是否生效依赖集群的网络插件实现。创建对象不代表当前 CNI 一定支持所有字段或行为。

2. Gateway API 的策略不是一个统一的 Kubernetes 核心对象

Gateway API 定义了路由和入口的通用模型,但认证、WAF、限流、外部授权、后端 TLS 校验等能力经常通过实现方扩展提供。例如某个 Gateway Controller 可能提供自定义 SecurityPolicy,另一个 Controller 可能使用不同的 CRD 或注解。

因此下面两件事必须区分:

HTTPRoute 的核心字段
= 可移植的路由语义

Controller 自定义 Policy CRD
= 某个实现提供的扩展能力

同名的“Policy”并不意味着跨实现兼容。生产迁移时,应逐项建立能力矩阵:

能力 Ingress 常见实现 Gateway API 核心 是否通常需要扩展
Host/Path 路由 HTTPRoute
HTTP 重定向 注解或实现能力 标准过滤器模型 视实现
Header 修改 注解或实现能力 标准过滤器模型 视实现
JWT/OIDC 注解或自定义资源 非统一核心策略
WAF 实现扩展 非统一核心策略
限流 实现扩展 非统一核心策略
L3/L4 Pod 隔离 NetworkPolicy 仍由 NetworkPolicy 负责
后端 TLS 校验 实现扩展 相关 API 支持情况依版本 常需核对

3. 策略附着位置决定作用范围

一个入口策略可能作用于:

  • 整个 Gateway;
  • 某个 listener;
  • 一个 HTTPRoute;
  • 某一条 route rule;
  • 某个 backend;
  • 后端 Service 或 Pod。

作用范围越大,越容易影响其他团队的流量;作用范围越小,越难统一管理。Gateway API 的角色拆分允许平台团队控制 Gateway,应用团队控制 Route,但具体策略附着模型仍依赖策略 API 和 Controller。


十一、Ingress 到 Gateway API 的迁移

迁移不是把:

kind: Ingress

机械替换成:

kind: HTTPRoute

因为两种 API 对入口、路由、TLS 和扩展能力的边界定义不同。

1. 先盘点 Ingress 的真实行为

至少记录:

  • 使用的 Ingress Controller 及版本;
  • IngressClass
  • 所有 annotations;
  • Host、Path 和 pathType;
  • TLS Secret 及证书签发方式;
  • rewrite、重定向、认证、限流、超时、重试;
  • 默认后端和错误页;
  • 云负载均衡器、源地址保留和健康检查;
  • Controller 到 Service 的协议;
  • 依赖的 NetworkPolicy

特别要逐个解释注解。下面两个注解看起来都只是“配置参数”,但迁移含义完全不同:

nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/auth-url: https://auth.example.com/check

前者改变 URI,后者改变请求认证流程。Gateway API 可能有标准 URLRewrite 过滤器,但认证通常需要实现方策略,不能直接一对一转换。

2. Ingress 到 Gateway API 的基本映射

Ingress:

spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - app.example.com
      secretName: app-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 8080

概念上的 Gateway API 拆分:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: public
  namespace: platform
spec:
  gatewayClassName: example-gateway-class
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "*.example.com"
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-example-tls
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              tenant: public
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api
  namespace: shop
spec:
  parentRefs:
    - name: public
      namespace: platform
      sectionName: https
  hostnames:
    - app.example.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api
      backendRefs:
        - name: api
          port: 8080

这里不能直接假设 wildcard-example-tls 可以从 platform 命名空间读取。certificateRefs 的跨命名空间限制和授权方式必须按照所使用 Gateway API 版本及 Controller 实现检查;更安全的默认方案是让证书 Secret 与 Gateway 位于同一命名空间,或使用实现支持的显式跨命名空间授权机制。

3. 推荐的迁移阶段

阶段一:建立旁路 Gateway

不要先删除生产 Ingress。安装 Gateway API CRD 和目标 Controller,在独立的 GatewayClass、地址或测试域名上发布等价 Route。

验证:

kubectl get gatewayclass
kubectl get gateway -A
kubectl get httproute -A
kubectl describe httproute -n shop api

同时用 curl --resolve 对新入口执行路径、Host、证书和错误码测试。

阶段二:逐条迁移路由

每迁移一条 Route,检查:

  • 正常路径;
  • 未匹配路径;
  • HTTP 到 HTTPS 重定向;
  • 证书过期和错误 SNI;
  • 后端无端点时的错误表现;
  • Header、客户端地址和协议头;
  • 长连接、WebSocket 或 gRPC;
  • 超时、重试和流量权重。

不要把“返回 200”作为唯一验证。入口可能返回的是错误页或缓存结果。

阶段三:迁移非标准能力

对每个 Ingress 注解分类:

  1. Gateway API 核心能力可表达;
  2. Gateway Controller 支持的扩展;
  3. 云负载均衡器专属能力;
  4. 只能在应用或 Service 层实现的能力;
  5. 没有等价替代,必须重新设计的能力。

例如,Ingress 的 rewrite 可能映射为 HTTPRoute 的 URLRewrite;但复杂正则重写、Lua 脚本、WAF 规则和 OIDC 流程通常不能仅靠标准 Route 完成。

阶段四:切换 DNS 或负载均衡器

DNS 切换受 TTL、客户端缓存和递归 DNS 行为影响,不会瞬时完成。切换前应保证:

  • 新入口证书已生效;
  • 新入口后端健康;
  • 监控同时覆盖旧入口和新入口;
  • 具备回切 DNS 或恢复旧负载均衡器的方案;
  • 数据库、会话和回源行为不依赖某个入口的隐含特性。

阶段五:观察后再删除旧对象

至少观察完整的业务高峰和证书轮换周期。确认没有旧域名、旧路径、Webhook、第三方回调或内部客户端仍依赖旧入口后,再删除 Ingress Controller 及其相关资源。


十二、证书管理与 Controller 的交互

证书通常由人工创建、云证书系统或 cert-manager 管理。cert-manager 的职责是申请、签发和轮换证书;它不会替代 Ingress Controller 或 Gateway Controller 完成流量转发。

典型流程是:

Certificate / Ingress 注解
  ▼
cert-manager 创建 CertificateRequest、Order、Challenge
  ▼
签发机构返回证书
  ▼
cert-manager 更新 kubernetes.io/tls Secret
  ▼
Ingress/Gateway Controller Watch 到 Secret 变化
  ▼
代理重新加载证书

因此证书轮换可能出现两个独立故障:

  1. cert-manager 没有成功更新 Secret;
  2. Secret 已更新,但入口 Controller 没有加载新证书。

检查:

kubectl get certificate,certificaterequest,order,challenge -n shop
kubectl describe certificate frontend-tls -n shop
kubectl get secret frontend-tls -n shop \
  -o jsonpath='{.data.tls\.crt}' | base64 -d |
  openssl x509 -noout -dates -subject

如果 Secret 中的日期已经更新,但客户端仍得到旧证书,应检查 Controller 日志、数据平面配置和前置云负载均衡器缓存。若客户端仍报证书不受信任,则还要检查签发链和客户端信任库。

生产环境不能把“Secret 存在”当成证书可用的证明。必须验证:

  • SAN 包含实际访问域名;
  • 证书未过期;
  • 证书链完整;
  • 私钥匹配;
  • TLS listener 或 Ingress 引用了正确 Secret;
  • Controller 已将证书加载到实际监听地址。

十三、生产边界和常见误解

误解一:Ingress 是 Kubernetes 内置的反向代理

Ingress 是 API 资源,Kubernetes 不会因为创建它就自动提供 NGINX、Envoy 或云负载均衡器。必须安装兼容的 Ingress Controller。

同理,Gateway API CRD 存在也不代表 Gateway Controller 已经运行。

误解二:Service 类型决定了 Ingress 的路由能力

Ingress 后端通常使用 ClusterIP Service。把 Service 改成 NodePort 或 LoadBalancer 不会自动增加 Host、Path、TLS 或 Header 路由能力,只会改变 Service 自身的暴露方式。

典型架构是:

云 LoadBalancer Service
  → Ingress Controller Pod
  → ClusterIP Service
  → 应用 Pod

而不是必须让每个应用 Service 都成为 LoadBalancer。

误解三:TLS 成功就说明路由成功

TLS 握手成功只证明客户端和某个入口端点完成了加密协商。仍可能发生:

  • SNI 选择了默认站点;
  • HTTP Host 不匹配;
  • Path 不匹配;
  • Route 没有被接受;
  • Service 没有端点;
  • 后端协议配置错误。

应分别测试证书、Host/Path 匹配和后端连通性。

误解四:NetworkPolicy 可以做 HTTP 认证和限流

NetworkPolicy 通常只能处理 IP、命名空间、Pod 选择器和端口等网络层条件。JWT、Cookie、HTTP Header、请求频率和业务用户权限需要入口扩展或应用层实现。

误解五:Gateway API 一定比 Ingress 更“快”

API 模型不会直接决定吞吐量。实际性能由数据平面实现、TLS 加解密、连接复用、日志、WAF、后端响应和负载均衡器配置共同决定。Gateway API 的主要价值是更清晰的职责边界、可扩展性、状态表达和跨实现的标准化方向,而不是一个由 API 名称保证的性能数字。


十四、一个可重复的最小验证流程

无论使用 Ingress 还是 Gateway API,都可以按以下顺序定位:

第一步:确认 Controller

kubectl get pods -A
kubectl get svc -A

确认 Controller Pod 正常,且入口 Service 有期望的端口和外部地址。

第二步:确认 API 对象和状态

kubectl get ingress -A
kubectl get gateway -A
kubectl get httproute -A

kubectl describe ingress -n shop frontend
kubectl describe gateway -n platform public
kubectl describe httproute -n shop api

状态中的 AcceptedResolvedRefs、地址和事件比 YAML 是否存在更重要。

第三步:确认 Service 与 EndpointSlice

kubectl get svc -n shop api
kubectl get endpointslice \
  -n shop \
  -l kubernetes.io/service-name=api

如果没有端点,应先修复 selector、Pod 标签或 readiness probe。

第四步:从集群内部测试后端

kubectl run curl --rm -it \
  --image=curlimages/curl:8.10.1 \
  --restart=Never -- \
  curl -v http://api.shop.svc.cluster.local:8080/healthz

若内部访问都失败,入口层不是首要问题。

第五步:从入口外部测试 Host、SNI 和路径

curl -v \
  --resolve app.example.test:80:203.0.113.10 \
  http://app.example.test/api

HTTPS:

curl -vk \
  --resolve app.example.test:443:203.0.113.10 \
  https://app.example.test/api

第六步:对照代理日志和后端日志

入口日志回答:

  • 请求是否到达;
  • 匹配到了哪个路由;
  • 是否生成 404、502 或 503;
  • 后端连接是否失败。

应用日志回答:

  • 请求是否到达 Pod;
  • Host、X-Forwarded-Proto、客户端地址是否符合预期;
  • 应用是否主动返回错误。

只有把两边时间戳、请求 ID 和状态码对齐,才能区分“入口没有转发”和“应用转发后报错”。


结语

Ingress 和 Gateway API 都描述入口路由,但它们解决问题的方式不同:

  • Ingress 把 Host、Path、TLS 和 Service 后端集中在一个相对简单的 API 中,兼容性广,但扩展能力常依赖实现特有注解。
  • Gateway API 把入口、监听器和路由拆分为 GatewayClassGatewayHTTPRoute 等资源,并通过 allowedRoutesReferenceGrant 和状态条件建立更清晰的控制边界。
  • Controller 才是把 API 对象转换为真实代理配置的执行者;对象创建成功不等于数据平面已就绪。
  • TLS 负责加密和身份验证,SNI、Host、Path、证书加载和后端协议必须分层验证。
  • NetworkPolicy 负责网络层访问控制,认证、限流、WAF 和业务授权通常属于入口扩展或应用策略。
  • 从 Ingress 迁移到 Gateway API 的关键不是语法替换,而是盘点现有 Controller 行为、注解和云厂商能力,再逐项验证标准能力与实现扩展的边界。

在生产环境中,最可靠的入口配置不是“YAML 看起来正确”,而是同时满足 API 状态、Controller 状态、TLS 实际证书、Service EndpointSlice、网络连通性和真实请求验证这几个条件。


系列导航与关联阅读

官方资料

本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。