上篇里,Dukaan 靠加服务器、读副本和安全的部署流程扛住了流量。但规模增长的本质,就是瓶颈永远会迁移——“我们优化了机器,却忘了优化组织。“本篇接着讲第 8–13 章:第一个微服务、Kafka 数据一致性、Docker 容器化、世界级搜索、CDN 全球分发、Kubernetes 编排。六步走完,Dukaan 从一台哮喘般的单机,变成一支由 Kubernetes 管理、可自愈、可扩展的容器乐团。

第 8 章 拆掉单体:我们的第一个微服务

故事线:团队扩到约 10 人,拆成 Growth(卖家体验)与 Operations(支付、物流、履约)两个 squad,但都还在同一个单体 Django 里写代码。某天两个小队并行改 Order 模型——一个加 tracking_id、shipping_provider,一个加 discount_code——各自在 Staging 测过,物流功能先上线,一小时后折扣功能上线,混乱爆发:支付确认 webhook 对每一笔订单都失败,钱扣了但订单没标 “Paid”,卖家看到的是顾客付款后订单凭空消失。Suumit 的电话立刻打来——这已经不是”网站慢一点”,而是”用户的钱出问题了”。半小时手忙脚乱调试后找到根因:Growth 加字段时顺手改了 Order 保存方式的细节,而物流变更恰好依赖旧的保存行为。

关键决策:复盘确认瓶颈不在服务器、数据库、流水线,而在单体本身——一张巨大的依赖网,支付耦合订单,订单再耦合物流、卖家、商品,“安全地”交付新功能的速度急速下降。单体像一家超级大餐厅,一个后厨做意面、tacos、中式面条,一个总厨盯所有工位;微服务像一个 food court,披萨档、taco 档各有小厨房、专门厨师和食材,优点是团队自治、技术专用化、故障隔离,代价是要面对网络通信、服务发现、分布式数据这些单体里不存在的问题。第一刀的选择标准有三条:一是业务关键度低——你学做菜不会一上来就给国宴做主菜,先从沙拉开始,Payments、Orders 这种生死级域必须排除;二是依赖少而清晰——从毛线球边缘松的线头开始拆,Orders 依赖 Users、Products、Payments、Logistics,牵一发动全身;三是领域边界清楚(bounded context),一句话能说清职责。最终选定 Storefront(店铺前台):挂掉时顾客没法”看”店铺,但不致命,卖家仍能管理后台,不丢钱不丢数据,“它是沙拉,不是主菜”;它几乎纯 read-only,从读副本取数据,与写逻辑耦合极少;一句话就是”展示卖家的公开只读商品目录”。附加优势是扩展曲线完全不同:热门店铺页一天上百万访问、卖家后台一天只有几千访问,可以给 Storefront 单独配高性能机器省下大量成本。拆法用 绞杀榕模式:绝不重写,像绞杀榕沿老树生长那样,选一块功能新建独立微服务,在旧单体前放一个路由器(Nginx 兼任),起初所有流量走旧单体,再按 use_new_storefront cookie 只让 /store/ 请求里打了标记的人走新服务——先内部员工,再 10%、50%,最终 100%;某块功能全量切流后,单体里那段旧代码就”死了”,可以安全删除,然后重复下一块。

数字:支付事故在第二个功能上线后 1 小时内爆发,半小时定位根因。Storefront 拆出后可独立扩展——单独配 20 台高性能服务器,而卖家后台维持 3 台更轻的服务器。

关键结论:

  • 永远不要从零重写复杂系统。风险太高,分阶段、逐块替换才是更安全也更高效的道路。
  • 绞杀榕模式是渐进替换单体的黄金标准,允许你在真实流量中验证新服务,把风险降到最低。
  • 现有反向代理(如 Nginx)本身就是实现 Strangler 模式的强工具,可在新旧系统间做精细流量路由。
  • 第一刀要选简单、低风险、边界清晰的部分——店铺前台、搜索这类读多写少的服务是理想的第一拆分对象。
  • 微服务能解决团队规模问题,但会带来更复杂的技术挑战:服务发现、网络延迟、分布式一致性成为新的必修课。

