跳转至

容器:把应用和它的世界一起打包

1. 这一章讲什么

以前发布软件 = 把文件拷到目标机器、手工装依赖、祈祷环境一致。容器回答「怎么打包、怎么运输」:把应用和它的依赖、配置、运行时打成一个镜像(image),镜像在任何装了容器引擎的机器上跑起来的样子都一样——「在我机器上能跑」这个病从此根治1。这一章从 Linux 内核机制讲起(namespace、cgroup),讲到 Docker 的用法和网络、存储,最后用一个 Flask 应用走完全程。

2. 顶层全景

  镜像(image): 只读的模板 = 应用 + 依赖 + 运行时,分层存储
     │ docker build(按 Dockerfile 一层层盖)
     ▼
  容器(container): 镜像跑起来的实例 = 只读镜像层 + 最顶一层可写层
     │ 由容器运行时(Docker/containerd)用 namespace + cgroup 隔离并限量
     ▼
  仓库(registry): 存镜像的地方(Docker Hub / 私有仓库),像代码的 GitHub

主线例子(全章共用):一个 Flask 小应用(app.py + requirements.txt + Dockerfile)从 build 到 run 到 push 的完整过程。

3. 核心原理

3.1 历史:内核老技术的第四次投胎

容器不是新发明,是把 Linux 已有的隔离机制组装成了产品2:

年代 里程碑 贡献
1970s Unix chroot 隔离文件系统目录(第一代)
2000 FreeBSD Jails 比 chroot 更强的隔离,一台机器切成几个独立系统
2000s VMware / Xen(2003)/ KVM(2006) 虚拟机成熟但每 VM 一份完整 OS,开销大
2004 Solaris Zones 多个隔离系统共享同一内核——轻量虚拟化的雏形
2008 LXC(IBM) 把 Linux 的 cgroups 和 namespaces 打包成容器技术
2013 Docker 镜像 + 分层文件系统 + Docker Hub,容器从此人人会用
2015 OCI(Open Container Initiative) 定容器格式和运行时的开放标准,Docker 和 rkt 都遵守
之后 containerd、CRI-O 更专用的运行时;CRI-O 专为 Kubernetes 设计

3.2 容器 vs 虚拟机:轻在哪

虚拟机是「一台物理机装一个 hypervisor,hypervisor 上每台虚拟机各跑一个完整操作系统」——每个 OS 都要吃 CPU、内存、启动时间。容器共享宿主机内核,每个容器只带自己的文件系统、进程和资源视图,所以启动是秒级、内存开销是 MB 级3。隔离靠两件 Linux 内核武器,书里用了一栋办公楼的类比:namespace 是分楼层(每层的人看不见别的层),cgroup 是楼管系统(分配和限制每层的电水暖)4。

3.3 namespace:六间隔离的「楼层」

namespace 给进程一套「私人视图」,六种各管一类资源5:

  1. PID:进程号空间隔离——每个容器都可以有自己的 PID 1(容器的主进程),不同容器的 PID 互不可见。
  2. NET:网络隔离——每个容器有自己的虚拟网卡、IP、端口表。
  3. MNT:挂载点隔离——容器 A 挂 Ubuntu 文件系统、容器 B 挂 Fedora,互不相干。
  4. IPC:进程间通信隔离——共享内存、消息队列都不跨容器。
  5. USER:用户 ID 隔离——容器里当 root 的进程,在容器外没有 root 权限(用户重映射)。
  6. CGROUP:资源限制视图隔离。

想亲眼看:Linux 上 ls -l /proc/1/ns/ 列出任意进程的 namespace5。

3.4 cgroup:给容器「定量配给」

cgroup(控制组)管分配和限额,四大职能6:资源上限(如「这组进程最多用 80% CPU、2 GB 内存」)、优先级(关键进程优先)、计量(用了多少,serverless 计费就靠它)、控制(整组冻结)。按子系统分:cpu、cpuset、memory、blkio(磁盘 I/O)、net_cls/net_prio(网络)、devices。父子可嵌套——一个数据库服务一个父组,下面每个实例一个子组6。书里有句具体的:Docker 运行时会盯资源,容器 CPU 冲到 80% 持续几秒就把它终止7;退出码 0 = 正常结束,非 0 = 出错7。

