docker——dockerfile的调优最佳实践_dockerfile unset_尤达c的博客-程序员宅基地

技术标签: Docker  docker  

前言

在容器领域,docker 公司提出的容器镜像已经成为目前容器打包交付的事实标准。构建镜像需要编写 Dockerfile,如何编写一个优雅的 Dockerfile 呢?在 Docker 公司的官方文档中给出了一篇

Best practices for writing Dockerfiles。
https://g.126.fm/03ncYHS

本文在此基础上做了一些修改,力图让大家在工作中写出一份不错的 Dockerfile。
本文分为三个部分,首先会直接给出一份 Dockerfile 的参考模板,然后说明如和构建高效的镜像并解释这个模板这样组织的原因,最后会补充说明一些编写过程中的常见问题。

一份简单的Dockerfile参考模板

docker 官方给出的参考文档中给出的 Dockerfile 指令接近 20 个,而我们平时在编写的时候,经常用到的不超过 10 个。因此,这里给出了一份 Dockerfile 的参考模板,几乎可以覆盖大部分的使用场景。

FROM base_image:tag    # 引用基础镜像 *必要*

ARG arg_key[=default_value1]     # 声明变量
ENV env_key=value2     			# 声明环境变量

# ①构建几乎不变的部分,例如整体的目录结构,build时依赖的文件和工具包等
COPY src dst
RUN command1 && command2 ...

WORKDIR /path/to/work/dir   # 设置工作目录 

# ②构建较少变动的部分,例如应用的依赖的文件、依赖的包等
COPY src dst
RUN command3 && command4 ...

# ③构建经常变动的部分,例如应用的编译生成
COPY src dst
RUN command5 && command6 ...

# 容器入口  *必要*
ENTRYPOINT ["/entry.app"]  # 指定容器启动时默认执行的命令
CMD ["--options"] # 指定容器启动时默认命令的默认参数

构建高效镜像生命周期

容器的一个重要的特点就是能够快速迭代,因此在容器镜像迭代的各个环节也应该尽量做到简洁高效。

1. 镜像build

精简 context: 每次 build,context 都会复制给 docker daemon,因此要去掉 context 中无关的部分
多层镜像:如果镜像很复杂,通常将其分成基础镜像(适用于多种应用,内容基本不变的部分)和应用镜像,应用镜像通过 FROM 基础镜像来减少 build 的步骤
利用构建缓存(build cache): 每次在 build 时,docker daemon 会默认从已在缓存中的父镜像开始,将下一条指令与从该基本镜像派生的所有子镜像进行比较,以查看是否其中一个是使用完全相同的指令构建的。如果不是,则缓存无效。因此,为了能够提高缓存的命中率,在编写 Dockerfile 时,应该尽量按照变动的频率来组织(如上文中的模板)
减少 layers: RUN, COPY, ADD 等指令会在 build 时产生对应的 layer,在较旧的 Docker 版本中,需要最小化镜像中的层数以确保其性能。因此,使用&&来连接多个 RUN 命令是一个常用的方法(如上文中的模板)
使用 multi-stage builds: 新特性,多阶段镜像构建,在Docker 多阶段构建镜像multi-stage有详细介绍

2. 镜像pull

docker 官方详细描述了 docker 镜像和容器在宿主机上的存储方式:https://docs.docker.com/storage/storagedriver/,简单来说就是:

  • 镜像层 ,只读,使用相同镜像的多个容器共用一份。镜像又按照 layers 分层:
    每层都有独立的 ID
    不同镜像如果有相同 ID 的 layer 时,共用一份

  • 容器层,可写,采用写时复制,容器在运行时修改的内容会在这一层
    根据镜像的存储方式,我们也可以加快镜像的 pull 过程:

  • 多层镜像:和 build 时的分层镜像一样,利用本地已经存储的基础镜像来减少需要 pull 的 size,
    利用 image layer 复用相同层:和 build 时利用缓存类似,利用本地已经存储的 layer 来减少需要 pull 的 size

  • 镜像预热:提前或空闲时 pull 镜像

常见问题

1. 注意Dockerfile中的指令是逐条执行,且相互独立

# 下面这种写法会报错,第二个RUN执行时的WORKDIR依旧是原来的目录,不是/some/dir
RUN cd /some/dir
RUN bash script.sh

