微服务与 K8s 协作的完整运行全流程(从传统到云原生)
- [一、先从"纯微服务时代"说起(没有 K8s)](#一、先从“纯微服务时代”说起(没有 K8s))
-
- [1.1 服务是如何"存在"的:Nacos(注册中心)](#1.1 服务是如何“存在”的:Nacos(注册中心))
- [1.2 请求是如何进来的:Spring Cloud Gateway(网关)](#1.2 请求是如何进来的:Spring Cloud Gateway(网关))
- [1.3 服务是如何被调用的:Feign(服务调用)](#1.3 服务是如何被调用的:Feign(服务调用))
- [1.4 到底调哪一个实例:Ribbon(客户端负载均衡)](#1.4 到底调哪一个实例:Ribbon(客户端负载均衡))
- [1.5 服务不稳定怎么办:Sentinel / Hystrix(自我保护)](#1.5 服务不稳定怎么办:Sentinel / Hystrix(自我保护))
- [1.6 把整条链路串起来(纯微服务完整运行图)](#1.6 把整条链路串起来(纯微服务完整运行图))
- [1.7 一句话总结"纯微服务时代"的本质](#1.7 一句话总结“纯微服务时代”的本质)
- 二、微服务"单独使用"时的完整逻辑链
-
- [2.1 流量如何进来:Gateway(网关)](#2.1 流量如何进来:Gateway(网关))
-
- [2.1.1 为什么所有请求必须先到网关](#2.1.1 为什么所有请求必须先到网关)
- [2.1.2 一个非常重要的设计含义](#2.1.2 一个非常重要的设计含义)
- [2.2 服务如何被发现:Nacos(注册中心)](#2.2 服务如何被发现:Nacos(注册中心))
-
- [2.2.1 服务启动时发生了什么](#2.2.1 服务启动时发生了什么)
- [2.2.2 注册中心真正解决的是什么问题](#2.2.2 注册中心真正解决的是什么问题)
- [2.3 服务如何调用:Feign + Ribbon](#2.3 服务如何调用:Feign + Ribbon)
-
- [2.3.1 Feign:让调用"像本地方法"](#2.3.1 Feign:让调用“像本地方法”)
- [2.3.2 Ribbon:真正决定"打到谁"](#2.3.2 Ribbon:真正决定“打到谁”)
- [2.4 如何避免系统被拖垮:Sentinel / Hystrix](#2.4 如何避免系统被拖垮:Sentinel / Hystrix)
-
- [2.4.1 问题的根源是什么](#2.4.1 问题的根源是什么)
- [2.4.2 应用如何"自救"](#2.4.2 应用如何“自救”)
- [2.5 把整条调用链完整串起来](#2.5 把整条调用链完整串起来)
- [2.6 传统微服务架构的本质特征(核心结论)](#2.6 传统微服务架构的本质特征(核心结论))
- 三、问题从哪里开始暴露?------规模一上来,一切都会变
-
- [3.1 实例频繁上下线,注册信息开始变得"不可信"](#3.1 实例频繁上下线,注册信息开始变得“不可信”)
-
- [3.1.1 规模变化带来的第一个冲击](#3.1.1 规模变化带来的第一个冲击)
- [3.1.2 典型现象](#3.1.2 典型现象)
- [3.2 心跳与注册中心压力,开始指数级放大](#3.2 心跳与注册中心压力,开始指数级放大)
-
- [3.2.1 小规模时不明显的问题](#3.2.1 小规模时不明显的问题)
- [3.2.2 问题是如何被放大的](#3.2.2 问题是如何被放大的)
- [3.3 应用被迫关心过多"非业务问题"](#3.3 应用被迫关心过多“非业务问题”)
-
- [3.3.1 应用职责开始失控](#3.3.1 应用职责开始失控)
- [3.3.2 一个典型场景](#3.3.2 一个典型场景)
- [3.4 部署、扩容、迁移,开始严重依赖人工经验](#3.4 部署、扩容、迁移,开始严重依赖人工经验)
-
- [3.4.1 "能跑"不等于"好管"](#3.4.1 “能跑”不等于“好管”)
- [3.4.2 系统规模越大,风险越集中](#3.4.2 系统规模越大,风险越集中)
- [3.5 问题的根因是什么?(非常关键)](#3.5 问题的根因是什么?(非常关键))
- [3.6 为什么此时引入 K8s 是"必然选择"](#3.6 为什么此时引入 K8s 是“必然选择”)
- [四、引入 K8s 后,系统发生了什么根本变化?](#四、引入 K8s 后,系统发生了什么根本变化?)
-
- [4.1 从"应用自理"到"平台托管"的根本转变](#4.1 从“应用自理”到“平台托管”的根本转变)
- [4.2 服务是如何"存在"的:从注册中心 → Pod 生命周期](#4.2 服务是如何“存在”的:从注册中心 → Pod 生命周期)
-
- [4.2.1 纯微服务时代的"存在方式"](#4.2.1 纯微服务时代的“存在方式”)
- [4.2.2 K8s 时代的"存在方式"](#4.2.2 K8s 时代的“存在方式”)
- [4.3 实例感知方式的变化:从"我去找你"到"平台给你一条路"](#4.3 实例感知方式的变化:从“我去找你”到“平台给你一条路”)
-
- [4.3.1 之前:应用必须"知道所有实例"](#4.3.1 之前:应用必须“知道所有实例”)
- [4.3.2 现在:应用只面对 Service](#4.3.2 现在:应用只面对 Service)
- [4.4 负载均衡发生了"位置迁移"](#4.4 负载均衡发生了“位置迁移”)
-
- [4.4.1 以前:负载均衡在应用内部](#4.4.1 以前:负载均衡在应用内部)
- [4.4.2 现在:负载均衡下沉到平台层](#4.4.2 现在:负载均衡下沉到平台层)
- [4.5 故障判断权的转移:从"我感觉不行了"到"平台说你不行了"](#4.5 故障判断权的转移:从“我感觉不行了”到“平台说你不行了”)
-
- [4.5.1 应用视角的局限性](#4.5.1 应用视角的局限性)
- [4.5.2 K8s 的全局视角](#4.5.2 K8s 的全局视角)
- [4.6 微服务框架的角色变化(非常关键)](#4.6 微服务框架的角色变化(非常关键))
-
- [4.6.1 微服务框架"退回本职工作"](#4.6.1 微服务框架“退回本职工作”)
- [4.7 一张"前后对照"的核心变化表(强记忆点)](#4.7 一张“前后对照”的核心变化表(强记忆点))
- [4.8 总结](#4.8 总结)
- [五、K8s 介入后,各组件如何重新分工](#五、K8s 介入后,各组件如何重新分工)
-
- [5.1 流量入口:Ingress + Gateway 的分层协作](#5.1 流量入口:Ingress + Gateway 的分层协作)
-
- [5.1.1 K8s 时代的完整入口路径](#5.1.1 K8s 时代的完整入口路径)
- [5.1.2 Ingress:集群级流量入口](#5.1.2 Ingress:集群级流量入口)
- [5.1.3 Gateway:应用级流量治理中心](#5.1.3 Gateway:应用级流量治理中心)
- [5.2 服务发现:Service + kube-proxy 全面接管](#5.2 服务发现:Service + kube-proxy 全面接管)
-
- [5.2.1 为什么原来的服务发现模式行不通了](#5.2.1 为什么原来的服务发现模式行不通了)
- [5.2.2 Service:稳定抽象的核心](#5.2.2 Service:稳定抽象的核心)
- [5.2.3 kube-proxy:真正的"流量分发者"](#5.2.3 kube-proxy:真正的“流量分发者”)
- [5.3 那 Nacos 还要不要?------角色必须收缩](#5.3 那 Nacos 还要不要?——角色必须收缩)
-
- [5.3.1 结论先行(非常重要)](#5.3.1 结论先行(非常重要))
- [5.3.2 为什么不能"双注册"](#5.3.2 为什么不能“双注册”)
- [5.4 Feign / Ribbon:从"决策者"退回"调用工具"](#5.4 Feign / Ribbon:从“决策者”退回“调用工具”)
-
- [5.4.1 Feign:依然有价值](#5.4.1 Feign:依然有价值)
- [5.4.2 Ribbon:角色发生本质变化](#5.4.2 Ribbon:角色发生本质变化)
- [5.5 Sentinel / Hystrix:依然不可或缺,但边界更清楚了](#5.5 Sentinel / Hystrix:依然不可或缺,但边界更清楚了)
-
- [5.5.1 K8s 能解决什么,不能解决什么](#5.5.1 K8s 能解决什么,不能解决什么)
- [5.5.2 微服务治理框架的真实定位](#5.5.2 微服务治理框架的真实定位)
- [5.6 把新的分工体系一口气串起来](#5.6 把新的分工体系一口气串起来)
- [5.7 总结](#5.7 总结)
- 六、一个"推荐的完整协作流程"
-
- [6.1 推荐的请求全流程(先给结论)](#6.1 推荐的请求全流程(先给结论))
- [6.2 Ingress:把"外部世界"与"集群内部"彻底隔离](#6.2 Ingress:把“外部世界”与“集群内部”彻底隔离)
-
- [6.2.1 Ingress 解决的不是业务,而是"边界问题"](#6.2.1 Ingress 解决的不是业务,而是“边界问题”)
- [6.2.2 为什么不让 Gateway 直接暴露到公网](#6.2.2 为什么不让 Gateway 直接暴露到公网)
- [6.3 Gateway:真正的"业务入口中枢"](#6.3 Gateway:真正的“业务入口中枢”)
-
- [6.3.1 Gateway 在 K8s 中的定位更清晰了](#6.3.1 Gateway 在 K8s 中的定位更清晰了)
- [6.3.2 一个典型例子](#6.3.2 一个典型例子)
- [6.4 Service:整个体系中最容易被低估的一层](#6.4 Service:整个体系中最容易被低估的一层)
-
- [6.4.1 Service 是"服务发现"的最终形态](#6.4.1 Service 是“服务发现”的最终形态)
- [6.4.2 为什么 Service 是微服务的"地基"](#6.4.2 为什么 Service 是微服务的“地基”)
- [6.5 kube-proxy:负载均衡真正发生的地方](#6.5 kube-proxy:负载均衡真正发生的地方)
-
- [6.5.1 kube-proxy 在做什么](#6.5.1 kube-proxy 在做什么)
- [6.5.2 一个关键变化(必须理解)](#6.5.2 一个关键变化(必须理解))
- [6.6 Pod:业务真正发生的地方](#6.6 Pod:业务真正发生的地方)
-
- [6.6.1 Pod 不再需要"证明自己活着"](#6.6.1 Pod 不再需要“证明自己活着”)
- [6.7 Sentinel:最后一道"业务级安全网"](#6.7 Sentinel:最后一道“业务级安全网”)
-
- [6.7.1 为什么 K8s 之后仍然需要 Sentinel](#6.7.1 为什么 K8s 之后仍然需要 Sentinel)
- [6.8 Controller:真正的"兜底之王"](#6.8 Controller:真正的“兜底之王”)
-
- [6.8.1 Controller 干的不是"救请求",而是"救系统"](#6.8.1 Controller 干的不是“救请求”,而是“救系统”)
- [6.9 把整条"推荐协作链路"一次记住](#6.9 把整条“推荐协作链路”一次记住)
- [6.10 总结](#6.10 总结)
- 七、服务不可用时,谁在兜底?
-
- [7.1 分层兜底原则](#7.1 分层兜底原则)
- [7.2 场景串联示例](#7.2 场景串联示例)
- 八、最终总结
-
- [8.1 核心理解:职责下沉 + 分层清晰](#8.1 核心理解:职责下沉 + 分层清晰)
- [8.2 功能性变化的组件总结](#8.2 功能性变化的组件总结)
- [8.3 为什么这些改变重要?](#8.3 为什么这些改变重要?)
- [8.4 场景串联记忆法](#8.4 场景串联记忆法)
- [8.5 本章一句话高度抽象](#8.5 本章一句话高度抽象)
一、先从"纯微服务时代"说起(没有 K8s)
在不使用 Kubernetes 的情况下,
微服务并不是"天生就有平台托管"的,而是完全靠应用自己把一切撑起来。
我们先看一套最经典、最具代表性的组合(很多系统今天仍在使用):
- Nacos(注册中心 / 配置中心)
- Spring Cloud Gateway(网关)
- Ribbon(客户端负载均衡)
- Feign(服务调用)
- Sentinel(限流 / 熔断)
- Hystrix(容错 / 降级,旧体系)
这套体系有一个非常鲜明的特点:
它是一套"以应用为中心"的架构
什么意思?
一句话概括就是:
服务不仅要"干业务",还要"操心自己怎么活、怎么找别人、怎么扛风险"。
下面我们把这套体系从底层逻辑拆开讲清楚。
1.1 服务是如何"存在"的:Nacos(注册中心)
在纯微服务时代,一个服务要想被别人调用,第一步不是写接口,而是"报到"。
一个服务启动时,内部会做这样几件事:
-
启动 JVM / 进程
-
读取自身配置(端口、服务名、环境)
-
向 Nacos 注册:
- 服务名
- IP
- Port
-
定期发送心跳,证明"我还活着"
于是,Nacos 里保存着类似这样的信息:
text
order-service:
- 10.0.1.12:8080
- 10.0.1.15:8080
可以这样理解:
Nacos 是"服务通讯录 + 存活证明中心"
⚠️ 这里有一个非常重要的隐含点(后面和 K8s 对比会用到):
实例 IP 和端口,是"真实存在、必须准确"的
1.2 请求是如何进来的:Spring Cloud Gateway(网关)
在没有容器平台兜底的情况下,系统通常会设计一个唯一入口:
text
用户 → Gateway → 各个服务
Gateway 在这里承担的角色非常重:
- 路由:请求该去哪个服务
- 鉴权:你有没有权限
- 限流:你是不是请求太猛
- 统一入口:不允许直接打到服务
可以把它理解为:
整个微服务系统的"门禁 + 分流大厅"
👉 所有流量,必须先经过 Gateway,
👉 任何服务都不应该直接暴露给外部。
1.3 服务是如何被调用的:Feign(服务调用)
当一个服务要调用另一个服务时,开发者并不会手写 HTTP 代码,而是用 Feign:
java
@FeignClient("order-service")
public interface OrderClient {
Order getOrder(Long id);
}
Feign 的本质是:
"把 HTTP 调用,伪装成一次本地方法调用"
但注意一件事:
👉 Feign 自己并不知道 order-service 在哪
它只知道一个名字。
1.4 到底调哪一个实例:Ribbon(客户端负载均衡)
这一步是纯微服务时代最核心、也最容易被忽略的地方。
流程是这样的:
-
Feign 发起调用
-
Ribbon 向 Nacos 拉取:
- order-service 的所有实例列表
-
Ribbon 在调用方进程内部:
- 根据算法(轮询、随机、权重等)
- 选出一个 IP:Port
-
请求直接打到该实例
也就是说:
负载均衡不是发生在"中间层",而是发生在"每一个调用方内部"
这是"客户端负载均衡"这个词的真实含义。
1.5 服务不稳定怎么办:Sentinel / Hystrix(自我保护)
在纯微服务体系中,还有一个非常现实的问题:
下游一旦慢或挂,上游就可能被拖死
因此,每个服务必须自带保护机制。
Sentinel / Hystrix 在这里做的事情包括:
- 请求太多 → 限流
- 下游太慢 → 熔断
- 错误率过高 → 快速失败
- 返回兜底结果,避免线程堆积
可以这样理解:
这是服务"对外依赖"的安全气囊
⚠️ 非常关键的一点:
这些保护逻辑,全部运行在应用进程内部
1.6 把整条链路串起来(纯微服务完整运行图)
现在,把上面所有组件连成一条线:
text
用户
↓
Gateway(统一入口 / 限流 / 鉴权)
↓
Feign(发起服务调用)
↓
Ribbon(从 Nacos 拉实例并选择一个)
↓
目标服务实例
↓
Sentinel / Hystrix(兜底保护)
你会发现一个非常明显的特征:
应用"知道"并"参与"了整个系统运行的每一个细节
1.7 一句话总结"纯微服务时代"的本质
请记住这段话,后面和 K8s 对比时非常重要:
在纯微服务架构中:
服务 = 业务逻辑 + 服务发现 + 负载均衡 + 容错 + 自我保护
这套体系:
- 能跑
- 能扛
- 能扩展
但代价是:
👉 应用承担了太多本该由平台解决的事情
二、微服务"单独使用"时的完整逻辑链
这一节我们只做一件事:
从"一个请求进入系统"开始,一步一步把整套微服务体系如何协作讲清楚
先记住一个总前提:
此时,没有平台兜底,一切系统能力都在"应用内部"完成
2.1 流量如何进来:Gateway(网关)
2.1.1 为什么所有请求必须先到网关
在传统微服务架构中,任何外部请求都不允许直接访问业务服务,而是统一走网关,例如:
text
用户
↓
Gateway
这里的 Gateway,通常是 Spring Cloud Gateway。
它的核心职责并不是"转发请求"这么简单,而是:
- 统一入口:屏蔽内部服务结构
- 请求路由:决定请求该去哪一个服务
- 身份鉴权:你有没有访问权限
- 基础限流:防止恶意或突发流量
可以把它理解为:
系统的"唯一大门 + 第一层安全防线"
2.1.2 一个非常重要的设计含义
Gateway 的存在,意味着:
内部服务不关心"用户从哪来",只关心"我该怎么处理业务"
这是微服务分层思想的第一步。
2.2 服务如何被发现:Nacos(注册中心)
2.2.1 服务启动时发生了什么
在没有平台托管的情况下,服务要想被访问,必须主动"告诉世界我在哪"。
一个服务启动后的典型流程是:
- 启动 JVM 进程
- 读取服务名、IP、端口
- 向 Nacos 注册实例信息
- 定期发送心跳
- 下线或宕机后被摘除
注册中心中维护的数据结构,本质上是:
text
order-service:
- 10.0.0.1:8080
- 10.0.0.2:8080
2.2.2 注册中心真正解决的是什么问题
可以用一句话概括:
Nacos 解决的是"服务在哪"和"还活着吗"的问题
但要注意一个关键前提:
IP + Port 是稳定、可直连、可信的
这一点,在后面与 K8s 协作时,会发生根本性变化。
2.3 服务如何调用:Feign + Ribbon
2.3.1 Feign:让调用"像本地方法"
当服务 A 调用服务 B 时,通常不会手写 HTTP 请求,而是使用 Feign:
java
@FeignClient("order-service")
Order getOrder(Long id);
Feign 的核心价值是:
隐藏网络细节,让开发者"像调用本地方法一样调用远程服务"
但这里有一个容易忽略的事实:
Feign 并不知道 order-service 在哪
2.3.2 Ribbon:真正决定"打到谁"
真正完成实例选择的是 Ribbon。
完整调用链路是:
- Feign 发起调用
- Ribbon 向 Nacos 拉取所有实例
- 在调用方 JVM 内部执行负载均衡算法
- 选中一个 IP:Port
- 请求直接发往该实例
text
Feign
↓
Ribbon(选择实例)
↓
某个真实 IP:Port
👉 这意味着一件非常重要的事情:
调用方"知道并维护着所有下游实例列表"
这就是所谓的客户端负载均衡。
2.4 如何避免系统被拖垮:Sentinel / Hystrix
2.4.1 问题的根源是什么
在分布式系统中,一个非常现实的风险是:
下游服务一慢,上游线程就会被大量占用
如果不加控制,最终结果往往是:
- 线程池耗尽
- 请求堆积
- 整个系统级联失败
2.4.2 应用如何"自救"
因此,纯微服务体系中,每个服务都必须内置保护能力,常见实现是:
- Sentinel
- Hystrix(旧体系)
它们提供的能力包括:
- 限流:请求过多直接拒绝
- 熔断:错误率过高时短路调用
- 降级:返回兜底结果,保护系统
一个非常关键的事实是:
这些判断和决策,全部发生在应用进程内部
2.5 把整条调用链完整串起来
现在,我们把所有组件连成一条完整路径:
text
用户
↓
Gateway(统一入口 / 第一层防护)
↓
Feign(发起服务调用)
↓
Ribbon(从 Nacos 选实例)
↓
目标服务实例
↓
Sentinel / Hystrix(限流 / 熔断 / 降级)
你会发现一个非常鲜明的特点:
应用不仅在"处理业务",还在"维护系统稳定性"
2.6 传统微服务架构的本质特征(核心结论)
这一整套机制,可以浓缩为一句话:
在纯微服务体系中:
应用自己管理应用的一切
包括但不限于:
- 实例注册与发现
- 实例健康判断
- 负载均衡决策
- 故障识别与自我保护
- 流量控制与降级
👉 能力很强,但复杂度完全压在应用身上
三、问题从哪里开始暴露?------规模一上来,一切都会变
在系统规模较小时,前面那套纯微服务逻辑链看起来运行良好:
- 服务能注册
- 调用能成功
- 限流、熔断也能兜住
但一旦进入真实生产环境 + 持续扩张阶段,问题并不是"会不会出现",而是:
一定会出现,而且是系统性、结构性的
下面我们从几个必然发生的角度,把问题拆开来看。
3.1 实例频繁上下线,注册信息开始变得"不可信"
3.1.1 规模变化带来的第一个冲击
当系统规模扩大后,服务实例会频繁发生:
- 自动扩容
- 手动扩容
- 版本发布
- 机器重启
- 故障迁移
这意味着一件事:
实例的 IP 和 Port,开始变成"高频变动资源"
但在纯微服务体系中:
- 注册中心假设实例是相对稳定的
- 调用方假设拉到的列表是可直接使用的
当这个前提被打破时,问题就开始显现。
3.1.2 典型现象
你会逐渐遇到这些情况:
- 注册中心里存在已经不可达的实例
- 刚下线的实例仍被调用
- 刚上线的实例流量迟迟打不过来
- 调用方缓存的实例列表与真实情况不一致
本质原因只有一句话:
实例状态变化速度,已经超过了"应用感知能力"的极限
3.2 心跳与注册中心压力,开始指数级放大
3.2.1 小规模时不明显的问题
在实例数量不多时:
- 心跳包很轻
- 注册中心压力可控
- 网络消耗可以忽略
但当实例数量上升到 几百、上千 时,情况会完全不同。
3.2.2 问题是如何被放大的
假设:
- 500 个服务实例
- 每个实例 5 秒一次心跳
那么注册中心每分钟要处理的就是:
text
500 × 12 = 6000 次心跳
这还不包括:
- 服务上下线的注册 / 反注册
- 调用方频繁拉取实例列表
- 配置变更通知
于是你会看到:
- 注册中心 CPU 飙升
- 心跳超时被误判为下线
- 大量实例被反复摘除、恢复
👉 服务本身没问题,系统却开始"自我扰乱"
3.3 应用被迫关心过多"非业务问题"
3.3.1 应用职责开始失控
回顾纯微服务体系中,一个服务实际要承担的事情:
- 业务逻辑
- 服务注册与发现
- 实例健康判断
- 负载均衡策略
- 限流与熔断
- 线程池保护
- 调用超时管理
随着规模扩大,开发者会明显感觉到:
写业务代码的时间越来越少,处理系统问题的时间越来越多
3.3.2 一个典型场景
当线上出现问题时,排查路径往往是:
- 是不是注册中心没同步?
- 是不是实例列表缓存没更新?
- 是不是 Ribbon 选到了坏实例?
- 是不是限流规则过严?
- 是不是线程池被打满?
这些问题的共同点是:
它们本不该由"业务服务"直接承担
3.4 部署、扩容、迁移,开始严重依赖人工经验
3.4.1 "能跑"不等于"好管"
在没有统一运行平台的情况下:
- 部署新服务,需要人工挑机器
- 扩容实例,需要手动调整配置
- 发布新版本,需要人为控制顺序
- 迁移机器,需要逐个处理服务
结果往往是:
- 强依赖经验丰富的人
- 操作步骤不可复制
- 容易出现人为失误
3.4.2 系统规模越大,风险越集中
当系统依赖少数人"稳住"时,本质上意味着:
系统的稳定性,建立在"人"而不是"机制"之上
这是任何长期运行系统都无法接受的状态。
3.5 问题的根因是什么?(非常关键)
把上面所有问题抽象后,你会发现它们都指向同一个根因:
"运行应用"这件事,被拆散在了无数个应用进程里
也就是说:
- 每个服务都在做一部分运行决策
- 没有一个统一的"全局视角"
- 没有一个持续对齐状态的控制系统
3.6 为什么此时引入 K8s 是"必然选择"
所以,在这个阶段引入 Kubernetes,并不是因为:
- 技术更新
- 架构流行
- 工具更炫
而是因为:
原有的"应用自理型运行模式",已经无法支撑系统规模继续增长
K8s 带来的,不是"替换某个组件",而是:
把"如何运行应用"这件事,从应用中剥离出来,交给一个统一、自动、可控的系统
四、引入 K8s 后,系统发生了什么根本变化?
一句话先给出结论(这是整篇里非常重要的一句话):
Kubernetes 接管了「服务如何存在」,
微服务框架退回到「服务如何协作」。
这不是"谁取代谁",而是一次系统职责的重新分工。
4.1 从"应用自理"到"平台托管"的根本转变
在引入 K8s 之前,我们已经看到:
应用既要干业务,又要操心自己怎么活
而 K8s 出现后,最核心的一件事发生了变化:
"实例是否存在、是否健康、是否需要重建"
不再由应用决定,而由平台统一负责
这一步,是整个架构发生质变的起点。
4.2 服务是如何"存在"的:从注册中心 → Pod 生命周期
4.2.1 纯微服务时代的"存在方式"
之前,一个服务实例的存在依赖于:
- 应用进程是否启动
- 是否成功注册到注册中心
- 心跳是否被正确感知
👉 "存在"是一个应用行为
4.2.2 K8s 时代的"存在方式"
在 K8s 中,一个服务实例(Pod)的存在由以下机制保证:
- 你声明:我要 3 个副本
- K8s 保证:任何时候都有 3 个 Pod 在运行
- Pod 挂了 → 自动重建
- Node 挂了 → 自动迁移
可以这样理解:
Pod 的"生死",不再属于应用,而属于平台
应用只需要回答一个问题:
"我被启动后,该怎么处理请求?"
4.3 实例感知方式的变化:从"我去找你"到"平台给你一条路"
4.3.1 之前:应用必须"知道所有实例"
在纯微服务体系中:
-
应用需要:
- 从注册中心拉实例列表
- 缓存
- 失效
- 自己做负载均衡
-
调用方直接感知 IP + Port
这是强感知、强耦合的方式。
4.3.2 现在:应用只面对 Service
在 K8s 中,服务访问的基本形态变成:
text
服务 A → Service → Pod
这里的 Service 是 K8s 的原生对象,它提供:
- 稳定的虚拟 IP
- 稳定的 DNS 名称
- 自动关联后端 Pod
应用只需要记住一件事:
我只访问 Service,不关心后面有多少 Pod、在哪台机器
4.4 负载均衡发生了"位置迁移"
这是一个非常容易被忽略、但极其关键的变化。
4.4.1 以前:负载均衡在应用内部
之前:
-
Ribbon 在 JVM 内
-
每个应用实例都在做:
- 实例选择
- 失败重试
- 列表更新
👉 负载逻辑分散在所有服务里
4.4.2 现在:负载均衡下沉到平台层
在 K8s 中:
- Service + kube-proxy
- 通过 iptables / IPVS
- 在节点层面完成转发
也就是说:
负载均衡从"应用逻辑",变成了"基础设施能力"
应用层只负责:
- 发请求
- 处理响应
4.5 故障判断权的转移:从"我感觉不行了"到"平台说你不行了"
4.5.1 应用视角的局限性
在没有 K8s 时:
-
应用通过:
- 超时
- 异常
- 错误率
-
来"猜测"对方是否健康
但应用只能看到请求结果,而看不到:
- 进程是否 OOM
- Node 是否异常
- 网络是否抖动
4.5.2 K8s 的全局视角
K8s 判断 Pod 是否健康,基于的是:
- 进程状态
- 探针(存活 / 就绪)
- Node 状态
- 实际运行情况
于是出现了一个本质变化:
"是否存活"由平台判断,
"是否可用"由应用决定
这是一个非常清晰、非常合理的职责边界。
4.6 微服务框架的角色变化(非常关键)
引入 K8s 后,很多人会产生疑问:
那微服务框架是不是就没用了?
答案恰恰相反。
4.6.1 微服务框架"退回本职工作"
在 K8s 体系下,微服务框架更专注于:
- 接口治理
- 调用规范
- 限流策略
- 熔断与降级
- 业务级容错
而不再需要关心:
- 实例在哪
- 实例怎么扩
- 实例挂了怎么办
4.7 一张"前后对照"的核心变化表(强记忆点)
可以用一句对照来记忆:
以前:
应用 = 业务 + 运行逻辑
现在:
应用 = 业务逻辑
平台 = 运行逻辑
4.8 总结
请牢牢记住这句话,它贯穿后面所有章节:
K8s 并不是替代微服务框架,
而是把"应用不该操心的事",统一收回到平台层。
五、K8s 介入后,各组件如何重新分工
这一章我们继续沿着"一次请求的完整路径"来看,但你会明显发现一件事:
请求路径变得更短了,
应用承担的责任也变少了。
这不是功能减少,而是职责被重新放回到更合适的位置。
5.1 流量入口:Ingress + Gateway 的分层协作
5.1.1 K8s 时代的完整入口路径
在 K8s 中,一个外部请求进入系统,典型路径是:
text
用户
↓
Ingress / LoadBalancer
↓
Gateway Service
↓
Gateway Pod
这里涉及两个层级的"入口",但职责完全不同。
5.1.2 Ingress:集群级流量入口
Ingress 的角色可以总结为一句话:
把"集群外的流量",安全、稳定地送进集群
它负责的事情包括:
- 域名与路径规则
- TLS 终止
- 七层转发
- 对接云负载均衡或边缘入口
可以把 Ingress 理解为:
K8s 集群的"城门"
它不关心业务,只关心:
- 流量从哪来
- 要进哪个 Service
5.1.3 Gateway:应用级流量治理中心
Ingress 把流量送进来之后,真正的业务入口,仍然是:
- Spring Cloud Gateway
Gateway 在 K8s 中的职责没有减弱,反而更纯粹:
- 业务路由
- 鉴权
- 灰度规则
- 应用级限流
于是边界变得非常清晰:
Ingress 解决"怎么进集群",
Gateway 解决"进来后怎么走业务逻辑"。
5.2 服务发现:Service + kube-proxy 全面接管
这是K8s 介入后最根本、最关键的变化。
5.2.1 为什么原来的服务发现模式行不通了
在 K8s 中,有一个必须接受的事实:
Pod 是"随时可能消失、重建、迁移"的
因此:
- Pod IP 不稳定
- 实例数量动态变化
- Node 随时可能被剔除
这意味着:
任何基于"实例 IP 稳定性"的注册模式,都会天然失效
5.2.2 Service:稳定抽象的核心
K8s 引入了 Service 这个核心抽象,它提供:
- 稳定的虚拟 IP(ClusterIP)
- 稳定的 DNS 名称
- 自动关联后端 Pod
应用之间的调用关系,变成:
text
服务 A
↓
访问 service-b
↓
Service
↓
kube-proxy
↓
某一个 Pod
应用只记住一件事:
"我访问的是 Service,不是某个实例。"
5.2.3 kube-proxy:真正的"流量分发者"
kube-proxy 运行在每个 Node 上,负责:
- 监听 Service 与 Pod 变化
- 维护后端 Pod 列表
- 通过 iptables / IPVS 转发流量
本质上:
负载均衡从"应用代码",下沉到了"节点网络层"。
这是一次极其重要的职责迁移。
5.3 那 Nacos 还要不要?------角色必须收缩
这是很多团队在实践中最容易纠结的一点。
5.3.1 结论先行(非常重要)
在 K8s 场景下的推荐做法是:
Nacos 只做配置中心,不再作为注册中心
5.3.2 为什么不能"双注册"
如果你同时使用:
- K8s Service 做服务发现
- Nacos 做实例注册
必然会出现:
- 两套实例视图
- 状态不同步
- 故障判断冲突
根本原因在于:
"实例的最终生死权",已经交给了 K8s
注册中心再介入,只会制造混乱。
5.4 Feign / Ribbon:从"决策者"退回"调用工具"
5.4.1 Feign:依然有价值
Feign 在 K8s 中仍然非常有用:
- 接口抽象
- 参数映射
- 调用封装
- 统一规范
它依然负责:
"如何发起一次服务调用"
5.4.2 Ribbon:角色发生本质变化
而 Ribbon 的核心职责已经被削弱:
- 不再拉取实例列表
- 不再做真正的负载均衡
- 不再感知 Pod 变化
Feign 的调用目标,直接变成:
text
http://order-service
真正的流量分发发生在:
Service + kube-proxy 层
5.5 Sentinel / Hystrix:依然不可或缺,但边界更清楚了
5.5.1 K8s 能解决什么,不能解决什么
K8s 负责的是:
- Pod 挂了 → 重建
- Node 挂了 → 迁移
- 副本数不对 → 补齐
但 K8s 不关心:
- 接口是不是慢
- 某个依赖是不是抖
- 局部是否已经不可用
5.5.2 微服务治理框架的真实定位
因此:
- Sentinel
- Hystrix(旧体系)
仍然非常重要,它们负责:
- 接口级限流
- 熔断
- 降级
- 业务兜底
可以用一句话准确区分:
K8s 管的是"活不活",
微服务治理管的是"好不好用"。
5.6 把新的分工体系一口气串起来
现在,我们把 K8s + 微服务 的完整协作路径串成一条线:
text
用户
↓
Ingress(集群入口)
↓
Gateway(业务入口)
↓
Feign(调用封装)
↓
Service(稳定访问点)
↓
kube-proxy(流量分发)
↓
Pod(业务实例)
↓
Sentinel / Hystrix(业务级保护)
你会清楚地看到:
平台负责"运行与调度",
应用专注"业务与协作"。
5.7 总结
请记住这句高度抽象、但极其准确的总结:
K8s 不是让微服务变复杂,
而是把"不该由微服务操心的复杂性",
统一收回到平台层。
六、一个"推荐的完整协作流程"
这一章非常重要,因为它不是在讲"概念",而是在回答一个现实问题:
在微服务 + K8s 体系下,一次请求,最合理、最稳定、最可扩展的流转方式是什么?
下面这条链路,你可以直接当成:
长期可演进的系统设计蓝本
6.1 推荐的请求全流程(先给结论)
text
用户
↓
Ingress(流量进入集群)
↓
Gateway(鉴权 / 路由 / 限流)
↓
Service(稳定发现)
↓
kube-proxy(转发)
↓
Pod(服务实例)
↓
Sentinel(调用保护)
↓
Controller(实例兜底)
接下来,我们逐层拆解,说明每一层"为什么必须存在、不能省"。
6.2 Ingress:把"外部世界"与"集群内部"彻底隔离
6.2.1 Ingress 解决的不是业务,而是"边界问题"
Ingress 的核心职责只有一句话:
负责"集群外 → 集群内"的流量接入
它做的事情包括:
- 域名、路径规则
- TLS 终止
- 与云负载均衡或边缘入口对接
可以这样理解:
Ingress 是集群的"对外接口规范层"
6.2.2 为什么不让 Gateway 直接暴露到公网
如果 Gateway 直接对外暴露:
- Gateway 必须关心网络暴露方式
- 必须处理证书、入口流量抖动
- 扩容与入口强绑定
而 Ingress 的存在,让你可以做到:
业务入口稳定,入口实现可替换
6.3 Gateway:真正的"业务入口中枢"
6.3.1 Gateway 在 K8s 中的定位更清晰了
这里的 Gateway,通常是 Spring Cloud Gateway。
它在 K8s 中只做业务相关的事:
- 路由到哪个服务
- 是否有访问权限
- 是否需要限流
- 是否走灰度规则
可以用一句话概括:
Gateway 只关心"业务请求怎么走",不关心"实例在哪"
6.3.2 一个典型例子
比如:
/api/order/**→ order-service/api/user/**→ user-service
Gateway 并不知道:
- order-service 有几个 Pod
- Pod 跑在哪台 Node
它只需要知道:
"order-service 这个名字是稳定的"
6.4 Service:整个体系中最容易被低估的一层
6.4.1 Service 是"服务发现"的最终形态
在 K8s 中,Service 提供了三样极其关键的能力:
- 稳定虚拟 IP
- 稳定 DNS 名称
- 自动维护后端 Pod 列表
这意味着:
服务发现,从"应用协议",变成了"平台能力"
6.4.2 为什么 Service 是微服务的"地基"
一旦所有服务只通过 Service 互相访问:
- Pod 可随时重建
- 实例可任意扩缩
- Node 可自由迁移
而应用层:
完全无感
6.5 kube-proxy:负载均衡真正发生的地方
6.5.1 kube-proxy 在做什么
kube-proxy 运行在每个 Node 上,负责:
- 监听 Service / Pod 变化
- 维护转发规则
- 将请求分发给后端 Pod
底层实现可能是:
- iptables
- 或 IPVS
但无论哪种,本质都是:
在网络层完成负载均衡
6.5.2 一个关键变化(必须理解)
以前:
负载均衡 = 应用逻辑
现在:
负载均衡 = 基础设施能力
这是系统稳定性的一个巨大提升点。
6.6 Pod:业务真正发生的地方
6.6.1 Pod 不再需要"证明自己活着"
在这条链路中,Pod 的职责非常纯粹:
- 接收请求
- 执行业务逻辑
- 返回结果
它不需要:
- 注册自己
- 发送心跳
- 感知其他实例
因为:
"我活不活"已经由 K8s 负责
6.7 Sentinel:最后一道"业务级安全网"
6.7.1 为什么 K8s 之后仍然需要 Sentinel
Sentinel 解决的是 K8s 刻意不解决的问题:
- 接口是否慢
- 下游是否抖动
- 局部是否过载
Sentinel 提供的是:
- 接口级限流
- 熔断
- 降级
- 快速失败
可以理解为:
这是"业务调用层"的安全网
6.8 Controller:真正的"兜底之王"
6.8.1 Controller 干的不是"救请求",而是"救系统"
在请求链路的最末端,还有一个不直接参与请求、但决定系统稳定性的角色:
- Deployment Controller
- ReplicaSet Controller
- Node Controller
它们做的事情是:
- Pod 少了 → 补
- Pod 挂了 → 重建
- Node 不行了 → 迁移
所以可以这样理解:
Controller 不救某一次请求,
但保证系统长期一定可用
6.9 把整条"推荐协作链路"一次记住
你可以用这句话来快速复述整套体系:
Ingress 把流量送进来,
Gateway 决定业务怎么走,
Service 提供稳定访问点,
kube-proxy 分发请求,
Pod 执行业务,
Sentinel 兜住抖动,
Controller 保证实例始终存在。
6.10 总结
这是一个可以直接写进设计文档的结论:
微服务 + K8s 的最佳协作方式,不是"谁替代谁",
而是让平台负责"运行确定性",
让应用专注"业务不确定性"。
七、服务不可用时,谁在兜底?
在微服务 + K8s 协作体系中,故障处理是分层的、职责明确的 。
每一层只兜自己能管的事,避免重复处理或遗漏。
| 故障类型 | 处理者 | 原理解析与示例 |
|---|---|---|
| 容器崩溃 | kubelet | 节点级别的"管家" 。kubelet 监控容器进程状态,如果发现进程退出或 OOM,会立即调用容器运行时重启容器。 例如:order-service 容器因内存溢出退出,kubelet 在 5 秒内自动拉起新容器。 |
| Pod 消失 | Controller | 期望状态守护者 。Controller(如 Deployment Controller)持续对比期望状态 vs 实际状态,发现副本不足时会创建新的 Pod。 例如:声明 3 个副本,实际 2 个存活 → Controller 自动补齐 1 个。 |
| Node 宕机 | Node Controller | 节点级健康管理 。Node Controller 发现 Node 不可达,会剔除该 Node 上的 Pod,并触发 Controller 重新调度到其他健康 Node。 例如:某台 Node 宕机,Pod 自动迁移到其他 Node,不影响业务请求(通过 Service + kube-proxy)。 |
| 实例不足 | HPA + Controller | 弹性扩缩容组合拳 。HPA 根据指标(CPU、内存、QPS)调整副本数,Controller 保证期望副本在集群中存在。 例如:订单高峰,CPU 使用率超过 80%,HPA 指令增加副本 → Controller 创建新 Pod。 |
| 接口慢 | Sentinel | 业务级调用保护 。Sentinel 对接口请求速率、响应时间进行监控,当接口延迟超过阈值时,限流或熔断,避免雪崩。 例如:user-service 响应超过 1s,Sentinel 限流 50% 流量,保障系统整体稳定。 |
| 下游异常 | 熔断 / 降级 | 局部服务自救 。当下游系统不可用时,调用方触发熔断或返回降级方案(fallback),防止故障传导。 例如:支付服务接口异常,订单服务返回默认支付状态而不阻塞主流程。 |
| 流量洪峰 | Gateway | 入口级防护 。Gateway 对请求进行限流、熔断、鉴权,防止瞬时洪峰冲垮内部服务。 例如:秒杀活动瞬间 10 万请求涌入,Gateway 对非 VIP 用户进行限流,保证系统可用。 |
7.1 分层兜底原则
- 节点层:kubelet → 保证容器存活
- Pod / 副本层:Controller → 保证期望副本数量
- 集群 / 节点健康层:Node Controller → 保证节点健康、Pod 迁移
- 弹性层:HPA → 按指标自动扩缩容
- 接口调用层:Sentinel / 熔断 → 保证请求可控
- 入口层:Gateway → 保证流量可控
口诀记忆法 :
"容器自生,Pod 自补,节点迁移,副本扩,接口限,调用降,入口稳"。
每个动作对应一行表格,顺口又容易串联。
7.2 场景串联示例
假设秒杀场景:
- 秒杀开始,流量瞬间涌入 → Gateway 限流
- 部分 Pod CPU 飙升 → HPA 扩容
- 某 Pod 因 OOM 崩溃 → kubelet 重启
- Node 宕机 → Node Controller 调度 Pod
- 下游库存接口慢 → Sentinel 限流 / 熔断
- 支付服务异常 → 降级返回默认结果
✅ 整条链路无缝协作,系统稳健,应用只专注业务逻辑。
八、最终总结
如果你只能记住一段话,那就记住:
K8s 负责让服务"存在且正确",
微服务框架负责让服务"协作且克制"。
不要让应用操心实例,
不要让平台理解业务。
8.1 核心理解:职责下沉 + 分层清晰
这句话背后的深意:
-
K8s 负责"活着"
- Pod 挂了 → Controller 自动补
- 容器崩 → kubelet 自动拉起
- Node 宕 → Node Controller 调度 Pod
- 扩缩容 → HPA + Controller 自动完成
-
微服务负责"好用"
- 接口慢 → Sentinel 限流 / 熔断
- 下游异常 → 降级 / fallback
- 灰度、路由、鉴权 → Gateway 管理
一句话理解:平台解决"存在性",应用解决"业务健康"。
8.2 功能性变化的组件总结
| 组件 | 在传统微服务的作用 | 在 K8s 下的作用/变化 | 原理解释 |
|---|---|---|---|
| 注册中心(Nacos) | 服务注册与发现,负载均衡 | 只做配置中心,不再做注册与发现 | Pod IP 不稳定,注册中心重复管理会冲突 |
| 客户端负载均衡(Ribbon) | JVM 内选实例 | 不再负载均衡,Feign 直接访问 Service | kube-proxy + Service 网络层 LB 已取代 |
| kube-proxy | 无 | 负责将 Service 请求分发到 Pod | 网络层维护 Pod 列表,自动更新规则 |
| Sentinel / Hystrix | 限流、熔断、降级 | 职责保持不变,仍管理业务健康 | 平台保证"存在",应用管"好不好用" |
| Gateway | 业务路由、限流、鉴权 | 职责保持不变,站在最外层 | Ingress 提供集群入口,Gateway 专注业务治理 |
| Deployment / Controller | 无 | 负责 Pod 数量、自愈 | 持续对比期望状态 vs 实际状态,保证稳定副本数 |
8.3 为什么这些改变重要?
-
注册中心退位
- 不再是实例管理者,避免状态冲突
- K8s 提供更稳定、更实时的 Service 发现与负载均衡
-
kube-proxy 替代 Ribbon
- 负载均衡下沉到网络层,消除应用层复杂逻辑
- Pod 弹性扩缩容时应用无需感知
-
微服务治理框架不可替代
- Sentinel 管的是"慢与异常",K8s 不处理
- 保持业务调用安全和局部稳定
-
网关必须站在最外层
- 边界清晰,统一做鉴权、路由、限流
- 避免每个服务重复实现流量控制
-
分层清晰 = 系统稳定
- 平台只管存在性
- 应用只管协作
- 每层兜自己该兜的,故障不会互相传递
8.4 场景串联记忆法
假设秒杀场景:
- 流量大 → Gateway 限流
- Pod CPU 高 → HPA 自动扩容
- Pod 崩 → kubelet 重启
- Node 宕 → Node Controller 调度 Pod
- 下游接口慢 → Sentinel 限流 / 熔断
每一层只管自己能管的事 → 系统整体稳,应用只关心业务。
8.5 本章一句话高度抽象
K8s 负责"活着",微服务负责"好用",
分层越清晰,系统越稳,运维越轻松,业务越专注。
这就是你现在真正理解的微服务 + K8s 协作哲学。