追求极简:Docker镜像构建演化史

作者简介:白明,东软互联网运营平台技术负责人,毕业于哈尔滨工业大学,Go语言专家,GopherChina讲师,技术培训师和撰稿人,博客tonybai.com作者,拥有多年后端服务架构设计和开发经验。目前专注于Docker容器和Kubernetes研究。 


本文首发自《程序员》,谢绝转载(责编/魏伟)

自从2013年dotCloud公司(现已改名为Docker Inc)发布Docker容器技术以来,到目前为止已经有四年多的时间了。这期间Docker技术飞速发展,并催生出一个生机勃勃的、以轻量级容器技术为基础的庞大的容器平台生态圈。作为Docker三大核心技术之一的镜像技术在Docker的快速发展之路上可谓功不可没:镜像让容器真正插上了翅膀,实现了容器自身的重用和标准化传播,使得开发、交付、运维流水线上的各个角色真正围绕同一交付物,“test what you write, ship what you test”成为现实。

对于已经接纳和使用Docker技术在日常开发工作中的开发者而言,构建Docker镜像已经是家常便饭。但如何更高效地构建以及构建出Size更小的镜像却是很多Docker技术初学者心中常见的疑问,甚至是一些老手都未曾细致考量过的问题。本文将从一个Docker用户角度来阐述Docker镜像构建的演化史,希望能起到一定的解惑作用。

1.镜像:继承中的创新


谈镜像构建之前,我们先来简要说一下镜像。

Docker技术从本质上说并不是一种新技术,而是将已有技术进行了更好地整合和包装。内核容器技术以一种完整形态最早出现在Sun公司的Solaris操作系统上,Solaris是当时最先进的服务器操作系统。2005年Sun发布了Solaris Container技术,从此开启了内核容器之门。

2008年,以Google公司开发人员为主导实现的Linux Container(即LXC)功能在被merge到Linux内核中。LXC是一种内核级虚拟化技术,主要基于Namespaces和Cgroups技术,实现共享一个操作系统内核前提下的进程资源隔离,为进程提供独立的虚拟执行环境,这样的一个虚拟的执行环境就是一个容器。本质上说,LXC容器与现在的Docker所提供容器是一样的。Docker也是基于Namespaces和Cgroups技术之上实现的。但Docker的创新之处在于其基于Union File System技术定义了一套容器打包规范,真正将容器中的应用及其运行的所有依赖都封装到一种特定格式的文件中去,而这种文件就被称为镜像(即image),原理见下图(引自Docker官网):

图1:Docker镜像原理


镜像是容器的“序列化”标准,这一创新为容器的存储、重用和传输奠定了基础,并且容器镜像“坐上了巨轮”传播到世界每一个角落,助力了容器技术的飞速发展。

与Solaris Container、LXC等早期内核容器技术不同,Docker还为开发者提供了开发者体验良好的工具集,这其中就包括了用于镜像构建的Dockerfile以及一种用于编写Dockerfil的领域特定语言。采用Dockerfile方式构建成为镜像构建的标准方法,其可重复、可自动化、可维护以及分层精确控制等特点是采用传统采用docker commit命令提交的镜像所不能比拟的。

2.“镜像是个筐”:初学者的认知


“镜像是个筐,什么都往里面装”- 这句俏皮话可能是大部分Docker初学者对镜像最初认知的真实写照。这里我们用一个例子来生动地展示一下。 

我们现在将httpserver.go这个源文件编译为httpd程序并通过镜像发布。源文件的内容如下:

//httpserver.go
package mainimport (        "fmt""net/http")func main() {        fmt.Println("http daemon start")fmt.Println("  -> listen on port:8080")http.ListenAndServe(":8080", nil)}


接下来,我们来编写用于构建目标镜像的Dockerfile:

