您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
DockOne技术分享(一):Dockerfile与Docker构建流程解读
 
作者 徐新坤 来源:dockone.io 火龙果软件  发布于 2015-5-4
   次浏览      
 

【编者的话】本次讨论主要对docker build的源码流程进行了梳理和解读,并分享了在制作Dockerfile过程中的一些实践经验,包括如何调试、优化和build中的一些要点。另外,还针对现有Dockerfile的不足进行了简要说明,并分享了对于Dockerfile的一些理解。

听众

这次的分享主要面向有一定Docker基础的。我希望你已经:

  • 用过Docker,熟悉docker commit命令
  • 自己动手编写过Dockerfile
  • 自己动手build过一个镜像,有亲身的体验
  • 我主要分享一些现在网上或者文档中没有的东西,包括我的理解和一些实践,有误之处也请大家指正。好了,正文开始:

    Dockerfile

    Dockerfile其实可以看做一个命令集。每行均为一条命令。每行的第一个单词,就是命令command。后面的字符串是该命令所要接收的参数。比如ENTRYPOINT /bin/bash。ENTRYPOINT命令的作用就是将后面的参数设置为镜像的entrypoint。至于现有命令的含义,这里不再详述。DockOne上有很多的介绍。

    Docker构建(docker build)

    docker build的流程

    docker build的流程(这部分代码基本都在docker/builder中)

  • 提取Dockerfile(evaluator.go/RUN)。
  • 将Dockerfile按行进行分析(parser/parser.go/Parse) Dockerfile,每行第一个单词,如CMD、FROM等,这个叫做command。根据command,将之后的字符串用对应的数据结构进行接收。
  • 根据分析的command,在dispatchers.go中选择对应的函数进行处理(dispatchers.go)。
  • 处理完所有的命令,如果需要打标签,则给最后的镜像打上tag,结束。
  • 在这里,我举一个例子来说明一下在第4步命令的执行过程。以CMD命令为例:

    func cmd(b *Builder, args []string, 
    attributes map[string]bool, original string) error {
    cmdSlice := handleJsonArgs(args, attributes)

    if !attributes["json"] {
    cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
    }

    b.Config.Cmd = runconfig.NewCommand(cmdSlice...)

    if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice));
    err != nil {
    return err
    }

    if len(args) != 0 {
    b.cmdSet = true
    }

    return nil
    }

    可以看到,b.Config.Cmd = runconfig.NewCommand(cmdSlice...)就是根据传入的CMD,更新了Builder里面的Config。然后进行b.commit。Builder这里的commit大致含义其实与docker/daemon的commit功能大同小异。不过这里commit是包含了以下的一个完整过程(参见internals.go/commit):

    1. 根据Config,create一个container出来。
    2. 然后将这个container通过commit(这个commit是指的docker的commit,与docker commit的命令是相同的)得到一个新的镜像。
    3. 不仅仅是CMD命令,几乎所有的命令(除了FROM外),在最后都是使用b.commit来产生一个新的镜像的。

    所以这会导致的结果就是,Dockerfile里每一行,最后都会变为镜像中的一层。几乎是有多少有效行,就有多少层。

    Dockerfile逆向

    通过docker history image可以看到该镜像的历史来源。即使没有Dockerfile,也可以通过history来逆向产生Dockerfile。

    [root@jd ~]# docker history 2d8
    IMAGE CREATED CREATED BY SIZE
    2d80e15fcfdb 8 days ago /bin/sh -c #(nop) COPY dir:86faa820e8bf5dcc06 16.29 MB
    0f601e909d72 8 days ago /bin/sh -c #(nop) ENTRYPOINT [hack/dind] 0 B
    68aed19c5994 8 days ago /bin/sh -c set -x && git clone https://githu 3.693 MB
    ebc6ef15552b 8 days ago /bin/sh -c #(nop) ENV TOMLV_COMMIT=9baf8a8a9f 0 B
    fe22e308201a 8 days ago /bin/sh -c set -x && git clone -b v1.0.1 htt 5.834 MB
    f514c504c9b1 8 days ago /bin/sh -c #(nop) COPY dir:d9a19910e57f47cb3b 3.114 MB
    e4e3ec8edf1a 8 days ago /bin/sh -c ./contrib/download-frozen-image.sh 1.155 MB
    6250561532fa 8 days ago /bin/sh -c #(nop) COPY file:9679abce578bcaa2c 3.73 kB
    ...

    例如0f601e909d72就是由ENTRYPOINT [hack/dind]产生。这里的信息展示的不完全,可以通过docker inspect -f {{.ContainerConfig.Cmd}} layer来看某一层产生的具体信息。

    如何做Dockerfile

    Dockerfile调试

    Dockerfile更多的像一个脚本,类似于安装脚本。特别是大篇幅的脚本,想一次写成是比较有难度的。免不了进行一些调试。调试时最好利用Dockerfile的cache功能,可以大幅度节约调试的时间。

    举个例子,如果我现在有一个Dockerfile。但是我发现。我还需要再开几个端口,或者再安装其他的软件。这个时候最好不要直接修改已经有的Dockerfile的内容。而是在后面追加命令。这样再build的时候,可以利用已有的cache。

    Dockerfile优化

    调试过后的Dockerfile当然可以作为最终的Dockerfile,提供给用户。但是调试的Dockerfile的缺点就是层数可能过多,而且不易越多。所以最好进行一定的优化和整理。经过整理的Dockerfile生成出来的镜像可以使得层数更少,条理更清晰,也可以更好的复用。

    DockerOne里有一篇文章写得很好,可以参考。

    这里有两点要强调:

  • 尽量生成一个base:这样便于版本的迭代和作为公用镜像。
  • 清晰的注释:有一些注释会帮助别人理解这些命令的目的
  • Dockerfile自动build

    有了Dockerfile,很多人都是在本地build。其实这个是相当耗时的。这个工作其实完全可以交给registry.hub.docker.com来完成。

    具体的做法就是:

  • 把你的Dockerfile上传到GitHub上。
  • 进入到registry.hub.docker.com的自己的账户中,选择Automated Build。
  • 然后就可以build了。
  • 根据你的Dockerfile内容大小,build时长不确定。但是应该算是比较快了。docker源码的Dockerfile在我本地build了一个多小时。但是registry.hub.docker.com只用了半小时左右。大约是因为外国的月亮比较圆吧。

    build完成后,可以在线查看版本信息等。本地需要的话,可以直接pull下来。

    国内有多家公司提供了registry.hub.docker.com的Mirror服务,可以直接从国内的源中pull下来。速度快很多。

    Dockerfile的不足

  • 层数过多:过多行的Dockerfile
  • 不能清理volume等配置:volume、expose等多个参数只能单向增加。不能删除。比如在某个镜像层加入了VOLUME /var/lib/docker。那么在该镜像之后的所有层将继承这一属性。
  • IMPORT功能
  • 其他

    现在我们回过头来看Docker的分层的另一个可能的用途。

    Docker的镜像可以看做是一个软件栈。那么其中有多个软件组成。好了,那么我们是不是可以考虑让软件进行自由叠加呢?

    比如:从CentOS镜像上安装了Python形成镜像A,从CentOS镜像上安装了Apache形成镜像B。如果用户想从CentOS上形成一个既有Python又有Apache的镜像,如何做呢?

    我想有两种方式,一种是dockerfile的import。我们可以基于镜像A,然后import安装Apache的Dockerfile,从而得到目标镜像。

    另外一种是可以直接引入,就是基于镜像A,然后我们直接把B的最后一层(假设B安装apache只形成了一层),搬到镜像A的子层上,不是也可以得到目标镜像么?

    以上主要是我分享的一些内容。大家可以一起来讨论。

       
    次浏览       
     
    相关文章

    云计算的架构
    对云计算服务模型
    云计算核心技术剖析
    了解云计算的漏洞
     
    相关文档

    云计算简介
    云计算简介与云安全
    下一代网络计算--云计算
    软浅析云计算
     
    相关课程

    云计算原理与应用
    云计算应用与开发
    CMMI体系与实践
    基于CMMI标准的软件质量保证
    最新活动计划
    LLM大模型应用与项目构建 12-26[特惠]
    QT应用开发 11-21[线上]
    C++高级编程 11-27[北京]
    业务建模&领域驱动设计 11-15[北京]
    用户研究与用户建模 11-21[北京]
    SysML和EA进行系统设计建模 11-28[北京]

    专家视角看IT与架构
    软件架构设计
    面向服务体系架构和业务组件的思考
    人人网移动开发架构
    架构腐化之谜
    谈平台即服务PaaS
    更多...   
    相关培训课程

    云计算原理与应用
    Windows Azure 云计算应用

    摩托罗拉 云平台的构建与应用
    通用公司GE Docker原理与实践
    某研发中心 Openstack实践
    知名电子公司 云平台架构与应用
    某电力行业 基于云平台构建云服务
    云计算与Windows Azure培训
    北京 云计算原理与应用