本节摘要:容器是自带土壤的花盆:把应用与它的全部依赖打包成一个可搬运、可复制的运行单元,做到一次构建、随处运行。本节梳理镜像、容器、仓库三者的关系,解释镜像分层机制,并用绿荫书屋的镜像构建做一次完整演示,为后续把"花盆"搬进"农田"打基础。
容器圈有句老话:"一次构建,随处运行"。它的底气来自一个朴素的做法:把应用程序连同它依赖的运行时、库文件、配置模板,一起塞进一个封闭的盒子。盒子搬到哪台机器上,打开就是同样的运行环境。这句行话反过来读更有味道:在容器普及之前,"在我机器上是好的"是工程师互相推诿的经典台词——环境差异才是那个年代部署问题的头号来源。
把这个盒子放到我们的农事框架里:容器就是花盆。花盆自带一整套土壤(文件系统),苗(应用进程)在盆里生长,与旁边的盆互不抢养分(隔离)。而花盆的"图纸"叫镜像:一份只读的模板,照着图纸能复制出无数个一模一样的花盆。图纸集中存放在镜像仓库里,谁要种,就去仓库拉一张图纸,本地照图成盆。
三者用一句话串起来:仓库存图纸,图纸造花盆,花盆长应用。动手感受一下:
# 从公共仓库拉取一张官方图纸(以 Nginx 为例) docker pull nginx:1.25 # 输出节选:每一行 Pull complete 对应一层下载完成 # 1.25: Pulling from library/nginx # a480a496ba95: Pull complete # f3ace1b8ce45: Pull complete # 11d6fdd0f8ea: Pull complete # Status: Downloaded newer image for nginx:1.25 # 照图纸起一只花盆,把本机的 8090 端口接到盆的 80 端口 docker run -d --name web-pot -p 8090:80 nginx:1.25 # 输出是一长串容器 ID,例如: # 3f9c2e4a1b8d7c... # 看看盆里的进程 docker top web-pot # UID PID PPID ... COMMAND # root 4211 4089 ... nginx: master process nginx -g daemon off;
三条命令过后,本机就有一株 Nginx 在花盆里活着。注意 pull 输出里的那些 "Pull complete":它们不是零碎的进度条,而是分层的证据。
镜像是多层只读文件叠出来的,每条构建指令对应一层,层层叠加构成最终视图。这么设计有两个直接好处:复用与差量传输。你拉十个基于同一个基础镜像的应用,基础层只下载一次;你升级应用只改了顶层几厘米的"表土",底层几十厘米的"基土"原样复用。这就是为什么容器化之后,交付物从动辄数 GB 的虚拟机磁盘缩到了几十 MB 的增量层。

贯穿全册的案例从这一节开工。绿荫书屋的网页前台暂时用 Nginx 托管静态页面,先给它造一张最简单的图纸。构建指令写法(内容保存为构建脚本,此处看要点):
FROM nginx:1.25 COPY site/ /usr/share/nginx/html/ # 两行就是两张层:基础层直接复用官方图纸,应用层只装书屋页面
# 构建并打上自己的标签 docker build -t greenlib/web:0.1.0 . # 输出节选: # Step 1/2 : FROM nginx:1.25 # Step 2/2 : COPY site/ /usr/share/nginx/html/ # Successfully tagged greenlib/web:0.1.0 # 推送到团队仓库(团队内共享图纸) docker push greenlib/web:0.1.0
将来集群里的机器不装编译工具、不拷源码,直接凭 greenlib/web:0.1.0 这个标签就能还原出一模一样的运行环境。标签就是图纸的版本号,这个细节后面滚动更新时还要靠它。
| 能力 | 花盆(单机容器) | 说明 |
|---|---|---|
| 环境一致性 | 已解决 | 依赖全部进盆,搬到哪都一样 |
| 快速启动 | 已解决 | 共享内核,秒级起盆,比虚拟机轻一大截 |
| 资源隔离 | 基本解决 | 进程、文件系统、网络命名空间隔离 |
| 故障自愈 | 没有 | 盆摔了就是摔了,没人扶 |
| 多机调度 | 没有 | 哪台机器有空、盆放哪,全靠人脑 |
| 服务互找 | 没有 | 盆的地址每次起都可能变 |
| 规模伸缩 | 没有 | 要几只盆、怎么加减,全靠手数 |
一句话:花盆把"种一株苗"这件事做到了极致,但"经营一片田"它无能为力。田里的问题,正是下一节的主角。
常有人问:虚拟机早就能隔离环境,为什么还要容器。两者确实都做隔离,但层次完全不同:虚拟机虚拟的是一整台计算机,每个虚拟机都背着一个完整的操作系统;容器只隔离进程的视野,操作系统内核是全体共享的。像一栋楼每户自装锅炉与全楼集中供暖的差别。
| 维度 | 虚拟机 | 容器 |
|---|---|---|
| 隔离层次 | 整机级,各自带内核 | 进程级,共享宿主内核 |
| 启动速度 | 以分钟计 | 以秒计 |
| 体积 | 数吉字节 | 数十兆字节 |
| 单机密度 | 几个到十几个 | 几十个到上百个 |
| 安全边界 | 更硬 | 相对软,靠内核机制加固 |
隔离更硬与体量更轻是一对矛盾,容器选择轻,把"硬"的部分交给内核的命名空间与控制组去补。这也解释了为什么容器适合承载服务,而强隔离需求(互不信任的多租户)有时仍选虚拟机——两者其实是搭档:云上的容器集群,本身就跑在虚拟机里。
能跑,别在生产用。latest 是个会漂移的指针:今天拉到的"最新"与下个月拉到的不是同一张图纸,出问题时你根本说不清线上跑的是哪一版。生产镜像一律钉死具体版本号,书屋从第一张图纸起就用三段式版本号,为的就是让"线上跑的是什么"永远是一句答得出的话。
像图纸与档案馆。仓库按名字组织(greenlib/web),名字下按标签存版本(0.9.1、1.0.0)。push 是归档新图纸,pull 是调档旧图纸,档案馆之间还可以互相镜像同步。
在。花盆摔了图纸无损,再造一只盆还是原来的样子。这个"实例可弃、模板永存"的性质,正是第三章往后一切自愈机制的起点。