第 16 章 · 网络的演进:从 Service 到 Gateway API
一句话结论:K8s 网络抽象(面向「如何把服务暴露出去」)的演进,是一条**「从『只解决最通用场景』到『逐步覆盖更复杂场景』」**的路——Service 的三种类型(ClusterIP/NodePort/LoadBalancer)解决了「内部访问 + 简单对外」,Ingress 解决了「L7 HTTP 路由」,而 Gateway API 则要解决 Ingress **「表达能力不足、各家实现各自加注解导致不可移植」**的根本问题。
说明:这一章讲的是「服务暴露的 API 抽象」怎么演进;而第 7 章讲的是「kube-proxy 的转发实现」怎么演进。两者是「接口」和「实现」两个不同的维度,合起来才是完整的网络演进图景。
1. 当年是什么样:Service 的三种类型
K8s 最早的对外暴露,就靠 Service 的三种 type:
| 类型 | 作用 | 解决的场景 |
|---|---|---|
| ClusterIP | 集群内部访问 | 服务之间互相调用 |
| NodePort | 在每个节点开一个固定端口 | 简单地从集群外访问 |
| LoadBalancer | 调云厂商的负载均衡器 | 生产级的对外暴露 |
# 三种类型的 Service,本质是「同一个抽象,不同程度的对外暴露」
apiVersion: v1
kind: Service
spec:
type: LoadBalancer # ClusterIP / NodePort / LoadBalancer 三选一
selector: { app: web }
ports: [ { port: 80, targetPort: 8080 } ]这套设计好在哪:它用「一个 Service 对象 + 一个 type 字段」,覆盖了从「纯内部」到「生产对外」的完整光谱,简单、够用。
但它的局限在哪:它只能做「基于端口」的简单负载均衡,做不了「基于 URL 路径/域名的路由」——比如「/api 路由到 A 服务、/web 路由到 B 服务、api.foo.com 路由到 C 服务」这种七层(L7)HTTP 路由,Service 做不到。
2. 第一步进化:Ingress(补上「L7 路由」的能力)
于是有了 Ingress——一个专门做「HTTP/HTTPS 七层路由」的抽象:
apiVersion: networking.k8s.io/v1
kind: Ingress
spec:
rules:
- host: api.example.com # 按域名路由
http:
paths:
- path: /v1 # 按路径路由
pathType: Prefix
backend:
service: { name: api, port: { number: 80 } }Ingress 的价值:把「域名 + 路径 → 后端服务」的七层路由规则声明式化了,解决了 Service 做不到的 L7 场景。
但 Ingress 很快就暴露出更深的问题:
- 表达能力太弱:Ingress 的字段极其简单,只能表达「域名 + 路径 → 服务」这种最基础的规则。想做「按请求头路由」「流量权重分配」「超时/重试策略」——没这些字段。
- 导致「注解地狱」(annotation hell):各家 Ingress controller(nginx、traefik、istio…)想实现「Ingress 没有的字段」时,只能往 annotation 里塞自己的私有扩展。结果就是——你的 Ingress 配置,换个 controller 就跑不起来,完全不可移植。
# 「注解地狱」:这些注解都是 nginx-ingress 私有的,换 traefik 全失效
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/rewrite-target: "/"
nginx.ingress.kubernetes.io/affinity: "cookie"这个坑的本质:Ingress 的「官方字段」太少,逼得各家实现去「私有注解」里找补,结果是『标准不标准,各家各说各话』。 这正是「抽象设计不足」的典型症状——接口的「最小公约数」定得太小,导致私有扩展泛滥。
3. 新方案:Gateway API(把「路由能力」重新设计成完整、可移植的抽象)
Gateway API(2023 年 GA)就是来解决 Ingress 这个根本缺陷的。它的核心改进:
- 更丰富的表达能力:引入了 Gateway、HTTPRoute、GRPCRoute 等资源,能表达「按请求头路由、按权重分流、灰度发布、跨命名空间路由」等 Ingress 做不了的事。
- 角色分离:把「基础设施人员」(管 Gateway)和「应用开发者」(管 HTTPRoute)的职责分开了——这反映了真实组织里的分工。
- 可移植性:把「路由」的语义标准化了,不再依赖私有注解,换个实现也能跑。
# Gateway API:HTTPRoute 能表达「按权重分流」,这是 Ingress 做不到的
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
spec:
rules:
- matches:
- path: { type: PathPrefix, value: /v1 }
backendRefs:
- name: api-v2 # 90% 流量到 v2
weight: 90
- name: api-v1 # 10% 流量到 v1(灰度发布)
weight: 10这个演进的本质:它不是在「Ingress 上打补丁」,而是认识到 Ingress 的抽象从一开始就定得太小,于是重新设计一个「完整、标准、可移植」的抽象。这和第 13 章「调度框架替代 extender」是同一类故事——当旧抽象的表达能力成为瓶颈时,正确做法是重新设计抽象,而不是继续堆私有扩展。
4. 这条演进线教给我们什么
-
「抽象定得太小」和「抽象定得太大」一样危险。Ingress 定得太小,逼出了「注解地狱」;如果当初定得太大,又会变成「没人能实现的庞然大物」。抽象的粒度,要刚好覆盖「真实的主流需求」,并给「演进」留出标准化的通道(而不是逼用户走私有注解)。
-
「标准」的价值,在于「可移植」。注解地狱的根源,是「没有标准、各搞各的」。Gateway API 的价值不在于「字段更多」,而在于「字段是标准的、跨实现一致的」——这才是它相比 Ingress 最本质的进步。
-
一个信号:当「私有注解」开始泛滥,说明「标准抽象」该升级了。 如果你设计的 API 被用户用大量「私有 hack」绕过,那不是用户的错,是你的抽象没覆盖真实需求。这时应该做的是「升级抽象」,而不是「教育用户别用注解」。
5. 思维流重放:为什么答案不是「给 Ingress 加字段」
上面讲了 Ingress 的「注解地狱」和 Gateway API 的方案,这一节重走「面对注解地狱,为什么正确的解法是重新设计抽象,而不是给 Ingress 打补丁」这个思考转折。
第 1 步 · 直觉:Ingress 表达能力弱,用户要「按请求头路由」「权重分流」做不了,于是各家往注解里塞私有扩展。最直接的想法——给 Ingress 加字段,把大家常用的注解「收编」成官方字段,问题不就解决了?
第 2 步 · 推演:但「注解地狱」真的是「字段不够多」吗?——不是。根因是角色没分开:Ingress 把一个对象既当「基础设施团队定义网关」、又当「应用团队声明路由」来用,两个团队的关注点拧在一个对象里,谁都想往里塞自己的东西。字段加得再多,角色不分,还是会乱。
第 3 步 · 反转:正确的解法是角色分离——把「网关」(Gateway,基础设施团队管)和「路由」(HTTPRoute,应用团队管)拆成两个独立对象。这样每个团队只声明自己那部分,互不干扰,扩展也有了各自的「家」。
第 4 步 · 权衡代价:Gateway API 比 Ingress 复杂(多对象、多角色、要理解分工),学习成本更高。值不值?——这和第 13 章「重新设计抽象而非堆补丁」是同一个判断:当旧抽象的表达能力成为瓶颈,且补丁(注解)已经泛滥成灾,继续打补丁的边际成本,已经超过重新设计的成本。 所以重做,是对的。
第 5 步 · 收敛:选 Gateway API。而更关键的是它的落地方式——不废弃 Ingress,让新旧长期并存、由生态提供过渡,让用户「因为新东西更好而主动迁移」,而不是「因为旧东西被杀而被迫迁移」。
一句话复盘:这一章最值钱的不是 Gateway API 本身,而是判断「该打补丁还是该重做」的那个信号——当「私有注解/私有 hack」开始泛滥,说明你的抽象没覆盖真实需求,这时该升级抽象,而不是教育用户别用 hack。
思考题
Q:Gateway API 更强大、更标准,但为什么 K8s 没有「直接废弃 Ingress」?反而是 Ingress 和 Gateway API 长期并存?这反映了一个什么演进原则?
参考答案:
因为 Ingress 有海量的存量用户和成熟的实现生态,直接废弃会造成巨大的迁移阵痛。K8s 的做法是「新抽象并行推出,旧抽象逐步淡出,中间由生态提供『过渡层』」:
- Gateway API 和 Ingress 长期并存,各自有各自的实现。
- 很多 Gateway API 实现(比如一些 ingress controller)同时支持 Ingress 和 Gateway API 两种资源,作为过渡。
- Ingress 并没有「被废弃」,而是「不再新增能力」,新场景引导用户走向 Gateway API。
这反映了一个重要的演进原则:「新事物取代旧事物」,正确的姿势不是「宣布旧的死亡」,而是「让新的足够好,让旧的自然淡出」。
- 直接废弃 = 逼用户在「还没准备好」时被迫迁移,招致强烈抵制(甚至拖慢新东西的采纳)。
- 并行推出 + 逐步淡出 = 让用户在「自己觉得划算」的时候主动迁移,新旧之间有一个平滑的、由市场(用户)决定节奏的过渡期。
核心心法:演进的节奏,应该由「新方案的价值」来驱动用户迁移,而不是由「旧方案的强制死刑」来逼迫用户迁移。 好系统让用户「因为新东西更好而离开旧的」,而不是「因为旧东西被杀掉而被迫离开」。这也是 K8s 在整个十年里,反复用的「温和演进」哲学(对照第 14 章 CSI 迁移、第 15 章 API 弃用的「给足缓冲期」)。