Docker 镜像优化与最佳实践

摘要:云栖TechDay41期,阿里云高级研发工程师御坂带来Docker镜像优化与最佳实践。从Docker镜像存储的原理开始,针对镜像的存储、网络传输,介绍如何在构建中对这些关键点进行优化。并介绍Docker最新的多阶段构建的功能,以解决构建依赖的中间产物问题。
node

如下是精彩内容整理:shell

镜像概念


镜像是什么?从一个比较具体的角度去看,镜像就是一个多层存储的文件,相较于普通的ISO系统镜像来讲,分层存储会带来两个优势,一个是分层存储的镜像比较容易扩展,好比咱们能够基于一个Ubuntu镜像去构建咱们的Nginx镜像,这样咱们只须要在Ubuntu镜像的基础上面作一些Nginx的安装配置工做,一个Nginx镜像工做就算制做完成了,咱们不须要从头开始去制做各类镜像。另外一点咱们能够优化镜像存储空间,假如咱们有两个镜像,Tag1.0镜像和 Tag2.0镜像,咱们若是以传统方式去传这两个镜像,每一个镜像大概130多兆,但若是咱们以分层的方式去存储两个镜像,咱们经过下面两个紫色的才能共享,能够节约大量的空间,两个镜像加起来只须要140多兆的空间就能够存下来。这样一是节省了存储空间,二是能够减小网络上的开销,好比咱们已经把下面镜像下载了,咱们要去下载上面镜像的时候,咱们只须要去下10M的部分。缓存

若是从抽象的角度去看,Docker镜像实际上是Docker提供的一种标准化的交付手段,传统应用在交付的时候实际上是交付一个可执行文件,这个可执行文件不包括它的运行环境,咱们可能会由于32位系统或64位系统,或者开发测试使用1.0软件,结果交付时候发现用户的环境是2.0等各类各样的问题,致使咱们要去花时间去排查,若是咱们以Docker镜像的标准化形式去交付,咱们就会避免掉这些问题。安全

镜像基本操做与存储方式网络


咱们的一个镜像会有一个坐标,一个镜像坐标基本上会由四个部分组成,前面会有一个镜像服务域名,每个服务提供商都会有不一样的域名,当咱们肯定服务提供商给咱们的域名以后,咱们通常会要到服务提供商那里去申请本身的命名空间,仓库名称通常是标识镜像的用途,好比说Ubuntu镜像、CentOS镜像,标签通常是用于去区分镜像版本,好比咱们对Ubuntu镜像可能会打一些16.04的包,在咱们肯定了一个镜像服务域名以及在云服务商申请命名空间以后,咱们就能够对镜像作一些操做了。app

首先咱们须要去登录,咱们会用第一条命令去登录,而后,当咱们在本地准备好一个镜像想要上传的时候,咱们先要对这个镜像进行打标,把它的坐标变成咱们如今须要上传镜像的坐标,而后再去作一些推送拉取的动做,最后针对Docker还提供两个额外命令去作镜像交付,若是咱们是特殊的环境,没有办法网络连通的时候,咱们能够将这个镜像打包成一个普通文件进行传输。好比咱们和公安合做,他们没有办法经过咱们的Registry下载镜像,咱们可能要把它打成一个普通文件,而后以U盘的方式去交付。maven

镜像存储细节工具


Docker镜像是存在联合文件系统的,每个镜像实际上是分层存储的,好比在第一层咱们添加了三个新文件,而后在这一层基础上咱们又增长了一层,添加了一个文件,第三层可能会须要作一些修改,咱们把File3作了一个修改移到上面来,而后删掉了File4,这里就会引到联合文件系统里面的写时复制机制,当咱们要去修改一个文件的时候,镜像依赖底层都是只读的,咱们不能去直接修改,好比咱们想去修改File3,咱们不能直接去修改这个文件,咱们须要在修改的时候把文件复制到当前这一层,好比说L3层,而后再去修改它。测试

一个镜像作好以后,当咱们想要知道镜像里面有哪一些内容的时候,咱们其实会有一个视图概念,咱们从联合文件系统的角度去看镜像的时候,其实咱们不会看到L一、L二、L3,咱们会最后看到结果,File一、File二、File3,File4就看不到了,而后在咱们了解原理以后,咱们就能够去理解容器运行起来是一个什么样的状况。容器运行起来和上面造成是相似的,图中下半部分,一样也是L一、L二、L3的三层镜像,当容器运行起来的时候,Docker daemon会动态生成一层可写层做为容器的运行层,而后当容器里面须要去修改一些文件,好比File2,也是copy on write机制把文件复制上来,而后作一些修改,新增文件的时候也是同样,而后容器在运行的时候也会有一个视图,当咱们把容器停掉的时候,视图一层就没有了,它会被销毁,可是容器层读写层还会保留,因此咱们把容器停掉再启动的时候,咱们依旧会看到咱们以前在容器里面的一些操做。优化

