如何构建更小、更快的 Docker 镜像

这里所说的更快,指的是构建速度更快,而非运行速度更快。

为了保证项目在不同操作系统或不同人员那里运行环境的一致性,一般项目源码会直接拷贝到 Dockerfile 中进行打包,而非由开发人员使用本机环境打包。最终无需安装项目运行环境,仅仅运行此 Dockerfile,就能保证项目不会因为环境问题而出现错误。同时,docker 容器在生产环境下也更容易被管理。

想要发挥 Docker 的高效率,减少 Dockerfile 的构建时间和体积是必须要考虑的。我们应该用有限的时间做更有意义的事情。

使用 Docker 缓存来节省 80% 镜像构建时间

这里以一个构建前端单页应用的 Dockerfile 为例,假设应用的打包目录为 build 文件夹,为了简单,运行此应用使用了 http-server 这个库,它的默认端口为 8080。Dockerfile 可以编写为如下内容:

FROM node:10-slim

WORKDIR /app

COPY . ./

RUN npm install --registry=https://registry.npm.taobao.org

RUN npm run build

RUN npm install -g http-server --registry=https://registry.npm.taobao.org

EXPOSE 8080

CMD [ "http-server", "/app/build" ]

先 build 一次 Dockerfile,然后不改动任何内容,再 build 一次,就会发现镜像构建几乎没有花费任何时间,所有的构建步骤全部都使用了缓存。

Dockerfile 中的每一条指令在构建过程中都会形成一个层,如果此层与上一次构建时层的内容没有发生变化,那么就直接使用上一次的缓存,所以就会出现这样的情况。

所以,提高缓存的命中率是节省时间的关键。

使用上面的 Dockerfie,如果改动了代码,只有 FROM node:10-slimWORKDIR /app 会使用缓存。而实际开发过程中,这两条指令在任何情况下几乎都会使用缓存,而构建单页应用真正耗时的步骤是安装依赖打包

如使用上面的这个 Dockerfile,一旦改动代码,COPY . ./ 这一行指令就会发生变动,则还是会进行一个完成的安装依赖和打包流程,Dockerfile 在第二次构建时并不会因为缓存而减少多少时间。

其实,在大多数情况下,安装依赖这个步骤是可以省掉的。

一般来说,实际开发过程中并不会每次提交代码时都会安装新的依赖包,所以,将安装依赖的步骤提前,就可以在大多数情况下省略掉安装依赖这一步骤了:

FROM node:10-slim

WORKDIR /app

COPY package.json ./

RUN npm install --registry=https://registry.npm.taobao.org

RUN npm install -g http-server --registry=https://registry.npm.taobao.org

COPY . ./

RUN npm run build

EXPOSE 8080

CMD [ "http-server", "/app/build" ]

再次构建镜像,就会发现,如果项目没有安装新的依赖,COPY . ./ 这条指令之前的内容全部都会使用缓存,仅仅只有 build 这一个动作需要花费时间了,做出这样的调整之后大约是能够节省 60% 到 90% 的时间的。

使用多级构建给镜像瘦身

如果构建镜像的机器和运行镜像的机器不是同一台,或者它们之间不存在内网环境,那么镜像每次构建后都需要通过网络传输到部署它的机器上。更小体积的镜像就更能节省时间。

Dockerfile 中的每一条指令在构建时都会产生一个新的层,而这个层会占用空间,很多人的优化办法是尽可能地减少指令条数。

例如:将两条命令合并成一条

RUN touch file
RUN rm file
RUN touch file && rm file

还有的优化方法是使用体积更小的基础镜像,例如我上面的那个例子,我用了 slim 版的 node 镜像,体积会更小。

而使了用多级构建之后,上面的两条可能都不需要考虑。

接下来将使用多级构建改造上面的 Dockerfile,为了使项目运行地更加优雅,这里使用 nginx 来作为静态资源服务器。

第一条指令后面加上了 AS static-file 其作用是给这个镜像个名字,最后一条指令是指:从上一个镜像中把打包出来的文件拷贝到当前镜像中。

一个 Dockerfile 中可以包含多个镜像,其最终构建出来的镜像体积其实是最后一个基础镜像的体积 + 从之前的镜像中拷贝出来的内容体积。

这里只拷贝了 build 目录,所以最终镜像的体积会非常小,大约只有 25 MB 左右。

FROM node:10-slim AS static-file

WORKDIR /app

COPY package.json ./

RUN npm install --registry=https://registry.npm.taobao.org

COPY . ./

RUN npm run build

FROM nginx:apline

COPY --from=static-file /app/build /usr/share/nginx/html/

多级构建其实并不是什么魔法,只是个语法糖而已。第一个镜像构建出内容,然后从第一个镜像中拷贝出内容,再丢到第二个镜像中继续构建构建,需要手动拷贝文件的这个操作,通过 Dockerfile 中的 COPY --from 这个语法实现了。

所以,如果最终构建出来的镜像体积过大的话,无需优化任何构建步骤,仅仅只需要在最后一步增加一个新的,包含运行环境的更小的镜像即可。这种镜像必要时也可以自己制作。

总结

合理利用缓存 + 多级构建,在无论多么复杂的场景下,都能轻松地制作出更加优秀的镜像,更能发挥出 Docker 的高效率。


   转载规则


《如何构建更小、更快的 Docker 镜像》 xnng 采用 知识共享署名 4.0 国际许可协议 进行许可。
 本篇
如何构建更小、更快的 Docker 镜像 如何构建更小、更快的 Docker 镜像
这里所说的更快,指的是构建速度更快,而非运行速度更快。 为了保证项目在不同操作系统或不同人员那里运行环境的一致性,一般项目源码会直接拷贝到 Dockerfile 中进行打包,而非由开发人员使用本机环境打包。最终无需安装项目运行环境,仅仅
2019-07-22
下一篇 
我的常用镜像和代理整理 我的常用镜像和代理整理
npm/yarnyarn:以下设置将 npm 换成 yarn 即可 设置代理 $ npm config set proxy http://127.0.0.1:1080 设置淘宝镜像 $ npm config set registry
2019-06-24
  目录