Dockerfile容器化应用最佳实践
将一个应用容器化,就是将这个应用构建成Docker镜像,然后以Docker容器方式来运行。
构建Docker镜像就是编写Dockerfile,并通过docker build
命令构建成Docker镜像。
可以用”小、快、好、省“来概括Dockerfile的最佳实践。
最佳实践
让构建出来的镜像尽可能”小“
Why?
小的镜像的好处包括构建镜像快,占用磁盘空间小,网络传输快(比如docker pull
和docker push
时速度快)。
How?
选择适合的尽量小的基础镜像
比如,你要从Linux操作系统基础镜像开始构建,可以参考下表来选择合适的基础镜像:
镜像名称 | 大小 | 使用场景 |
---|---|---|
busybox | 1.15MB | 临时测试用 |
alpine | 4.41MB | 主要用于测试,也可用于生产环境 |
centos | 200MB | 主要用于生产环境,支持CentOS/Red Hat,常用于追求稳定性的企业应用 |
ubuntu | 81.1MB | 主要用于生产环境,常用于人工智能计算和企业应用 |
debian | 101MB | 主要用于生产环境 |
尽量减少镜像层的数目
RUN
、ADD
和COPY
命令每次执行时都会创建一个镜像层,每个镜像层都会有一定的磁盘开销。通过合并Dockerfile命令,比如将多条安装包和库文件命令合并成一个
RUN
命令(通过\
来换行),可以显著减少镜像层的数目。参考文档:
不要安装不必要的包
不要安装不必要的包,可以减小构建出来Docker镜像的大小。
构建后删除临时的包
比如解压压缩包后,因为容器运行时不会用到原来的压缩包,因此可以删除原有的压缩包。
让构建镜像过程尽可能“快”
Why?
加快构建的好处不言而喻,比如加快构建Docker镜像环节可以提升整个部署流水线的效率。
How?
使用清洁的构建上下文(build context)
Docker CLI是由给定一个目录或者URL作为构建上下文的,在镜像构建之前会将该上下文发送到Docker Daemon。建议在一个新的目录里创建Dockerfile,然后在该目录里只添加镜像包含的文件。这为
docker build
命令提供了一个更加清洁的上下文并且允许更快地构建镜像。否则,扫描带有多个文件的目录将不必要地减慢构建速度。参考文档:https://docs.docker.com/engine/reference/builder/#usage
利用构建时缓存(build cache)
Docker build构建镜像时会重复使用已经构建过的中间层作为缓存来加快构建过程。而Docker build构建时是按照Dockerfile顺序构建的,因此需要将不经常变化的生成层的命令放在前面,把经常变化的生成层的命令放在后面。
参考文档:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache
让镜像变得”好用“
Why?
好用的镜像,造就好用的容器。好用的容器是指可以使用Docker 标准命令前后台运行,传入运行时环境变量控制容器行为,传入运行命令替换缺省命令,优雅的停止,方便查看日志和进入容器内部调试。
How?
选择官方基础镜像
选择官方认证的基础镜像,是构建出好用的镜像的前提。
设置正确的程序入口点
容器的程序入口点就是容器启动后要启动的进程。
比如一个Java应用的容器启动后,要启动的进程通常是
java -jar app.jar
。Dockerfile中的
CMD
命令和ENTRYPOINT
命令的多种格式,以及单独使用和混合使用很容易混淆,请参考:- https://docs.docker.com/engine/reference/builder/#entrypoint
https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact
不要加入不必要的限制
不要加入不必要的限制,比如只能以root用户运行,只能以特定uid 运行,特权端口,特权模式运行。
让构建镜像变得”省事、省心“
Why?
Dockerfile越复杂,越容易出错;Dockerfile有问题,运行的容器会有安全隐患。
How?
选择高级别基础镜像
尽量使用高级别基础镜像来简化编写Dockerfile的工作,比如为Java应用容器化使用openjdk基础镜像,而不是从alpine 操作系统基础镜像开始构建。
保持Dockerfile尽可能简单
参考Docker官方和其它一些好的开源软件的Dockerfile,让Dockerfile尽量保持简单,可读。
使用经过安全扫描的基础镜像
经过安全扫描的基础镜像,可以让容器运行在一个安全的环境。
FAQ
Q: ARG
和ENV
命令的区别。
A: ARG
支持在构建镜像时(build-time)修改参数的值,比如docker build --build-arg var=xxx
。
ENV
支持在运行容器时(run-time) 修改参数的值,比如docker run -e var=yyy
。
Q: ADD
和 COPY
命令的区别。
A: 推荐使用COPY命令。
Q: Docker build的cache问题。
A: ADD
和COPY
命令利用cksum来检查同名文件是否存在在缓存中,但是对cksum的计算不包括last-modified time和last-accessed time,有可能导致缓存机制失效。如果你的应用每次都基于一个同名文件,需要注意这个问题。
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache
Q: RUN
, CMD
和 ENTRYPOINT
的区别。
A: RUN
命令是在docker build
构建时被执行,在容器运行时不会被执行。
CMD
和 ENTRYPOINT
命令是在docker run
运行容器时被执行,在容器构建时不会被执行。
Docker缺省的ENTRYPOINT是/bin/sh -c
。
- https://docs.docker.com/engine/reference/builder/#entrypoint
- https://docs.docker.com/engine/reference/builder/#cmd
- https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact
- https://aboullaite.me/dockerfile-run-vs-cmd-vs-entrypoint/