摘要: 云栖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能力,针对一些镜像优化我们提供了一些镜像层信息浏览功能,我们后续也会提供分析,推出镜像安全扫描、镜像同步。
本文为云栖社区原创内容,未经允许不得转载,如需转载请发送邮件至yqeditor@list.alibaba-inc.com
相关推荐
使用场景及目标:适用于需要构建和优化Docker镜像的项目,帮助读者掌握Dockerfile的最佳实践,提高镜像的构建速度和安全性。 其他说明:文章还包括详细的调试与测试方法,以及Dockerfile编写时需要注意的一些最佳...
此外,教程提供了故障排除和最佳实践的建议,确保读者在实际操作中能够有效应对问题和优化容器化应用。 这篇教程非常适合Java全栈开发的初学者,帮助他们快速上手Docker,并在实际项目中应用这些技术。通过学习本...
此外,文章还讨论了容器化过程中需要注意的优化与最佳实践,如减小镜像大小、配置环境变量、增强安全性以及实现日志管理和监控。最后,文章强调了自动化与 CI/CD 的重要性,建议使用 CI/CD 工具自动化构建、测试和...
文中重点讨论了 Docker 在企业级部署中的角色,以及如何利用 Kubernetes 和 Docker Swarm 等编排工具实现自动化部署、资源管理和优化、安全性保障等方面的最佳实践。此外,文章通过多个实际案例,详细阐述了微服务...
在Docker容器中运行SSHD服务常常被视为一种便利的手段,允许远程管理和操作容器内的应用程序。然而,这种做法并非总是最佳实践,原因在于它...理解并利用Docker原生功能以及最佳实践,可以更好地优化容器的使用和管理。
容器的安全最佳实践包括使用安全的基础镜像、限制容器的权限、实施网络策略以及定期更新和扫描容器镜像以发现和修复漏洞。 最后,**监控**是确保应用稳定运行的关键。这包括设置警报、指标收集和性能分析。...
总结来说,银行建设容器云平台的最佳实践涵盖了明确目标、评估组织能力、技术选型和文化建设等多个层面。通过这些步骤,企业可以构建出一个高效、灵活且适应未来发展的云平台,从而在数字化转型中占据优势。
【某汽车主机厂容器最佳实践-需求定义】 在汽车制造业中,随着业务的快速发展和对IT支持的需求增强,容器技术已成为提升效率和敏捷性的关键工具。这篇文档详细阐述了某汽车主机厂在构建容器云平台过程中的需求定义...
在Docker容器中运行应用时,使用非root用户的最佳实践是为了提高安全性。这个Bitnami Docker映像遵循了这一原则,以降低潜在的安全风险。 **WordPressShell** "WordPressShell"可能指的是一个工具或脚本,允许用户...
### 容器云平台可行性评估:风险管理和关键技术路线选型-最佳实践 #### 一、背景与原因 当前,随着互联网金融的快速发展,对于传统金融企业的IT架构提出了更高的要求。为了应对这一挑战,构建一个现代化的容器云...
10. **最佳实践与案例研究**:可能包含了一些实际的案例研究,展示了不同组织如何成功地实施多容器集群管理和DevOps实践。 总的来说,这份文档提供了对现代DevOps实践中多容器集群管理的全面洞察,对任何寻求优化其...
在镜像封装和部署方面,存在一系列的最佳实践原则和规范,如命名规范、健康检查规范、镜像tag配置规范、使用ConfigMap配置环境变量等。设计原则强调无状态服务、消除不必要的依赖、数据持久化、单一业务原则、日志...
#### 一、容器编排的原则与最佳实践 **1.1 可伸缩性** - **弹性**:容器编排系统必须具备自动扩展容器的能力,确保即使在遇到故障或者高负载时也能保持系统的弹性。 - **水平扩展**:用户应能轻松增减节点,根据...
10. **项目管理与最佳实践**:分享云项目实施过程中的管理方法,如风险管理、变更控制,以及云服务优化和持续改进的最佳实践。 通过《HCIE-Cloud Service Solutions Architect V2.0 培训教材实验手册》的学习,学员...
OpenStack 是一个强大的开源云计算平台,它允许组织构建和管理私有云基础设施。私有云实践,特别是基于OpenStack的...同时,随着OpenStack社区的持续发展,保持对新版本和最佳实践的关注也是私有云运维人员的重要任务。
- 对于开发人员的培训至关重要,他们需要理解如何在容器内安全地构建和运行应用程序,以及如何遵循安全最佳实践。 6. 自动化与策略执行: - 利用自动化工具来实施安全策略,比如使用Policy-as-Code来定义和强制...
6. 性能优化和最佳实践:讨论如何优化蝴蝶容器的性能,以及在实际部署中应遵循的最佳实践。 为了更全面地掌握这些知识点,读者需要阅读提供的博客文章,分析源代码,以及动手操作压缩包中的示例。这将是一个既有...
6. **故障排查与最佳实践**:提供了常见问题的解决方案,以及优化容器服务运行的最佳实践,帮助用户提高运维效率。 7. **API与SDK**:可能包括API的使用方法,以及如何利用SDK进行自动化操作,以实现更高级的集成和...