容器:把应用和它的世界一起打包
容器:把应用和它的世界一起打包
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:
- PID:进程号空间隔离——每个容器都可以有自己的 PID 1(容器的主进程),不同容器的 PID 互不可见。
- NET:网络隔离——每个容器有自己的虚拟网卡、IP、端口表。
- MNT:挂载点隔离——容器 A 挂 Ubuntu 文件系统、容器 B 挂 Fedora,互不相干。
- IPC:进程间通信隔离——共享内存、消息队列都不跨容器。
- USER:用户 ID 隔离——容器里当 root 的进程,在容器外没有 root 权限(用户重映射)。
- 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"] # 容器启动时执行的命令然后:
docker build -t flask-docker-app .—— 句点表示「用当前目录当构建上下文」,-t给镜像起名。每条 Dockerfile 指令出一层,逐层缓存。docker run -p 4000:5000 flask-docker-app—— 端口映射是本章最容易错的点:4000:5000= 宿主机 4000 → 容器 5000;浏览器开http://localhost:4000实际访问的是容器里的 Flask。如果本地没这个镜像,Docker 会默认去 Docker Hub 拉。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. 可带走的
- 容器 = 共享内核的隔离进程组;VM 虚拟硬件,容器只虚拟视图3。
- namespace 管隔离(六层楼层),cgroup 管限量(配给制)——一个管「看不见」,一个管「用多少」46。
- 镜像分层:每条 Dockerfile 指令一层,只存增量;层不可变所以可缓存、可回滚、可共享8。
- 容器是临时的:写层随容器消失,持久化用 volume(首选)/bind mount(开发)/tmpfs(敏感)12。
-p 4000:5000记法:宿主机端口:容器端口,顺序别反14。- bridge 是默认私网;要跨主机用 overlay;要独立 MAC 用 macvlan;要绝对隔离用 none13。
- Dockerfile 本身就是 IaC;「构建-测试-部署同一个镜像」是 CI/CD 一致性的根基16。
- 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」) |
Footnotes
-
出处:「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」)。 ↩ -
出处:「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」)。 ↩ ↩2 -
出处:「6 - Packaging and shipping with containers」第 87–100 段(
text/11-ch06-6-packaging-and-shipping-with-containers.txt:92,搜「same OS kernel」)。 ↩ ↩2 -
出处:「6 - Packaging and shipping with containers」第 241–259 段(
text/11-ch06-6-packaging-and-shipping-with-containers.txt:245,搜「office building analogy」)。 ↩ ↩2 -
出处:「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/」)。 ↩ ↩2 -
出处:「6 - Packaging and shipping with containers」第 213–239 段(
text/11-ch06-6-packaging-and-shipping-with-containers.txt:220,搜「Resource Limitation」)。 ↩ ↩2 ↩3 -
出处:「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」)。 ↩ ↩2 -
出处:「6 - Packaging and shipping with containers」第 260–296 段(
text/11-ch06-6-packaging-and-shipping-with-containers.txt:265,搜「immutable layer」;:275搜「read-only」)。 ↩ ↩2 ↩3 -
出处:「6 - Packaging and shipping with containers」第 326–335 段(
text/11-ch06-6-packaging-and-shipping-with-containers.txt:329,搜「docker build」)。 ↩ -
出处:「6 - Packaging and shipping with containers」第 344–358 段(
text/11-ch06-6-packaging-and-shipping-with-containers.txt:344,搜「dockerd」)。 ↩ -
出处:「6 - Packaging and shipping with containers」第 359–370 段(
text/11-ch06-6-packaging-and-shipping-with-containers.txt:361,搜「version control system for code」)。 ↩ -
出处:「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」)。 ↩ ↩2 -
出处:「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」)。 ↩ ↩2 -
出处:「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」)。 ↩ ↩2 -
出处:「6 - Packaging and shipping with containers」第 790–829 段(
text/11-ch06-6-packaging-and-shipping-with-containers.txt:797,搜「Build, Ship, and Run」)。 ↩ -
出处:「6 - Packaging and shipping with containers」第 831–854 段(
text/11-ch06-6-packaging-and-shipping-with-containers.txt:847,搜「is a form of IaC」)。 ↩ ↩2 -
出处:「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」)。 ↩ ↩2 -
出处:「6 - Packaging and shipping with containers」复习题(
text/11-ch06-6-packaging-and-shipping-with-containers.txt:920,搜「security practices」)。 ↩