坑:两个功能单独测试都通过、一起上线就冲突——单体内并行开发的安全边界根本不存在,流水线完全按要求执行,它没有错。Storefront 只是简单只读服务,这次拆分相对温和,但它掀开了潘多拉魔盒:以后拆 Orders 时,order-service 怎么找 user-service 验证顾客?怎么和 payment-service 确认交易?支付成功扣款但 order-service 创建订单前崩了怎么办?这就是分布式事务,软件工程里最难的问题之一。从这一步起,“我们不再只是写应用的人,而被迫成长为真正的分布式系统工程师”。

第 9 章 不可违背的承诺:用 Kafka 保证数据一致性

故事线:第一个微服务成功后,把数据一致性串起来的关键,是第 7 章那个用 Postgres LISTEN/NOTIFY 的小 Python 服务 Cache Invalidator:听到商品变更广播就去删 Redis 缓存。某天一位卖家打电话来:“我这一个小时一直在做闪购!主商品价格改来改去,但顾客看到的一直都是旧价格。“日志显示 Cache Invalidator 已经无声无息地死了一个多小时——一次网络抖动断连,脚本没有自动恢复。重启后问题消失,但暴露了又一个单点故障。

关键决策:LISTEN/NOTIFY 不是为生产级消息传递设计的系统——它太脆弱,监听服务宕机期间广播的消息永久丢失,“数据库只是朝空气里喊一句,没人听见,就没了”;它扩不起来,天生适合一对一,再来一个 search-service 就得再写一个监听脚本;它不可观测,无法确认消息是否被处理。“说白了,我们的系统建立在一个美好希望上……那不是工程,那叫祈祷。“调研发现消息系统有两派哲学:传统消息队列(RabbitMQ)像邮政系统,消费者取走信件执行完任务就把信丢掉,消息是短暂的(ephemeral);分布式日志(Kafka)像报社,生产者是记者,把”店铺 456 的价格已变更”刊登到 product_updates 栏目(Topic),多个独立订阅者都能读,读完文章也不消失,整份报纸是一份持久完整的历史。顿悟点:同一个业务事件未来会有很多系统响应——一笔订单创建后要通知 shipping 履约、notification 发邮件、analytics 更新销售数据,“一条消息,多方响应”与报纸模型完全契合;持久性正好补上最渴望的可靠性,缓存失效服务挂了消息只在报纸上安全积压,恢复后从上次位置继续读。决定把 Dukaan 的中央神经系统建在 Apache Kafka 上。落地三步:第一,先上托管 Kafka 服务(Confluent Cloud、Aiven 这类),点几下就有生产级容错集群——在早期采用复杂技术时优先借助托管服务,把团队注意力留给”怎么用它解决业务问题”,不到一小时 Kafka”印报机”就上线;第二,用 Debezium 做 CDC(Change Data Capture)——它不是盯作者有没有喊”我改了”,而是像一台架在主手稿上方的高精度扫描仪,直接读数据库事务日志(WAL),哪怕改一个字都能捕获并格式化成标准消息发进 Kafka。这意味着单体应用甚至不需要知道 Kafka 的存在,只管把数据写进 PostgreSQL,应用层和消息系统彻底解耦,不存在”开发者忘了补发消息”;第三,重写消费者:删掉 LISTEN 代码换成标准 Kafka consumer 库,订阅 product_updates,解析 store_id 后对 Redis 执行删除。新链路是:Monolith App → Master DB(WAL)→ Debezium → Kafka Topic → Consumer Service(s) → Redis。

数字:Cache Invalidator 无声死了一个多小时,期间该卖家全部商品更新未触发缓存失效;Kafka 集群不到一小时上线;消费者组让失效器可以同时跑 2–3 个实例。

关键结论:

  • 简陋的消息机制很容易变成单点故障。想要真正可靠的服务间通信,需要像 Kafka 这样可持久化的事件日志。
  • CDC 是一种非常强的模式:借助 Debezium 直接从数据库日志流出事件,比在应用代码里到处手工发消息更可靠、也更解耦。
  • 面对复杂基础设施,早期优先借助托管服务,把精力集中在”用技术解决业务”,而不是一开始就陷入”如何把技术本身维护好”。
  • 每往技术栈里加一个强工具,都会增加运维复杂度,必须同步投入监控、知识和流程才能驾驭好。
  • 有了中央神经系统,你才算有了真正的地基。稳定可靠的事件流,是继续拆单体、做成真正微服务架构的关键前提。

