Kubernetes 基础体系 · 第 29/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Ingress 与 Gateway API:路由、TLS、Controller、策略和迁移
在 Kubernetes 中,“入口”不是一个单独的负载均衡器,而是一条由多个对象和组件共同完成的请求路径:
客户端
│ DNS
▼
云负载均衡器或节点端口
│ TCP/HTTP/TLS
▼
Ingress Controller 或 Gateway Controller
│ 根据路由规则转发
▼
Service
│ 根据 EndpointSlice 选择后端端点
▼
Pod
Ingress 和 Gateway 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-apiService 的 8080 端口。
它没有直接监听端口,也不会自己接收数据包。API Server 只是存储并校验对象,真正处理流量的是 Controller 管理的数据平面。
2. Controller 负责把对象变成运行配置
Controller 通常执行以下循环:
- 通过 Kubernetes API Watch
Ingress、Service、EndpointSlice、Secret等对象。 - 根据
ingressClassName或GatewayClass判断某个对象是否由自己负责。 - 校验引用的 Service、端口、TLS Secret 和路由规则。
- 生成 NGINX、Envoy、HAProxy、云负载均衡器或其他代理的配置。
- 重新加载或动态更新数据平面。
- 将处理结果写回对象的
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:HTTPHost头或 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/hosts将www.example.test指向 Controller 的外部地址; frontendPod 已经就绪;- 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 可移植性。需要可迁移的清晰语义时,应优先使用 Exact 或 Prefix。
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。
若同一路径同时存在 Exact 和 Prefix,规范定义优先选择更具体的匹配。实现仍应通过状态和实际请求验证,尤其是使用复杂路径、正则或 rewrite 注解时。
4. Host 匹配和通配符
Ingress 支持精确主机名和受限的通配符。例如:
rules:
- host: "*.example.com"
通常可以匹配:
a.example.comapi.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
这里有三个容易混淆的事实:
- TLS 证书主要用于证明主机身份和加密连接。
- SNI 发生在 HTTP 请求之前,用于选择证书。
- 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。
hosts 与 rules.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 没有匹配路由;502、503:路由可能已匹配,但后端连接失败或没有可用端点;- 连接超时:可能是云负载均衡器、安全组、节点端口、网络策略或代理监听问题。
查看 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 的 Terminate 和 Passthrough 模型描述得更明确,但具体实现仍需支持该能力。
另一种模式是 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 路由 |
TCPRoute、TLSRoute、UDPRoute |
面向特定协议的路由,支持情况依实现和版本而异 |
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/publicGateway 的httplistener;- 只匹配
www.example.test; - 将
/请求发送到同一命名空间的frontend:80Service。
要使它真正成立,需要同时满足两个方向的授权:
- Gateway 的
allowedRoutes必须允许shop命名空间; 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 条件的请求,在两个后端之间按权重分配。其直觉是:
但这个公式表达的是逻辑权重,不保证短时间窗口内精确得到 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>
常见原因:
ingressClassName或gatewayClassName错误;- 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 注解分类:
- Gateway API 核心能力可表达;
- Gateway Controller 支持的扩展;
- 云负载均衡器专属能力;
- 只能在应用或 Service 层实现的能力;
- 没有等价替代,必须重新设计的能力。
例如,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 变化
▼
代理重新加载证书
因此证书轮换可能出现两个独立故障:
cert-manager没有成功更新 Secret;- 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
状态中的 Accepted、ResolvedRefs、地址和事件比 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 把入口、监听器和路由拆分为
GatewayClass、Gateway、HTTPRoute等资源,并通过allowedRoutes、ReferenceGrant和状态条件建立更清晰的控制边界。 - Controller 才是把 API 对象转换为真实代理配置的执行者;对象创建成功不等于数据平面已就绪。
- TLS 负责加密和身份验证,SNI、Host、Path、证书加载和后端协议必须分层验证。
NetworkPolicy负责网络层访问控制,认证、限流、WAF 和业务授权通常属于入口扩展或应用策略。- 从 Ingress 迁移到 Gateway API 的关键不是语法替换,而是盘点现有 Controller 行为、注解和云厂商能力,再逐项验证标准能力与实现扩展的边界。
在生产环境中,最可靠的入口配置不是“YAML 看起来正确”,而是同时满足 API 状态、Controller 状态、TLS 实际证书、Service EndpointSlice、网络连通性和真实请求验证这几个条件。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes DNS:CoreDNS、Service Discovery、Search、缓存和故障
- 下一篇:Kubernetes CNI 网络:Pod IP、路由、Overlay、Underlay 和插件选型
- 延伸:Kubernetes Service:ClusterIP、NodePort、LoadBalancer、EndpointSlice 和流量
- 延伸:Kubernetes 证书与 TLS:集群 PKI、cert-manager、轮换和故障
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论