首先,参见官方文档:

dockerfile_best-practices

有如下几点说明:红色标注的是重点

  • Create ephemeral containers(构建无状态的容器)

  • Understand build context(理解上下文,不引入多余文件)

  • Pipe Dockerfile through stdin(无需上下文的情况,通过stdin构建)

  • Exclude with .dockerignore(排除context中文件,参见 .dockerignore文件)

  • Use multi-stage builds(多阶段构建,并合理利用缓存:排序原则->最基础的RUN放在前面)

  • Don’t install unnecessary packages(不安装不必要的包,中间过程文件,可以删除和clean:rm -rf src/* && yum clean all)

  • Decouple applications(为了更好管理容器,不推荐在一个容器中部署多个进程)

  • Minimize the number of layers(减少层数,合并RUN、COPY、ADD、LABEL)

  • Sort multi-line arguments(为了直观,RUN参数较多时,建议分成多行并排序)

  • Leverage build cache(同上,多阶段构建合理利用缓存)

  • Dockerfile instructions(Dockerfile指令优化)

怎么理解“合理利用缓存”?

即:尽量把变化频率小的往前放,经常可能变化的命令往后放。

因为假设把经常变化的指令放在前面,缓存没有命中,则后面都要重新打镜像。

类似 COPY WORKDIR ENV LABEL等命令,可以往后放。

什么是“多阶段构建”?

多阶段构建的应用场景:

需要在容器中build应用,生成目标文件,然后再把目标文件拷贝到容器中运行。

比如:

  1. 编写编译容器Dockerfile,把源Java代码拷贝进容器,然后编译,生成在一个目录中。

    docker build -t java:build .

  2. 运行java:build容器,然后把编译好的产物从容器拷贝出来到宿主机上

    docker cp 容器:/home/java/code.class  ./

  3. 编写生产环境Dockerfile

  4. 将code.class拷贝到生产环境Dockerfile中,最终生成目标镜像

    docker build java:production .

    rm  code.class #删除宿主机的文件

    docker rmi java:build  #删除无用(中间状态的构建容器)

    docker rm -f java:build容器

使用“多阶段构建”,将编译过程和打包过程,合并在一起,build过程中,会自动运行中间状态容器中的命令,拿到中间产物。Dockerfile如下:

FROM java as build #1.构建阶段别名
COPY code.java /home/java
WORKDDIR  /home/java
RUN java -c code.javaFROM java as production #2.构建阶段别名
#重点!!! 直接从第一阶段拷贝产物文件
COPY --form=build /home/java/code.class /home/java/code.class
WORKDIR  /home/java
CMD ["java","code"]

直接生成最后一个阶段构建的容器

docker build -t java:production .

假设想单独生成某个阶段容器

docker build -t java:build  --target=build(构建阶段名称)  .

注意,这个中间件状态容器,并没有CMD和ENTRYPOINT,没有真正的启动起来,但是build过程中RUN指令会执行。要注意一点的是,RUN指令不要长期运行,应该是运行一段时间就能结束的。

Dockerfile指令优化

1、COPY指令和ADD指令的区别

1)COPY 是传统的复制文件

格式:COPY <源路径>... <目标路径>

<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。

目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。

此外,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。

2)ADD 是更高级的复制文件指令,在 COPY 基础上增加了一些功能。

比如 <源路径> 可以是一个 URL,或者压缩文件,ADD会自动拉取、解压文件。

在 Docker 官方的最佳实践文档中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。

因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。

2、CMD 与 ENTRYPOINT的区别

CMD

CMD 指令设置镜像中的默认启动命令和参数. 容器启动时,如果没有单独指定启动命令,则默认执行镜像中 CMD。

设置启动命令时, 应该尽量使用 JSON 格式 CMD ["command", "arg1", "arg2"]

例如 nginx 的启动方式: CMD ["nginx", "-D"]

ENTRYPOINT

当启动主程序之前还需要执行大量的前置操作时, 可以将 ENTRYPOINT 的入口指令设置为一个脚本 start.sh。

当 dockerfile 中指定了 ENTRYPOINT 的时候, docker run 如果在镜像之后添加的指令, 那么这些指令将被当做 ENTRYPOINT 的参数执行。

如果 dockerfile 中同时有 CMD 和 ENTRYPOINT 指令, 当 CMD 指令可执行时, 它将在 ENTRYPOINT 之前运行; 如果 CMD 不是可执行的命令, 则将作为 ENTRYPOINT 的命令参数追加。

下面这种写法,执行时,相当于 top -b -c

FROM ubuntu
ENTRYPOINT [ "top", "-b" ]
CMD [ "-c" ]

CMD和ENTRIPOINT详解:https://www.jb51.net/article/136264.htm

注意:

1、CMD命令会被docker run命令覆盖,但是ENTRYPOINT不会,但也可以使用--entrypoint参数指定新的脚本。

2、CMD [xxx] 命令不能用 && 串联多个指令。

比如 CMD ["ls", "&&", "tail", "-f", "app.log"],这样写是错的,只能像下面这样写:

CMD ls && tail -f app.log

3、但是CMD后面跟的命令,有些限制,比如 nohup start.sh & 加上 tail -f app.log

CMD nohup start.sh & && tail -f app.log

这样写是错的。建议改成sh脚本。

ENTRIPOINT编写指南:

1) set -e

你写的每个脚本都应该在文件开头加上set -e, 这句语句告诉bash如果任何语句的执行结果不是true则应该退出

2) exec "$@"

几乎在每个docker-entrypoint.sh脚本的最后一行, 执行的都是 exec "$@"命令,它的意思是匹配所有参数,原封不动的执行。

这个命令的意义在于你已经为你的镜像预想到了应该有的调用情况, 当实际使用镜像的人执行了你没有预料到的可执行命令时, 将会走到脚本的这最后一行, 去执行用户新的可执行命令。

参见:https://www.cnblogs.com/breezey/p/8812197.html

3) entry-point指令的正常执行的最后一句,要前台执行

为什么?因为“docker容器在其主进程完成时退出”,entry-point执行完后,docker容器就退出了。让docker容器一直运行的办法就是,entry-point命令不结束,如果最后一个命令是后台运行,entry-point脚本就结束了。

所以,建议使用 exec 执行最后一个命令。如果最后一句是执行一个脚本,那么这个脚本中的最后一个命令也要后台运行。举个例子entry-point最后一句是 exec start.sh,start.sh里面最后一句是exec run.sh,那么实际上最后执行的是run.sh,务必保证它是前台运行(执行完不会退出的)。

如果最后那个命令不支持前台运行。那么下面是两种保持脚本不退出的解决方案:

if [[ $1 == "-d" ]]; then    while true; do sleep 1000; done
fi    if [[ $1 == "-bash" ]]; then    /bin/bash
fi    

3、WORKDIR

尽量使用绝对路径;

切换目录的时候尽量使用 WORKDIR, 而不是使用 RUN cd /data。

4、USER

如果容器中的应用程序运行时不需要特殊的权限, 可以通过 USER 指令把应用程序的所有者设置为非 root 用户. 如果该用户不存在, 首先需要使用 RUN 命令在镜像中创建用户。

如果在每次编译镜像时, 对用户的 UID/GID 有要求需要保持一致, 应该在新建用户和组的时候指定 UID和 GID。

在镜像中避免使用sudo 命令,因为该命令使用的 TTY 不确定, 对接收信号量也会造成影响。如果确实需要使用 sudo 功能, 则可使用 gosu 命令替代。

可以用 root 用户初始化一个 daemon, 然后用非 root 用户启动这个 daemon

为了减少镜像体积, 应该避免不必要的用户切换。

5、Label

添加Label,以帮助按项目组织镜像,记录许可信息,帮助自动化或其他原因。

Label会增加层数,例如下面的:

# Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

旧版本docker建议将多行Label合并成一行,但是新版本(1.10以后),多行LABEL只会增加1层,不会增加多层。

6、Env

Each ENV line creates a new intermediate layer, just like RUN commands. This means that even if you unset the environment variable in a future layer, it still persists in this layer and its value can be dumped. You can test this by creating a Dockerfile like the following, and then building it.

FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER
$ docker run --rm test sh -c 'echo $ADMIN_USER'
mark

To prevent this, and really unset the environment variable, use a RUN command with shell commands, to set, use, and unset the variable all in a single layer. You can separate your commands with ; or &&. If you use the second method, and one of the commands fails, the docker build also fails.

FROM alpine
RUN export ADMIN_USER="mark" \&& echo $ADMIN_USER > ./mark \&& unset ADMIN_USER
CMD sh

总结:如果是固化到镜像中的环境变量,使用ENV XXX=xxx,但如果只是临时使用,则使用 RUN export XXX=xxx格式。

7、RUN

apt-get指令的话,推荐使用固定格式的“缓存清除”指令,形如:

RUN apt-get update && apt-get install -y

Docker官方的示例为:

RUN apt-get update && apt-get install -y \aufs-tools \automake \build-essential \curl \dpkg-sig \libcap-dev \libsqlite3-dev \mercurial \reprepro \ruby1.9.1 \ruby1.9.1-dev \s3cmd=1.1.* \&& rm -rf /var/lib/apt/lists/*

注意,官方 Debian and Ubuntu images 会自动 run apt-get clean, 因此不需要显示声明。

另外,注意bash的管道( | )问题:

RUN wget -O - https://some.site | wc -l > /number

Docker executes these commands using the /bin/sh -c interpreter, which only evaluates the exit code of the last operation in the pipe to determine success. 上例中只要 wc -l 命令成功,build就成功了,即使 wget 命令失败了。

If you want the command to fail due to an error at any stage in the pipe, 设置 set -o pipefail && to ensure that an unexpected error prevents the build from inadvertently succeeding. For example:

RUN set -o pipefail && wget -O - https://some.site | wc -l > /number

注意:不是所有的 shells 都支持 -o pipefail 选项:

比如 the dash shell on Debian-based images, 建议使用下面的格式来显式声明/bin/bash 设置 pipefail 选项:

RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site |

8、使用最小化Linux镜像时,添加必要常用指令

例如ping、wget、curl、tar、tail、more、vi、vim、cat、sed、netstat、ps、top、ifconfig、hostname、telnet、lsof、tcpdump、tree、tee、cut、wc、touch、find、head、sort、du、df、ip、nslookup、route、traceroute等。

实际经验:如果没有这些基础命令,需要在容器内排查问题,比如查看端口、tcp连接、线程等,都做不到。

9、Dockfile离线安装和使用本地文件、软件包时不使用ADD和COPY

举个例子,你在Dockerfile中,要安装一个 xxxx.rpm 包,这个包有5GB,如果使用COPY或者ADD指令,将添加一个包含了这个5GB的文件层,简单的解决办法为搭建一个HTTP Server,通过url去下载这个本地文件。

python3以下,在文件目录执行:

python -m SimpleHTTPServer 8069

python3执行:

python -m http.server --bind 192.168.178.20 8000

然后就可以使用 wget http://192.168.178.20:8000/xxxx.rpm 下载这个文件了。

实践经验

参见:《我的Dockerfile构建笔记》

Dockerfile最佳实践【原创、很多实践经验】相关推荐

  1. 最佳实践 ADO.NET实用经验无保留曝光

    ADO.NET作为微软最新的数据访问技术,已经在企业开发中得到了广泛的应用.对于一线的开发人员来说,掌握基本的概念和技术之后,提高应用水平和解决实际问题的最有效手段,莫过于相互交流彼此的最佳时间经验经 ...

  2. Dockerfile最佳实践(二)

    本文讲的是Dockerfile最佳实践(二),[编者的话]本文是 Docker 入门教程第三章-DockerFile 进阶篇的第二部分.作者主要介绍了 Docker 的变化.常用指令以及基础镜像的最佳 ...

  3. DockerFile最佳实践:

    2019独角兽企业重金招聘Python工程师标准>>> 以下是我们列出的基本的DockerFile最佳实践: 保持常见的指令像MAINTAINER以及从上至下更新DockerFile ...

  4. FPGA的设计艺术(8)最佳的FPGA开发实践之严格遵循过程

    文章目录 前言 如何花费更少的时间去调试? 为什么使用过程? 需要多少过程? 最小的过程 明确需求 数字设计方案 逻辑设计 功能仿真 板上验证 版本控制 编码指南:简短的技术组合,可最大程度地减少错误 ...

  5. 小程序遵循的语法_2020年遵循的最佳应用程序开发实践

    小程序遵循的语法 As per the stats, there were around 6 billion mobile app users in 2018-19. With increased d ...

  6. python数据分析实训心得_Python代码在实践过程中的经验总结

    Python代码在实践过程中的经验总结 关于Python脚本,在具体的实践过程中经常会遇到一些问题,下面将其总结,便于使用.考虑使用 Logger(logger 怎么配置,需要输出哪些信息 - 可以反 ...

  7. 《服务的最佳实践》再实践——定时关闭程序

    转载请注明出处:http://blog.csdn.net/chengbao315/article/details/50997218 最近读书读到了安卓的服务组件(再次推荐偶像的书,郭霖<第一行代 ...

  8. 神策数据实战学堂开课,分享行业最佳业务和技术实践

    近来,"数据驱动"概念大热,从精益分析到增长黑客,处处都有神策数据的身影.专题沙龙.数据驱动峰会.分析师培训营.在线公开课等,我们一直走在探索数据驱动的道路前沿,致力于帮助用户实现 ...

  9. vue2实践揭秘pdf_Vue2实践揭秘

    本书以Vue2的实践应用为根基,从实际示例入手,详细讲解Vue2的基础理论应用及高级组件开发,通过简明易懂的实例代码,生动地让读者快速.全方位地掌握Vue2的各种入门技巧以及一些在实际项目中的宝贵经验 ...

  10. 省培计算机实践作业,计算机软件基础强化实践能力培养实践部分考核作业.doc...

    计算机软件基础强化实践能力培养实践部分考核作业 V:1.0 精选考核制度 计算机软件基础强化实践能力培养实践部分考核作业 2020- -6 6- -8 8 <计算机软件基础>强化实践能力培 ...

最新文章

  1. 不要在覆写的方法中用super
  2. java 显示天气的小程序_超级简单的微信小程序获取今日天气预报代码 小程序获取七日天气...
  3. python同时输入多个变量_python同时给多个变量赋值|python3教程|python入门|python教程...
  4. 中移4G模块-ML302-OpenCpu开发-PCF8591测量电压
  5. poi doc转docx_编写简历及Python转Word文档为Pdf(续)
  6. java打包----“Artifacts”
  7. 6 rethad 自定义硬盘_Windows10必备6款优质软件,每个都是神器
  8. 数据分析进阶 - 评分模型权重计算方法
  9. Postgresql数据库介绍15——客户端认证
  10. unity物理引擎详解
  11. 【鸿蒙】鸿蒙App应用-《记账软件》开发步骤
  12. 美团点评 2019校招 前端方向职位试卷在线考试
  13. python穷举法列举_穷举法
  14. TCP Congestion性能测试分析
  15. #PRBS# PRBS7高速串行总线的常用测试码型
  16. 智能卡 PSAM 卡片文件结构
  17. 【机器学习】李宏毅-预测PM2.5
  18. 2048和多地址入口_王者荣耀2020周年庆返场皮肤投票入口 周年庆皮肤返场投票地址...
  19. java解析outlook的msg邮件(outlook-message-parser)
  20. XP中服务与后门技术

热门文章

  1. 刚学c++window编程没多久写了一个整人小软件有些bug
  2. c语言程序设计库搜索app,C语言编程宝典app
  3. Android实现异步从网络加载图片列表
  4. 引用element-ui的Drawer抽屉组件报错问题
  5. fsolve算得停不下来matlab,fsolve计算结果问题
  6. 【patch】x64dbg_2018_10_11导出的内存补丁文件内容如何理解地址偏移(va内存,Rva相对,Fva文件)
  7. Linux 网络代理设置
  8. 10年磨一剑,软件编程走火入魔之:把简单的功能做个彻彻底底、把劳动成果重复利用
  9. 基于视觉显著性的图像分割
  10. HTML5网页在线代码编辑器源码 适用各类项目代码在线编辑