3.5 分层:镜像为什么快

镜像由层(layer)组成:Dockerfile 里每条指令(RUN、COPY、ADD)生成一层,只存与上一层的差异8。层的三个性质:

  • 不可变:改不了旧层,改动只能叠新层——所以回滚容易,被污染的那层换掉即可,底下不动;
  • 可共享:十个镜像都基于同一个 Ubuntu 底层,宿主机上那份底层只存一次;
  • 拉取增量:更新镜像只需传变化的层,网络流量小。

容器跑起来后,镜像层只读,所有写操作落在最顶上一层可写层;容器删了,可写层随之蒸发——这就是「容器是临时(ephemeral)的」的含义,也是数据持久化要专门解决的缘由8。

3.6 Docker 的四个角色

  • Docker Client(CLI):你敲命令的地方。三条基本命令:docker build(按 Dockerfile 造镜像)、docker pull(从仓库拉镜像)、docker run(造容器并运行,本地没有镜像会自动先拉)9。
  • Docker Host:跑容器的那台机器(物理机/虚拟机)。
  • Docker Daemon(dockerd):后台常驻服务,真正干活的人——管容器生命周期、镜像构建与推拉、网络与存储,听 REST API 的令10。
  • Registry(仓库):存镜像版本的地方,镜像靠 tag 区分版本;Docker Hub 是默认公共仓库,企业常用私有的(AWS ECR、Azure ACR、GCR、GitLab Registry)11。

3.7 数据:容器没了,数据怎么办

容器可写层随容器删除而消失,要持久化三选一12:

方式 存哪 何时用
volume Docker 管的一块空间(/var/lib/docker/volumes/) 默认首选;可挂给多个容器;支持远程存储驱动
bind mount 宿主机上任意的具体路径 开发时把代码目录挂进容器;性能最好但绑死路径、安全性要自已把关
tmpfs 宿主机内存 敏感数据不落盘;容器没了数据即消失(如 --tmpfs /data:rw,size=100m)

3.8 网络:六种接法,按例记

书里每种网络都给了「三台容器 + 具体 IP」的图,照例子记最快13:

  • bridge(默认):私有内网,像接了一台虚拟交换机。容器互通,外部默认进不来(要配端口转发)。例子:docker0 网桥上 Container1=172.18.0.2、Container2=.3、Container3=.4。
  • host:容器直接用宿主机的 IP 和端口——少一层转发延迟,但端口会冲突(容器1 占 80、容器2 占 8080),还牺牲了网络隔离。
  • overlay:跨主机的虚拟内网(容器1 在 192.168.1.20、容器2/3 在 192.168.1.21,但大家同在 10.0.0.x overlay 网段),是 Docker Swarm 跨机通信的基础——Kubernetes 有自己的一套,下章讲。
  • ipvlan:容器直接拿物理网段的 IP(192.168.1.21/22/23),免 NAT。
  • macvlan:每个容器一个独立 MAC 地址——书里给的使用场景很具体:金融合规要求每个应用实例有独立 MAC+IP 供审计,且网络设备按 MAC 过滤流量,这时只有 macvlan 能满足。
  • none:完全断网,只剩 localhost——做安全敏感的一次性任务。

3.9 主线例子:Flask 应用容器化全程

三件套:app.py(一个路由返回 "Hello, Docker!")、requirements.txt(只一行 flask==1.1.2)、Dockerfile14:

FROM python:3.8-slim      # 基础镜像:带 Python 3.8 的精简 Linux
WORKDIR /app              # 容器内工作目录
COPY . /app               # 把当前目录拷进容器
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 5000               # 容器内应用监听的端口
ENV NAME Docker
CMD ["python", "app.py"]  # 容器启动时执行的命令

