前置回顾
第 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 rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: api-svc port: number: 80
|
提交后的完整流量链路(串联第 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 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: 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: - hosts: - api.example.com secretName: api-tls-secret rules: - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: api-svc port: number: 80
|
$ kubectl create secret tls api-tls-secret \ --cert=tls.crt \ --key=tls.key
|
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 自动加载新证书
|
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
---
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 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
|
注解是 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 = 各公司的门牌和指示牌(每家公司只管自己的路线).各层职责分离,互不越权.
完整示例
apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: name: nginx spec: controllerName: k8s.nginx.org/nginx-gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: production-gateway namespace: infra spec: gatewayClassName: nginx listeners: - name: http port: 80 protocol: HTTP allowedRoutes: namespaces: from: All - name: https port: 443 protocol: HTTPS tls: mode: Terminate certificateRefs: - name: wildcard-tls allowedRoutes: namespaces: from: Selector selector: matchLabels: gateway-access: "true"
---
apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: api-route namespace: app-team-a spec: parentRefs: - name: production-gateway namespace: infra 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,无需注解)
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 - name: api-v2-svc port: 80 weight: 10
---
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;两者可共存渐进迁移
动手练习
- 安装 ingress-nginx(Helm),创建一个 Ingress 将
test.local 路由到一个 Nginx Deployment + ClusterIP Service.修改 /etc/hosts 后用浏览器验证.
- 在同一个 Ingress 中配置两条路径规则:
/api → api-svc,/ → frontend-svc.用 curl 验证不同路径到达不同后端.
- 配置 TLS:用 openssl 生成自签名证书,创建 tls Secret,启用 HTTPS.验证
curl -k https://test.local 能正常返回.
- 添加 rewrite-target 注解:外部访问
/app/xxx 时,后端收到的路径应为 /xxx.用 kubectl logs 查看后端 Nginx 的 access log 确认路径变化.
- (进阶)安装支持 Gateway API 的 Controller(如 nginx-gateway-fabric 或 Envoy Gateway),创建 GatewayClass → Gateway → HTTPRoute 完整链路.对比 Ingress 写法的差异.
- (进阶)用 HTTPRoute 的
backendRefs.weight 做一个 90/10 金丝雀分流,用脚本发 100 次请求统计命中比例.