Dockerfile最佳实践【原创、很多实践经验】
首先,参见官方文档:
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应用,生成目标文件,然后再把目标文件拷贝到容器中运行。
比如:
编写编译容器Dockerfile,把源Java代码拷贝进容器,然后编译,生成在一个目录中。
docker build -t java:build .
运行java:build容器,然后把编译好的产物从容器拷贝出来到宿主机上
docker cp 容器:/home/java/code.class ./
编写生产环境Dockerfile
将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最佳实践【原创、很多实践经验】相关推荐
- 最佳实践 ADO.NET实用经验无保留曝光
ADO.NET作为微软最新的数据访问技术,已经在企业开发中得到了广泛的应用.对于一线的开发人员来说,掌握基本的概念和技术之后,提高应用水平和解决实际问题的最有效手段,莫过于相互交流彼此的最佳时间经验经 ...
- Dockerfile最佳实践(二)
本文讲的是Dockerfile最佳实践(二),[编者的话]本文是 Docker 入门教程第三章-DockerFile 进阶篇的第二部分.作者主要介绍了 Docker 的变化.常用指令以及基础镜像的最佳 ...
- DockerFile最佳实践:
2019独角兽企业重金招聘Python工程师标准>>> 以下是我们列出的基本的DockerFile最佳实践: 保持常见的指令像MAINTAINER以及从上至下更新DockerFile ...
- FPGA的设计艺术(8)最佳的FPGA开发实践之严格遵循过程
文章目录 前言 如何花费更少的时间去调试? 为什么使用过程? 需要多少过程? 最小的过程 明确需求 数字设计方案 逻辑设计 功能仿真 板上验证 版本控制 编码指南:简短的技术组合,可最大程度地减少错误 ...
- 小程序遵循的语法_2020年遵循的最佳应用程序开发实践
小程序遵循的语法 As per the stats, there were around 6 billion mobile app users in 2018-19. With increased d ...
- python数据分析实训心得_Python代码在实践过程中的经验总结
Python代码在实践过程中的经验总结 关于Python脚本,在具体的实践过程中经常会遇到一些问题,下面将其总结,便于使用.考虑使用 Logger(logger 怎么配置,需要输出哪些信息 - 可以反 ...
- 《服务的最佳实践》再实践——定时关闭程序
转载请注明出处:http://blog.csdn.net/chengbao315/article/details/50997218 最近读书读到了安卓的服务组件(再次推荐偶像的书,郭霖<第一行代 ...
- 神策数据实战学堂开课,分享行业最佳业务和技术实践
近来,"数据驱动"概念大热,从精益分析到增长黑客,处处都有神策数据的身影.专题沙龙.数据驱动峰会.分析师培训营.在线公开课等,我们一直走在探索数据驱动的道路前沿,致力于帮助用户实现 ...
- vue2实践揭秘pdf_Vue2实践揭秘
本书以Vue2的实践应用为根基,从实际示例入手,详细讲解Vue2的基础理论应用及高级组件开发,通过简明易懂的实例代码,生动地让读者快速.全方位地掌握Vue2的各种入门技巧以及一些在实际项目中的宝贵经验 ...
- 省培计算机实践作业,计算机软件基础强化实践能力培养实践部分考核作业.doc...
计算机软件基础强化实践能力培养实践部分考核作业 V:1.0 精选考核制度 计算机软件基础强化实践能力培养实践部分考核作业 2020- -6 6- -8 8 <计算机软件基础>强化实践能力培 ...
最新文章
- 不要在覆写的方法中用super
- java 显示天气的小程序_超级简单的微信小程序获取今日天气预报代码 小程序获取七日天气...
- python同时输入多个变量_python同时给多个变量赋值|python3教程|python入门|python教程...
- 中移4G模块-ML302-OpenCpu开发-PCF8591测量电压
- poi doc转docx_编写简历及Python转Word文档为Pdf(续)
- java打包----“Artifacts”
- 6 rethad 自定义硬盘_Windows10必备6款优质软件,每个都是神器
- 数据分析进阶 - 评分模型权重计算方法
- Postgresql数据库介绍15——客户端认证
- unity物理引擎详解
- 【鸿蒙】鸿蒙App应用-《记账软件》开发步骤
- 美团点评 2019校招 前端方向职位试卷在线考试
- python穷举法列举_穷举法
- TCP Congestion性能测试分析
- #PRBS# PRBS7高速串行总线的常用测试码型
- 智能卡 PSAM 卡片文件结构
- 【机器学习】李宏毅-预测PM2.5
- 2048和多地址入口_王者荣耀2020周年庆返场皮肤投票入口 周年庆皮肤返场投票地址...
- java解析outlook的msg邮件(outlook-message-parser)
- XP中服务与后门技术
热门文章
- 刚学c++window编程没多久写了一个整人小软件有些bug
- c语言程序设计库搜索app,C语言编程宝典app
- Android实现异步从网络加载图片列表
- 引用element-ui的Drawer抽屉组件报错问题
- fsolve算得停不下来matlab,fsolve计算结果问题
- 【patch】x64dbg_2018_10_11导出的内存补丁文件内容如何理解地址偏移(va内存,Rva相对,Fva文件)
- Linux 网络代理设置
- 10年磨一剑,软件编程走火入魔之:把简单的功能做个彻彻底底、把劳动成果重复利用
- 基于视觉显著性的图像分割
- HTML5网页在线代码编辑器源码 适用各类项目代码在线编辑