常见的存储驱动主要有AUFS、OverlayFS,还有Device Mapper,前两种驱动都是基于文件,它的原理就是须要修改一个文件的时候把整个文件复制上去作修改, Device Mapper更偏底层一点,它是基于块设备的,它的好处在于当我想要修改一个文件的时候,我不会将整个文件拷上去,我会将文件修改的一些存储块拷上去作一些修改,当我有一些大文件想要修改的时候,Device Mapper会比AUFS、OverlayFS好不少。因此AUFS和OverlayFS就比较适合传统的WEB应用,它的文件操做不会不少,可是它可能对咱们的应用启动速度会有一些要求,好比我可能常常要发布,我但愿可以启动比较快,可是对于文件修改的一些效率我不是很关心,那可使用基于文件的驱动,当咱们是一些计算密集型的应用时候,咱们就能够选择Device Mapper,虽然启动比较慢,可是它的运行效率相对表现要好一些。

镜像自动化构建


咱们构建一个镜像的时候,Docker其实提供了一个标准化的构建指令集,当咱们去用这些构建指令去写相似于脚本,这种脚本咱们称之为DockerFile,Docker能够自动解析DockerFile,并将其构建成一个镜像,因此你就能够简单的认为这是一个标准化的脚本。DockerFile在作一些什么?首先第一行FROM指令表示要以哪个镜像做为基础镜像进行构建,咱们用了openJDK的官方镜像,以JAVA环境做为基础,咱们在镜像上面准备跑一个JAVA应用,而后接下来两条LABLE是对镜像进行打标,标下镜像版本和构建日期,而后接下来的六个RUN是作了一个maven安装,maven是JAVA的一个生命周期管理工具,接下来将一些源代码从外面的环境添加到镜像里面,而后两条RUN命令作了打包工做,最后写了一个启动命令。


总的来讲DockerFile写的还能够,至少思路是很清晰的,一步一步从基础镜像选择到编译环境,再把源代码加进去,而后再到最后的构建,启动命令写好,可读性、可维护性均可以,可是仍是能够进行优化的。

咱们能够减小镜像的层数, Docker对于Docker镜像的层数是有必定要求的,除掉最上面在容器运行时候的读写层之外,咱们一个镜像最多只能有127层,若是超过可能会出现问题,因此第二行命令LABLE就能够把它合成一层,减小了层数,下面六个RUN命令作了maven的安装工做,咱们也能够把它作成一层,把这些命令串起来,后面的构建咱们也能够把它合成一层,这样咱们一下就把镜像层数从14层减小到7层,减掉了一半。

咱们在作镜像优化的时候,咱们但愿可以尽可能减小镜像的层数,可是和它相对应的是咱们DockerFile的可读性,咱们须要在这二者之间作折中,咱们在保证可读性不受很大影响的状况下去尽可能减小它,其实六条RUN命令在作一件事,就是作maven环境打结,作编译环境的准备工做。


接下来咱们继续对镜像进行优化,咱们能够作一些什么工做呢?在安装maven构建工具的时候咱们多加了一行,咱们把安装包和展开目录删掉了,咱们清理了构建的中间产物,咱们要去注意每个构建指令执行的时候,尽可能把垃圾清理掉,咱们经过apt-get去装一些软件的时候,咱们也能够去作这样的清理工做,就是把这些软件包装完以后就能够把它删掉了,这样能够尽可能减小空间,经过增长一行命令,咱们能够把镜像的大小从137M削减到119M。

经过apt-get去装软件或者命令基本上是全部编写DockerFile的人都去写的,因此官方已经在debian、Ubuntu的仓库镜像里面默认加了Hack,它会去帮助你在install自动去把源代码删掉。


咱们能够利用构建的缓存,Docker构建默认会开启缓存,缓存生效有三个关键点,镜像父层没有发生变化,构建指令不变,添加文件校验和一致。只要一个构建指令知足这三个条件,这一层镜像构建就不会再执行,它会直接利用以前构建的结果,根据构建缓存特性咱们能够加一行RUN,这里是以JAVA应用为例,通常一个JAVA应用的pom文件都是描述JAVA的一些依赖,而在咱们日常的开发过程当中这些依赖包发生变化的频率比较低,那么咱们就能够把POM加进来,把POM文件依赖所有都准备好,而后再去下源代码,再去作构建工做,只要咱们没有把缓存关掉,咱们每次构建的时候就不须要从新下安装包,这样能够节省大量时间,也能够节省一些网络流量。