# 改成下面两种之一
RUN cd /some/dir && bash script.sh
RUN bash /some/dir/script.sh

2. 提防“过度”缓存

前文也提到过,Dockerfile 中每条指令逐条执行,且相互独立。大部分的指令在 build 时会生成对应的一层(layer),并被缓存。这种机制在绝大部分的情况下都工作的很好,但是有时也会产生问题:

# Dockerfile1
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y nginx

# Dockerfile2
FROM ubuntu:18.04
RUN apt-get update
RUN apt-get install -y nginx curl

如上,原 Dockerfile1 使用一段时间之后修改成 Dockerfile2(只修改了install这一行)。由于缓存机制(假设之前 build 的缓存还存在),Dockerfile2 在 build 时,update这一行不会真的执行,而是直接拿之前的缓存。此时再到第三行执行时,安装的 nginx 和 curl 可能就不是当前的最新版本。

# 官方推荐的apt-get使用方式:
RUN apt-get update && apt-get install -y \
    curl \
    nginx=1.16.* \
    && rm -rf /var/lib/apt/lists/*

3. ARG与ENV

两种指令都可以用来定义变量,但是使用上有很多要注意的点:

  • FROM 前的 ARG 只能在 FROM 中使用,如果在 FROM 后也要使用,需要重新声明

     ARG key=value
     FROM xxx${key}xxxx
     ARG key # 这里需要再次声明才能使用
    
  • ARG 变量的作用范围是 build 阶段 ARG 之后的指令,不会带入镜像

  • ENV 环境变量作用范围是 build 阶段 ENV 声明的指令,并且会编入镜像,容器运行时也会这些环境变量也生效

  • CMD 和 ENTRYPOINT 中不能使用 ARG 和 ENV 定义的变量

  • 当 ARG 和 ENV 变量同名时(无论是谁先定义),ENV 环境变量的值会覆盖 ARG 变量

  • ENV 会产生中间层(layer),被编入镜像,即使使用 unset 也无法去掉,例如:

      FROM alpine
      ENV ADMIN_USER="mark"  # 此时产生了layer
      RUN echo $ADMIN_USER > ./mark
      RUN unset ADMIN_USER 
      # 使用unset只是去掉了build时的环境变量,但是最终生成的镜像中还是会有这个变量
      
      # 运行镜像还是会打印环境变量
      docker run --rm test sh -c 'echo $ADMIN_USER'
      mark
      
      # 如果想要消除这种影响,可以改成:
      FROM alpine
      RUN export ADMIN_USER="mark" \
          && echo $ADMIN_USER > ./mark \
          && unset ADMIN_USER
      CMD sh
    

4. COPY与ADD

两个指令几乎相同,当你只想复制本地 context 中的文件到镜像中时,请无脑用 COPY。

COPY 与 ADD 使用时,注意以下规则:

1、注意文件的属性,复制时可以同时修改属主和属组 COPY/ADD [--chown=<user>:<group>] <src> <dest>
2、如果不清楚目录与反斜线对这两个指令的影响,对所有目录都加上反斜线就比较好理解了,如COPY - <src_dir>/ <dest_dir>/,因为:
3、<src>是目录时,是否带反斜线都只会复制目录下的所有文件,不会复制目录本身,如果要复制目录本身,需要使用``的父目录
4、<dest>是目录时,必须带反斜线才会把文件复制到dest下
5、<src>必须在 context 下,不能使用../跳出 context

6、ADD 指令除了 COPY 的所有功能外,还有以下特性,如非必要,尽量少用:
	<src>是本地 tar 文件(常见的压缩格式)时,会自动解包
	<src>可以是 url,支持从远程拉取

5. CMD与ENTRYPOINT

又是一对很类似的指令,二者若有多条出现都是只执行最后一条,使用时需要注意:

  • CMD 单独使用时,用来指定容器启动时默认执行的命令
  • ENTRYPOINT 单独使用时,可以完全取代 CMD
  • ENTRYPOINT 和 CMD 一起使用时,CMD 变成 ENTRYPOINT 的默认参数
  • 推荐使用 ENTRYPOINT/CMD 的 exec 书写形式:即ENTRYPOINT [“entry.app”, “arg”],因为 shell 书写形式(ENTRYPOINT entry.app arg)会额外启动 shell 进程

下表列出了 CMD 与 ENTRYPOINT 的各种组合时的效果:

在这里插入图片描述

另外,通过在 docker run 最后的添加字段,可以指定 ENTRYPOINT 的实际参数

  # 镜像 test_entrypoint
  ENTRYPOINT ["./entry.app"]
  CMD ["--help"]

  # 运行 test_entrypoint
  docker run test_entrypoint # 即./entry.app --help
  # 带参数运行
  docker run test_entrypoint -a -t  # 即 ./entry.app -a -t

6. multi-stage builds

Docker 17.05 之后的版本支持一种新的 build 方式:多阶段构建(multi-stage builds)。与传统方式的区别在与,多阶段构建能够使用多个 FROM 将整个 build 阶段分成多个阶段:

  • 通过为不同阶段命名,可以通过一份 Dockerfile 来管理 debug、test、product 等多种环境的镜像
  • 通过COPY --from=stage_name,来复制中间 stage 的文件到目标阶段,使得最终生成更小的镜像

例如,上文提到的模板就可以通过多阶段构建的方式来优化。假设我们最终只想得到 entry.app 及其运行环境,而不需要它的编译环境,那么可以通过如下方式优化最终生成的镜像的大小:

# 使用多阶段构建,这里命名一个builder阶段,生成编译后的app
FROM base_image:tag AS builder   

ARG arg_key[=default_value1]     # 声明变量
ENV env_key=value2     # 声明环境变量

# 构建整体的目录结构,build时依赖的文件和工具包等
COPY src dst
RUN command1 && command2 ...

WORKDIR /path/to/work/dir   # 设置工作目录 

# 构建编译环境
COPY src dst
RUN command3 && command4 ...

# 编译生成entry.app
COPY src dst
RUN compile_entry_app

# 构建最终镜像的阶段,只保留应用和其运行环境,编译的依赖都不需要
FROM base_image:tag
COPY src dest    # 复制运行环境
WORKDIR /path/to/work/dir   # 设置工作目录 
COPY --from=builder entry.app . # 从builder阶段复制app
# 容器入口
ENTRYPOINT ["/entry.app"]  # 指定容器启动时默认执行的命令
CMD ["--options"] # 指定容器启动时默认命令的默认参数
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_51971452/article/details/115384200

智能推荐

FC6 装beryl 3D桌面以及beryl使用方法-程序员宅基地

baseurl=http://users.skynet.be/betatux/fedora/fc6/$basearch enabled=0 gpgcheck=0 [betatux-debug] name=BETATUX - $basearch - Debug baseurl=http://users.skynet.be/betatux/fedora/fc6/$basearch-debug...

python异常值替换为缺失值_删除异常值(+/-3std)并在Python/pandas中替换为np.nan-程序员宅基地

我看到了几种接近解决我问题的方法但是到目前为止他们还没有帮助我成功。我相信下面的解决方案是我所需要的,但是仍然会出现一个错误(而且我没有声誉点来评论/质疑它):link(我得到以下错误,但不知道在管理以下命令时,.copy()或添加“inplace=True”的位置:设置为复制警告:试图在数据帧切片的副本上设置值。试着用.loc[row_indexer,col_indexer] = value代替...

YOLOAir_neck c3 注意力机制-程序员宅基地

NMS、Merge-NMS、DIoU-NMS、Soft-NMS、CIoU-NMS、DIoU-NMS、GIoU-NMS、EIoU-NMS、SIoU-NMS、Soft-SIoUNMS、Soft-CIoUNMS、Soft-DIoUNMS、Soft-EIoUNMS、Soft-GIoUNMS 等持续更新中。以上组件模块使用统一模型代码框架、统一任务形式、统一应用方式,模块组件化可以帮助用户自定义快速组合 Backbone、Neck、Head,使得网络模型多样化,助力科研改进检测算法,构建更强大的网络模型。_neck c3 注意力机制

mysql - limit和order by 使用_limit orderby_百步送剑的博客-程序员宅基地

mysql - limit和order by 使用1,使用的位置SELECT select_list  [ INTO new_table ]  FROM table_source  [ WHERE search_condition ]  [ GROUP BY group_by_expression ]  [ HAVING search_condition ]  [ ORDER BY order_expression [ ASC | DESC ] ]  [LIMIT a,b]可以看到:or_limit orderby

使用npm或者yarn命令报错的问题解决_"couldn't find a configuration settings named \"re-程序员宅基地

新机使用npm或者yarn会出现权限不足报错的问题,需要去命令行下开启权限命令如下:get-ExecutionPolicy set-ExecutionPolicy RemoteSigned;使用管理员打开cmd或者powershell,先使用get-ExecutionPolicy 查看是否开启Restricted默认是关闭,需要使用set-ExecutionPolicy RemoteSigned 设置将它开启,输入Y按回车不放心再查看一遍状态,显示RemoteSigned 就代表可以使_"couldn't find a configuration settings named \"registry"

安卓开发从零开始!熬夜肝完这份Framework笔记,全网独家首发!_安卓开发 从零-程序员宅基地

写这篇文章的目的是想说说这段时间一直被不断提起搞得人心惶惶的话题,裁员。为什么突然聊这个,本来一直是想避开这个话题的,一是网上已经有了铺天盖地的消息不想要再造成大家的恐慌,二是我身边几乎没有发生这样的事情,没有什么特别大的感触。但是上周五这件事居然发生在了我身边,谈谈我的感受吧。当然,事件的主角不是我,具体是哪家厂也不方便透露,怕被找上门,很慌…事情的经过我听同事的描述和网上大家遇到的是大致相同的:早上接到HR电话通知,签字后立马进行交接,收拾东西,下午走人,留下一堆坑,和一脸懵逼的其他同事…听同事说被_安卓开发 从零

随便推点

航空航天大类C语言程设第二次上机练习_double x; scanf("%lf",&x) 中添加-程序员宅基地

航空航天大类C语言程设第二次上机练习第二期更新,主要是第二次上机及练习赛的内容。Q1(上机A题)一个非负的浮点数(小数部分不超过六位),输出它的小数部分(六位小数保留)和整数部分(空格隔开)这个题只要稍微熟悉一下变量声明就很好做了,注意给的是非负浮点数,所以少了个坑(狗头)先给出代码#include <stdio.h>int main() { double x; sca..._double x; scanf("%lf",&x) 中添加

Specular IBL_specularibl-程序员宅基地

https://learnopengl.com/#!PBR/IBL/SpeSpecular IBLIn the&nbsp;previous&nbsp;tutorial we've set up PBR in combination with image based lighting by pre-computing an irrad..._specularibl

boss直聘改回系统头像_BOSS直聘“带”薪直播告诉你,跟对老板有多重要?-程序员宅基地

一将无能,累死千军。对于应届生来说,找到一个能够将自己职场经验所得毫无保留地传授给你“好师父”,甚至比择业更重要一些。罗永浩曾经和媒体表达过,招人无非三种方式最吸引人,钱、公司价值观、团队老板的个人魅力。应届生毕业时,很多时候进入的是买方市场,因为没有经验,在薪资和企业的选择上较为有限,能否跟上一个“好师父”,一定程度上也影响了他们未来的职业生涯。昨天,BOSS直聘和央视新闻展开了一次“..._boss直聘有必要换真实头像吗

MySQL执行顺序_mysql 执行顺序-程序员宅基地

mysql执行顺序_mysql 执行顺序

linux查询jiffies命令,linux HZ Tick Jiffies_点圆的博客-程序员宅基地

HZ的不同值会影响timer (节拍)中断的频率2.2jiffies及其溢出全局变量jiffies取值为自操作系统启动以来的时钟滴答的数目,在头文件中定义,数据类型为unsignedlongvolatile(32位无符号长整型)。关于jiffies为什么要采用volatile来限定,可参考《关于volatile和jiffies.txt》。jiffies转换为秒可采用公式:(jiffies/..._linux应用层获取jiffise

数据安全(反爬虫)之「防重放」策略_防重放可以防爬虫么-程序员宅基地

在大前端时代的安全性一文中讲了 Web 前端和 Native 客户端如何从数据安全层面做反爬虫策略,本文接着之前的背景,将从 API 数据接口的层面讲一种技术方案,实现数据安全。一、 API 接口请求安全性问题API 接口存在很多常见的安全性问题,常见的有下面几种情况即使采用 HTTPS,诸如 Charles、Wireshark 之类的专业抓包工具可以扮演证书颁发、校验的角色,因此可以..._防重放可以防爬虫么

推荐文章

热门文章

相关标签