尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

云原生深度学习主线:Operator、Service Mesh、Dapr 实战笔记

云原生深度学习主线:Operator、Service Mesh、Dapr 实战笔记 这两年如果让我只挑一个持续学习的主线我会毫不犹豫地选云原生深度Kubernetes Operator、Service Mesh、Dapr。云原生这个概念喊了好几年现在已经不是“要不要上云原生”的问题而是“上了之后谁能把平台玩明白”的问题。光会写 YAML 部署个微服务根本不够看真正的分水岭在于你能不能把 Kubernetes 当成一套可编程的控制平面把应用调度、流量治理乃至中间件能力全部下沉到平台层。这篇整理是我这段时间的学习笔记写给同样打算往云原生方向深挖的人做参考。不管你是后端开发、SRE 还是架构师只要后面想碰平台工程、业务中台、技术底座这三个方向基本是绕不开的主干道。1. 为什么要把云原生深度作为持续学习主线1.1 云原生能力分层使用、运维、扩展先聊一个扎心的现状很多人所谓的“会用 Kubernetes”其实是会照着文档敲kubectl apply遇到问题会看 Pod 状态和日志最多再加一点 Helm 模板能力。这套技能应付单个小项目没问题但放到多团队、多集群、成百上千个微服务的环境里立刻就露馅了。因为你始终是被动使用 Kubernetes 提供的能力而不是自己定义能力。我习惯把云原生能力分成三层。第一层是“使用层”也就是熟悉 Deployment、Service、ConfigMap 这些原生资源能部署应用、能排查故障。这一层是基本功但天花板很低。第二层是“运维层”你开始写一些脚本、CronJob、以及简单的自动化工具去管理集群和应用生命周期比如滚动发布、备份恢复、日志采集。能做到这一层的人已经能在一家公司里活得不错了。第三层是“扩展层”说白了就是能造轮子把业务运维逻辑沉淀成 Kubernetes Operator把流量策略抽象成 Service Mesh 配置把中间件访问标准化成 Dapr 这类运行时。只有到了这一层你才算真正把云原生当成了平台能力来设计而不是把它当成部署工具的替代品。为什么会把这三个方向放在一起因为它们刚好覆盖了平台化的三个关键维度Operator 管应用生命周期和运维自动化Service Mesh 管流量和服务间通信Dapr 管业务对中间件基础设施的访问方式。三者合起来就是把“功能代码”和“分布式系统基础设施”解耦的完整拼图。1.2 这三个方向解决的现实痛点学习任何一个技术方向之前先搞清楚它治什么病否则学完只是多记住几个名词。Operator 治的病是“运维经验随人走”。资深工程师知道一个数据库集群应该怎么备份、怎么扩副本、怎么处理故障转移但这些东西如果只存在于个人脑子和操作手册里那公司永远没办法把这些能力规模化。Operator 把这类经验固化成代码和规则让 Kubernetes 控制平面自己去盯、自己去修。这不只是自动化而是把运维这件事从一个“岗位”变成了一个“可复制的产品”。Service Mesh 治的病是“网络拓扑散落各处”。微服务一多超时、重试、熔断、灰度、流量观测这些能力如果都写在业务代码里每次调整都得改代码、发版而且每个语言的技术栈实现还不一样。Service Mesh 把这些能力往下移到网络层业务代码不需要关心对端是什么版本、流量要怎么切你只需要在控制面上改几条策略后面的流量自动按规则走。Dapr 治的病则是“中间件 SDK 绑架业务代码”。传统微服务模式里你想用 Redis、Kafka、MySQL就要引入对应的 SDK还要处理连接池、重试、序列化、运行时异常。一旦换一个中间件实现业务代码就得跟着改一遍。Dapr 的做法是把这些能力封装成标准化的 HTTP/gRPC 接口业务代码只面向抽象接口说话具体底层是什么中间件可以随时换甚至可以本地开发用一个实现、生产环境换另一个实现。这三个痛点如果你工作中已经遇到了那说明你的学习方向没选错而且越早深挖回报越明显。2. Kubernetes Operator把运维经验写成代码2.1 Operator 到底是什么以及它解决的问题Operator 的核心思想其实不难懂就是利用 Kubernetes 的“声明式 API 控制循环”机制把一个领域对象的期望状态交给控制器去维护。你在集群里提交一个Memcached自定义资源Operator 控制器看到之后就开始工作创建 Deployment、观察副本数、检查 Pod 健康情况、发现偏差就去纠正直到实际状态和期望状态完全一致。我打个比方吧。你往冰箱里放了一箱饮料并贴了张纸条写着“任何时候冰箱里必须有十瓶可乐”。冰箱本身不会自动补货但你雇了一个管理员他每隔几分钟看一眼冰箱少于十瓶就去买这个管理员就是 Operator。在 Kubernetes 里Deployment自动帮你保证“任何时候必须有三个 Pod 在运行”这算内置运营商而针对你业务特定的应用比如一个多节点数据库、一套消息队列集群、一个带状态的应用Kubernetes 并没有内置对应策略这时候就得自己写 Operator 来充当那个“管理员”。所以 Operator 能解决的问题远不止“自动创建 Deployment”。它可以处理应用升级顺序、数据备份、故障恢复、扩缩容前的预检查、版本兼容校验等等。尤其对于有状态应用比如数据库、搜索集群原生 Deployment 根本摆不平这时候 Operator 几乎是唯一靠谱的解法。云原生圈里常说“一个成功的项目总有一个 Operator”就是因为任何复杂应用跑到 Kubernetes 上最终都要把运维逻辑变成代码。2.2 最小可用的 Operator 示例用 Operator SDK 管理 Memcached如果想上手 Operator建议直接从 Kubebuilder 这种脚手架开始不要一上来就纠结调谐循环的每行代码。脚手架帮你把 Controller、CRD、RBAC、Webhook 的骨架都搭好你只需要往里填业务逻辑。我用一个极简例子演示关键步骤。先初始化项目# 创建项目目录结构 kubebuilder init --domain example.dev --repo example.dev/memcached-operator # 创建 Memcached 资源类型和对应控制器 kubebuilder create api --group cache --version v1 --kind Memcached --resource --controller接下来核心工作集中在Reconcile方法里。所谓 Reconcile就是“对一下账”读当前状态对比期望状态如果有偏差就动手改。对于 Memcached Operator最简单的对账逻辑可以这样写func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var memcached cachev1.Memcached if err : r.Get(ctx, req.NamespacedName, memcached); err ! nil { return ctrl.Result{}, client.IgnoreNotFound(err) } deploy : appsv1.Deployment{} err : r.Get(ctx, types.NamespacedName{ Namespace: req.Namespace, Name: memcached.Name, }, deploy) if err ! nil { if errors.IsNotFound(err) { // 资源不存在用期望状态创建 Deployment return ctrl.Result{}, r.createDeployment(ctx, memcached) } return ctrl.Result{}, err } // 资源存在对比副本数不一致就同步 return ctrl.Result{}, r.syncReplicas(ctx, memcached, deploy) }你可能注意到这里没有调用任何kubectl命令而是直接通过 client-go 在集群 API 层面做增删改查。这正是 Operator 和普通脚本的本质区别普通脚本是一次性执行Operator 是一个永远运行、随时待命的控制循环。部署到集群也很简单一般就是make install安装 CRDmake deploy部署控制器。为了在本地快速调试也可以用make run直接在本地把控制器跑起来它会通过 KubeConfig 连到你当前集群。2.3 我踩过的 Operator 设计坑第一个坑是“过度频繁地读取资源”。刚写 Operator 的时候我习惯在每个 Reconcile 循环里直接调用r.Get去查关联资源结果发现控制器的 CPU 和 API QPS 高得离谱。后来才意识到要善用 Indexer 和 Watch让 Kubernetes 把变化事件主动推给你而不是你自己反复去查。设计好的 Operator 应该“事件驱动”不是“轮询驱动”。第二个坑是 RBAC 权限问题。Kubebuilder 生成的 RBAC 规则不会覆盖你自定义的所有资源经常出现控制器跑起来后明明逻辑正确却一直报forbidden。一开始我为了省事直接给控制器绑cluster-admin结果被安全团队喊去喝茶。正确做法是在// kubebuilder:rbac注释里把要访问的资源和动作精确标出来再重新生成 YAML。第三个坑是版本升级策略。很多新手只考虑 Operator 第一次创建资源的场景忘了后续版本升级。比如应用从 v1 升到 v2Controller 要能识别旧版本资源还要决定是先删旧 Pod 还是先起新 Pod。这类升级策略一定要在设计阶段就写在 CRD 的 Spec 里而不是事后打补丁。有人觉得 Operator 门槛高其实门槛主要体现在两点一是需要对 K8s 的 API 机制有足够理解二是对你要管理的那个领域有足够理解。前者可以靠看书和读源码补后者只能靠业务经验积累。3. Service Mesh流量治理与零信任的落地载体3.1 Sidecar 模式的价值把网络能力从应用里搬走Service Mesh 看起来是个复杂话题但只要你理解了 Sidecar就理解了它一半。在网格架构里每个服务 Pod 旁边都跟了一个轻量级代理最常见的是 Envoy所有进出服务的流量都从这个代理走。代理负责处理 TLS、超时、重试、熔断、流量拆分、观测等事情业务进程完全不感知。这带来的最大价值是让“网络怎么走”和“业务做什么”彻底分离。以前你写微服务要自己处理服务发现、负载均衡、重试逻辑每个语言还各有各的库。现在这些能力被下沉到基础设施层业务代码该干嘛干嘛网络策略由平台统一管控。打个不恰当的比方原来每个员工都要自己开车去食堂现在公司配了个车队路线、接送时间、排队规则都是车队统一调度员工只需要出门上车就行。Service Mesh 还有一个重要能力是身份认证和授权。传统的微服务鉴权往往依赖内网环境“默认可信”但一旦横向移动攻击得逞内网就完全裸奔。在 Mesh 体系里每条链路都可以自动做双向 TLS通过 SPIFFE 标准给每个工作负载下发身份证书实现零信任网络。这个能力在等保、合规、金融类项目里越来越重要。3.2 一次最小可用的 Istio 金丝雀发布实践目前最主流的 Service Mesh 实现还是 Istio虽然它被吐槽过“重”但在功能完整度和社区生态上仍然是最能打的那个。如果你只是想入门我更推荐先跑一个最小金丝雀发布比直接上全套可观测性更能建立体感。假设你有一个order-svc服务部署了 v1 和 v2 两个版本分别打上了version: v1和version: v2的标签。通过 Istio 的DestinationRule定义子集再通过VirtualService控制流量分配就可以实现“绝大多数用户还是走 v1只有小比例流量切到 v2”的灰度apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: order-svc spec: hosts: - order-svc http: - match: - headers: version: exact: v2 route: - destination: host: order-svc subset: v2 - route: - destination: host: order-svc subset: v1 --- apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: order-svc spec: host: order-svc subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2配置下发后实际上有一条策略在控制面上生效——这个操作不需要业务代码参与也不需要重新构建镜像。你可以动态调整权重比如把第一条 match 删掉改成weight: 10和weight: 90就能让 10% 流量进入 v2。灰度期间如果发现异常最干净的做法是直接删掉或者改掉 VirtualService 里的路由规则让所有流量回到 v1整个过程以秒级完成。如果你想要一个更低成本的轻量选择也可以看 Linkerd它的资源消耗真的低很多但功能边界也更克制。选型这件事我在下面细说。3.3 选型与落地时的避坑清单Service Mesh 落地最大的坑不是技术本身而是“以为上了 Mesh 就不用治理了”。实际上它把治理从代码层搬到了配置层配置本身就是一种需要治理的资产。下面这些坑我在网维项目和朋友交流中反复见过。首先是资源开销。Sidecar 模式意味着每个 Pod 都要多一个代理进程内存和 CPU 都会相应增加。Envoy 默认配置吃资源不低如果业务 QPS 本身就小你可能发现 Mesh 带来的开销比例高得吓人。解决办法是提前做压测按业务请求量评估每个 Pod 需要额外预留多少资源而不是等上了生产再被迫扩容。其次是 TLS 问题。Istio 默认情况下可能开启或要求双向 TLS但如果你有很多历史系统没做好证书轮换开启强制 mTLS 的瞬间会导致大量服务之间握手失败。落地节奏应该先是“允许明文”逐步观测再切到“强制 mTLS”。然后是可观测性。很多人被 Istio 的 Grafana、Kiali、Jaeger 吸引但如果不设置采样率全量采集链路数据会把监控系统直接打挂。我一般会把采样率从 100% 调到 1% 到 10%只有在定位问题时才临时提高。链路数据一旦落到存储里还要考虑清理策略不然半个月磁盘就被撑爆。选型上我直接把经验放在下面这张表里省得你再翻资料方案优点缺点适合场景Istio功能全、生态好、可扩展性强资源占用高、配置复杂中大型微服务治理、金融合规场景Linkerd轻量、低延迟、易上手功能边界内建扩展能力弱中小团队、K8s 原生轻治理自研或 no Mesh零依赖、灵活每项能力都要造轮子架构极简、技术栈完全统一综合来看如果你刚起步先用 Linkerd 建立对 Mesh 的直观感受如果你要长期深入Istio 几乎是绕不开的学习对象。4. Dapr可移植微服务运行时把中间件变成构建块4.1 别再搞混Dapr 和 Service Mesh 到底区别在哪很多人一听到 Dapr 也有“Sidecar”就想当然认为它和 Service Mesh 是一类东西这个误解要纠正。Service Mesh 管的是“请求怎么从服务 A 到服务 B”Dapr 管的是“服务 B 怎么把状态存起来、怎么发消息、怎么调用其他服务”。前者面向网络的传输层后者面向业务的应用语义层。用更生活化的说法来区分Service Mesh 就像高速公路关心车怎么开、路况怎样、要不要限流分道Dapr 则更像城市里的物流调度中心关心的是货怎么装、怎么送到不同地址、中间要不要仓储暂存。两者解决的问题层次完全不同共用一个 Sidecar 模式只是因为它们都选择了控制面与数据面分离这个架构范式。Dapr 提供了一组所谓的“构建块”State Management、Pub/Sub、Service Invocation、Bindings、Actors、Workflow、Configuration 等等。每个构建块都提供标准接口业务代码通过 Dapr 暴露的 HTTP/gRPC API 来访问对应能力不需要引入任何中间件的专有 SDK。比如你要用 Redis 存状态在本地客户端里发一个带state的请求即可换成别的存储后端只需要改组件定义文件业务代码一行不用动。我做了一个对比方便你直接记住两者边界维度Service MeshDapr关注层次网络通信与流量治理应用状态、消息、调用绑定典型能力灰度、熔断、mTLS、遥测状态管理、发布订阅、Actor与业务代码关系完全透明业务无感业务代码显式调用构建块 API典型代表Istio、LinkerdDapr明白了这个区别你才不会在技术评审会上被领导反问“我们已经上了 Istio为什么还要 Dapr”时哑口无言。4.2 用 Dapr Pub/Sub 快速落地一个事件驱动示例上手 Dapr 最直接的路径是用它做一个发布订阅功能。假设你的应用要发一个“订单已创建”事件其他服务要监听这个事件做后续处理。传统做法是引入 Kafka 或 RabbitMQ 的 SDK然后写一堆连接和消费代码。用 Dapr 之后你只需要定义组件文件业务代码只关心“往主题上发一条消息”和“接收主题推送”。本地开发时先初始化 Daprdapr init然后定义一个 Redis 作为 Pub/Sub 后端组件apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pubsub spec: type: pubsub.redis version: v1 metadata: - name: redisHost value: localhost:6379 - name: redisPassword value: 发布端用 Python 或任意语言都行核心就是调用 Dapr Sidecar 的 HTTP 接口from dapr.clients import DaprClient with DaprClient() as client: resp client.publish_message( pubsub_namepubsub, topic_nameorders, datab{order_id: 12345}, ) print(resp.status_code)订阅端就更有意思了你的应用只需要暴露一个 HTTP 端点Dapr Sidecar 会把订阅好的消息直接 POST 过来from fastapi import FastAPI, Request app FastAPI() app.post(/orders) async def handle_order(request: Request): payload await request.json() print(payload) return {ok: True}当然为了让 Dapr 知道你的服务订阅了orders主题还需要在代码里或者通过 Dapr CLI 声明订阅关系。这套模式最香的地方在于你本地用 Redis 就能开发调试到了生产环境把组件定义中的type换成pubsub.kafka之类的云原生消息队列业务代码不需要做任何修改。4.3 Dapr 在实际项目中的取舍与经验Dapr 听上去很美但它并不是银弹。我自己在项目里试过有几个感受必须说清楚。第一Sidecar 数量会成倍增加。每个启用 Dapr 的 Pod 都会多一个 Sidecar如果你的服务拆分很细整体资源开销会明显上涨。必须让团队知道这一点而不能只把它当成“免费的魔法”。一般来说服务数量在几十个以内时收益最明显超过上百个你需要非常强的平台工程能力来管理这么多 Sidecar。第二Dapr 对团队的统一约束要求很高。因为所有中间件访问都走标准接口团队不能再随便绕过 Dapr 直连某个中间件否则状态一致性和可观测性都会出问题。如果你在一个既有多语言、又有多个中间件的环境里Dapr 的价值会最大化但如果你们本来就是一套 Java Spring Cloud 的技术栈那引入 Dapr 可能反而是重复造轮子Spring Cloud 的现有组件已经解决了大部分问题。第三版本演进要注意。Dapr 的 API 虽然总体稳定但不同版本之间的行为差异还是存在的。比如我在测试时就遇到过组件配置字段不兼容的问题。我的建议是先在非核心项目里试用一个完整版本跑一个完整迭代周期之后再考虑上生产不要一上来就用一个刚发布的版本。最后我还想多说一句Dapr 的 Actor 构建块非常有趣但也是个“看着简单、用起来难”的能力。尽量不要在业务早期阶段引入 Actor等真遇到需要强一致性且有明确实体边界的业务场景时再考虑否则很容易把分布式系统写成单体状态堆积。5. 持续学习路线图从会用到能造再到能设计5.1 三个月到六个月的主线任务拆解很多人学习失败不是缺资料而是缺一条清晰的主线。我是这么给自己拆解的。第一个月专注 Operator。先跟着 Kubebuilder 官方文档写一个简单 Operator理解 CRD 和 Controller 的交互第二周开始读 client-go 源码里 informer 和 workqueue 的实现理解缓存和事件机制最后花一周做一个真实的小需求比如给某个内部服务写一个自动扩缩容的 Operator。学 Operator 的最终检验标准是你能否不看资料就独立设计出一个自定义资源和它的控制器。第二、第三个月转向 Service Mesh。先在自己的环境里面部署 Istio把 Bookinfo 示例跑通然后做金丝雀发布、mTLS、故障注入这三个实验接下来阅读 Istio 的 VirtualService 和 DestinationRule 的源码结构理解配置如何被转换为 Envoy 的配置。这个阶段的关键指标是你拿到一张网络拓扑图能说出每一条访问链路在网格里对应哪些配置。第四个月开始学 Dapr。先把所有构建块过一遍然后挑 Pub/Sub 和 State Management 做一个小项目比如一个简单的订单系统数据落到 Redis异步通知通过 Dapr 发送。之后尝试写一个自定义组件理解 Dapr 的组件模型和可移植性边界。第五、六个月做综合项目把 Operator、Mesh、Dapr 放到同一个环境里协同工作模拟一个内部平台底座。5.2 配套技能和源码阅读策略除了三个主方向本身配套技能决定了你能钻多深。Go 语言是必须的这三个核心组件的源码基本都是 Go不会 Go 的话读源码的效率会大打折扣。Kubernetes API 的设计规范也很重要尤其是 ObjectMeta、Spec、Status 这些设计模式理解了它们你才不会把 CRD 写成一锅粥。如果后面想深挖 Service Mesh 性能网络协议和 eBPF 相关知识会是加分项但不必一开始就碰。源码阅读我推荐“从入口抓主线”的方法。以 Operator 为例不要从头到尾读 client-go而是先在你的控制器里打断点看一次 Reconcile 触发的完整调用链。这样你只关心与当前场景相关的路径而不是把所有工具库都翻一遍。读 Istio 源码也一样先从一个配置对象如何被校验、分发到下发到 Envoy 的过程看起比通读全仓库要高效得多。另外一个老生常谈但必须强调的习惯保持英文文档阅读能力。云原生项目的最佳实践和技术讨论基本都在英语世界中译版往往滞后且丢失细节。你不需要多高的英语水平能顺畅读懂文档和 issue 讨论就行随着阅读量增加这个能力会自然成长。5.3 如何把学习输出沉淀成个人体系学习如果不产出三个月后基本就只剩“我好像学过”的印象。我的习惯是每周写一篇实验小结记录环境、步骤、遇到的问题和最终结论。这听起来很简单但坚持下来之后你会发现自己对知识点的理解颗粒度越来越细。那些当时没想明白的问题往往在写文档的过程中突然想通了。还有一个很有效的方法是“讲给别人听”。不管是团队内部技术分享还是写社区帖子只要你能把一个概念用大白话讲得让不太懂的人听懂说明你真的理解了。我在学 Service Mesh 的时候就给同事讲过一遍“Sidecar 是怎么劫持流量的”讲完之后才知道自己哪些地方其实还没吃透。学习要有闭环。输入阶段是读文档、跑 demo内化阶段是写代码、改配置输出阶段是写笔记、讲给别人。形成这个闭环之后你学到的不是零散知识点而是一张能连接起来的技术网络。6. 一些个人体会与建议聊到最后说几句实在话。我见过太多人追新框架刚出个新技术就急着扑上去结果每个都只懂点皮毛什么都没沉淀下来。云原生深度的这三个方向每个都值得投入至少三到六个月它们不是互相替代的关系而是层层递进的关系。在我个人实际学习顺序上强烈建议先 Operator再 Service Mesh后 Dapr。理由是 Operator 能帮你把 Kubernetes 的扩展机制吃透这是理解后面所有控制面技术的地基。Service Mesh 把网络层的复杂性变得可编程这时候你对“平台能力”的感知会上升一个台阶。Dapr 再把你推向业务开发的另一侧让你从代码视角思考如何屏蔽基础设施差异。顺序反了的话很容易陷入“到处都懂、到处都浅”的状态。踩过几次坑之后我的体会是学习过程中最好的老师不是文档而是线上问题。你在真环境里遇到的一个 RBAC 报错、一次网格配置误伤、一个状态偏差比你看十篇文章都管用。所以千万别只在自己电脑上跑测试环境有条件的话找一个边缘业务或者内部工具先练手。云原生深度这件事说到底不是一门“知识型”学科而是一门“工程型”学科真正的成长只能来自一次次看得见的生产实践。
返回列表