坑:Kafka 不是魔法,它是一套需要认真维护和监控的有状态分布式系统——Broker 都健康吗?消息有没有被正确生产、正确消费?消费者是否出现积压(lag)?Topic 是否正在把 Broker 磁盘打满?“我们把旧系统的脆弱,换成了新系统的运维复杂度。像是从简陋小房子,搬进了一座自带发电厂的大庄园……这就是进入微服务世界的入场费。“

第 10 章 集装箱革命:Docker 入门

故事线:一条双头龙。第一头叫”不一致”:新工程师 Anjali 用 Python 库 reportlab-ng 做卖家 PDF 发票,在自己 MacBook 上完美,部署到 Staging 后文字错位、排版全乱,她拉下一样的代码重跑又是完美的——“我也不知道为什么……它在我机器上能跑。“几小时排查后发现:Ubuntu 服务器上的系统字体库和 MacBook 略有差异,而 PDF 库恰好依赖系统字体。第二头叫”低效率”:Suumit 在预算会上指着 AWS 账单问”上面写着我们跑了 20 台应用服务器,但流量图显示大部分时间只有一半机器在忙,那另外 10 台在干嘛?“每台服务器都是一整个完整虚拟机,有自己单独的操作系统,为安全起见每台只放一两个服务——Server A CPU 只用到 20%,Server B 才 30%,却没法合并负载。“我们像是为整辆车付了钱,却只坐了一个座位”,每月悄悄烧掉几千美元。

关键决策:两个龙头喂养于同一个根本问题——打包和运行软件的方式。Docker 就是软件世界的标准化集装箱:20 世纪 60 年代之前海运装满各种尺寸的桶、麻袋、木箱,装卸全靠人工;标准化集装箱出现后,吊机、火车、卡车不再关心箱子里装了什么。Docker 把代码、指定版本的 Python、依赖库甚至部分操作系统环境打包进一个标准盒子(container),扔到任何装了 Docker 的机器上结果都一样——“容器本身,就是那台机器”。三个核心概念:Dockerfile 是蓝图(IKEA 说明书),逐步写明从基础镜像、装 Python、拷代码、装依赖到启动命令;Image 是扁平包装盒,构建好可运输但尚未运行;Container 是组装好的书架,同一个镜像可以拉出无数个完全一致、彼此隔离的容器。容器化 Django 单体的 Dockerfile 无非是:从官方精简基础镜像起步、先拷依赖清单再安装(利于缓存)、再拷代码、容器启动时用 Gunicorn 起服务。与虚拟机的差别是成本关键:VM 像独栋房子,每个都跑一整套 Guest OS;容器像公寓楼,共享宿主机 OS kernel,但每套公寓有自己的门、墙和家具,仍然隔离。容器启动是秒级不是分钟级,更重要的是密度(density):一台原来只放一两个应用进程的大服务器,可以同时跑 10、20 甚至 50 个隔离容器。随之而来的是工作流革命——开发者不再推代码让服务器在自己环境里跑,而是 docker build 出镜像、本机 docker run 验证、推到 Container Registry(Docker Hub 或 Amazon ECR),Staging 和 Production 从 Registry 拉预先构建好的镜像运行。“我们不再交付代码,而开始交付镜像”,“它在我机器上能跑”从工作流层面被完全判了死刑。

数字:20 台应用服务器大部分时间只有约一半在忙;Server A CPU 20%、Server B 30%,每月低效烧掉几千美元;本地 Python 3.9.6 对服务器 3.9.2 这类环境漂移是常态。容器化后同一台宿主机可同时跑 5 个 Django 单体容器、10 个高流量 storefront-service 容器、2 个缓存失效器容器、3 个图片缩放服务容器;应用服务器舰队缩减了 60% 以上。

关键结论:

  • Docker 一次性解决了两头龙:环境不一致(“我机器上能跑”)与服务器低利用率。
  • Dockerfile 是运行环境的蓝图,把依赖和运行条件写死,保证应用”到哪都按同一种方式跑”。
  • 把思维从”交付代码”切换为”交付镜像”,镜像是应用和其运行环境的一体化、不可变打包产物。
  • 容器可以显著提高服务器密度,在单台宿主机上运行多个隔离容器能大幅降低基础设施成本。
  • 管理几个容器很轻松,管理几百个会变成噩梦。一旦全面进入容器世界,你迟早会需要一个容器编排器。

