01 Platform and DevOps Preliminaries
一段 Bits 可以先作为数据读入内存,然后被 CPU 作为指令执行:数据的这种灵活性是虚拟机镜像的基础,计算机的 boot process 同理。
-> 虚拟机镜像文件作为一个数据文件被传输,虚拟化软件读取这个文件。
计算机的资源有 CPU/内存/磁盘/网络 这几种。硬件是有限的,昂贵的;操作系统通过隔离(Isolation)和共享(Sharing),制造了一种假象,让每个程序都觉得自己独占了整台电脑。
程序执行需要状态(state)和指令,服务可以是有状态或无状态,这和分布式系统的设计有联系。
- 有状态: 服务会记住之前的交互(比如登录状态)。
- 无状态: 每次请求都是独立的,不保存历史信息。如果需要数据,必须通过参数输入或从数据库读取。
理解了单机资源管理后,视角就放大到多台计算机的协作:单机隔离得再好也有上限,于是要把成千上万台机器连在一起,也就是分布式系统。
一旦跨机器,单机的秩序就失效了。这里提出几个关键问题:
- Discovery:组件如何找到对方?
- Scaling:组件如何扩展处理更多请求?
- Message communication patterns:消息是怎么从一个组件传到另一个组件的?即,消息从 A 到 B 经过了哪些中转站(如网关、负载均衡器)的路径?
- Failed component recovery:组件挂了怎么恢复?系统必须能检测,然后自动重启或切换到备用机。
单机的资源隔离和共享机制能在某种程度上类比到分布式系统(比如虚拟机、容器),但分布式环境增加了新的挑战——组件之间要通信、要扩容、要处理失败。
所有的架构设计(微服务、容器、负载均衡)最终都是在两个指标之间做取舍:性能和可用性。
02 Virtualization
VM
虚拟机(VM)是一个软件构造,它把物理主机的 CPU、内存、磁盘和网络资源虚拟出来,让软件以为自己运行在真实的物理机上。
VM 需要一个管理虚拟机的操作系统 Hypervisor,它有两种类型:
- Type 1:直接运行在主机硬件(云端服务器、数据中心)上。
- Type 2:装在现有的 OS(Windows/Mac) 上作为一项服务,让用户可以运行那些和主机 OS 不兼容的其他 OS。
Hypervisor 主要有两项职责:
- 管理各 VM 内部的代码执行,包括 I/O 和系统调用;
- Hypervisor 负责拦截 VM 发出的指令(特别是 I/O 请求和系统调用),并把它们安全地转发给物理硬件。 这保证了隔离性——一个 VM 崩溃不会波及另一个。
- 管理 VM 本身,创建、销毁、监控、资源分配。
VM 的启动类似于物理机,通过 Boot Loader 加载操作系统,VM 中的软件几乎感觉不到自己是在虚拟机里运行。
VM 镜像(Image)和实例(Instance)是很容易混淆的概念。前者是静止的状态,是一个包含 OS 和软件的 bit 集合,存在磁盘上的文件;而后者是运行的状态,当 Hypervisor 把镜像读入内存并开始执行,它就变成了一个实例。
创建新的镜像有几种方式:
- 从已有运行节点快照
- 基于已有镜像添加软件
- 从零安装操作系统并制作镜像
镜像一般只包含操作系统和基础程序,服务通过配置(configuration)加载,这样能减少镜像管理复杂度。
Containers
VM 的问题在于太重量级了,每个 VM 都带几个 GB 的操作系统,传输慢、启动久;于是诞生了容器(Container)。
容器不再虚拟化硬件,而是虚拟化操作系统,所有容器共享同一个主机的 OS 内核。它是可执行镜像,包含服务和依赖库,由容器运行时引擎(Docker、Kubernetes等)管理。每个容器本质是被隔离的一组进程,它有自己的文件系统、进程空间、网络命名空间、用户/权限视图。
注意,容器能做到看起来像机器,是 Linux 内核提供的能力,而不是 Docker 发明的:
- namespace:让进程以为自己是系统里唯一的进程、唯一的网络
- cgroup:限制 CPU、内存、IO,用量像配额
- chroot / overlay fs:提供一个看起来完整的文件系统
Docker、containerd 只是把这些东西打包成一个工具。
因此可以解释下面的现象:
1)容器启动非常快,因为只启动进程,不 boot kernel
2)容器镜像很小,因为不需要 kernel 等硬件资源,只需要用户态依赖
3)容器不能跨内核。Linux 容器只能跑 Linux 程序,Windows 容器只能跑 Windows,不能在 Linux 容器里跑 Windows 内核级程序
-> 容器和直接在 OS 里运行程序的区别在于视野、资源边界、依赖不一样;容器里装的是程序本身,和 “为了让某个程序在任意 Linux 上确定性地跑起来,所需要的全部用户态东西” (动态库、文件系统结构、运行配置和参数)。缩小化地类比,如果整台物理机是 Python,那么 Dockerfile 可以类比装虚拟环境时需要的 requirements. txt。
容器通过层(Layers)构建,每层对应一个增量修改,更新时只需传输修改的层,从而能减少开销。
-> 比如一个 LAMP 栈(Linux, Apache, MySQL, PHP),只修改了 PHP 代码,部署时就只需要传输 PHP 这一层,下面的层如果目标机器已经有,就不传了。
容器注册表(Container Registry)用于存储和分发容器镜像。它类似 Git 版本控制,可以 pull、push、打标签管理版本;分为公共和私有注册表,私有注册表能保证安全性和规范性。
Serverless / FaaS
这是抽象的最顶层:在 Serverless 中,开发者连容器镜像都不用管了,只需要提供代码(函数)。
-> 实质上是短生命周期容器,云提供商负责容器运行环境,开发者只需提供函数或服务代码,云端按需分配、执行和回收容器。
这种模式要求容器无状态(因为容器随时可能会回收,不能在本地存任何东西),状态数据需存储在云提供的基础设施服务中。它的缺点在于冷启动时间可能比较长,但之后请求处理速度非常快(微秒级)。