Docker镜像原理和最佳实践

镜像

1e5af1d2967565d1f108b223504c4ef3e3fba7f0

传统企业是以交付应用的方式进行发布的,交付应用至关于可执行性程序,其整个应用与环境是分开维护的。随着容器技术的兴起,提出了交付环境的概念。交付环境与交付应用相比,交付的不只是可执行程序,还交付可执行程序依赖的配置文件、类库甚至是整个文件系统。在Docker语境里面,环境就是镜像。从上图左下角镜像示例图能够看出,镜像自己的组织结构是分层的。其优势是,虽然它包含了全部的依赖,可是发布部署的时候不会显著增长信息的传输量。shell

d86fed40ec09ed0d6202149f312369b98284c348

镜像的表示分为四部分:红色的部分是镜像中心域名,黄色的部分是镜像命名空间,咱们能够根据命名空间进行权限控制等操做,绿色是镜像的名称,每一个镜像有一个版本(即标签)。Docker官方的镜像不须要镜像中心的域名,有一些镜像能够省略命名空间。缓存

镜像基本操做

1b4b2df72ef2dce5ca3fa15b0f17168e5a02e72d

镜像制做——Dockerfile

76e88ba9c2ea795890dbce68a84aa48cc93193d7

第一行是一个FROM指令,使用了一个叫alpine的基础镜像。全部的镜像均可以用来作基础镜像,咱们一般不须要关心最基础的镜像是怎么来的,只须要在现有镜像的基础上,构建新的镜像便可。咱们在构建的时候,Docker依赖这个基础镜像,在这个基础镜像之上咱们再作一些改动,生产新的镜像。FROM指令必需要有,并且只能有一个,一般是放在整个Dockerfile的最前面。markdown

RUN指令作的是在镜像里安装一些软件,或者作一些须要的操做。Docker RUN以后执行了Shell指令,它对镜像里面的内容作了一些改动,最后再执行Docker COMMIT,把当前容器里面的改动持久化到镜像里面。RUN命令能够执行屡次。可是一般来讲建议把须要的指令都写在一条RUN命令里面,用&&符号链接起来,好处是镜像只增长了一层,能够加快镜像构建的速度,减小镜像的层次。网络

在讨论ADD指令前,咱们先看看构建命令的输入。构建命令的输入内容包括两项,第一项是Dockerfile,另外一项是Dockerfile依赖的上下文目录。第一条ADD命令就是至关于把www目录从上下文目录拷贝到镜像目录下面,目录名是相同的。第二条ADD的命令是添加文件到镜像,还有一个相似的命令是COPY,ADD命令与COPY命令作的事情是同样的。可是ADD命令能作的更多,好比,你的源不必定是Dockerfile上下文目录里面的内容,能够是一个网络路径。ADD命令还能将可识别的压缩文件进行解压,而后再放到镜像里面。优化

EXPOSE命令,至关于告诉Docker的守护进程,当前这个镜像在运行变成一个容器的时候,其监听的端口是什么,上图中其监听的是80端口。这样声明出去,其余与当前容器在同一个网络里面的容器能够经过这个端口来访问这个当前容器。这个地方其实并无指定主机的端口,主机端口须要在启动容器的时候,再具体指定。阿里云

CMD命令会作两件事情,但须要依赖一些前提条件,好比上图中作的事情是指定了镜像运行时候的首进程。spa

1bbd20f1c95113dd7ce89a1f5694b3eca74ea7ab

CMD指定首进程的方式有两种。一种是shell方式,以上图这种方式来指定可执行文件及其参数,实际上它的首进程会先启动一个shell进程,而后再将可执行文件及它的参数做为shell进程的参数传进去再启动。另一种方式叫exec方式,它会把这些参数用方括号括起来,看上去是一个列表。这种启动方式的首进程就不是shell,而是可执行文件自己。3d

7b6431599d25a29007f085e7c0671d6b34b2f0e4

