Docker镜像原理和最佳实践

简介: 在云栖TechDay : Docker深度实践专场,阿里云的高级开发工程师谭林华分享了《Docker镜像原理和最佳实践》。他主要从镜像是什么、镜像基本操作、镜像制作方法、镜像优化、镜像常见问题峰等方面进行了分享。

镜像

1e5af1d2967565d1f108b223504c4ef3e3fba7f0

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

d86fed40ec09ed0d6202149f312369b98284c348

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

镜像基本操作

1b4b2df72ef2dce5ca3fa15b0f17168e5a02e72d

镜像制作——Dockerfile

76e88ba9c2ea795890dbce68a84aa48cc93193d7

第一行是一个FROM指令,使用了一个叫alpine的基础镜像。所有的镜像都可以用来做基础镜像,我们通常不需要关心最基础的镜像是怎么来的,只需要在现有镜像的基础上,构建新的镜像即可。我们在构建的时候,Docker依赖这个基础镜像,在这个基础镜像之上我们再做一些改动,生产新的镜像。FROM指令必须要有,而且只能有一个,通常是放在整个Dockerfile的最前面。

RUN指令做的是在镜像里安装一些软件,或者做一些需要的操作。Docker RUN之后执行了Shell指令,它对镜像里面的内容做了一些改动,最后再执行Docker COMMIT,把当前容器里面的改动持久化到镜像里面。RUN命令可以执行多次。但是通常来说建议把需要的指令都写在一条RUN命令里面,用&&符号连接起来,好处是镜像只增加了一层,可以加快镜像构建的速度,减少镜像的层次。

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

EXPOSE命令,相当于告诉Docker的守护进程,当前这个镜像在运行变成一个容器的时候,其监听的端口是什么,上图中其监听的是80端口。这样声明出去,其他与当前容器在同一个网络里面的容器可以通过这个端口来访问这个当前容器。这个地方其实并没有指定主机的端口,主机端口需要在启动容器的时候,再具体指定。

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

1bbd20f1c95113dd7ce89a1f5694b3eca74ea7ab

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

7b6431599d25a29007f085e7c0671d6b34b2f0e4

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

镜像优化

减小镜像大小

比如很常见的一个需求——镜像太大了。我们之前交付的是应用,通常一个可执行文件,它本身是很小。但是整个镜像如果太大的话,它整个传输过程中会增加部署的时间。减小镜像大小有几种方式:

使用轻量发行版的基础镜像。然后这个地方典型代表就是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这种不需要编译的解释性语言来说,可以考虑直接进行构建。
相关文章
|
20天前
|
Kubernetes 监控 开发者
掌握容器化:Docker与Kubernetes的最佳实践
【10月更文挑战第26天】本文深入探讨了Docker和Kubernetes的最佳实践,涵盖Dockerfile优化、数据卷管理、网络配置、Pod设计、服务发现与负载均衡、声明式更新等内容。同时介绍了容器化现有应用、自动化部署、监控与日志等开发技巧,以及Docker Compose和Helm等实用工具。旨在帮助开发者提高开发效率和系统稳定性,构建现代、高效、可扩展的应用。
|
16天前
|
缓存 Linux 网络安全
docker的镜像无法下载如何解决?
【10月更文挑战第31天】docker的镜像无法下载如何解决?
531 29
|
12天前
|
存储 关系型数据库 Linux
【赵渝强老师】什么是Docker的镜像
Docker镜像是一个只读模板,包含应用程序及其运行所需的依赖环境。镜像采用分层文件系统,每次修改都会以读写层形式添加到原只读模板上。内核bootfs用于加载Linux内核,根镜像相当于操作系统,上方为应用层。镜像在物理存储上是一系列文件的集合,默认存储路径为“/var/lib/docker”。
|
18天前
|
存储 监控 Linux
docker构建镜像详解!!!
本文回顾了Docker的基本命令和管理技巧,包括容器和镜像的增删改查操作,容器的生命周期管理,以及如何通过端口映射和数据卷实现容器与宿主机之间的网络通信和数据持久化。文章还详细介绍了如何使用Docker部署一个简单的Web应用,并通过数据卷映射实现配置文件和日志的管理。最后,文章总结了如何制作自定义镜像,包括Nginx、Python3和CentOS镜像,以及如何制作私有云盘镜像。
90 2
|
19天前
|
关系型数据库 MySQL Docker
docker环境下mysql镜像启动后权限更改问题的解决
在Docker环境下运行MySQL容器时,权限问题是一个常见的困扰。通过正确设置目录和文件的权限,可以确保MySQL容器顺利启动并正常运行。本文提供了多种解决方案,包括在主机上设置正确的权限、使用Dockerfile和Docker Compose进行配置、在容器启动后手动更改权限以及使用 `init`脚本自动更改权限。根据实际情况选择合适的方法,可以有效解决MySQL容器启动后的权限问题。希望本文对您在Docker环境下运行MySQL容器有所帮助。
33 1
|
21天前
|
存储 Java 开发者
成功优化!Java 基础 Docker 镜像从 674MB 缩减到 58MB 的经验分享
本文分享了如何通过 jlink 和 jdeps 工具将 Java 基础 Docker 镜像从 674MB 优化至 58MB 的经验。首先介绍了选择合适的基础镜像的重要性,然后详细讲解了使用 jlink 构建自定义 JRE 镜像的方法,并通过 jdeps 自动化模块依赖分析,最终实现了镜像的大幅缩减。此外,文章还提供了实用的 .dockerignore 文件技巧和选择安全、兼容的基础镜像的建议,帮助开发者提升镜像优化的效果。
|
25天前
|
存储 缓存 Java
Java应用瘦身记:Docker镜像从674MB优化至58MB的实践指南
【10月更文挑战第22天】 在容器化时代,Docker镜像的大小直接影响到应用的部署速度和运行效率。一个轻量级的Docker镜像可以减少存储成本、加快启动时间,并提高资源利用率。本文将分享如何将一个Java基础Docker镜像从674MB缩减到58MB的实践经验。
39 1
|
12天前
|
缓存 JavaScript 安全
深入理解Docker镜像构建过程
深入理解Docker镜像构建过程
43 0
|
3月前
|
存储 安全 Ubuntu
Docker 镜像与 Docker 容器的区别
【8月更文挑战第27天】
317 5
|
3月前
|
存储 Ubuntu 应用服务中间件
在Docker中,怎么快速查看本地的镜像和容器?
在Docker中,怎么快速查看本地的镜像和容器?
下一篇
无影云桌面