如今阿里云的容器镜像服务其实已经提供了构建功能,咱们在统计用户失败案例的时候就会发现,网络缘由致使的失败占90%,好比若是用户经过node开发NPM在安装一些软件包的时候常常卡在中间。因此咱们建议加一个软件源,咱们把阿里云maven地址加到里面去,咱们把配置项加到阿里云的软件地址,加阿里云的maven源做为软件包的下载目标,时间直接少了40%,这样对一个镜像构建的成功率也是有帮助的。

多阶段构建


DockerFile最终须要作到的产物实际上是JAVA应用,咱们对于构建、编译、打包或者安装这些事情都不关心,咱们要的实际上是最后的产物。因此,咱们能够采起分步的方式去作镜像构建,首先咱们将以前遇到的全部问题所有都作成基础镜像,上面FROM镜像其实已经改了新的,镜像里面已经把软件源的地址改为了Maven,缓存都已经作好了。咱们会去利用缓存,而后添加源代码,咱们把前面构建的事情作成了镜像,让镜像去完成构建,而后咱们才会去完成把JAVA包拷进去,启动工做,可是两个DockerFile实际上是两个镜像,因此咱们须要一段脚本去辅助它,第一行的shell脚本是作第一个构建指令,咱们指定以Bulid的DockerFile去启动构建,而后生成一个APP Bulid镜像,接下来两行脚本是把镜像生成出来,把里面的构建产物拷出来,而后咱们再去作构建,最后把咱们须要的JAVA应用给构建出来,这样咱们的DockerFile相比以前就更加清晰了,并且分步很简单。


Docker在17.05以后官方支持了多阶段构建,咱们把下面的脚本去掉了,咱们不须要一段辅助脚本,咱们只须要在后面申明基础镜像的地方标记,咱们第一阶段的构建产物名字叫什么,咱们就能够在第二个构建阶段里面用第一个构建阶段的产物。好比咱们第一阶段把JAVA应用构建好,把Maven包里面的target下面的JAVA架包拷到新的镜像里面,而后在全部优化作完以后效果如图,咱们在第一次构建的时候,优化前102秒,在Docker构建优化后只花55秒就完成了,主要优化在网络上面。当咱们修改了JAVA文件从新进行构建,第二次构建花了86秒,由于Maven安装那一块被缓存了,咱们利用了构建缓存,因此少掉20多秒,优化后只花了8秒,由于全部的源代码前面的一些软件包下载所有被缓存了,咱们直接拉新的镜像,而后依赖没有变,直接进行构建,因此8秒基本上是完整构建时间。

咱们再来看一下存储空间上面的优化,第一次构建咱们在优化前把镜像打出来有137M,可是在咱们整个优化以后,只有81M了,这里的基础镜像由JDK改为JRE,为何?由于以前咱们把全部流程都放在一个镜像里面时,咱们是须要去作构建的,构建时须要去RUN Maven,这种状况下没有JDK环境是RUN不起来的,可是若是咱们分阶段,把构建交给Maven镜像来作,把真正运行交给新的镜像来作,就不必用JDK了,咱们直接用JRE,优化以后镜像少了将近50%。当咱们修改源代码从新进行构建的时候,因为镜像成共享的缘由,第二次构建在优化前其实多加了两层到三层,一共有9M,可是优化后的第二次构建只增长1.93KB,这样咱们针对DockerFile的优化就已经作完了。

镜像优化有哪些重要的点呢?具体以下:

1. 减小镜像的层数,尽可能把一些功能上面统一的命令合到一块儿来作;

2. 注意清理镜像构建的中间产物,好比一些安装包在装完以后就把它删掉;

3. 注意优化网络请求,咱们去用一些镜像源,去用一些网络比较好的开源站点,这样能够节约时间、减小失败率;

4. 尽可能去用构建缓存,咱们尽可能把一些不变的东西或者变的比较少的东西放在前面,由于不变的东西都是能够被缓存的;

5. 多阶段进行镜像构建,将咱们镜像制做的目的作一个明确,把咱们的构建和真正的一些产物作分离,构建就用构建的镜像去作,最终产物就打最终产物的镜像。

容器镜像服务

最后介绍一下阿里云容器镜像服务。这个服务已经公测一年了,如今咱们的服务公测是所有免费的,如今在全球的12个Region都已经部署了咱们的服务,每一个Region其实都有内网服务和VPC网络服务,若是ECS也在一样的Region,那么它的服务是很是快的。而后团队管理和组织账号功能也已经上线了,镜像购建和镜像消息通知其实都是一些DevOps能力,针对一些镜像优化咱们提供了一些镜像层信息浏览功能,咱们后续也会提供分析,推出镜像安全扫描、镜像同步。

相关文章
相关标签/搜索