最近做了一个好玩的工具,叫 。其中有一项工作是为不同的工具来构建 Docker 镜像,让他们都运行在 Docker 中(实际上,是兼容 Docker image 的其他 sandbox 系统,没有直接用 Docker)。支持的工具越来越多,为了节省资源,Build 的 Docker image 就越小越好,文件越少,其实启动速度也会略微快一些,也会更安全一些。
这篇文章来介绍一下做 Docker Image 的一些技巧。
在之前的博客 Docker (容器) 的原理 中介绍过 Docker image 是如何工作的。简单来说,就是使用 Linux 的 overlayfs, overlay file system 可以做到,将两个 file system merge 在一起,下层的文件系统只读,上层的文件系统可写。如果你读,找到上层就读上层的,否则的话就找到下层的给你读。然后写的话会写入到上层。这样,其实对于最终用户来说,可以认为只有一个 merge 之后的文件系统,用起来和普通文件系统没有什么区别。
有了这个功能,Docker 运行的时候,从最下层的文件系统开始,merge 两层,得到新的 fs 然后再 merge 上一层,然后再 merge 最上一层,最后得到最终的 directory,然后用 chroot改变进程的 root 目录,启动 container。
了解了原理之后,你会发现,这种设计对于 Docker 来说非常合适:
- 如果 2 个 image 都是基于 Ubuntu,那么两个 Image 可以共用 Ubuntu 的 base image,只需要存储一份;
- 如果 pull 新的 image,某一层如果已经存在,那么这一层之前的内容其实就不需要 pull 了;
后面 build image 的技巧其实都是基于这两点。
Dockerfiledocker built
技巧1:删除缓存
aptpip
Dockerfile
在包安装好之后,去删除缓存。
一个常见的错误是,有人会这么写:
RUN
docker build --squashRUN
一些常见的包管理器删除缓存的方法:
yum | yum clean all |
---|---|
dnf | dnf clean all |
rvm | rvm cleanup all |
gem | gem cleanup |
cpan | rm -rf ~/.cpan/{build,sources}/* |
pip | rm -rf ~/.cache/pip/* |
apt-get | apt-get clean |
RUNdnf
可以写成这种形式,比较清晰。
技巧2:改动不频繁的内容往前放
通过前文介绍过的原理,可以知道,对于一个 Docker image 有 ABCD 四层,B 修改了,那么 BCD 会改变。
aptdnfpip install
比如下面这个 Dockerfile,就会在每次代码改变的时候都重新 Build 大部分 layers,即使只改了一个网页的标题。
requirements.txtpip
技巧3:构建和运行 Image 分离
我们在编译应用的时候需要很多构建工具,比如 gcc, golang 等。但是在运行的时候不需要。在构建完成之后,去删除那些构建工具是很麻烦的。
我们可以这样:使用一个 Docker 作为 builder,安装所有的构建依赖,进行构建,构建完成后,重新选择一个 Base image,然后将构建的产物复制到新的 base image,这样,最终的 image 只含有运行需要的东西。
pup
golang
技巧4:检查构建产物
这是最有用的一个技巧了。
dive 是一个 TUI,命令行的交互式 App,它可以让你看到 docker 每一层里面都有什么。
dive ubuntu:latesttab
ctrlU
ctrlSpacencdu