1. 从容器构建镜像的手动流程
- 核心工作流:
- 从现有镜像创建并启动一个容器。
- 在容器的文件系统中进行改动(如安装软件、添加文件)。
- 使用
docker commit提交改动,生成一个包含新文件层的新镜像。
- 审计改动:使用
docker diff命令可以查看容器相对于其基础镜像的文件系统变化。输出结果包括:- A (Added):新增文件。
- C (Changed):修改的文件。
- D (Deleted):删除的文件。
- 提交选项:
docker commit允许设置作者(-a)、提交信息(-m)以及环境变量、工作目录等元数据属性。
2. 深入理解镜像与分层结构
镜像并非一个单一文件,而是由多个只读层堆叠而成的集合。
- 联合文件系统 (UFS):Docker 利用 UFS 将多层文件系统挂载为一个统一的视图。容器对镜像层的任何改动都会记录在最顶层的“可写层”中。
- 写时复制 (Copy-on-Write):当修改底层的文件时,Docker 会先将该文件复制到顶层再进行修改。
- 体积限制与层级限制:
- 删除文件并不减小体积:在新的层中删除底层文件,只是在顶层标记该文件不可见,底层文件依然存在于镜像中,因此镜像总大小反而可能增加。
- 层数限制:某些联合文件系统(如 AUFS)有最大层数限制(如42层)。
3. 导出与导入扁平化文件系统
为了减小镜像体积或去除构建历史,可以使用导出功能。
docker export:将容器的整个文件系统压缩成一个 tar 归档文件,它会丢弃所有的分层历史和元数据,仅保留当前状态。docker import:将 tar 归档文件重新导入为一个单层镜像。- 优点:可以创建一个非常小的、不含冗余历史的“扁平镜像”。
4. 镜像标签与版本控制最佳实践
- 仓库与标签:仓库(Repository)是镜像的命名桶,而标签(Tag)则指向特定的层 ID。
latest标签的陷阱:latest标签是一个“移动的目标”,它并不总是指向最稳定的版本,过度依赖它会导致生产环境的版本不一致。- 最佳实践:
- 为镜像分配具体的版本号标签(如
1.9.1),并在更新时移动主版本标签(如1.9)以指向最新的补丁版本。 - 保持软件版本与镜像版本的一致性,利用细粒度的重叠标签体系。
- 为镜像分配具体的版本号标签(如
比喻理解:
镜像的手动构建就像是在制作一本剪贴报。每一页代表一个镜像层,你在一页上写的字或贴的照片(即 docker diff 记录的 A、C、D)都被永久保存。如果你想“删掉”第一页的一个错别字,你只能在第二页贴一张白纸盖住它,但第一页的字其实还在报本里,所以报本只会越来越厚。而 docker export 就像是用复印机把整本报纸印成了一张平整的纸,历史痕迹不见了,虽然变薄了,但你也无法再翻看之前的修改记录了。