本文主要讨论了对docker build的源码流程进行了梳理和解读,并分享了在制做Dockerfile过程当中的一些实践经验,包括如何调试、优化和build中的一些要点。另外,还针对现有Dockerfile的不足进行了简要说明,并分享了对于Dockerfile的一些理解。这是2015年初第一次在社区的微信分享,原文刊载在dockone社区git
此次的分享主要面向有必定Docker基础的。我但愿你已经:docker
我主要分享一些如今网上或者文档中没有的东西,包括个人理解和一些实践,有误之处也请你们指正。好了,正文开始。apache
Dockerfile其实能够看作一个命令集。每行均为一条命令。每行的第一个单词,就是命令command。后面的字符串是该命令所要接收的参数。好比ENTRYPOINT /bin/bash。ENTRYPOINT命令的做用就是将后面的参数设置为镜像的entrypoint。至于现有命令的含义,这里再也不详述。DockOne上有不少的介绍。json
docker build的流程(这部分代码基本都在docker/builder中)bash
在这里,我举一个例子来讲明一下在第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):数据结构
不只仅是CMD命令,几乎全部的命令(除了FROM外),在最后都是使用b.commit来产生一个新的镜像的。app
因此这会致使的结果就是,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的cache功能,能够大幅度节约调试的时间。
举个例子,若是我如今有一个Dockerfile。可是我发现。我还须要再开几个端口,或者再安装其余的软件。这个时候最好不要直接修改已经有的Dockerfile的内容。而是在后面追加命令。这样再build的时候,能够利用已有的cache。
调试事后的Dockerfile固然能够做为最终的Dockerfile,提供给用户。可是调试的Dockerfile的缺点就是层数可能过多,并且不易越多。因此最好进行必定的优化和整理。通过整理的Dockerfile生成出来的镜像可使得层数更少,条理更清晰,也能够更好的复用。
DockerOne里有一篇文章写得很好,能够参考。
这里有两点要强调:
有了Dockerfile,不少人都是在本地build。其实这个是至关耗时的。这个工做其实彻底能够交给registry.hub.docker.com来完成。
具体的作法就是:
根据你的Dockerfile内容大小,build时长不肯定。可是应该算是比较快了。docker源码的Dockerfile在我本地build了一个多小时。可是registry.hub.docker.com只用了半小时左右。大约是由于外国的月亮比较圆吧。
build完成后,能够在线查看版本信息等。本地须要的话,能够直接pull下来。
国内有多家公司提供了registry.hub.docker.com的Mirror服务,能够直接从国内的源中pull下来。速度快不少。
如今咱们回过头来看Docker的分层的另外一个可能的用途。
Docker的镜像能够看作是一个软件栈。那么其中有多个软件组成。好了,那么咱们是否是能够考虑让软件进行自由叠加呢?
好比:从CentOS镜像上安装了Python造成镜像A,从CentOS镜像上安装了Apache造成镜像B。若是用户想从CentOS上造成一个既有Python又有Apache的镜像,如何作呢?
我想有两种方式,一种是dockerfile的import。咱们能够基于镜像A,而后import安装Apache的Dockerfile,从而获得目标镜像。
另一种是能够直接引入,就是基于镜像A,而后咱们直接把B的最后一层(假设B安装apache只造成了一层),搬到镜像A的子层上,不是也能够获得目标镜像么?