坑:Docker 擅长构建、发布、运行单个容器,但生产环境成了几百个容器分布在多台服务器上的复杂系统——某台服务器挂了,上面那 50 个容器怎么自动搬到健康机器?某个容器崩了谁自动重启?发布新版本如何把 100 个旧容器平滑换成 100 个新容器且不产生停机?storefront-service 的容器怎么找到 core-api 的容器?“我们现在拥有的是一支庞大的容器乐团,但管理方式仍然是人工逐个告诉每位乐手什么时候上台。“这需要指挥家——Kubernetes。

第 11 章 聪明的店员:打造世界级搜索

故事线:问题先出现在一封带着不满情绪的邮件里,而不是监控报警里。增长最快的卖家之一经营大型线上鞋店,Diwali 大促投放,广告流量很高、销售却低得离谱:“他们搜 ‘running shoes’,结果显示 0 个。我明明有 200 多种跑鞋!搜 ‘sneakers’ 也找不到。我的爆款商品叫 Nike Air Zoom Pegasus 38 Men’s Running Shoe,但如果用户输入 Nike Pegasus running shoes,它也不会出来……你们让我在丢钱。“分析数据更残酷:使用过搜索框的用户,转化率远低于手动浏览商品的用户。

关键决策:根源是 MVP 时代一条朴素的 SQL——SELECT * FROM products WHERE name ILIKE '%running shoes%',本质是大小写不敏感的子串匹配。它像图书馆里一个过于死板、也有点笨的管理员:你问 “American Cars”,他只拿书名里完整包含这个短语的书,The Automobiles of the USA 直接忽略。这个笨店员在 4 个关键点上辜负用户:要求输入非常完美(顺序必须一致,Nike shoes for running 找不到 Nike Running Shoe for Men;搜复数 shoes 找不到标题用单数 Shoe 的商品);没有上下文理解(不知道 sneakers 和 trainers 同义,不知道 run/runs/running 是同一词根);完全不容错(输成 runing shoes 直接返回空);不按相关性排序(按数据库默认顺序返回,描述里写 “inspired by vintage running shoes” 的 T 恤可能排在真正热销跑鞋前面)。更糟的是 LIKE '%...%' 很难用索引,往往触发全表扫描,商品从几百涨到几万只会越来越慢,还拖累读副本。结论:不是”再打磨一下”,而是从方向上换掉他。答案是很久就明确的 Elasticsearch——不是 PostgreSQL 的替代品,而像一辆 Formula 1 赛车,只有一个目标:在海量文本里以毫秒级速度进行极其聪明的搜索。如果 PostgreSQL 是谨慎看守原始藏书的图书管理员,Elasticsearch 就是把每本书都读透的天才研究助理,为每个概念建好交叉索引。它的四大超能力:一,倒排索引——搜索前先把所有商品读一遍,建立”每个词对应哪些商品”的映射("nike" -> [Product 1, Product 5, Product 88]……),搜索时只是取出 nike、running、shoes 三份列表快速求交集,几毫秒出结果,这是它在数百万文档里依然飞快的根本原因;二,高级文本分析——分词(Tokenization)、标准化转小写(Normalization)、词干提取(Running→run、Shoes→shoe,光这一点就解决了大量搜索失败)、自定义同义词词典(告诉它 sneakers、trainers、kicks 都当作 shoes);三,相关性评分——用 BM25 这类算法判断:命中 product_title 远比命中 product_description 重要,稀有关键词比常见词更值钱,用户最可能购买的商品总排在最前;四,模糊匹配——按编辑距离容忍错别字,输入 runing sheos 也能给出 running shoes。实现上顺理成章:新建独立微服务 search-service,唯一职责是管理 Elasticsearch 集群并给 Storefront 暴露搜索 API,然后把它注册成 Kafka 已有 Topic 的另一个消费者——“像是在已经建好的电网里,再插一个新电器”。