然后:

  1. docker build -t flask-docker-app . —— 句点表示「用当前目录当构建上下文」,-t 给镜像起名。每条 Dockerfile 指令出一层,逐层缓存。
  2. docker run -p 4000:5000 flask-docker-app —— 端口映射是本章最容易错的点:4000:5000 = 宿主机 4000 → 容器 5000;浏览器开 http://localhost:4000 实际访问的是容器里的 Flask。如果本地没这个镜像,Docker 会默认去 Docker Hub 拉。
  3. docker push <仓库名>/flask-docker-app —— 推到远程仓库,测试/生产环境 docker pull 下来,跑的是同一个比特。

3.10 与 DevOps 的对位:Build / Ship / Run

书里把容器的生命三步和 DevOps 阶段一一对上15:

Docker 三步 干什么 对位的 DevOps 阶段
Build 开发者写 Dockerfile,环境即代码,开发与生产一致 Code + Build
Ship 镜像推仓库、带版本 tag,新版不影响已部署应用 Release
Run 运维 pull 镜像、部署、扩缩,配 Swarm/Kubernetes Deploy + Operate

七条对齐理由里最有力的两条:Dockerfile 本身就是一种 IaC(环境定义成代码可审查可复现);CI/CD 流水线里「构建镜像→容器里测→部署同一个镜像」保证了测的和跑的是同一个东西16。

3.11 Docker 的十二宗罪(书里自己列的)

共享内核 = 内核漏洞殃及所有容器;持久化要自己想办法;多机编排复杂(引出 Kubernetes);跨主机网络绕;存储驱动有 I/O 开销;短生命容器让监控日志变难;容器蔓延(container sprawl)——不加节制地乱起容器;legacy 应用难容器化;资源配额配错会互相拖垮;学习曲线陡17。每一条都是下一章或后续章节的引子。

4. 作者的判断与证据

有出处的:演化史(chroot→LXC→Docker→OCI)与文献一致2;namespace/cgroup 的机制描述是内核文档级的事实陈述。

作者的实际演示:Flask 例子是真实可复现的(build/run/push 命令逐条给出),IP 例子(172.18.0.x、192.168.1.x)是标准的教学网段,照着敲就能在自家机器上看到同样的输出。

书里没说透、我们补一句:容器共享内核既是轻量的原因也是安全边界——容器防「意外」,不防「恶意」,强安全隔离仍然要 VM 或 Kata Containers 这类手段(补充,不在书里,来自通用知识)。

5. 边界与局限

  • 只教单机 Docker;多机编排明确推给第 7 章(Kubernetes)。
  • 安全实操(镜像扫描、非 root 运行、只读文件系统)只在复习题出现18。
  • Compose 工具一笔带过。
  • Windows 容器、macOS 虚拟化层只字未提(全书默认 Linux 语境)。

6. 可带走的

  1. 容器 = 共享内核的隔离进程组;VM 虚拟硬件,容器只虚拟视图3。
  2. namespace 管隔离(六层楼层),cgroup 管限量(配给制)——一个管「看不见」,一个管「用多少」46。
  3. 镜像分层:每条 Dockerfile 指令一层,只存增量;层不可变所以可缓存、可回滚、可共享8。
  4. 容器是临时的:写层随容器消失,持久化用 volume(首选)/bind mount(开发)/tmpfs(敏感)12。
  5. -p 4000:5000 记法:宿主机端口:容器端口,顺序别反14。
  6. bridge 是默认私网;要跨主机用 overlay;要独立 MAC 用 macvlan;要绝对隔离用 none13。
  7. Dockerfile 本身就是 IaC;「构建-测试-部署同一个镜像」是 CI/CD 一致性的根基16。
  8. Docker 的天花板 = 单机;几百上千个容器的管理 = 下一章的 Kubernetes17。

7. 原文地图