CMD命令与ENTRYPOINT命令有什么区别?若是CMD命令和ENTRYPOINT命令中有一个没有指定任何命令的话,实际上剩下的那个就是具体指定容器首进程的命令。可是若是两个命令同时存在,整个容器起来的首进程又是怎么指定呢?好比,如今指定了一个ENTRYPOINT命令,它有一个可执行文件,而后指定了可执行文件的参数。它生成这个首进程的话,就是刚刚提到的是以shell方式起来的,因此它解释出来的话,前面会加一个shell命令,而后再紧跟可执行文件,而后再跟上可执行文件的参数。若是在指定了ENTRYPOINT命令的同时又指定了这个CMD命令, CMD命令也有一个可执行文件及其参数。最后其效果是,在指定了ENTRYPOINT的状况下,若是有CMD命令,那么直接加在ENTRYPOINT命令后面,看成它的参数。整个这一串是做为首进程的指令起动起来的。blog

镜像优化

减少镜像大小

好比很常见的一个需求——镜像太大了。咱们以前交付的是应用,一般一个可执行文件,它自己是很小。可是整个镜像若是太大的话,它整个传输过程当中会增长部署的时间。减少镜像大小有几种方式:进程

使用轻量发行版的基础镜像。而后这个地方典型表明就是alpine的发行版。目前Docker的官方镜像基本都有一个基于alpine发行版制做的版本。其实咱们在选择某一个基础镜像的时候会考量不少;

清理没必要要的安装包和临时文件。在传输过程当中,不一样镜像大小的传输速度差异很明显。

加快镜像构建速度

如今应用的发布需求可能愈来愈多,好比说一天要发布不少次。咱们在作镜像构建的时候,最经常使用的一个操做就是下载软件包去安装。因此咱们推荐用国内的镜像软件源下载,好比使用阿里云的软件源进行下载。

构建使用缓存的条件包括:父层信息没有发生变化;当前构建指令没有变化;当前指令依赖的本地上下文没有发生变化。

镜像常见问题

惟一识别某个镜像

好比,一个Dockerfile构建了,推到了仓库,可是从仓库拉下来的时候,怎么知道拉到本地去部署的就是推上去的那个呢?因此说须要有一个标识来惟一识别某一个镜像。可使用Docker镜像ID,其摘要信息是本地镜像配置文件的摘要。也可使用manifest的摘要,这是另一个惟一的标识符。好比说咱们在使用Docker push命令的时候,会发现它的标准输出里面最后一行会输出一个摘要字符串,这个字符串就是manifest摘要。这两个摘要信息是不同的。可是有依赖关系,好比manifest文件里面其实就保存了那个镜像ID,因此在给定一个manifest文件的基础上只可能有一个镜像ID是与之对应,可是反过来就不必定了。由于manifest还有一些别的信息。

8ff72e442014653a46162ba105abff41758a99f3

上图里面有两个镜像中心,即两个registry,多个节点上都有Docker。在高版本的Docker基础上,从镜像中心拉镜像A到上面这个Docker,而后查看它的镜像ID。另一个Docker也从相同registry拉镜像ID,而后查看镜像ID,它能保证这两个镜像ID是同样的。若是把这个镜像推到别的仓库,换一个名字,用Docker tag从新打一个标签,又把这个镜像推到了另一个镜像中心,而后又有第三个Docker的节点把它拉下来,这些操做都能保证无论这个镜像怎么传输,其镜像ID是一致的,即内容可定位的。

惟一识别某个镜像:经过镜像版原本管理镜像,好处是镜像版本带有语义信息,缺点是镜像版本能够被覆盖;经过manifest摘要来管理镜像,好处是镜像惟一肯定,缺点是镜像版本不带有语义信息。

正确管理容器内的进程

镜像要求指定容器的首进程,即完成三个工做:给这个容器里面其余进程正确传递信号,正确回收僵尸进程,等待子进程的退出。

编译型语言和解释型语言

对编译性语言和解释性语言,其构建是有区别的。编译性语言好比说JAVA,它自己是有源代码,须要通过一个编译打包的过程,生成一个war包,最后再去执行构建,这是比较推荐的方式。因此,建议对于编译性的语言,首先对它作一个打包动做,打包生成的war包放到Dockerfile上下文目录,而后经过拷贝的方式添加到镜像中,而不是在Dockerfile远程拉下来。这里推荐使用阿里云的持续构建平台——CRP平台。这个平台作的事情就是,拿到源代码以后,首先打包,打成war包做为一个Dockerfile构建上下文的一个输入,而后再拿到镜像中心构建服务进行构建。

可是像PHP这种不须要编译的解释性语言来讲,能够考虑直接进行构建。