数字:搜 “running shoes” 返回 0 个结果,而该卖家有 200 多种跑鞋;搜索在数百万文档上毫秒级返回。

关键结论:

  • 仅靠简单数据库查询(比如 SQL LIKE)并不能替代真正的搜索引擎,对电商体验来说需要专门工具。
  • Elasticsearch 提供了电商搜索最关键的能力:相关性排序、拼写容错、语言分析,这些能力直接决定转化率和用户体验。
  • 事件驱动架构加上 Kafka,是同步数据库、缓存、搜索索引等多系统状态的极其优雅且强大的方案。
  • “一条事件、多方消费(fan-out)“是构建可扩展、低耦合微服务架构的核心模式之一。

坑:败在起点——为了快速做 MVP 选了最天真的实现,地基本身错了,几条花哨的 SQL 技巧修修补补没有意义。数据流一旦用对架构就漂亮得迷人:卖家改商品 → 落在 Mumbai 的 PostgreSQL → Debezium 盯到 WAL 变化 → 写入 Kafka product_updates → 缓存失效器删 Redis、搜索服务把同一条消息转成 JSON 文档送进 Elasticsearch 建索引,两个消费者互不耦合。主单体对缓存和搜索引擎的存在一无所知,不改主应用代码就能基于同一条事件流不断长出新能力。

第 12 章 配送小哥:静态资源的 CDN

故事线:平台不再是印度本土产品,东南亚、欧洲、南美的店铺不断冒出来,全球化暴露出新薄弱点——它和 CPU、内存、代码都没关系,直接来自光速。最先抱怨的是一位巴西圣保罗的手工艺品卖家:“可我的顾客在 São Paulo 打开商品图时,图片加载特别慢。他们以为是我网站坏了。“几乎同时,Suumit 把 AWS 账单发来,在 Data Transfer Out from EC2 一行标了红——这部分成本涨得比服务器本身还快。两个问题其实是一个问题的两个面。

关键决策:全部基础设施都集中在 AWS Mumbai 区域。一张商品图的旅程:圣保罗卖家上传 2MB 高清图,沿海底光缆跨越 14,000 多公里存到 Mumbai;顾客请求这张图再走 14,000 公里到 Mumbai,服务器找到后原路发回。受制于光速,光物理路程就给每张图片增加数秒加载。“Data Transfer Out”本质是 AWS 按流量向公网”寄数据”收的邮费,每天发几百万张大图时邮资变成巨额成本。我们的服务器像一座唯一的中央图书馆设在 Mumbai,保存着全世界的相册——巴西有人要看照片,就真的把照片从 Mumbai 邮寄过去。标准答案是 CDN:把一座 Mumbai 总图书馆变成全球连锁图书馆,São Paulo、London、Singapore、Tokyo 都有分馆;新相册发布到总馆(源站)后自动复制到各分馆,顾客请求自动路由到最近的 edge location(边缘节点)。这叫边缘缓存(caching at the edge):某地区第一次有人请求某文件时,边缘节点回源 Mumbai 取副本并存下,之后该地区其他用户直接从本地副本秒级返回。CDN 一次性解决两个问题:速度——静态文件从更近的节点返回,加载时延从数秒降到毫秒级;成本——CDN 服务商以更低成本采购带宽,单位流量价格远低于自己从 EC2 出网。落地选了 Amazon CloudFront(整套基础设施都在 AWS,同一控制台管理复杂度最低),分三步:先把静态文件源站迁到 Amazon S3——Django 改成卖家上传图片时直接传到 Mumbai 的专用 bucket dukaan-product-images,作为唯一真相源;再创建 CloudFront Distribution——Origin Domain 指向该 S3 bucket,缓存策略 TTL 设 24 小时,点击创建后 AWS 花约 15 分钟把配置同步到全球数百个边缘节点,得到形如 d123xyzabcdef.cloudfront.net 的域名;最后改应用链接,配了自定义域名 cdn.dukaan.app,图片地址从 https://dukaan.app/media/seller123/product.jpg 换成 https://cdn.dukaan.app/seller123/product.jpg。