主题 原书章 原文位置
演化史(chroot→LXC→Docker→OCI) 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:45(搜「chroot」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:58(搜「in 2013」)
容器 vs VM 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:92(搜「same OS kernel」)
六种 namespace 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:182(搜「PID (Process IDs) Namespace」)
cgroups 四职能与子系统 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:220(搜「Resource Limitation」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:235(搜「cpuset」)
办公楼市类比 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:245(搜「office building analogy」)
CPU 80% 终止与退出码 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:156(搜「80%」)
镜像分层与不可变 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:265(搜「immutable layer」)
Docker 架构四组件 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:326(搜「Docker Client」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:344(搜「dockerd」)
三种持久化(volume/bind/tmpfs) 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:389(搜「docker volume create my_volume」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:411(搜「Bind mounts」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:437(搜「tmpfs mounts」)
六种网络及 IP 实例 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:456(搜「bridge network」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:504(搜「overlay network」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:549(搜「Macvlan」)
容器生命周期 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:593(搜「Lifecycle of a Docker container」)
Flask 三件套与 Dockerfile 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:670(搜「Hello, Docker」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:682(搜「FROM python:3.8-slim」)
build/run/push 三步 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:748(搜「docker build -t」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:757(搜「4000:5000」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:776(搜「docker push」)
Build/Ship/Run 对位 DevOps 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:800(搜「Build phase」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:807(搜「Ship phase」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:822(搜「Run phase」)
十二条局限 6 - Packaging and shipping with containers text/11-ch06-6-packaging-and-shipping-with-containers.txt:859(搜「Security Concerns」) · text/11-ch06-6-packaging-and-shipping-with-containers.txt:872(搜「Container Sprawl」)

  1. 出处:「6 - Packaging and shipping with containers」第 1–9 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:2,搜「packaging refers to keeping the application code」)。 ↩

  2. 出处:「6 - Packaging and shipping with containers」第 33–80 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:45,搜「chroot」;:56 搜「cgroups」;:74 搜「Open Container Initiative」)。 ↩↩

  3. 出处:「6 - Packaging and shipping with containers」第 87–100 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:92,搜「same OS kernel」)。 ↩↩

  4. 出处:「6 - Packaging and shipping with containers」第 241–259 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:245,搜「office building analogy」)。 ↩↩

  5. 出处:「6 - Packaging and shipping with containers」第 176–212 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:182,搜「PID (Process IDs) Namespace」;:211 搜「/proc/1/ns/」)。 ↩↩

  6. 出处:「6 - Packaging and shipping with containers」第 213–239 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:220,搜「Resource Limitation」)。 ↩↩↩

  7. 出处:「6 - Packaging and shipping with containers」第 153–162 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:156,搜「80%」;:160 搜「exits with a code 1」)。 ↩↩

  8. 出处:「6 - Packaging and shipping with containers」第 260–296 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:265,搜「immutable layer」;:275 搜「read-only」)。 ↩↩↩

  9. 出处:「6 - Packaging and shipping with containers」第 326–335 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:329,搜「docker build」)。 ↩

  10. 出处:「6 - Packaging and shipping with containers」第 344–358 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:344,搜「dockerd」)。 ↩

  11. 出处:「6 - Packaging and shipping with containers」第 359–370 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:361,搜「version control system for code」)。 ↩

  12. 出处:「6 - Packaging and shipping with containers」第 371–447 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:383,搜「/var/lib/docker/volumes/」;:441 搜「volatile storage」)。 ↩↩

  13. 出处:「6 - Packaging and shipping with containers」第 449–592 段(bridge :456 搜「172.18.0.2」;host :486 搜「--network host」;overlay :509 搜「Docker Swarm」;macvlan :552 搜「financial institution」;none :581 搜「loopback interface」)。 ↩↩

  14. 出处:「6 - Packaging and shipping with containers」第 645–788 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:670,搜「Hello, Docker」;:682 搜「python:3.8-slim」;:762 搜「host_port」)。 ↩↩

  15. 出处:「6 - Packaging and shipping with containers」第 790–829 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:797,搜「Build, Ship, and Run」)。 ↩

  16. 出处:「6 - Packaging and shipping with containers」第 831–854 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:847,搜「is a form of IaC」)。 ↩↩

  17. 出处:「6 - Packaging and shipping with containers」第 856–883 段(text/11-ch06-6-packaging-and-shipping-with-containers.txt:859,搜「share the same kernel」;:882 搜「Orchestration Complexity」)。 ↩↩

  18. 出处:「6 - Packaging and shipping with containers」复习题(text/11-ch06-6-packaging-and-shipping-with-containers.txt:920,搜「security practices」)。 ↩