网站建设规划任务书,建设银行信用卡网站会员注册,wordpress 有道智云,无锡网站制作专业服务公司作者简介#xff1a;白明#xff0c;东软互联网运营平台技术负责人#xff0c;毕业于哈尔滨工业大学#xff0c;Go语言专家#xff0c;GopherChina讲师#xff0c;技术培训师和撰稿人#xff0c;博客tonybai.com作者#xff0c;拥有多年后端服务架构设计和开发经验。目… 作者简介白明东软互联网运营平台技术负责人毕业于哈尔滨工业大学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官网图1Docker镜像原理镜像是容器的“序列化”标准这一创新为容器的存储、重用和传输奠定了基础并且容器镜像“坐上了巨轮”传播到世界每一个角落助力了容器技术的飞速发展。与Solaris Container、LXC等早期内核容器技术不同Docker还为开发者提供了开发者体验良好的工具集这其中就包括了用于镜像构建的Dockerfile以及一种用于编写Dockerfil的领域特定语言。采用Dockerfile方式构建成为镜像构建的标准方法其可重复、可自动化、可维护以及分层精确控制等特点是采用传统采用docker commit命令提交的镜像所不能比拟的。2.“镜像是个筐”初学者的认知“镜像是个筐什么都往里面装”- 这句俏皮话可能是大部分Docker初学者对镜像最初认知的真实写照。这里我们用一个例子来生动地展示一下。 我们现在将httpserver.go这个源文件编译为httpd程序并通过镜像发布。源文件的内容如下//httpserver.go
package mainimport ( fmtnet/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见下图 图2Docker镜像分层探索虽然Docker引擎利用缓存机制可以让同主机下非首次的镜像构建执行得很快但是在Docker技术热情催化下的这种构建思路让docker镜像在存储和传输方面的优势荡然无存要知道一个ubuntu-server 16.04的虚拟机ISO文件的大小也就不过600多MB而已。3.“理性的回归”builder模式的崛起Docker使用者在新技术接触初期的热情“冷却”之后迎来了“理性的回归”。根据上面分层镜像的图示我们发现最终镜像中包含构建环境是多余的我们只需要在最终镜像中包含足够支撑httpd运行的运行环境即可而base image自身就可以满足。于是我们应该剔除不必要的中间层图3去除不必要的分层现在问题来了如果不在同一镜像中完成应用构建那么在哪里、由谁来构建应用呢至少有两种方法在本地构建并COPY到镜像中借助构建者镜像builder image构建。不过方法1本地构建有很多局限性比如本地环境无法复用、无法很好融入持续集成/持续交付流水线等。而借助builder image进行构建已经成为Docker社区的一个最佳实践Docker官方为此也推出了各种主流编程语言的官方base image包括go、java、nodejs、python以及ruby的等。借助builder image进行镜像构建的流程原理如下图图4借助builder image进行镜像构建的流程图通过原理图我们可以看到整个目标镜像的构建被分为了两个阶段第一阶段构建负责编译源码的构建者镜像第二阶段将第一阶段的输出作为输入构建出最终的目标镜像。我们选择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 200MB200MB目标镜像的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 imagegolang base image就提供了alpine版本。接下来我们就用这种方式构建出一个基于alpine base image的极小目标镜像。图7借助alpine builder image进行镜像构建的流程图我们新建两个用于alpine版本目标镜像构建的DockerfileDockerfile.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.2MB16.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 --frombuilder /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等热点数据库技术展开从核心技术的深挖到高可用实践的剖析打造精华压缩式分享举一反三思辨互搏报名及更多详情可点击「阅读原文」或扫描下方二维码查看。