摘要

本文主要讨论了对docker build的源码流程进行了梳理和解读,并分享了在制作Dockerfile过程中的一些实践经验,包括如何调试、优化和build中的一些要点。另外,还针对现有Dockerfile的不足进行了简要说明,并分享了对于Dockerfile的一些理解。这是2015年初第一次在社区的微信分享,原文刊载在dockone社区

听众

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

  • 用过Docker,熟悉docker commit命令
  • 自己动手编写过Dockerfile
  • 自己动手build过一个镜像,有亲身的体验

我主要分享一些现在网上或者文档中没有的东西,包括我的理解和一些实践,有误之处也请大家指正。好了,正文开始。

Dockerfile

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

Docker构建(docker build)

docker build的流程

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

  1. 提取Dockerfile(evaluator.go/RUN)。
  2. 将Dockerfile按行进行分析(parser/parser.go/Parse) Dockerfile,每行第一个单词,如CMD、FROM等,这个叫做command。根据command,将之后的字符串用对应的数据结构进行接收。
  3. 根据分析的command,在dispatchers.go中选择对应的函数进行处理(dispatchers.go)。
  4. 处理完所有的命令,如果需要打标签,则给最后的镜像打上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的命令是相同的)得到一个新的镜像。

不仅仅是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来完成。

具体的做法就是:

  1. 把你的Dockerfile上传到GitHub上。
  2. 进入到registry.hub.docker.com的自己的账户中,选择Automated Build。
  3. 然后就可以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的子层上,不是也可以得到目标镜像么?

Dockerfile与Docker构建流程解读的更多相关文章

  1. Docker基本命令与使用 —— Dockerfile指令与构建(三)

    一.Dockerfile指令上 1.指令格式 # Comment 注释, 以#开头 INSTRUCTION argument 以大写的指令+参数 #First Dockerfile 注释 FROM u ...

  2. Docker DockerFile文件指令 & 构建

    1.dockerfile指令格式 # Comment注释 INSTRUCTION argument指令名 + 参数 2.普通指令 1. FROM 已存在的镜像,基础镜像,第一条非注释指令 FROM & ...

  3. DevOps实践之一:基于Docker构建企业Jenkins CI平台

    基于Docker构建企业Jenkins CI平台 一.什么是CI 持续集成(Continuous integration)是一种软件开发实践,每次集成都通过自动化的构建(包括编译,发布,自动化测试)来 ...

  4. 使用 Spring Cloud 和 Docker 构建微服务架构

    如何使用Spring Boot.Spring Cloud.Docker和Netflix的一些开源工具来构建一个微服务架构. 本文通过使用Spring Boot.Spring Cloud和Docker构 ...

  5. docker 构建自己的image 镜像文件

    docker build 构建自己的镜像文件. 1.在本地工程中运行生成一个springboot的可运行的jar. 因为我习惯用eclipse,所以在eclipse下新建一个springboot的工程 ...

  6. [笔记] 基于nvidia/cuda的深度学习基础镜像构建流程 V0.2

    之前的[笔记] 基于nvidia/cuda的深度学习基础镜像构建流程已经Out了,以这篇为准. 基于NVidia官方的nvidia/cuda image,构建适用于Deep Learning的基础im ...

  7. 使用docker构建第一个spring boot项目

    在看了一些简单的docker命令之后 打算自己尝试整合一下docker+spring boot项目本文是自己使用docker+spring boot 发布一个项目1.docker介绍 docke是提供 ...

  8. 使用Docker构建持续集成与自动部署的Docker集群

    为什么使用Docker " 从我个人使用的角度讲的话  部署来的更方便 只要构建过一次环境 推送到镜像仓库 迁移起来也是分分钟的事情 虚拟化让集群的管理和控制部署都更方便 hub.docke ...

  9. selenium结合docker构建分布式测试环境

    selenium是目前web和app自动化测试的主要框架.对于web自动化测试而言,由于selenium2.0以后socker服务器由本地浏览器自己启动且直接通过浏览器原生API操作页面,故越来越多的 ...

随机推荐

  1. (JS+CSS)实现图片放大效果

    代码很简单,在这里就不过多阐述,先上示例图: 实现过程: html部分代码很简单 <div id="outer"> <p>点击图片</p> &l ...

  2. easyui datagrid 点击表头的事件

    在用datagrid的时候我们可能要用到点击表头来触发一个function,这里有个简单的例子. 首先你得有个能用的datagrid. <div>    <table id=&quo ...

  3. UOJ263 【NOIP2016】组合数问题

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000作者博客:http://www.cnblogs.com/ljh2000-jump/转 ...

  4. iOS使用Charts框架绘制—柱形图

    首先看一下最终要实现的效果: 最终效果 一.初始化barChartView 绘制柱形图需要用到BarChartView这个类,下面是初始化代码: self.barChartView = [[BarCh ...

  5. 一次配置jdk环境变量的感悟

    开发java也一年多了,昨日一次偶然的机会,想在dos命令下执行一个程序,发现在 命令行输入 javac Test.java的时候,竟然提示javac不是内部命令, 之后输入 java ,也提示不是内 ...

  6. redis 实践—— sorted set, hash set

    在这里就不谈redis的安装与启动啦,网上太多人写这个了. 从最近的一个项目[钻石夺宝]说起,如果大家有玩过一元夺宝或者全名夺宝的话,大概会知道如果参与人数多的话,每隔几秒.快的话每隔一秒都会新生成一 ...

  7. 那天有个小孩跟我说LINQ(六)转载

    2  LINQ TO SQL完结(代码下载)      我们还是接着上次那个简单的销售的业务数据库为例子,打开上次那个例子linq_Ch5 2.1 当数据库中的表建立了主外键 ①根据主键获取子表信息 ...

  8. SQL 维护用得到的监控语句

    使用DMV来分析SQL Server启动以来累计使用CPU资源最多的语句.例如下面的语句就可以列出前50名 s2.dbid, ( , ( ( ) )) AS sql_statement, execut ...

  9. android Fragment中使用Toolbar

    在Activity中可以直接使用 setSupportActionBar(toolbar); 就可以重写 onCreateOptionsMenu 和 onOptionsItemSelected 方法: ...

  10. AtCoder Grand Contest 027 (AGC017) D - Modulo Matrix 构造

    原文链接https://www.cnblogs.com/zhouzhendong/p/AGC027C.html 题解 首先我们假装 max mod min = 1 然后对着这个构造. 将各自黑白染色, ...