一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
1. 前言
我们针对镜像的所有操作,包括制作镜像、运行镜像、修改镜像都可以通过命令来完成,但是对于镜像的制作这里强烈建议使用 Dockerfile,使用 Dockerfile 有如下好处。
- Dockerfile 可随源代码一起由git管理,方便做版本控制。
- 如果别人想使用我们自制的镜像,我们只需要提供 Dockerfile 文件,而不需要提供镜像本身。
- Dockerfile 相对于成品镜像来讲,能更直观的体现镜像的制作流程。
好的 Dockerfile 就像好的代码一样,有一些比较成熟的规范可以参考,遵循良好的 Dockerfile 编写规范,可以尽量做到让镜像构建时间更短,镜像体积更小。
2. 编写Dockerfile的一些实践经验
在编写 Dockerfile 时,可以遵循 Docker 官方的一些规范及社区常期以来习得的实践经验,这样可以让我们编写的 Dockerfile 有更好的可读性和实用性。
2.1 一个容器内跑一个进程(尽量)
首先这是一个理想化的规范,由于 Docker 的本质是宿主机上的一个特殊的进程,为了更直观简约,期望一个Docker 内只运行一个进程,能做到最好,但并不是强制的。像很多公司把业务从虚拟机迁移到容器环境,其中有很多历史的原因,一个容器中必需运行多个进程,对程序进行重构也是不太现实的,所以跑多个进程也不是不可接受的。
2.2 注意Dockerfile的可读性
人生很短,对自己和别人好一点,Dockerfile 也是一种代码,可以在必要的地方加上注释,注意代码格式,让自己和别人都看起来赏心悦目。
2.3 保持生成镜像的简洁性
容器里不要安装不需要的东西,比如说与运行程序无关的东西,像编辑器、下载工具等,比如说需要运行 Java 程序,实际上安装 JRE 即可,而不用安装 JDK,对于服务器来讲,安装的东西越少,出错的概率也就最小。
2.4 合理利用构建缓存
Dockerfile 在构建过程中的每一条指令,都会对应生成镜像中的一层,所以为了让镜像更精简,最好把能压缩的多行指令放到一行,当一条指令在执行时,发现其父镜像层已经存在时,并且下一条指令和已经存在的镜像层中的指令相同,则会利用镜像缓存。
镜像缓存的命中流程为:
- 从当前层开始判断所有子层中的指令是否一致,如果一致则命中缓存。
- 诸如 ADD 和 COPY 一类的指令还需要判断它们复制到镜像中的文件 checksum 值,如果目标文件与原来的不同,则不能命中缓存。
基于此,在编写 Dockerfile 时,应该把不常变动的指令放到前面,容易变动的指令放到后面。
2.5 设置镜像内时区
如果我们把 DockerHub 上拉下来的镜像直接在国内使用,那么会发现其时区并不是国内的,这时需要进行一些设置,下面列出常用操作系统的修改方式:
Alpine
RUN apk add tzdata
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" >> /etc/timezone
Ubuntu
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" >> /etc/timezone
CentOS
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
3. 常用指令讲解
CMD 和 ENTRYPOINT
二者都是 Docker 容器执行命令的入口,它们都可以用两种方式来指定启动命令。
exec 模式
通过 CMD/ENTRYPOINT["command", "param"]的方式指定命令,这是通过 Linux 的 exec 机制来实现的,是 Docker 官方推荐的方式。这种方式启动的容器,1 号进程就是 CMD/ENTRYPOINT 后面指定的命令。
shell 模式
通过 CMD/ENTRYPOINT command param的方式指定命令,这是通过 Linux 的 shell 来实现的,使用这种方式时,CMD/ENTRYPOINT 后面的命令会被以 /bin/sh -c command 的方式拉起,所以 1 号进程会是 shell。
CMD 和 ENTRYPOINT 的区别
通常镜像中会定义默认的启动命令,但是在进行容器编排时,可能想通过 K8S 等系统重新指定启动命令,那么此时 CMD 定义的命令可以被直接覆盖,而 ENTRYPOINT 指定的命令需要使用--entrypoint 显式覆盖。
COPY 和 ADD
这两个命令都可以从外部向镜像内部添加文件,COPY 只有简单的复制功能,而 ADD 可以通过 URL 下载文件并进行自动解压,它有很多参数,所以 COPY 更简单直观,如果想使用 ADD 则需要详细学习和测试,避免产生计划外的效果。
WORKDIR
在 Dockerfile 中使用大量的 cd 操作来切换目录是不优雅的,WORKDIR 可以容易的指定进入容器时的工作目录。
总结
Dockerfile 在写的时候应该像写代码一样,反复思考优化,不断改进,直至成为理想的状态,初学者可以参考阅读一些优质的 Dockerfile,比如 Nginx 的 Dockerfile。