数字:2MB 图片往返 14,000 多公里;TTL 24 小时;Distribution 部署约 15 分钟同步到全球数百个边缘节点。效果:页面打开时间从 6 秒以上降到 2 秒以内(第一次加载仍稍慢,是 cache miss,边缘节点要先回源 Mumbai);月末账单上 “Data Transfer Out from EC2” 几乎被砍掉,只剩一条小得多的 CloudFront 费用,带宽成本下降 70% 以上。

关键结论:

  • 只要用户地理分布足够广,CDN 就不是可选项,而是必需品。它通常是最容易落地、也最有性能回报的优化之一。
  • CDN 一次性解决速度和成本两个问题:让用户从更近的节点取内容降低延迟,同时减少源站出网流量成本。
  • 静态资源要与应用服务器分离:图片、CSS、JavaScript 放进专门的对象存储(如 Amazon S3),再由 CDN 对外分发,是更可扩展也更省钱的架构。
  • 落地路径很直接:建一个存储 bucket 做源站,创建 CDN Distribution 指向它,再把页面里的静态资源链接切到新的 CDN URL。

坑:这条双头龙(海外体验差 + 出网成本失控)拖到被卖家投诉、被账单标红才处理。其实一次相对简单的架构升级,就同时让全球用户体验大幅提升、替公司省下一大笔钱。

第 13 章 指挥家:用 Kubernetes 编排一切

故事线:容器从十几个涨到上百个后,“我一度成了公司里薪水最高、压力最大的’手动码头工人’“。崩溃点在星期六凌晨 2 点:告警 Host Unreachable: app-server-07,一台 EC2 硬件故障整机下线,机器上同时跑着 30 个不同容器(API、缓存失效器、图片缩放服务等)。修复过程成了一场人工灾备演练:拉起一台空白 EC2、SSH 上去装 Docker、努力回想挂掉的机器上到底跑了哪 30 个容器,然后凌晨 2 点对着终端一条条手敲 30 个 docker run,祈祷别在长长的镜像名和端口映射里打错一个字。花了将近 1 小时才恢复。发布新版本同样可怕:一次 storefront-service 升级要替换 50 个运行中容器,只能手工停一个、起一个、等健康、再换下一个。Suumit 看出来了:“你看起来像一个星期没睡了。我们交付速度是快了,但你成了新的瓶颈。公司稳定性不能建立在你半夜手工敲命令这件事上。“于是他说:“我需要消失一周……把 Kubernetes 这玩意儿啃明白。我觉得这是我们活到下一阶段的唯一办法。”