// Dockerfile
From ubuntu:14.04RUN apt-get update \&& apt-get install -y software-properties-common \&& add-apt-repository ppa:gophers/archive \&& apt-get update \&& apt-get install -y golang-1.9-go \git \&& rm -rf /var/lib/apt/lists/*ENV GOPATH /root/goENV GOROOT /usr/lib/go-1.9ENV PATH="/usr/lib/go-1.9/bin:${PATH}"COPY ./httpserver.go /root/httpserver.goRUN go build -o /root/httpd /root/httpserver.go \&& chmod +x /root/httpdWORKDIR /root
ENTRYPOINT ["/root/httpd"]


执行镜像构建:

# docker build -t repodemo/httpd:latest .//...构建输出这里省略...# docker imagesREPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
repodemo/httpd                   latest              183dbef8eba6        2 minutes ago       550MB
ubuntu                           14.04               dea1945146b9        2 months ago        188MB


整个镜像的构建过程因环境而定。如果您的网络速度一般,这个构建过程可能会花费你10多分钟甚至更多。最终如我们所愿,基于repodemo/httpd:latest这个镜像的容器可以正常运行:

# docker run repodemo/httpdhttp daemon start-> listen on port:8080


一个Dockerfile产出一个镜像。Dockerfile由若干Command组成,每个Command执行结果都会单独形成一个层(layer)。我们来探索一下构建出来的镜像:

# docker history 183dbef8eba6IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
183dbef8eba6        21 minutes ago      /bin/sh -c #(nop)  ENTRYPOINT ["/root/httpd"]   0B27aa721c6f6b        21 minutes ago      /bin/sh -c #(nop) WORKDIR /root                 0Ba9d968c704f7        21 minutes ago      /bin/sh -c go build -o /root/httpd /root/h...   6.14MB... ...aef7700a9036        30 minutes ago      /bin/sh -c apt-get update       && apt-get...   356MB
.... ...<missing>           2 months ago        /bin/sh -c #(nop) ADD file:8f997234193c2f5...   188MB


我们去除掉那些Size为0或很小的layer,我们看到三个size占比较大的layer,见下图: 

图2:Docker镜像分层探索


虽然Docker引擎利用缓存机制可以让同主机下非首次的镜像构建执行得很快,但是在Docker技术热情催化下的这种构建思路让docker镜像在存储和传输方面的优势荡然无存,要知道一个ubuntu-server 16.04的虚拟机ISO文件的大小也就不过600多MB而已。

3.“理性的回归”:builder模式的崛起


Docker使用者在新技术接触初期的热情“冷却”之后迎来了“理性的回归”。根据上面分层镜像的图示,我们发现最终镜像中包含构建环境是多余的,我们只需要在最终镜像中包含足够支撑httpd运行的运行环境即可,而base image自身就可以满足。于是我们应该剔除不必要的中间层:

图3:去除不必要的分层


现在问题来了!如果不在同一镜像中完成应用构建,那么在哪里、由谁来构建应用呢?至少有两种方法:

  1. 在本地构建并COPY到镜像中;

  2. 借助构建者镜像(builder image)构建。


不过方法1本地构建有很多局限性,比如:本地环境无法复用、无法很好融入持续集成/持续交付流水线等。而借助builder image进行构建已经成为Docker社区的一个最佳实践,Docker官方为此也推出了各种主流编程语言的官方base image,包括go、java、nodejs、python以及ruby的等。借助builder image进行镜像构建的流程原理如下图:

图4:借助builder image进行镜像构建的流程图


通过原理图,我们可以看到整个目标镜像的构建被分为了两个阶段:

  1. 第一阶段:构建负责编译源码的构建者镜像;

  2. 第二阶段:将第一阶段的输出作为输入,构建出最终的目标镜像。


我们选择golang:1.9.2作为builder base image,构建者镜像的 Dockerfile.build如下:

// Dockerfile.buildFROM golang:1.9.2WORKDIR /go/src
COPY ./httpserver.go .RUN go build -o httpd ./httpserver.go


执行构建:

# docker build -t repodemo/httpd-builder:latest -f Dockerfile.build .


构建好的应用程序httpd放在了镜像repodemo/httpd-builder中的/go/src目录下,我们需要一些“胶水”命令来连接两个构建阶段,这些命令将httpd从构建者镜像中取出并作为下一阶段构建的输入:

# docker create --name extract-httpserver repodemo/httpd-builder# docker cp extract-httpserver:/go/src/httpd ./httpd# docker rm -f extract-httpserver# docker rmi repodemo/httpd-builder


通过上面的命令,我们将编译好的httpd程序拷贝到了本地。下面是目标镜像的Dockerfile:

// Dockerfile.targetFrom ubuntu:14.04COPY ./httpd /root/httpd
RUN chmod +x /root/httpdWORKDIR /root
ENTRYPOINT ["/root/httpd"]


接下来我们来构建目标镜像:

# docker build -t repodemo/httpd:latest -f Dockerfile.target .


我们来看看这个镜像的“体格”:

# docker imagesREPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
repodemo/httpd                   latest              e3d009d6e919        12 seconds ago      200MB


200MB!目标镜像的Size降为原来的1/2还多。

4.“像赛车那样减去所有不必要的东西”:追求最小镜像


前面我们构建出的镜像的Size已经缩小到200MB,但这还不够。200MB的“体格”在我们的网络环境下缓存和传输仍然很难令人满意。我们要为镜像进一步减重,减到尽可能的小,就像赛车那样,为了能减轻重量将所有不必要的东西都拆除掉:我们仅保留能支撑我们的应用运行的必要库、命令,其余的一律不纳入目标镜像。当然不仅仅是Size上的原因,小镜像还有额外的好处,比如:内存占用小,启动速度快,更加高效;不会因其他不必要的工具、库的漏洞而被攻击,减少了“攻击面”,更加安全等。

图5:目标镜像还能更小些么?


一般应用开发者不会从scratch镜像从头构建自己的base image以及目标镜像的,开发者会挑选适合的base image。一些“蝇量级”甚至是“草量级”的官方base image的出现为这种情况提供了条件。

图6:一些base image的Size比较(来自imagelayers.io截图)


从图中看,我们可以有两个选择:busybox和alpine。

单从镜像的size上来说,busybox更小。不过busybox默认的libc实现是uClibc,而我们通常运行环境使用的libc实现都是glibc,因此我们要么选择静态编译程序,要么使用busybox:glibc镜像作为base image。

而alpine image是另外一种蝇量级base image,它使用了比glibc更小更安全的musl libc库。不过和busybox image相比,alpine image体积还是略大。除了因为musl比uClibc大一些之外,alpine还在镜像中添加了自己的包管理系统apk,开发者可以使用apk在基于alpine的镜像中添加需要的包或工具。因此,对于普通开发者而言,alpine image是更佳的选择。不过alpine使用的libc实现为musl,与基于glibc上编译出来的应用程序并不兼容。如果直接将前面构建出的httpd应用塞入alpine,在容器启动时会遇到下面错误,因为加载器找不到glibc这个动态共享库文件:

standard_init_linux.go:185: exec user process caused "no such file or directory"


对于Go应用来说,我们可以采用静态编译的程序,但一旦采用静态编译,也就意味着我们将失去一些libc提供的原生能力,比如:在linux上,你无法使用系统提供的DNS解析能力,只能使用Go自实现的DNS解析器。

我们还可以采用基于alpine的builder image,golang base image就提供了alpine版本。接下来,我们就用这种方式构建出一个基于alpine base image的极小目标镜像。

图7:借助alpine builder image进行镜像构建的流程图


我们新建两个用于alpine版本目标镜像构建的Dockerfile:

Dockerfile.build.alpine和Dockerfile.target.alpine:
//Dockerfile.build.alpineFROM golang:alpineWORKDIR /go/src
COPY ./httpserver.go .RUN go build -o httpd ./httpserver.go// Dockerfile.target.alpineFrom alpineCOPY ./httpd /root/httpd
RUN chmod +x /root/httpdWORKDIR /root
ENTRYPOINT ["/root/httpd"]


构建builder镜像:

#  docker build -t repodemo/httpd-alpine-builder:latest -f Dockerfile.build.alpine .# docker imagesREPOSITORY                       TAG                 IMAGE ID            CREATED              SIZE
repodemo/httpd-alpine-builder    latest              d5b5f8813d77        About a minute ago   275MB


执行“胶水”命令:

# docker create --name extract-httpserver repodemo/httpd-alpine-builder# docker cp extract-httpserver:/go/src/httpd ./httpd# docker rm -f extract-httpserver# docker rmi repodemo/httpd-alpine-builder


构建目标镜像:

# docker build -t repodemo/httpd-alpine -f Dockerfile.target.alpine .# docker imagesREPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
repodemo/httpd-alpine            latest              895de7f785dd        13 seconds ago      16.2MB


16.2MB!目标镜像的Size降为不到原来的十分之一,我们得到了预期的结果。

5.“要有光,于是便有了光”:对多阶段构建的支持


至此,虽然我们实现了目标Image的最小化,但是整个构建过程却是十分繁琐,我们需要准备两个Dockerfile、需要准备“胶水”命令、需要清理中间产物等。作为Docker用户,我们希望用一个Dockerfile就能解决所有问题,于是就有了Docker引擎对多阶段构建(multi-stage build)的支持。注意:这个特性非常新,只有Docker 17.05.0-ce及以后的版本才能支持。

现在我们就按照“多阶段构建”的语法将上面的Dockerfile.build.alpine和Dockerfile.target.alpine合并到一个Dockerfile中:

//DockerfileFROM golang:alpine as builderWORKDIR /go/src
COPY httpserver.go .RUN go build -o httpd ./httpserver.goFrom alpine:latestWORKDIR /root/
COPY --from=builder /go/src/httpd .
RUN chmod +x /root/httpdENTRYPOINT ["/root/httpd"]


Dockerfile的语法还是很简明和易理解的,即使是你第一次看到这个语法也能大致猜出六成含义。与之前Dockefile最大的不同在于在支持多阶段构建的Dockerfile中我们可以写多个“From baseimage”的语句,每个From语句开启一个构建阶段,并且可以通过“as”语法为此阶段构建命名(比如这里的builder)。我们还可以通过COPY命令在两个阶段构建产物之间传递数据,比如这里的传递的httpd应用,这个工作之前我们是使用“胶水”代码完成的。

构建目标镜像:

# docker build -t repodemo/httpd-multi-stage .# docker imagesREPOSITORY                       TAG                 IMAGE ID            CREATED             SIZE
repodemo/httpd-multi-stage       latest              35e494aa5c6f        2 minutes ago       16.2MB


我们看到通过多阶段构建特性构建的Docker Image与我们之前通过builder模式构建的镜像在效果上是等价的。

6.来到现实


沿着时间的轨迹,Docker镜像构建走到了今天。追求又快又小的镜像已成为了Docker社区的共识。社区在自创builder镜像构建的最佳实践后终于迎来了多阶段构建这柄利器,从此构建出极简的镜像将不再困难。





12月23日,SDCC 2017之数据库线上峰会即将强势来袭,秉承干货实料(案例)的内容原则,邀请了来自阿里巴巴腾讯微博网易等多家企业的数据库专家及高校研究学者,围绕Oracle、MySQL、PostgreSQL、Redis等热点数据库技术展开,从核心技术的深挖到高可用实践的剖析,打造精华压缩式分享,举一反三,思辨互搏,报名及更多详情可点击「阅读原文」或扫描下方二维码查看。



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/525707.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

特征选择算法在微博业务应用中的演进历程

近年来&#xff0c;人工智能与机器学习的应用越来越广泛&#xff0c;尤其是在互联网领域。在微博&#xff0c;机器学习被广泛地应用于微博的各个业务&#xff0c;如Feed流、热门微博、消息推送、反垃圾、内容推荐等。值得注意的是&#xff0c;深度学习作为人工智能和机器学习的…

c winform mysql类_C#连接MySQL数据库操作类

首先需要安装MySQL Connector Net 6.8.3然后在项目中引用MySQL Connector&#xff0c;如下图所示C#连接MySQL的操作类代码如下&#xff1a;public class MySQLHelper{private string db_host "localhost"; //数据库服务器private string db_port "3306";…

病历智能处理引擎的架构设计、实现和应用

作者简介&#xff1a;吴大帅&#xff0c;新屿算法工程师&#xff0c;曾供职于宅米网、新达达&#xff0c;从事系统架构设计、算法设计等工作。 李智慧&#xff0c;《大型网站技术架构&#xff1a;核心原理与案例分析》作者&#xff0c;从事大型网站、分布式系统、大数据方面的研…

Heron:来自Twitter的新一代流处理引擎应用篇

作者 | 吴惠君&#xff0c;吕能&#xff0c;符茂松责编 | 郭芮【导语】 本文对比了Heron和常见的流处理项目&#xff0c;包括Storm、Flink、Spark Streaming和Kafka Streams&#xff0c;归纳了系统选型的要点。此外实践了Heron的一个案例&#xff0c;以及讨论了Heron在这一年开…

2017 JavaScript 调查报告概述

本文转载自极光日报知乎专栏&#xff0c;地址&#xff1a;https://zhuanlan.zhihu.com/p/32260460简评&#xff1a;最近一份超过 23,000 名开发者参与的关于 JavaScript 的调查报告 - the State of JavaScript 2017 出炉了。内容包含框架的流行趋势、薪资水平等等&#xff0c;感…

python动态_python --动态类型

动态类型(dynamic typing)是Python另一个重要的核心概念。我们之前说过&#xff0c;Python的变量(variable)不需要声明&#xff0c;而在赋值时&#xff0c;变量可以重新赋值为任意值。这些都与动态类型的概念相关。动态类型在我们接触的对象中&#xff0c;有一类特殊的对象&…

微博热点事件背后数据库运维的“功守道”

作者 | 张冬洪责编 | 仲培艺【导语】 微博拥有超过3.76亿月活用户&#xff0c;是当前社会热点事件传播的主要平台。而热点事件往往具有不可预测性和突发性&#xff0c;较短时间内可能带来流量的翻倍增长&#xff0c;甚至更大。如何快速应对突发流量的冲击&#xff0c;确保线上服…

sql devalop连接mysql_SQL-Mysql表结构操作

一 前言本篇内容是关于 基本的数据库操作&#xff0c;建表&#xff0c;表结构修改等内容&#xff1b;学习本篇的基础是知识追寻者以前发布的文章&#xff1a;公众号&#xff1a;知识追寻者知识追寻者(Inheriting the spirit of open source, Spreading technology knowledge;)二…

云计算平台2017年盘点——真正成为新技术新应用的基础架构

作者简介&#xff1a;吴凯&#xff0c;北京云途腾科技有限责任公司首席运营官&#xff0c;具有20年的IT和软件服务行业管理经验&#xff0c;历任多家著名外资及民营IT企业的核心高级管理职位&#xff0c;是中国云计算专家委员会专家委员&#xff0c;中国开源云联盟及云计算开源…

Spark SQL在100TB上的自适应执行实践

作者&#xff1a;汪愈舟 俞育才 郭晨钊 程浩&#xff08;英特尔&#xff09;&#xff0c;李元健&#xff08;百度&#xff09;责编&#xff1a;钱曙光&#xff08;qianshgcsdn.net&#xff09;Spark SQL是Apache Spark最广泛使用的一个组件&#xff0c;它提供了非常友好的…

分包组包 北斗通信_蓝牙mesh底层传输层(分包和组包)

当传输大于15字节的上层传输层PDU时&#xff0c;底层传输层就需要对上层传输层PDU进行分包并重新组包为了减少底层传输层包的数量&#xff0c;这里使用块应答机制。问题&#xff1a;怎么通过块应答机制减少底层传输层包的数量&#xff1f;上层传输层需要分成两个底层传输层PDU的…

别人在忙挖矿,京东架构师却悄悄用区块链搞了件大事

作者介绍&#xff1a; 赵铭&#xff0c;京东商城区块链研发工程师&#xff0c; 主要从事区块链底层研究设计工作&#xff0c;主攻方向为分布式账本结构&#xff1b; 孙海波&#xff0c;目前担任京东Y事业部供应链研发负责人&#xff0c;负责的业务包括订单履约、库存优化、采购…

用sklearn mysql_Sklearn之Linear Regression

import matplotlib.pyplot as pltimport numpy as npfrom sklearn import datasets, linear_modelfrom sklearn.metrics import mean_squared_error, r2_score# 加载糖尿病数据集diabetes datasets.load_diabetes()# 只使用一个特征diabetes_X diabetes.data[:, np.newaxis, …

2017年30个惊艳的Python开源项目

【摘要】本文来自Mybridge&#xff0c;介绍了过去一年里30个惊艳的Python开源项目。点击阅读原文每一个都可以在GitHub上看到更为详细的内容。以下是译文。在过去的一年里&#xff0c;Mybridge AI 比较了近15000个开源Python项目&#xff0c;选择了前30名&#xff08;概率只有0…

mycloud php5 mysql_mycloud 刷debian乐趣之-owncloud 私有云+芒果云的安装

既然刷了debian了&#xff0c;那么就可以安装很多软件了。这篇文章&#xff0c;我打算讲讲大众化的最初始的配置方案。具体后续优化&#xff0c;例如memcached,https等&#xff0c;可以参考官方文档。第一步、安装apache,#当然&#xff0c;你如果喜欢nginx&#xff0c;也可以的…

关于区块链,程序员需要了解什么

作者 | 曹严明如果说比特币是对传统货币的一种颠覆&#xff0c;那么比特币的基础技术——区块链则是对传统编程范式的一种颠覆。区块链技术被看作是一次Paradigm Shift。也许很多人对 “颠覆”这种说法不以为然&#xff0c;因为现在这个词已经被用滥了&#xff08;如今哪个好一…

java两个矩阵相乘_java计算两个n阶矩阵相乘

自己是个新手&#xff0c;研究java数据结构&#xff0c;看到两个n阶矩阵相乘的题目&#xff0c;自己就试着写了一个简单的demo&#xff0c;哪里有不好的地方&#xff0c;也希望大家多提提意见&#xff0c;直接上代码了&#xff1a;package com.shujujiegou01;public class JuZh…

混合云异军突起 英特尔的全“芯”体验为企业保驾护航

近几年&#xff0c;混合云在IT界异军突起&#xff0c;各大厂商纷纷布局混合云市场&#xff0c;企业更愿意将数据存放在私有云中&#xff0c;但是同时又希望可以获得公有云的计算资源&#xff0c;在这种情况下混合云被越来越多的采用&#xff0c;它将公有云和私有云进行混合和匹…

搭建java_搭建JAVA环境

1 jdk的获取jdk的获取路径有很多这种&#xff0c;我把我网盘上存的分享个大家&#xff0c;大家直接下载就可以了。http://pan.baidu.com/s/1pLsJLtp(这是一个公开链接&#xff0c;直接打开下载就可以了)。2 jdk的安装(1)单击刚刚下载的安装文件将会弹出欢迎对话框。(2)点击“下…

java数组下标越界_BUG-并行流与数组下标越界-思考与总结

BUG-并行流与数组下标越界-思考与总结今天线上环境报异常&#xff0c;发现了一个之前没注意过的问题&#xff0c;记录一下。1. 异常信息异常信息如下&#xff1a;Caused by: java.lang.ArrayIndexOutOfBoundsExceptionat java.lang.String.getChars(String.java:826)at java.la…