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

以下是精彩内容整理:

镜像概念

镜像是什么?从一个比较具体的角度去看,镜像就是一个多层存储的文件,相较于普通的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的包,在我们确定了一个镜像服务域名以及在云服务商申请命名空间之后,我们就可以对镜像做一些操作了。

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

镜像存储细节

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

一个镜像做好之后,当我们想要知道镜像里面有哪一些内容的时候,我们其实会有一个视图概念,我们从联合文件系统的角度去看镜像的时候,其实我们不会看到L1、L2、L3,我们会最后看到结果,File1、File2、File3,File4就看不到了,然后在我们了解原理之后,我们就可以去理解容器运行起来是一个什么样的情况。容器运行起来和上面形成是类似的,图中下半部分,同样也是L1、L2、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的优化就已经做完了。

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

  • 减少镜像的层数,尽量把一些功能上面统一的命令合到一起来做;
  • 注意清理镜像构建的中间产物,比如一些安装包在装完之后就把它删掉;
  • 注意优化网络请求,我们去用一些镜像源,去用一些网络比较好的开源站点,这样可以节约时间、减少失败率;
  • 尽量去用构建缓存,我们尽量把一些不变的东西或者变的比较少的东西放在前面,因为不变的东西都是可以被缓存的;
  • 多阶段进行镜像构建,将我们镜像制作的目的做一个明确,把我们的构建和真正的一些产物做分离,构建就用构建的镜像去做,最终产物就打最终产物的镜像。

容器镜像服务

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

Docker 镜像优化与最佳实践相关推荐

  1. 新IT运维时代 | Docker运维之最佳实践-下篇

    2019独角兽企业重金招聘Python工程师标准>>> 上篇针对操作系统.主机配置.容器镜像.容器运行时四大方面分享一些Docker的运维经验,本篇将着重在Docker Daemon ...

  2. Docker镜像优化

    Docker镜像优化 原文:Docker镜像优化 前言 上篇博文说到使用Visual Studio Tools for Docker帮助我们生成Dockerfile,现在我们讨论下生成的Dockerf ...

  3. 多线程高并发 底层锁机制与优化的最佳实践——各种锁的分类 || synchronized 关键字 倒底锁的是什么东西?|| CAS与ABA问题||锁优化||轻量级锁一定比重量级锁的性能高吗

    多线程高并发 底层锁机制与优化的最佳实践 各种锁的分类 加上synchronized 关键字,共享资源就不会出错 synchronized 关键字 倒底锁的是什么东西? synchronized 锁的 ...

  4. Docker 镜像优化:从 1.16GB 到 22.4MB

    作者 | The Agile Crafter Docker 是一个供软件开发人员和系统管理员使用容器构建.运行和与分享应用程序的平台.容器是在独立环境中运行的进程,它运行在自己的文件系统上,该文件系统 ...

  5. 第 3 章 镜像 - 018 - 镜像命名的最佳实践

    为镜像命名 创建镜像时 docker build 命令时已经为镜像取了个名字,例如:  docker build -t ubuntu-with-vi 这里的 ubuntu-with-vi 就是镜像的名 ...

  6. Dockerfile制作jdk镜像和微服务镜像部署的最佳实践【Dockerfile实战】

    因为公司之前搭建测试服务器是我搭建的,其中包含使用docker来部署微服务项目,于是将这篇Dockerfile的最佳实践记录于此,为避免大家被坑,希望此文能帮你解除疑惑~ ps:因为是公司服务器不是个 ...

  7. 一文了解 NextJS 并对性能优化做出最佳实践

    点击上方 前端Q,关注公众号 回复加群,加入前端Q技术交流群 引言- 从本文中,我将从是什么,为什么,怎么做来为大家阐述 NextJS 以及如何优化 NextJS 应用体验. 一.NextJS是什么- ...

  8. oracle adg性能,ADG设计及优化的最佳实践

    ​作者介绍 梁铭图,新炬网络首席架构师,十多年数据库运维.数据库设计.数据治理以及系统规划建设经验,拥有Oracle OCM.Togaf企业架构师(鉴定级).IBM CATE等认证,曾获dbaplus ...

  9. 新IT运维时代 | Docker运维之最佳实践-上篇

    容器技术的发展可以分为两个阶段,第一个阶段聚焦在IaaS层,仅仅把容器当做更轻量级虚拟机来使用,解决了应用运行时进程级资源隔离的问题:随着Docker的出现,容器虚拟化才有了统一的平台,由此容器技术发 ...

最新文章

  1. “AI就是统计学”?阿里AI负责人金榕逐条驳诺奖得主萨金特
  2. javaScript tips —— z-index 对事件机制的影响
  3. 解释与构造---无理数能级
  4. C#进阶之路(一):委托
  5. 作为一名产品经理,我是如何快速做项目计划的?
  6. 【原创】如何组织好一个学习小组
  7. 凉凉夜色为我思念成河
  8. java.lang.OutOfMemoryError: Java heap space 解决方法
  9. python浮点数类型与数学_Python3标准库:decimal定点数和浮点数的数学运算
  10. 计算机组成原理mbps,2016年湖北师范学院计算机组成原理(同等学力加试)复试笔试仿真模拟题...
  11. 一步步编写操作系统 66 浅析c库函数与系统调用1
  12. Bing Speech Recognition 标记
  13. 凭什么说这门编程语言是下一代 Java?
  14. java数字小游戏_java数字小游戏
  15. 计算机专业英语词汇pdf,计算机专业英语词汇1700词.pdf
  16. Java微信支付APIV3密钥生成全过程
  17. Windows Server 2008 终端服务授权激活-离线
  18. 神经网络的5个应用场景,人工神经网络应用场景
  19. 儿童学习与发展指南《倾听与表达》篇
  20. 态度和态度改变:影响思维和情绪

热门文章

  1. Ubuntu赋予普通用户特定目录权限
  2. docker端口映射失效解决方法
  3. Android 软键盘弹出时把原来布局顶上去的解决方法
  4. Oracle中用户和架构之间的区别?
  5. 如何在python中检查文件大小?
  6. 如何打开电脑上的安全策略
  7. Win10系统如何共享文件夹,教你怎么操作
  8. boost::bind with ros action,ros中SimpleActionServer用boost::bind绑定多个参数
  9. 读写文件RandomAccessFile
  10. python urllib下载文件怎么停止_python下载文件的三种方法