☁️ 第四章 构建云服务的技术
📄 一句话概括

容器解决了【打包一致】,但没解决【谁来决定跑在哪、跑几个、坏了怎么办】——编排做的就是这件事:你声明想要的状态,它负责让实际状态一直等于它;而正因为这套规则是所有厂商共同实现的,同一份声明可以放到任意一家云上执行,这才是【多云】在技术层面能成立的原因。

📝 本节和前面几节的关系

上一节(4-6)讲的是容器本身:它很轻、启动很快、能打包得很干净。这一节讲容器多了之后的问题。

一台机器上跑一个容器,你自己动手就行;跑几十台机器、几百个容器,就没人能手工管了。从【一个】到【很多】之间的那一步,就是编排。 教材这一节的标题里有【实现多云计算】四个字,说的正是编排带来的另一个后果:因为大家用的是同一套规则,所以你的东西能在云之间搬。

一、容器解决了什么,没解决什么

先说清楚分界,否则会误以为【有了容器就不需要别的了】:

已经解决(容器给的) 仍然没解决(编排要给的)
应用和依赖打进一个镜像 这个镜像该跑在哪台机器上
在任何机器上以同样方式启动 要同时跑几个副本
启动只要一两秒 其中一个挂了,怎么补回来
应用之间互不干扰 怎么更新到新版本且不中断服务
流量怎么分到这几个副本上

右列这五件事,全都需要【看全局】才能决定——而容器本身只看得到自己那一个进程。编排器就是那个看全局的角色。

二、编排做的四件事

以 Kubernetes 为例,它的功能可以归成四组:

做什么 具体是什么 没有它会怎样
调度 决定这个容器放到哪台机器上 你自己一台台挑
自愈 副本少了就补、机器挂了就搬 半夜起来手工重启
扩缩容 按 CPU 或队列长度自动加减副本 高峰时手工加机器
发布 滚动更新、回滚到上一版 停机发版,出事手工回退

这四件事有一个共同点:都是【反复发生的、可以写成长规则的判断】。所以编排器的本质是把运维的判断写成声明式规则:

你说:【我要 3 个运行中】
它做:一直让实际状态等于 3 个,直到你改口

注意这跟【执行一次命令】的区别。命令是【做一次】,声明是【一直保持】。这个区别是后面 4-8 讲【不可变基础架构】的前提。

三、为什么它让多云成为可能

这一节标题里的【实现多云计算】,指的是:

同一份编排配置,放到不同的云上,行为是一样的。

成立的原因有两层:

  1. 接口层:Kubernetes 是开源的、由多家共同参与的项目,所有云厂商都提供【兼容版】——你在 A 家写的配置,在 B 家能用。
  2. 抽象层:你的配置描述的是【我要 3 个副本、要一个域名、要一块盘】,没有描述【用哪家的虚拟机、哪家的负载均衡】——这些由云厂商的实现去对接。

所以多云的可行路径是【把差异压到最下面一层】:

层次 是否跨云一致 说明
应用与编排配置 一致 换云基本不用改
网络与存储的对接 需要替换 换云要改配置
底层机器与机房 完全不同 换云等于换供应商

换句话说:多云从来不是【所有东西都能随便搬】,而是【越往上越容易搬】。 这正是 2-9 讲【多云是风险对冲而不是技术炫技】时的那层意思。

⚠️ 2026 现状:容器与编排的今天
  • 编排这一层已经收敛了。Kubernetes 成了新应用的事实标准,讨论的重点从【用不用】变成了【怎么用好】。同时 Argo CD 这类声明式发布工具跑在约六成的集群里——说明【声明式】不只是编排的做法,也成了发布的做法。
  • 但虚拟机没有消失。多数组织现在是混合部署:老系统在虚拟机里,新系统在容器里,同一个平台同时管两者。教材写这节时【容器 vs 虚拟机】还像是一个选择题,现在是并存题。
  • 隔离强度的短板有了新的补法:微虚拟机(Firecracker、Kata Containers 这类)在容器外面套一层极轻的虚拟机边界,把【启动快】和【隔离强】两件事往一起凑。所以【容器隔离弱】这句话在 2026 年要加一个前提:取决于你用哪种运行时。

四、动手 Demo:少一个就补一个

看到什么算成功:

==================================================================================================
【Demo 7】容器编排:你要的 3 个副本,少一个就补一个
==================================================================================================
  Kubernetes 的兜底逻辑只有一句:
  【你声明想要的状态,它负责让实际状态一直等于它】。

  场景一:声明副本数 = 3,然后挂掉 1 个。

    期望状态:3 个运行中
    实际状态:2 个运行中(1 个被杀了)
    控制器动作:新建 1 个
    结果:3 个运行中(12 秒内完成)

  场景二:滚动更新,副本数 6,一次多起 1 个、最多停 1 个。

    步骤      旧版本    新版本    总运行数
    ------------------------------------------------------------
    开始      6         0         6
    第 1 步   6         1         7
    第 2 步   5         2         7
    第 3 步   4         3         7
    第 4 步   3         4         7
    第 5 步   2         5         7
    第 6 步   1         6         7
    第 7 步   0         6         6
    ------------------------------------------------------------
    全程可用副本不低于 5 个,业务一次都没断。

  这就是 4-7 标题里【实现多云】的那层意思:
  同一份声明式配置,放到哪家的 Kubernetes 上都按同一套规则执行。
  换云服务商时,改的是底下的机器,不是上面的编排逻辑。

