Dockerfile 的一些实践经验

date
Mar 23, 2021
slug
practice-experience-for-dockerfile
status
Published
summary
去年一个人把公司内部分老项目和新项目添加了 Docker 支持,重新设计了 CD/CI 流程;同时由于基本是单机服务,编排容器用的 Docker Compose,因此在这个过程中,也积累了一些经验。
tags
Docker
type
Post
去年一个人把公司内部分老项目和新项目添加了 Docker 支持,重新设计了 CD/CI 流程;同时由于基本是单机服务,编排容器用的 Docker Compose,因此在这个过程中,也积累了一些经验。

一、减少镜像大小

1.1 Alpine

使用 Alpine 或者是带有 alpine 版本的镜像作为基础镜像
下图是这个博客分别用 node:14.13.0 node:14.13.0-alpine3.12 构建后的镜像大小对比
notion image
具体关于 Alpine 的介绍可以看看这个链接

1.2 多阶段构建

在构建一些运行文件和源代码分离的项目时(Vue.jsReact.jsGo 等),可以考虑使用多阶段构建让最后生成的镜像只含有运行文件

1.2.1 Vue.js

1.3 如果希望所有容器都通过端口访问,可以考虑使用 Caddy

我个人比较偏向于所有的容器之间都通过端口访问,那么就会遇到一些服务本身没有映射功能端口的情——例如 PHP——因此,可以考虑使用 Caddy在容器内部代理端口
Caddyfile
Dockerfile

二、减少 Layer 层的数量

减少层数有利于提高 Push 和 Pull 的速度,提高部署效率;在 Dockerfile中,每一行的命令都会生成一个新层,因此可以通过命令合并的方式降低层数,比如使用 && \ 分隔运行多个命令

三、提高构建速度

3.1 容易产生变动的命令放在最后执行

虽然 docker build 的过程中可以使用缓存提高速度,但是如果某一行 Dockerfile内容发生了变动,那么它以及之后所有的命令都需要重新执行(不包括多阶段构建中的其他阶段)。

3.2 多阶段构建

多阶段构建不仅能够帮助降低镜像大小,也可以在一定程度上提高构建的速度,比如说在 go build 的时候,运行镜像的基础环境已经完成了;
同时,每个部分之间的缓存判断是独立的,因此就算修改了构建部分的 Dockerfile,运行部分的构建依然可以使用缓存。

3.2.1 Go

3.3 使用 BuildKit

BuildKit 中能够使用 cache 解决程序依赖包如果发生变动就需要全部重新下载的问题,具体请参考这份文档

四、成功率和稳定性

基础镜像尽量不使用类似 latest这种非固定镜像的 Tag,尽量使用精确的版本号比如说:node:14.13.0-alpine3.12

五、时区问题

由于大部分镜像的时区都不是东八区,因此在构建成镜像时时间判断、Log 时间标记可能都会出现问题,因此时区也是需要考虑的问题
通过 Dockerfile 解决(Alpine):
运行时解决(通过同步服务器的宿主机时间):