阶段一 · Kubernetes 核心

Ingress 与 Gateway API

前置回顾

第 04 篇解决了"Pod IP 不稳定"的问题: Service 给一组 Pod 分配稳定的 ClusterIP + DNS + 负载均衡.
但也留下了两个未解决的痛点:

  • LoadBalancer 类型每个 Service 独占一个云 LB(20 个微服务 = 20 个 LB,成本爆炸)
  • Service 只工作在四层(TCP/UDP),无法根据域名,URL 路径做路由.

本篇的 Ingress 和 Gateway API 就是解决"如何用一个入口承载所有七层路由"的问题.

从 Service 到 Ingress:四层不够用

先看一个典型的生产场景:

有 3 个微服务需要对外暴露:
- api.example.com → API 服务
- app.example.com → 前端 SPA
- admin.example.com → 管理后台

方案 A:每个服务一个 LoadBalancer Service
→ 3 个云 LB,3 个公网 IP,3 份费用
→ 每个 LB 只做 TCP 转发,TLS 证书要分别配
→ 无法做基于路径的路由(/api/* → 后端 A,/static/* → CDN)

方案 B:一个 Ingress Controller + Ingress 规则
→ 1 个云 LB(Ingress Controller 本身用 LoadBalancer Service 暴露)
→ 所有七层路由规则集中在 Ingress 对象中
→ 统一 TLS 终结,域名路由,路径路由,限流,重写
→ 成本 = 1 个 LB + 1 组 Ingress Controller Pod

Service = 每栋楼各自的门禁: 只能刷卡进自己的楼.
Ingress = 园区大门的保安亭: 来访者报出要去哪栋楼(域名),几楼(路径),保安亭统一登记放行,指路.所有外部流量只经过一个入口,保安亭按规则分发.

Ingress 架构:三层分工

Ingress 体系由三个部分组成:

┌──────────────────────────────────────────────────────────┐
│ 互联网流量 │
└────────────────────────────┬─────────────────────────────┘

┌──────────────────────────────────────────────────────────┐
│ ① Ingress Controller(实际处理流量的反向代理 Pod) │
│ 如 Nginx Ingress / Traefik / Envoy / HAProxy │
│ 本身通过一个 LoadBalancer Service 暴露到公网 │
└────────────────────────────┬─────────────────────────────┘
↓ watch
┌──────────────────────────────────────────────────────────┐
│ ② Ingress 对象(路由规则声明) │
│ 定义:域名 + 路径 → 转发到哪个 Service │
│ 是纯粹的 API 对象,不处理流量 │
└────────────────────────────┬─────────────────────────────┘
↓ 转发到
┌──────────────────────────────────────────────────────────┐
│ ③ 后端 Service(ClusterIP)→ Pod │
│ Ingress Controller 把请求转发到 Service 的 ClusterIP │
│ Service 再负载均衡到后端 Pod │
└──────────────────────────────────────────────────────────┘

关键理解:Ingress 对象只是路由规则的"声明",不处理任何流量.
真正干活的是 Ingress Controller: 一个运行在集群内的反向代理进程(通常是 Nginx 或 Envoy),它 watch Ingress 对象的变化,动态生成自己的路由配置(如 nginx.conf),然后按规则转发请求.

Ingress Controller 不是 K8s 自带的

创建 Ingress 对象但集群里没有 Ingress Controller,什么都不会发生: 没人 watch 它,没人执行规则.
K8s 只提供 Ingress API 的定义,实现由第三方提供.常见选择:ingress-nginx(官方维护的 Nginx 方案),Traefik,Kong,Envoy Gateway 等.
安装 Controller 通常是 Helm 一条命令的事.

Ingress 对象详解

基础示例

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
ingressClassName: nginx # 指定由哪个 Controller 处理(集群可装多个)
rules:
- host: api.example.com # 匹配域名
http:
paths:
- path: / # 匹配路径前缀
pathType: Prefix
backend:
service:
name: api-svc # 转发到的 Service 名
port:
number: 80 # Service 的 port(不是 targetPort)

提交后的完整流量链路(串联第 04 篇的 Service 知识):

用户浏览器 → https://api.example.com/users
↓ DNS 解析到云 LB 的公网 IP
云 LB → Ingress Controller Pod(Nginx)
↓ 匹配 Ingress 规则:host=api.example.com, path=/users → api-svc:80
Ingress Controller → api-svc(ClusterIP: 10.96.0.50:80)
↓ kube-proxy iptables DNAT
api Pod(10.244.1.5:8080)处理请求

基于域名的路由

spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-svc
port:
number: 80
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-svc
port:
number: 80

三个域名,三个后端服务,但只经过同一个 Ingress Controller(同一个 LB).

基于路径的路由

spec:
rules:
- host: example.com
http:
paths:
- path: /api
pathType: Prefix # /api,/api/users,/api/orders 都匹配
backend:
service:
name: api-svc
port:
number: 80
- path: /static
pathType: Prefix
backend:
service:
name: cdn-svc
port:
number: 80
- path: /
pathType: Prefix # 兜底:其他路径走前端
backend:
service:
name: frontend-svc
port:
number: 80

pathType 的三种取值

  • Prefix:按 / 分隔的路径前缀匹配./api 匹配 /api,/api/,/api/users,但不匹配 /api2.
  • Exact:严格完全匹配,/api 只匹配 /api,不匹配 /api/.
  • ImplementationSpecific:由 Controller 自行决定匹配语义(不推荐,移植性差).

默认后端

当请求不匹配任何 rule 时,可以配置一个兜底:

spec:
defaultBackend: # 不匹配任何 host/path 时的兜底
service:
name: default-404-svc
port:
number: 80
rules:
- host: api.example.com
...

对象 与 Controller 的关系

到这里你可能注意到一个模式, Deployment, Service, Ingress 的 YAML 定义本质上都是同一回事: 一条存储在 etcd 中的 JSON 记录(你提交的 YAML 被 API Server 转为 JSON 写入 etcd).
对象本身不执行任何逻辑,只是"期望状态的声明".真正干活的是 Controller: 一个持续运行的进程,通过 watch API Server 获取对象变化,然后执行动作使实际状态趋近期望状态.

需要注意的是,有些 Controller 是集群内置的,有些需要你额外部署.

对象 Controller 部署形态
Service → Endpoints Endpoints Controller 内置(kube-controller-manager 内的 goroutine)
Service → iptables 规则 kube-proxy 内置(DaemonSet,每节点一个 Pod)
Deployment → ReplicaSet Deployment Controller 内置(kube-controller-manager)
Ingress → Nginx 配置 Ingress Controller 需额外安装(Deployment,2-3 副本)
Gateway → 路由规则 Gateway Controller 需额外安装(Deployment)

所有 Controller 的运行逻辑完全一致:连接 API Server → 订阅某类对象变化 → 收到事件 → 执行动作 → 循环.
无论是 kube-controller-manager 里的一个 goroutine,还是 ingress-nginx 的一个独立 Pod,干的都是这件事.区别只是"谁帮你启动了这个进程"(内置的由集群安装工具自动部署,扩展的由你 Helm install 或 kubectl apply).

没有 Controller 的对象只是一条死记录

这也是为什么"创建 Ingress 对象但没装 Ingress Controller,什么都不会发生": 没有进程 watch 它,这条 etcd 记录就静静躺在那里,不产生任何实际效果.
同理,如果 kube-proxy 挂了,Service 的 ClusterIP 也无法转发流量: 对象还在,但没人执行规则.

TLS 终结

Ingress 支持在入口层做 HTTPS 终结: 客户端到 Ingress Controller 之间走 HTTPS,Controller 到后端 Service 之间走 HTTP(集群内部网络,通常不需要加密).

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
spec:
ingressClassName: nginx
tls: # TLS 配置
- hosts:
- api.example.com # 对哪些域名启用 HTTPS
secretName: api-tls-secret # 证书存在哪个 Secret 中
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
# 证书 Secret 的创建方式
# 方式一:手动创建(已有证书文件)
$ kubectl create secret tls api-tls-secret \
--cert=tls.crt \
--key=tls.key

# 方式二:cert-manager 自动签发(推荐)
# cert-manager watch Ingress 的 tls 配置,自动向 Let's Encrypt 申请证书
# 并创建对应的 Secret,到期自动续签

cert-manager 自动证书管理

手动管理证书在服务多的时候不现实.cert-manager 是 K8s 生态中证书管理的事实标准:

工作流程:
① 安装 cert-manager(Helm 一条命令)
② 创建 ClusterIssuer(告诉 cert-manager 去哪申请证书)
③ Ingress 加一个 annotation → cert-manager 自动完成剩下的事

cert-manager 的控制循环:
watch Ingress with annotation "cert-manager.io/cluster-issuer"
→ 检查 tls.secretName 对应的 Secret 是否存在且有效
→ 不存在或即将过期 → 向 Let's Encrypt 发起 ACME 挑战
→ 颁发证书 → 创建/更新 Secret
→ Ingress Controller 自动加载新证书
# ClusterIssuer:全集群共用的证书签发器
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx

---
# Ingress 加一个 annotation 即可
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auto-tls-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod # ← 触发自动签发
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: api-tls-auto # cert-manager 会自动创建这个 Secret
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80

生产最佳实践

始终使用 cert-manager + Let's Encrypt 自动管理证书.
手动管理证书的两大风险:忘记续签导致服务中断,证书文件泄露.cert-manager 全自动处理申请,续签,Secret 创建,证书过期前 30 天自动续签.

Nginx Ingress 常用注解

Ingress 的标准 API 字段有限(只定义了域名,路径,后端).更细粒度的行为通过 annotations 传递给具体的 Controller 实现.以最常用的 ingress-nginx 为例:

注解 作用 示例值
nginx.ingress.kubernetes.io/rewrite-target 重写转发路径 /$2
nginx.ingress.kubernetes.io/ssl-redirect HTTP 自动跳转 HTTPS "true"
nginx.ingress.kubernetes.io/proxy-body-size 请求体大小限制 "50m"
nginx.ingress.kubernetes.io/proxy-connect-timeout 连接后端超时 "60"
nginx.ingress.kubernetes.io/limit-rps 每秒请求限流 "100"
nginx.ingress.kubernetes.io/affinity 会话亲和 "cookie"
nginx.ingress.kubernetes.io/cors-allow-origin CORS 跨域 "https://app.example.com"

路径重写示例

常见需求:外部路径带前缀 /api/v1/users,但后端服务只认 /users.

metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
rules:
- host: example.com
http:
paths:
- path: /api(/|$)(.*) # 正则捕获组
pathType: ImplementationSpecific
backend:
service:
name: api-svc
port:
number: 80

# 效果:
# 请求 /api/v1/users → 转发到 api-svc,路径变为 /v1/users
# 请求 /api/health → 转发到 api-svc,路径变为 /health

注解是 Controller 特有的,不可移植

nginx.ingress.kubernetes.io/ 开头的注解只有 ingress-nginx 认识.换成 Traefik 或 Kong,同样的功能需要不同的注解甚至不同的 CRD.
这正是 Ingress API 被诟病的地方--标准部分太少,高级功能全靠非标准注解.Gateway API 就是为了解决这个问题.

Ingress API 的局限

Ingress 从 Kubernetes 1.1 引入,设计于 2015 年.随着服务网格,多租户,TCP/UDP 路由等需求涌现,它的局限越来越明显:

局限 具体表现
只支持 HTTP/HTTPS TCP/UDP/gRPC 路由无法用标准 Ingress 表达
高级功能依赖注解 限流,重写,重试,镜像等全靠非标准 annotation,跨 Controller 不可移植
无角色分离 集群管理员和应用开发者在同一个对象上操作--谁能改 TLS?谁能加路由?无法细分权限
无跨 namespace 路由 Ingress 只能引用同 namespace 的 Service(除非 Controller 自己扩展)
单一 Gateway 绑定 Ingress 通过 ingressClassName 绑定 Controller,但无法表达"共享同一个 IP 但不同 listener"

这些问题催生了 Gateway API--Kubernetes 官方设计的下一代流量路由标准.

Gateway API - 下一代路由标准

Gateway API 不是"另一种 Ingress",而是对流量路由做了根本性的架构重新设计.
核心思想:将"基础设施层"和"应用层"的关注点分离到不同的 API 对象中.

三层模型

┌────────────────┐
│ 基础设施层(集群管理员) │
├────────────────┤
│ • GatewayClass │
└────────────────┘
┌─────────────┐
│ 平台层(平台团队) │
├─────────────┤
│ • Gateway │
└─────────────┘
┌───────────────────────────────────┐
│ 应用层(开发者) │
├───────────────────────────────────┤
│ • HTTPRoute │
│ • TCPRoute / GRPCRoute / TLSRoute │
└───────────────────────────────────┘
层次 API 对象 管理者 职责
基础设施层 GatewayClass 集群管理员 / 云提供商 定义用什么 Controller 实现(类似 StorageClass 定义存储后端)
平台层 Gateway 平台工程师 声明监听端口,协议,TLS 证书,允许哪些 namespace 的 Route 绑定
应用层 HTTPRoute / GRPCRoute / TCPRoute / TLSRoute 应用开发者 声明路由规则:域名 + 路径 → 后端 Service
  • Ingress 就像一张平面的白纸: 管理员和开发者在同一张纸上写所有东西.
  • Gateway API 像一栋办公楼:GatewayClass = 建筑设计图纸(定义结构),Gateway = 物业管理(管大门,电梯,安防),HTTPRoute = 各公司的门牌和指示牌(每家公司只管自己的路线).各层职责分离,互不越权.

完整示例

# 1. GatewayClass - 集群管理员创建(通常由 Controller 安装时自动创建)
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: nginx # 起个名字
spec:
controllerName: k8s.nginx.org/nginx-gateway-controller

---
# 2. Gateway - 平台团队创建
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: infra # 可以放在独立的 namespace
spec:
gatewayClassName: nginx # 引用 GatewayClass
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All # 允许所有 namespace 的 Route 绑定
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: wildcard-tls # 通配符证书 Secret
allowedRoutes:
namespaces:
from: Selector
selector:
matchLabels:
gateway-access: "true" # 只有带此标签的 ns 可绑定 HTTPS

---
# 3. HTTPRoute - 应用开发者创建(在自己的 namespace)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: app-team-a # 应用团队自己的 namespace
spec:
parentRefs:
- name: production-gateway
namespace: infra # 绑定到哪个 Gateway
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /v1
backendRefs:
- name: api-v1-svc
port: 80
- matches:
- path:
type: PathPrefix
value: /v2
backendRefs:
- name: api-v2-svc
port: 80

Gateway API vs Ingress 对比

维度 Ingress Gateway API
协议支持 仅 HTTP/HTTPS HTTP / HTTPS / gRPC / TCP / TLS / UDP
角色分离 无--所有配置在一个对象 三层分离:GatewayClass / Gateway / Route
跨 namespace 不支持(标准 API) 原生支持:Route 可绑定其他 ns 的 Gateway
高级路由 依赖非标准注解 标准 API 字段:Header 匹配,权重分流,请求镜像等
可移植性 基础规则可移植,注解不可移植 核心功能全标准化,Controller 间可移植
成熟度 GA(稳定,生态完善) v1.0 GA(2023.10),生态快速追赶中
适用场景 简单的域名/路径路由足够 多团队多租户,TCP/gRPC,需要流量治理

高级路由能力(标准 API,无需注解)

# 基于 Header 匹配
rules:
- matches:
- headers:
- name: x-canary
value: "true"
backendRefs:
- name: api-canary-svc
port: 80

---
# 权重分流(金丝雀发布)
rules:
- backendRefs:
- name: api-v1-svc
port: 80
weight: 90 # 90% 流量走 v1
- name: api-v2-svc
port: 80
weight: 10 # 10% 流量走 v2

---
# 请求重定向
rules:
- matches:
- path:
type: PathPrefix
value: /old-api
filters:
- type: RequestRedirect
requestRedirect:
path:
type: ReplaceFullPath
replaceFullPath: /v2/api
statusCode: 301

---
# 请求头修改
rules:
- filters:
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Forwarded-Proto
value: https
remove:
- X-Internal-Only
backendRefs:
- name: api-svc
port: 80

金丝雀发布不再需要注解

Ingress 做金丝雀发布(如 ingress-nginx 的 canary-weight 注解)是非标准的: 换个 Controller 就不认了.
Gateway API 的 backendRefs.weight 是标准字段,所有实现了 Gateway API 的 Controller 都支持.

Ingress vs Gateway API:如何选择

选择决策树:

场景简单?(几个域名 + 路径路由 + TLS)

├── 是 → Ingress 完全够用,生态成熟,文档丰富

└── 否 → 考虑 Gateway API:
├── 需要 TCP/gRPC 路由? → Gateway API
├── 多团队共享入口需要权限隔离? → Gateway API
├── 需要标准化的金丝雀/流量分流? → Gateway API
├── 想避免注解锁定特定 Controller? → Gateway API
└── 现有 Controller 不支持 Gateway API? → 暂用 Ingress

两者可以共存

同一个集群可以同时使用 Ingress 和 Gateway API: 它们是独立的 API 对象,不冲突.可以逐步迁移:新服务用 Gateway API,旧服务继续用 Ingress,直到全部切换完成.

完整流量链路回顾

将 04 篇(Service)和本篇(Ingress/Gateway)串联,从外部请求到达 Pod 的完整链路:

[用户浏览器] → [云 LB<br/>(公网 IP)] → [Ingress Controller<br/>(Nginx Pod)] → [ClusterIP Service] → [应用 Pod]
逐步拆解:

① 用户浏览器 → DNS → 云 LB 公网 IP
DNS 将 api.example.com 解析到云 LB 的外部 IP

② 云 LB → Ingress Controller Pod
LB 健康检查通过的节点 → NodePort → Ingress Controller Pod
(Ingress Controller 本身通过 LoadBalancer Service 暴露)

③ Ingress Controller 内部
TLS 终结(解密 HTTPS → 明文 HTTP)
匹配 Ingress/HTTPRoute 规则:host + path → 确定后端 Service
转发请求到 Service 的 ClusterIP:Port

④ Service(ClusterIP)→ Pod
kube-proxy 的 iptables 拦截到 ClusterIP 的报文
DNAT 到某个后端 Pod IP:targetPort
Pod 处理请求,响应原路返回

与第 04 篇的对接点

Ingress Controller 本身就是一个 Deployment + LoadBalancer Service 的组合(第 03,04 篇的知识).它转发请求到后端时用的是 ClusterIP Service(第 04 篇).
所以 Ingress 不是替代 Service,而是建立在 Service 之上的七层路由层.

小结

快速回顾

  • 为什么需要 Ingress:Service 只做四层转发,无法按域名/路径路由;LoadBalancer 每个 Service 占一个 LB 成本高.Ingress 用一个入口承载所有七层路由
  • 三层架构:Ingress Controller(实际干活的反向代理)+ Ingress 对象(路由规则声明)+ 后端 Service
  • 路由能力:基于域名,路径(Prefix/Exact),默认后端
  • TLS 终结:Ingress 层解密 HTTPS,后端走 HTTP;cert-manager 自动签发/续签 Let's Encrypt 证书
  • 注解:高级功能(重写,限流,CORS)通过 Controller 特有注解实现,不可跨 Controller 移植
  • Gateway API 三层模型:GatewayClass(基础设施)→ Gateway(平台)→ HTTPRoute/GRPCRoute/TCPRoute(应用),角色分离 + 标准化
  • Gateway API 优势:多协议支持,跨 namespace,标准化权重分流/Header 匹配,可移植性强
  • 选型建议:简单场景用 Ingress 足够;多团队/多协议/需要流量治理用 Gateway API;两者可共存渐进迁移

动手练习

  1. 安装 ingress-nginx(Helm),创建一个 Ingress 将 test.local 路由到一个 Nginx Deployment + ClusterIP Service.修改 /etc/hosts 后用浏览器验证.
  2. 在同一个 Ingress 中配置两条路径规则:/api → api-svc,/ → frontend-svc.用 curl 验证不同路径到达不同后端.
  3. 配置 TLS:用 openssl 生成自签名证书,创建 tls Secret,启用 HTTPS.验证 curl -k https://test.local 能正常返回.
  4. 添加 rewrite-target 注解:外部访问 /app/xxx 时,后端收到的路径应为 /xxx.用 kubectl logs 查看后端 Nginx 的 access log 确认路径变化.
  5. (进阶)安装支持 Gateway API 的 Controller(如 nginx-gateway-fabric 或 Envoy Gateway),创建 GatewayClass → Gateway → HTTPRoute 完整链路.对比 Ingress 写法的差异.
  6. (进阶)用 HTTPRoute 的 backendRefs.weight 做一个 90/10 金丝雀分流,用脚本发 100 次请求统计命中比例.