个人主页 我不会起名字322 欢迎各位大佬莅临其他栏目 技术栈学习笔记 其他栏目 力扣Hot100题目解析 其他栏目 Go项目学习笔记 文章目录云原生的两层抽象Kubernetes 管生命周期Service Mesh 管流量一个绕不开的困惑先把云原生这个词摆正Kubernetes 的边界在哪里过去的做法把治理逻辑塞进 SDK解法把治理下沉到进程之外数据面与控制面别被两个词吓到一个具体场景5% 灰度代价没有免费的第二层那到底什么时候该上回到最初的那个困惑云原生的两层抽象Kubernetes 管生命周期Service Mesh 管流量一个绕不开的困惑我刚开始接触云原生的时候脑子里一直有个结解不开。我们的服务明明已经跑在 Kubernetes 上了Pod 会自动重启扩容只要改一个replicas滚动更新也不用停机。那为什么社区还在拼命推 Service MeshService 之间调用Kubernetes 不是已经用Service对象给我做负载均衡了吗后来我把这个问题想明白了答案其实一句话Kubernetes 管的是服务能不能活着Service Mesh 管的是服务之间怎么说话。这是两个完全不同的问题。这篇文章就把这两层抽象拆开讲清楚它们各自负责什么、边界在哪里、什么时候你才真的需要第二层。先把云原生这个词摆正很多人把云原生理解成把应用搬到云上这是最大的误解。搬到云上只是换了台机器。云原生的意思是应用从设计的第一天起就假设自己运行在一个随时会挂、随时会扩缩、节点随时会消失的环境里。这个假设一旦成立很多事情就必须换个做法应用不能把状态写在本地磁盘上因为下次它可能被调度到另一台机器不能假设有一个固定的 IP因为 Pod 重建后 IP 就变了启动必须快因为扩容是按秒算钱的配置不能编在镜像里因为同一份镜像要能在测试和生产都跑。容器解决了环境一致性Kubernetes 解决了调度和自愈而微服务架构解决了团队协作和独立部署。这三件事凑齐之后一个新的问题浮出来了服务数量一多服务之间的通信本身就成了一个巨大的、需要专门治理的领域。Service Mesh 就是冲着这个问题来的。Kubernetes 的边界在哪里要理解第二层的必要性先得知道第一层到底做到了什么程度。给一个最普通的 DeploymentapiVersion:apps/v1kind:Deploymentmetadata:name:order-servicespec:replicas:3selector:matchLabels:app:order-servicetemplate:metadata:labels:app:order-servicespec:containers:-name:appimage:registry.local/order-service:1.4.2ports:-containerPort:8080readinessProbe:httpGet:path:/healthzport:8080initialDelaySeconds:5再加上一个 ServiceKubernetes 就帮你做了这些事调度把 3 个副本分散到不同节点自愈容器挂了就重启节点挂了就在别处重建服务发现order-service.default.svc.cluster.local这个域名始终可用四层负载均衡kube-proxy 写 iptables/IPVS 规则把请求分发到某个 Pod IP滚动更新配合 readinessProbe一批批换掉旧 Pod。注意我做了一个加粗的限定词四层。Kubernetes 的 Service 抽象基本停留在 TCP/UDP 层面。它知道把这个连接转给某个 Pod但它不知道、也不关心这次请求是 HTTP/2 还是 gRPC路径是/v1/order还是/v2/order我想把 5% 的流量打到新版本上怎么做下游超时了三次要不要熔断熔断后多久恢复服务 A 调用服务 B双方凭什么互相信任要不要 mTLS一次请求跨了 6 个服务怎么把它串成一条调用链这些都属于七层的服务治理能力。Kubernetes 把它们留给了应用自己或者留给了别的东西。过去的做法把治理逻辑塞进 SDK在 Service Mesh 成熟之前主流答案是 Spring Cloud 和 Dubbo 这样的框架。它们的思路是给每个服务引入一个 SDK治理逻辑写在 SDK 里。你在pom.xml里加上spring-cloud-starter-netflix-eureka、spring-cloud-starter-openfeign、spring-cloud-starter-circuitbreaker再写上几行配置负载均衡、服务发现、熔断、重试就都有了。这套方案非常好上手但用了几年之后问题会以这几种形式冒出来第一语言被绑死。Spring Cloud 的世界里只有 Java。你新来的推荐团队想用 Go 写那就得重新用 Go 实现一遍服务发现、负载均衡、熔断而且行为还要和 Java 那套对齐。第二升级等于全量改造。某天发现选用的熔断组件有 bug或者负载均衡策略要调整你得把引入这个 SDK 的几十个服务全部改依赖、重新打包、重新发版。服务越多这件事越难推动。第三业务代码和治理代码纠缠。一个新建服务的工程模板里一半是业务一半是框架配置。新人接手时根本分不清哪些是需要理解的业务逻辑哪些是祖传的框架粘合代码。第四治理能力不统一。不同团队用的版本不一样超时时间、重试次数、熔断阈值全凭各自喜好出了线上问题谁也不知道这条链路上到底发生了什么。这些问题的共同根源是治理能力和业务进程耦合在一起了。解法把治理下沉到进程之外Service Mesh 的思路非常干脆既然治理逻辑和业务逻辑没什么关系那就别放在同一个进程里。给每个服务实例旁边再起一个轻量代理让所有进出这个实例的流量都先经过它。这就是 Sidecar 模式。业务进程只做业务代理进程只做通信治理两者在同一个 Pod 里共享网络命名空间。# 一个 Pod 里两个容器业务 代理spec:containers:-name:app# 业务容器只管业务image:registry.local/order-service:1.4.2-name:istio-proxy# 代理容器管所有流量image:docker.io/istio/proxyv2:1.20.0好处立刻显现语言无关。不管你是 Java、Go、Python 还是 Node流量都是从 Pod 的网卡进出的代理一律能拦。团队用什么语言写业务Mesh 完全不关心。业务无侵入。业务代码里不再需要引入任何治理 SDK甚至不需要知道 Mesh 的存在。升级与业务解耦。代理支持热升级换代理版本不用重新编译业务镜像。策略统一。所有服务的超时、重试、熔断、加密策略都在一个地方配置行为天然一致。当网格里的 Sidecar 数量足够多、连接关系足够密的时候这些代理连起来就成了一张网——名字就是这么来的。数据面与控制面别被两个词吓到Service Mesh 的架构里有两个高频词数据面和控制面。用一句话区分数据面真正转发流量的那些代理。它干的是体力活每来一个请求它都要判断该发给谁、超时多久、要不要重试。控制面不碰任何业务流量它只负责告诉数据面你应该这么做。它干的是管理活。用 Envoy 举例。Envoy 是最主流的数据面代理也是 CNCF 的毕业项目它自己不需要知道集群里有哪些服务——它的全部行为都来自一份动态下发的配置。而这份配置是谁给它的控制面。控制面和数据面之间的通信协议就是xDS。你可以把 xDS 理解成代理圈的 USB 接口只要是实现了 xDS 的代理就能被任意一个会说 xDS 的控制面管理。这一点很重要它意味着数据面和控制面可以分别演进、分别替换。一个具体场景5% 灰度讲抽象概念容易飘我们看一个真实需求。需求order-service上线了 v2先把 5% 的流量导过去观察半小时没问题再全量。用 SDK 方案怎么做你需要在网关或调用方代码里写权重逻辑或者引入一个带权重的负载均衡实现然后改配置、发版。如果调用方有十几个服务、五种语言这个改动要重复十几遍。用 Service Mesh 怎么做改两个 YAML 就行。第一个是虚拟服务定义流量怎么分apiVersion:networking.istio.io/v1beta1kind:VirtualServicemetadata:name:order-servicespec:hosts:-order-servicehttp:-route:-destination:host:order-servicesubset:v1weight:95-destination:host:order-servicesubset:v2weight:5第二个是目标规则定义v1 和 v2 到底是哪些 PodapiVersion:networking.istio.io/v1beta1kind:DestinationRulemetadata:name:order-servicespec:host:order-servicesubsets:-name:v1labels:version:v1-name:v2labels:version:v2kubectl apply之后几秒内全网格生效。想改成 1%改个数字再 apply。想按请求头分流让内部测试账号永远走 v2在http下面加一个match就行。整个过程不碰一行业务代码不需要任何一个服务重新发版。这才是 Service Mesh 真正的价值——它把流量治理从一个需要改代码、发版的开发任务变成了一个改配置的运维动作。代价没有免费的第二层如果 Service Mesh 全是好处那它早就一统天下了。实际落地时你要为它付出这些东西性能开销。每个请求都要多走一跳本地代理。社区在 1.5 版本时代做过基准测试1000 个服务、2000 个 Sidecar 的规模下代理给 P90 延迟带来的增量在 6 毫秒左右。听起来不多但如果你的服务链路有 10 跳累加起来就是几十毫秒。对延迟极度敏感的链路要专门评估。架构复杂度。引入 Mesh 意味着你的系统里多了控制面组件、多了 CRD、多了一层需要理解的概念。以前排查问题看应用日志现在可能还得看代理的访问日志和配置分发状态。运维门槛。你得有人真正理解这套东西。控制面挂了会怎样、代理启动时拿不到配置会怎样、Sidecar 注入失败了怎么发现——这些问题在故障时全部会变成线上事故。容器化的前提。一个 Sidecar 对应一个应用实例这是最舒服的形态。如果在虚机上部署、一个代理要罩住多个应用代理本身就成了单点收益会打折扣。那到底什么时候该上我的判断标准很朴素看你是不是真的被多语言和治理策略统一这两件事卡住了。不建议上的情况服务数量在二十个以内团队清一色 JavaSpring Cloud 用得挺顺没有跨语言需求也没有专职的基础设施团队当前最大的痛点是业务迭代速度而不是服务治理。在这些前提下引入 Service Mesh你换来的是架构更先进的心理满足付出的是实实在在的复杂度和延迟。值得上的情况技术栈已经多语言化每个语言各自维护一套治理 SDK重复投入很大服务数量上百治理策略散落在各处无法统一管控有明确的灰度发布、故障注入、全链路加密封锁等强需求已经有成熟的容器化和 Kubernetes 运维能力团队有能力消化这层复杂度。顺序上我会建议先容器化再 Kubernetes最后才是 Service Mesh。这个顺序不是教条而是因为每一层都依赖前一层的成熟度。跳过中间那层直接上 Mesh大概率会变成一场灾难。回到最初的那个困惑现在再回答开头的问题Kubernetes 都做了负载均衡为什么还要 Service Mesh因为 Kubernetes 解决的是**“这个连接该转给哪个 Pod”而 Service Mesh 解决的是这次调用该不该发生、怎么发生、出了问题怎么办**。前者是编排层的能力后者是治理层的能力。它们不是替代关系而是叠加关系——Service Mesh 站在 Kubernetes 的肩膀上把服务最细粒度的通信控制权从业务代码里拿了出来交给了基础设施。理解了这条分界线你再看云原生那一堆名词就不会觉得它们是在互相抢地盘了。