1. 镜像是什么?不是压缩包,而是“可复现文件系统快照”
镜像的直觉比喻是“分层千层饼”:底层是基础镜像(如 Debian/Alpine),上面一层层叠加你的依赖、构建产物与配置。 真正重要的点是:镜像层是只读的;容器运行时,会在最上面加一个可写层。
- Layer:只读文件系统变更集合(增删改)。
- Image:一组有顺序的 layer + 元数据(入口、环境变量等)。
- Container:image + writable layer + 运行时隔离配置。
直观结论:容器里“写文件”通常不应该当成持久化。你要么写到挂载卷(volume),要么写到外部存储(DB/对象存储),要么把状态交给平台。
2. 构建缓存:为什么 Dockerfile 的顺序能把 CI 从 30 分钟压到 5 分钟?
镜像构建的缓存命中是以 layer 为单位的:如果某一步的输入变化了(Dockerfile 指令、复制进来的文件、build args),这一层以及它之后的层都需要重建。 所以 Dockerfile 最关键的“工程技巧”之一是:把变化最频繁的东西尽量放在后面,把稳定的依赖安装放在前面,并用 lockfile 控制输入稳定性。
图 1:镜像分层与缓存命中。把“稳定输入”(依赖、系统包)放下层,把“高频变化”(业务代码)放上层,CI 时间会明显下降。
3. 运行与隔离:容器不是虚拟机,它是“被隔离的进程集合”
容器在 Linux 上的本质是:一组进程运行在同一个内核上,但通过 namespace 看到“不同的世界”,通过 cgroup 被限制资源。 它像一间带玻璃墙的办公室:看起来独立,但地基是同一栋楼(同一个内核)。
- PID namespace:容器看到的进程树是自己的。
- NET namespace:容器的网卡、路由、端口空间是自己的。
- MNT namespace:挂载点隔离(看到的文件系统不同)。
- cgroup:CPU、内存、IO 等资源限制与统计。
安全提醒:容器不是强安全边界。尤其是特权容器、挂载宿主机敏感路径、过宽的 Linux capabilities,都会把“玻璃墙”砸碎。
图 2:容器隔离边界。namespace 让你“看起来独立”,cgroup 让你“被限制资源”。它们让容器轻量,但不等于强隔离。
4. tag vs digest:为什么交付必须用 digest 才能“可信可追溯”?
tag 是方便人记的名字,比如 web:prod、app:1.2.3。 但 tag 可能被重打:同一个 tag 在不同时间指向不同内容。digest 则是内容地址(content-addressed),只要内容不变,digest 就不变。 所以在 CD/GitOps 中,“上线的版本”应该用 digest 表达,这样才可追溯、可回滚、可审计。
结论:tag 用于“选择与展示”,digest 用于“交付与审计”。上线用 digest,展示用 tag,不打架。
图 3:tag vs digest 的交付追溯链。tag 可能漂移,digest 才是“上线的内容真相”。配合 SBOM/签名/Provenance 才能做到可信交付。
5. 最小落地清单(从“会用 Docker”到“能工程化交付”)
- Dockerfile 顺序优化:稳定层在下、易变层在上;锁文件稳定依赖层输入。
- 构建上下文控制:写好 .dockerignore,别把无关文件送进构建。
- 运行时无状态:把持久化写到 volume 或外部系统;容器重启应可恢复。
- 最小权限:非 root、最小 capabilities、避免特权与宿主敏感挂载。
- 交付用 digest:上线引用 digest;tag 用于展示;建立追溯链(SBOM/签名/审计)。
你现在应该能回答:
1) 为什么 Dockerfile 顺序会影响缓存与 CI 时间?你会怎么重排?
2) 容器隔离靠什么实现?它是不是强安全边界?为什么?
3) 上线版本应该用 tag 还是 digest 表达?如何做到可追溯与可回滚?
1) 为什么 Dockerfile 顺序会影响缓存与 CI 时间?你会怎么重排?
2) 容器隔离靠什么实现?它是不是强安全边界?为什么?
3) 上线版本应该用 tag 还是 digest 表达?如何做到可追溯与可回滚?