
- 如果 2 个 image 都是基于 Ubuntu,那么两个 Image 可以共用 Ubuntu 的 base image,只需要存储一份;
- 如果 pull 新的 image,某一层如果已经存在,那么这一层之前的内容其实就不需要 pull 了;
技巧1:删除缓存
一般的包管理器,比如 apt,pip 等,下载包的时候,都会下载缓存,下次安装同一个包的时候不必从网络上下载,直接使用缓存即可。 但是在 Docker Image 中,是不需要这些缓存的。所以在 Dockerfile 中下载东西一般会使用这种命令:在包安装好之后,去删除缓存。 一个常见的错误是,有人会这么写:
RUN dnf install -y --setopt=tsflags=nodocs \httpd vim && \systemctl enable httpd && \dnf clean all
Dockerfile 里面的每一个 RUN 都会创建一层新的 layer,如上所说,这样其实是创建了 3 层 layer,前 2 层带来了缓存,第三层删除了缓存。如同 git 一样,在一个新的 commit 里面删除了之前的文件,其实文件还是在 git 历史中的,最终的 docker image 其实没有减少。 但是 Docker 有了一个新的功能,
FROM fedoraRUN dnf install -y mariadbRUN dnf install -y wordpressRUN dnf clean all
<font style="color:rgb(21, 166, 117);background-color:rgb(249, 242, 244);">docker build --squash</font>。squash 功能会在 Docker 完成构建之后,将所有的 layers 压缩成一个 layer,也就是说,最终构建出来的 Docker image 只有一层。所以,如上在多个 RUN 中写 clean 命令,其实也可以。不太喜欢这种方式,因为前文提到的,多个 image 共享 base image 以及加速 pull 的 feature 其实就用不到了。
一些常见的包管理器删除缓存的方法:
| 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 |
RUN true \&& dnf install -y --setopt=tsflags=nodocs \httpd vim \&& systemctl enable httpd \&& dnf clean all \&& true
技巧2:改动不频繁的内容往前放
通过前文介绍过的原理,可以知道,对于一个 Docker image 有 ABCD 四层,B 修改了,那么 BCD 会改变。 根据这个原理,在构建的时候可以将系统依赖往前写,因为像 apt,dnf 这些安装的东西,是很少修改的。然后写应用的库依赖,比如<font style="color:rgb(21, 166, 117);background-color:rgb(249, 242, 244);">pip install</font>,最后 copy 应用。
比如下面这个 Dockerfile,就会在每次代码改变的时候都重新 Build 大部分 layers,即使只改了一个网页的标题。
可以改成,先安装 Nginx,再单独 copy requirements.txt,然后安装 pip 依赖,最后 copy 应用代码。
FROM python:3.7-buster# copy sourceRUN mkdir -p /opt/appCOPY myapp /opt/app/myapp/WORKDIR /opt/app# install dependencies nginxRUN apt-get update && apt-get install nginxRUN pip install -r requirements.txtRUN chown -R www-data:www-data /opt/app# start serverEXPOSE 8020STOPSIGNAL SIGTERMCMD ["/opt/app/start-server.sh"]
FROM python:3.7-buster# install dependencies nginxRUN apt-get update && apt-get install nginxCOPY myapp/requirements.txt /opt/app/myapp/requirements.txtRUN pip install -r requirements.txt# copy sourceRUN mkdir -p /opt/appCOPY myapp /opt/app/myapp/WORKDIR /opt/appRUN chown -R www-data:www-data /opt/app# start serverEXPOSE 8020STOPSIGNAL SIGTERMCMD ["/opt/app/start-server.sh"]
技巧3:构建和运行 Image 分离
在编译应用的时候需要很多构建工具,比如 gcc,golang 等。但是在运行的时候不需要。在构建完成之后,去删除那些构建工具是很麻烦的。 可以这样:使用一个 Docker 作为 builder,安装所有的构建依赖,进行构建,构建完成后,重新选择一个 Base image,然后将构建的产物复制到新的 base image,这样,最终的 image 只含有运行需要的东西。 比如,这是安装一个 golang 应用 pup 的代码:使用 golang 这个 1G 多大的 image 来安装,安装完成之后将 binary 复制到 alpine,最终的产物只有 10M 左右。这种方法特别适合一些静态编译的编程语言,比如 golang 和 rust.
FROM golang as buildENV CGO_ENABLED 0RUN go install github.com/ericchiang/pup@latestFROM alpine:3.15.4 as runCOPY --from=build /go/bin/pup /usr/local/bin/pup
技巧4:检查构建产物
这是最有用的一个技巧了。 dive 是一个 TUI,命令行的交互式 App,它可以让你看到 docker 每一层里面都有什么。 dive ubuntu:latest 命令可以看到 ubuntu image 里面都有什么文件。内容会显示为两侧,左边显示每一层的信息,右边显示当前层(会包含之前的所有层)的文件内容,本层新添加的文件会用黄色来显示。通过 tab 键可以切换左右的操作。
- https://jvns.ca/blog/2019/11/18/how-containers-work–overlayfs/
- https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html
- http://docs.projectatomic.io/container-best-practices/
引用链接
xbin.io: https://xbin.io/
Docker (容器) 的原理: https://www.kawabangga.com/posts/4224
overlayfs: https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html
chroot: https://en.wikipedia.org/wiki/Chroot
其实就是一个 tar 包: https://www.kawabangga.com/posts/4224
image 符合 Docker 的规范: https://github.com/moby/moby/blob/master/image/spec/v1.md
Build 一个最小的 Redis Docker Image: https://www.kawabangga.com/posts/4411
