高效编写 Dockerfile 的几条准则 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
hansonwang99
V2EX    程序员

高效编写 Dockerfile 的几条准则

  •  1
     
  •   hansonwang99
    hansonwang99 2018-07-11 22:34:29 +08:00 5452 次点击
    这是一个创建于 2727 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Profile


    概述

    • Dockerfile 是专门用来进行自动化构建镜像的编排文件(就像 Jenkins 2.0 时代的 Jenkinsfile 是对 Jenkins 的 Job 和 Stage 的编排一样),我们可以通过 docker build 命令来自动化地从 Dockerfile 所描述的步骤来构建自定义的 Docker 镜像,这比我们去命令行一条条指令执行的方式构建高效得多。

    • 另一方面,由于 Dockerfile 提供了统一的配置语法,因此通过这样一份配置文件,我们可以在各种不同的平台上进行分发,需要时通过 Dockerfile 构建一下就能得到所需的镜像。

    • 最后一个必须提的优点便是:Dockerfile 通过与镜像配合使用,使得 Docker 镜像构建之时可以充分利用 “镜像的缓存功能”,因此也提效不少!

    然而写 Dockerfile 也像写代码一样,一份精心设计、Clean Code 的 Dockerfile 能在提高可读性的同时也大大提升 Docker 的使用效率

    因此下面就结合实践来讲几条 Dockerfile 的实践心得!



    基础镜像的选择有讲究

    在我的文章 《利用 K8S 技术栈打造个人私有云(连载之:基础镜像制作与实验)》 中,我们是基于某个 Linux 基础镜像作为底包,然后打包进我需要的功能从而形成自己的镜像。

    这里选择基础镜像时是有讲究的:

    • 一是 应当尽量选择官方镜像库里的基础镜像;
    • 二是 应当选择轻量级的镜像做底包

    就典型的 Linux 基础镜像来说,大小关系如下:

    Ubuntu > CentOS > Debian 

    因此相比 Ubuntu,其实更推荐使用最轻量级的 Debian 镜像,而且它也是一个完整的 Release 版,可以放心使用



    多使用标签 Tag 有好处

    • 构建镜像时,给其打上一个易读的镜像标签有助于帮助了解镜像的功能,比如:
    docker build -t=“ centos:wordpress" . 

    例如上面的这个 centos 镜像是用来做 wordpress 用的,所以已经集成了 wordpress 功能,这一看就很清晰明了

    • 再者,我们也应该在 Dockerfile 的 FROM 指令中明确指明标签 Tag,不要再让 Docker daemon 去猜,如
    FROM debian:codesheep 


    充分利用镜像缓存

    什么是镜像缓存?

    由 Dockerfile 最终构建出来的镜像是在基础镜像之上一层层叠加而得,因此在过程中会产生一个个新的 镜像层。Docker daemon 在构建镜像的过程中会缓存一系列中间镜像。

    docker build 镜像时,会顺序执行 Dockerfile 中的指令,并同时比较当前指令和其基础镜像的所有子镜像,若发现有一个子镜像也是由相同的指令生成,则 命中缓存,同时可以直接使用该子镜像而避免再去重新生成了。

    为了有效地使用缓存,需要保证 Dockerfile 中指令的 连续一致,尽量将相同指令的部分放在前面,而将有差异性的指令放在后面

    **举例:**假如我想用 Dockerfile 方式 基于最基本的 CentOS 镜像来构建两个不同的镜像时,两个 Dockerfile 的开头可以相同:

    FROM centos:latest # 下面安装两个常用的工具 RUN yum install -y net-tools.x86_64 RUN yum install lrzsz ######## 上面为两个 Dockerfile 文件中相同的部分###### ######## 下面为两个 Dockerfile 文件中不同的部分###### ...... 


    ADD 与 COPY 指令的正确使用

    虽然两者都可以添加文件到镜像中,但在一般用法中,还是推荐以 COPY 指令为首选,原因在于 ADD 指令并没有 COPY 指令来的纯粹,ADD 会添加一些额外功能,典型的如下 ADD 一个压缩包时,其不仅会复制,还会自动解压,而有时我们并不需要这种额外的功能。

    ADD codesheep.tar.gz /path 

    除此之外,在需要添加多个文件到镜像中的时候,不要一次性集中添加,而是选择 按需 在必要时 逐个 添加即可,因为这样有利于利用镜像缓存



    ##尽量使用 docker volume

    虽然上面一条原则说推荐通过 COPY 命令来向镜像中添加多个文件,然而实际情况中,若文件 大而多 的时候还是应该优先用 docker -v 命令来挂载文件,而不是依赖于 ADD 或者 COPY



    CMD 和 ENTRYPOINT 指令 的正确理解使用

    Dockerfile 制作镜像时,会组合 CMD 和 ENTRYPOINT 指令来作为容器运行时的默认命令:即 CMD + ENTRYPOINT。此时的默认命令组成中:

    • ENTRYPOINT 指令部分固定不变,容器运行时是无法修改的
    • 而 CMD 部分的指令也可以改变,表现在运行容器时,docker run 命令中提供的参数会覆盖 CMD 的指令内容。

    举个例子:

    FROM debian:latest MAINTAINER [email protected] ENTRYPOINT [ "ls", "-l"] CMD ["-a"] 

    若以默认命令运行容器,可以发现,执行的是 ls -a -l 命令:

    ls -l -a

    docker run 中增加参数 -t

    docker run -it --rm --name test debian:codesheep -t 

    也可以发现执行的是 ls -l -t,即 Dockerfile 中的 CMD 原参数被覆盖了:

    ls -l -t

    因此推荐的使用方式是:

    • 使用 exec 格式的 ENTRYPOINT 指令 设置固定的默认命令和参数

    • 使用 CMD 指令 设置可变的参数



    不推荐在 Dockerfile 中 做端口映射

    Dockerfile 可以通过 EXPOSE 指令 将容器端口映射到主机端口上,但这样会导致镜像在一台主机上仅能启动一个容器!

    所以应该在 docker run 命令中来用 -p 参数来指定端口映射,而不要将该工作置于 Dockerfile 之中:

    #尽量避免这种方式 EXPOSE 8080:8899 #选择仅仅暴露端口即可,端口映射的任务交给 docker run 去做 EXPOSE 8080 


    使用 Dockerfile 来共享镜像

    推荐通过共享 Dockerfile 的方式来共享镜像,优点多多:

    • 通过 Dockerfile 构建的镜像用户可以清楚地看到构建的过程

    • 就像 Jenkinsfile 可以加入版本控制从而追踪 CI 系统的变迁和步骤的回滚一样,Dockerfile 作为一个编排文件同样可以入库做版本控制,这样也可以回溯

    • 使用 Dockerfile 构建的镜像具有确定性,没有玄学的成分



    后记

    如果有兴趣,也可以抽点时间看看作者一些关于容器化、微服务化方面的文章:

    作者相关的 SpringBt 实践文章在此:



    36 条回复    2018-07-12 11:54:02 +08:00
    td width="10" valign="top">
    wl2358
        1
    wl2358  
       2018-07-11 22:40:20 +08:00 via Android
    最近在学 dicker, mark
    mason961125
        2
    mason961125  
       2018-07-11 22:41:36 +08:00
    所以,开头放张图的意义是什么?
    ngg0707
        3
    ngg0707  
       2018-07-11 22:44:58 +08:00 via iPhone
    很有帮助
    AlphaTr
        4
    AlphaTr  
       2018-07-11 22:49:26 +08:00 via iPhone
    有一点不太认同,希望能讨论讨论:tag 那个,我更倾向于 wordpress:v4.3.3 这样的,对于用户来说,不需要关心底层镜像是 centos 还是 ubuntu,关心的是 wordpress 的版本或者其他信息;私以为这里的 tag 类似于 git 的 tag,多用于版本
    hansonwang99
        5
    hansonwang99  
    OP
       2018-07-11 22:59:45 +08:00
    我觉得楼上的理解也挺好呢
    lizheming
        6
    lizheming  
       2018-07-11 23:21:04 +08:00 via iPhone
    @AlphaTr 我觉得只是他的例子举的不太好,意思都是差不多,作者想说的应该和你的意思一样。
    ofnh
        7
    ofnh  
       2018-07-11 23:32:18 +08:00 via Android
    是 ls -l -a 吧
    mritd
        8
    mritd  
       2018-07-12 00:13:02 +08:00 via iPhone
    @lizheming 无版本号 / 时间戳 tag 等同于 latest
    yuanfnadi
        9
    yuanfnadi  
       2018-07-12 00:42:21 +08:00
    很重要的多部构建没有提到。可以极大的缩小镜像体积。
    shiny
        10
    shiny  
    PRO
       2018-07-12 00:50:20 +08:00
    不谈谈 alpine linux 吗
    billwsy
        11
    billwsy  
       2018-07-12 01:40:36 +08:00 via iPhone
    Tag 成 centos:wordpress 真的好吗?不怕冲突?以后上传到 registry 也不方便啊
    billwsy
        12
    billwsy  
       2018-07-12 01:45:06 +08:00 via iPhone
    另外 Entrypoint 运行时也是可以改的

    至于所说的 layer,倒不如造一个自己的 base,所有其他 image 从中继承;我们系统里的 57 个 image 就组织成了这样的树状,然后一个脚本自动解析依赖关系
    blless
        13
    blless  
       2018-07-12 02:28:19 +08:00 via iPhone
    感觉只是基础的构建…官方构建一般不会有两个 yum install,而且推荐安装跟清理一起做了 不然下一层清理体积并不会减小
    blless
        14
    blless  
       2018-07-12 02:30:33 +08:00 via iPhone
    添加文件按需逐个添加效率也太低了…而且如果第二个文件就不一样 后面缓存也全没用了…
    blless
        15
    blless  
       2018-07-12 02:31:15 +08:00 via iPhone   2
    ……楼主你这文章就不要用"准则"了,明显误导别人
    ysicing
        16
    ysicing  
      &nbp;2018-07-12 07:04:24 +08:00 via Android
    基础镜像很少用 centos 的吧,不应该是最常用 alpine 或 debian
    sdrzlyz
        17
    sdrzlyz  
       2018-07-12 07:27:38 +08:00 via Android
    确定 Expose 是这个意思?这个“准则”有点误认啊。。。基础镜像不提 alpine ?
    naiba
        18
    naiba  
       2018-07-12 07:34:35 +08:00 via Android
    层数越多占用空间越大…
    hxsf
        19
    hxsf  
       2018-07-12 07:54:25 +08:00 via iPhone
    @blless +1
    to 楼主 layer 越多越容易缓存也越大,
    有 run 的时候基本缓存失效 不如一把梭,layer 少点还小
    entrypoint 也是能改的
    推荐用-v 挂文件什么意思 有些东西要持久化才挂载 其他作为容器一部分就好了,不然分发的时候除了 image 还得分发其他文件。

    另外:标题确实不太好
    1daydayde
        20
    1daydayde  
       2018-07-12 08:11:08 +08:00 via iPhone
    @blless +1 不认同楼主的“镜像缓存”,这样只是方便你本地下次打镜像更快一点而已,但是却浪费了空间,使镜像多了一层。
    beginor
        21
    beginor  
       2018-07-12 08:14:03 +08:00 via Android
    为了达到最小化体积,将所有的动作都放到一个 install.sh , 然后 dockerfile 只有一句 exec,这种做法是否可取呢?

    https://github.com/beginor/docker-geoserver
    jhsunnyshine
        22
    jhsunnyshine  
       2018-07-12 08:31:33 +08:00 via Android
    好评,给赞
    wenzhoou
        23
    wenzhoou  
       2018-07-12 08:32:25 +08:00 via Android
    @beginor 这个能减小体积吗?
    laincat
        24
    laincat  
       2018-07-12 08:57:38 +08:00
    不管怎么样,读一下
    tandaly
        25
    tandaly  
       2018-07-12 09:07:00 +08:00
    我是来看桌面的
    hcivincentchan
        26
    hcivincentchan  
       2018-07-12 09:18:13 +08:00
    entrypoint 在容器启动时 是可以重新指定的好吗
    beginor
        27
    beginor  
       2018-07-12 10:46:49 +08:00
    @wenzhoou 能,我原来都是写在 Dockerfile, 放到 install.sh 的话,build 出来的镜像可以减少 100 多兆
    beginor
        28
    beginor  
       2018-07-12 10:47:45 +08:00
    @wenzhoou 而且 sh 文件容易进行测试, 可以一边运行, 一边编写
    wenzhoou
        29
    wenzhoou  
       2018-07-12 11:06:41 +08:00 via Android
    @beginor 相信跟你放到哪里写是没有关系的。因为理论上是一样的执行。
    g8287694
        30
    g8287694  
       2018-07-12 11:17:11 +08:00
    ADD 是不是无法添加非本目录的文件?
    lfzyx
        31
    lfzyx  
       2018-07-12 11:21:17 +08:00
    上面说 entrypoint 可以改的,人家设计这个目的是让你不会轻易去修改。就像私有变量不是不能访问,只是让你明白不应该直接去访问
    mcfog
        32
    mcfog  
       2018-07-12 11:24:13 +08:00
    我现在基本形成了本能反应:看到这种开头一张图,内容里面大量副标题和加粗的公众号文笔 就自动判定成垃圾水文,速读垂直扫几眼直接拉到底,看到一排链接,嗯,枪毙,再见
    raysonx
        33
    raysonx  
       2018-07-12 11:28:09 +08:00
    不同意尽量使用 VOLUME 的说法。
    除非你只是用 docker 做开发,也就是把依赖全都打进 image,然后挂载本地代码。
    如果做生产用途,除了数据和一部分配置,其他应该全部都打进 image,以保证“一次构建,处处运行”。
    godjob
        34
    godjob  
       2018-07-12 11:30:14 +08:00
    @beginor 这样做就脱离 docker 的宗旨了,docker 的目的就是一个完整的环境集装箱,在任何环境都可以运行,如果启动时再安装环境和软件,每次启动都可能有不同的因素干扰,就不能保证每个 doker 容器的环境一致
    NNS71L068O2v70DB
        35
    NNS71L068O2v70DB  
       2018-07-12 11:30:38 +08:00
    这篇文章和我看的 docker-从入门到实践有很大出入
    beginor
        36
    beginor  
       2018-07-12 11:54:02 +08:00 via Android
    @godjob 不是启动时安装, 是编译时执行安装, 最终还是装好了所有软件的镜像。


    @wenzhoou 会的, 原来需要 Dockerfile 中写多个 run 指令, 现在只需要一个指令 run install.sh , 这样镜像只有一层, 能显著减小最终镜像的体积。install.sh 还是一句一句来写安装的命令
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4844 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 03:56 PVG 11:56 LAX 19:56 JFK 22:56
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86