读结果:

30 秒实验

在能上网的机器上(或直接看文档),执行:

kubectl get pods

如果没有装,就搜【Kubernetes 官方教程 交互式】,打开网页版的在线终端,跑一条 kubectl get pods -A,看两件事:

  1. 看 NAME 那一列:一堆名字后面跟着一串随机字符(-7d4b9c8f5-x2k9p),那串随机字符就是每次重建时新长出来的。
  2. 看 READY 那一列:写着 1/1 或 2/2——斜杠左边是当前就绪的容器数,右边是你声明的数量。

做完这个你会看到【声明式】的形状:你从来不告诉它【去启动一个容器】,你只告诉它【我要几个】;它自己盯着那个数字。

常见误区

  1. 错误:用了 Kubernetes 就等于上了云原生。——纠正:编排只是云原生四件事里的一件。另外三件是:应用被拆成微服务(4-10)、用声明式方式描述(4-8)、容器不可变(4-8)。如果应用本身还是一个改一行就要全量发布的大单体,那放进容器并编排起来,只是在把它更快地部署出去而已——好处有,但有限。所以判断标准是【应用是不是按这个思路设计的】,不是【用没用某个工具】。

  2. 错误:Kubernetes 能做多云,所以可以随时把系统从 A 云搬到 B 云。——纠正:能搬的是【编排层】,搬不动的东西还很多。数据放在哪(对象存储、数据库)、网络怎么连(专线、私有连接)、各家的托管服务(消息队列、AI 接口)——这些换云都要改。所以多云的现实做法通常是【把可搬的部分放上层,把绑定的部分留在原地】,而不是【整套平移】。能搬的那部分值多少钱,要看你的系统里有多少是真正无状态的。

  3. 错误:声明式就是【用配置文件代替敲命令】。——纠正:区别在【谁负责维持结果】。敲命令是你让它做一次;配置文件交上去之后,有一个持续的控制器在盯着结果,并且在偏离时主动纠偏。所以声明式的价值不在写法,在有东西一直在替你验收。这也是为什么同样的 YAML 放到一个【只按一次执行】的系统上是没用的——它需要一个一直运行的控制器才成立。

  4. 错误:滚动更新期间【最多停 1 个】,说明这个策略很安全。——纠正:安全的上限取决于你的容量余量。如果 6 个副本平时 CPU 就跑到 80%,那么在只有 5 个可用时,剩下的那 5 个会一起被压到 96%——可能滚着滚着就雪崩了。所以滚动更新必须配一个前提:容量要有余量。这也是后面的 30 秒实验里值得看【就绪数/声明数】的原因——它告诉你现在有几个是真的能接流量的。

自测三问(点开对答案) 1. 【我要 3 个】和【启动 3 个】这两句话,差在哪? —— 差在**时间维度**。【启动 3 个】是一次的动作,做完就结束了;【我要 3 个】是一条持续的规则,**只要系统还在运行,就有人在不停地比较期望和实际**。前者的结果是【曾经有 3 个】,后者的结果是【一直是 3 个】。**这个区别带来的可靠性提升非常大**——因为实际系统里,容器每天都在因为各种原因退出(内存不足、节点故障、升级),**没有纠偏机制的话,几天之后剩下的数量是随机的。** 2. 为什么说多云的可行路径是【把差异压到最下面一层】? —— 因为**越往上越抽象,越抽象越不依赖具体实现**。你描述【我要 3 个副本、要一个域名】,这件事在任何一家云上都成立;你描述【我要一块 500 GB 的 SSD 云盘挂在 /data】,也在多数云上成立;但你描述【我要用某家的某个专用网络功能】——那就搬不动了。**所以多云实践的要点是【主动把可替换性设计进去】**:能用通用接口的就别用专用功能,**这是有代价的(可能性能差一点、功能少一点),换来的就是退路。** 3. 为什么【自愈】不需要知道故障原因? —— 因为**它管的是结果,不是原因**。控制器的逻辑是【期望 3、实际 2 → 补 1】,它不需要知道那个容器是被杀了、还是节点掉了、还是程序自己退了——**因为这些原因处理方式都一样:再起一个**。这个设计的巧妙之处在于:**它把一个开放的、无穷多种可能性的问题(故障诊断),换成了一个封闭的、只有一个动作的问题(补齐数量)**。代价是它不解决根因——容器反复重启说明程序有问题,那要人去看日志。

相关笔记