关键决策:人工方式在 4 个方面无能为力——没有自愈能力(容器崩了只能等人手工拉起)、没有智能调度(容器跑哪台机器全靠人拍脑袋)、没有自动化发布(手工停旧启新风险高得离谱)、没有服务发现(order-service 要调 user-service,后者 50 个容器随时重启、迁移、换 IP)。我们缺的不是某个单点工具,而是整个软件类别:容器编排器。理解它最好的比喻是交响乐团:单个容器是乐手(storefront-service 是小提琴手、数据库连接器是大提琴手、图片缩放是打击乐手),把 100 个技术高超的乐手扔进房间让他们”自己演奏”,得到的只会是灾难性噪音;指挥家不演奏乐器,他手里拿着总谱(desired state,目标状态),如果某位乐手的琴弦断了(容器崩了),马上示意另一位顶上。Kubernetes 就是这个指挥家——它不替代 Docker,Docker 仍是每位乐手的乐器,Kubernetes 是统筹整支乐团的大脑。核心词汇:Node 是乐手坐的椅子(装好 K8s 组件的 EC2 服务器);Pod 是真正坐在椅子上的乐手——最小可调度单位,包装一个或多个容器,绝大多数时候可以把 Pod 理解成”一个正在运行的容器”;Service 是乐团分区牌子——Pod 会死、会拿到新 IP,Service 为一组 Pod 提供稳定恒定的固定 IP 和 DNS 名称,像 20 个小提琴手前面立一块 “Violins” 的牌子,自动挑健康 Pod 转发,相当于内部负载均衡器,Pod 崩掉替换后调用方完全无感知;Deployment 是总谱——写着”我要 50 个 storefront-service 的 Pod 副本""用 dukaan/storefront:v2.1 这个镜像""换谱子时用 rolling update 一位位替换,确保音乐不中断”。Kubernetes 的魔法在于持续拿总谱对比实际状态:总谱要求 50 个 Pod、实际只剩 49 个,它不会慌,只会看见”现实和目标不一致”,立刻在健康 Node 上补一个——这就是自愈。哲学转变是从命令式到声明式:旧方式像事无巨细的经理教木工学徒”先拿 4 条腿、再找坐板、左前腿用 3 颗螺丝拧上去”,Kubernetes 方式是把椅子蓝图交给一位经验丰富的老木匠——“我要一把四条腿、蓝色、20 英寸高的椅子”,他按蓝图造出来并持续维护,使现实始终贴着蓝图。这些声明写在 YAML 里。第一份 storefront-deployment.yaml 的要点无非是:声明这是一个 Deployment,要求 50 个副本,通过 app: storefront 标签找到自己管的 Pod,容器镜像 dukaan/storefront:v2.1,监听 8000 端口。交给指挥家的动作只有一条命令:kubectl apply -f storefront-deployment.yaml——Kubernetes 读入目标状态,对比当前发现匹配 Pod 数为 0,reconciliation loop 触发,调度器在不同 Node 上智能找空间,几秒内启动 50 份一模一样的 Pod。发布 v2.2 时只把 YAML 里镜像一行改成 v2.2,再执行同一条命令——它自己推导差异,开始 rolling update:杀一个旧 v2.1、拉一个新 v2.2、等健康、再换下一个,50 个副本不停机平滑切换。“这就是我们一直梦寐以求的:自动、安全、无聊的部署流程。“最后是让用户听到音乐:Pod 只活在集群内部私有网络里,“我们拥有了一支乐团,却没有观众”。Service 的四种开门方式——ClusterIP 是内部对讲机,默认类型,只能在集群内访问,服务间通信的主力;NodePort 是消防通道,把服务暴露到每台 Node 的高位端口(如 Node_IP:30080),可用但不优雅不安全,适合调试或临时访问;LoadBalancer 是正门大厅,K8s 自动向云平台申请一个真正的外部负载均衡器,适合对外公开单个服务;Ingress 是聪明的前台接待——每开一个 LoadBalancer 就多一层成本,10 个服务总不能买 10 个大门,Ingress 放在多个 Service 前面,一个外部 LoadBalancer 接住所有流量,Ingress Controller 按票(URL 路径 /api、/storefront 或主机名 api.dukaan.app、store.dukaan.app)分流量。对 storefront-service 我们最终选了 Ingress:整个集群一个主入口,规则是访问 dukaan.app/store/* 的请求都转给 storefront-service 的内部 ClusterIP,再 kubectl apply 交上去。“那一刻,排练厅的门终于打开了。”

数字:凌晨 2 点的故障机上跑着 30 个容器,人工恢复花了将近 1 小时;一次 storefront-service 升级要替换 50 个运行中容器;Deployment 先后声明 50 个副本;发布 v2.2 只改 YAML 一行。

关键结论:

  • Kubernetes 是容器乐团的指挥家,它用声明式管理取代高风险的人肉操作。
  • 拥抱声明式配置:别再告诉系统”怎么做”,而是写 YAML 明确声明”我要什么结果”,让 Kubernetes 自己对齐现实。
  • 理解核心组件:Node 是服务器,Pod 是运行实例,Deployment 是总谱,Service 负责通信。
  • Kubernetes 天生提供强大的自愈能力:Pod 挂了会自动补,Node 故障了也会被重新调度。
  • 给外部流量选对入口工具:内部通信用 ClusterIP,面向用户的统一外部入口优先用 Ingress。

坑:学习曲线”简直像一堵直立的墙”,文档里全是陌生词——Pods、Deployments、Services、Ingress、ReplicaSets、YAML,像在学一门外星语言;但自动化、自愈、系统级编排的承诺实在太诱人。更深的教训是 Suumit 那句话:人的带宽不能成为公司稳定性的依赖,“公司稳定性不能建立在你半夜手工敲命令这件事上”。


到这里,公司的技术引擎已从头到脚重造了一遍:从一台哮喘般的单机,到一支由 Kubernetes 管理的、可自愈、可扩展、稳健的容器乐团。但堡垒即将迎来一场真正的风暴——第 14 章,Shark Tank 效应的压力测试。