Docker 容器为什么傲娇?全靠镜像撑腰!

从以上示例中可以看出,一个 CentOS 镜像大小只有 202MB,但在安装系统时,一个 CentOS 大概有几 GB,这与操作系统有关。

先观察 Linux 原本的操作系统结构,如图所示??

从上图中可以看到,两个不同的 Linux 发行版提供了各自的 rootfs 文件系统,而它们共用的是底层宿主机的 Kernel。

假设宿主机的系统是 Ubuntu 16.04,Kernel 版本是 4.4.0,无论 base 镜像原本的发行版 Kernel 版本如何,在这台宿主机上都是 4.4.0。

下面通过示例来验证,示例代码如下:

镜像的本质

Docker 镜像是一个只读的文件系统,由一层一层的文件系统组成,每一层仅镜像的本质包含前一层的差异部分,这种层级文件系统被称为 UnionFS。

大多数 Docker 镜像都在 base 镜像的基础上进行创建,每进行一次新的创建就会在镜像上构建一个新的 UnionFS。

查看 ubuntu:15.04 镜像的层级结构,示例代码如下:

在以上示例中,不仅可以看到先前下载的 Ubuntu15.04 镜像,还可以看到其他镜像,说明 docker images 是查看本地所有镜像的命令。而查看到的信息中,除了镜像名称,还有版本 、镜像 ID 、创建时间以及镜像大小。

接着,通过命令查看镜像的构建过程,示例代码如下:

镜像的这种分层机制最大的一个好处就是:共享资源。

例如,有很多个镜像都基于一个基础镜像构建而来,那么在本地的仓库中就只需要保存一份基础镜像,所有需要此基础镜像的容器都可以共享它,而且镜像的每一层都可以被共享,从而节省磁盘空间。

因为有了分层机制,本地保存的基础镜像都是只读的文件系统,不用担心对容器的操作会对镜像有什么影响。

为了将零星的数据整合起来,人们提出了镜像层 (Image Layer) 这个概念,如图所示??

下图所示为一个镜像层,我们能够发现,一个层并不仅仅包含文件系统的改变,它还能包含其他重要的信息。

除此之外,每一层还有一个指向父镜像层的指针。如果没有这个指针,说明它处于最底层,是一个基础镜像,如图所示??

查找本地镜像

Docker 本地镜像通常是储存在服务器上的,下面验证本地镜像的储存路径,示例代码如下:

从以上示例中可以看到,结果显示中有多项镜像信息,下面对信息进行解释。

?? REPOSITORY

镜像仓库,即一些关联镜像的集合。

例如,Ubuntu 的每个镜像对应着不同的版本。与 Docker Registry 不同,镜像仓库提供 Docker 镜像的存储服务。

即 Docker Registry 中有很多镜像仓库,镜像仓库中有很多镜像 (相互独立)。

?? TAG

镜像的标签,常用来区分不同的版本,默认标签为 latest。

?? IMAGE ID

镜像的ID ,镜像的唯一标识,常用于操作镜像 (默认值只列出前 12 位)。

?? CREATED

镜像创建的时间。

?? SIZE

镜像的大小。

?? 参数用法

在 docker images 命令后加上不同的参数就形成了不同的查询方式,导致不同的查询结果。

下面介绍各参数的含义以及用法。

  • -a

表示显示所有本地镜像,默认不显示中间层镜像,这是工作中经常使用到的参数,用来从本地镜像中寻找符合生产条件的镜像。

示例代码如下:

  • -no-trunc

表示使用不截断的模式显示,并显示完整的镜像 ID 。

示例代码如下:

构建镜像

Docker 的官方镜像库 Docker Hub 发布了成千上万的公共镜像供全球用户使用。用户可以直接拉取(下载)所需要的镜像,提高了工作效率。但是在很多工作环境中,一旦对镜像有特殊需求,就需要我们手动去构建镜像。

?? 使用 docker commit 命令构建镜像

使用 docker commit 命令将容器的可读可写层转换为一个只读层,这样就把一个容器转换成了一个不可变的镜像,如图所示??

从以上示例中可以看到,容器启动之后,主机名发生了改变,说明用户直接进入了容器,再进行操作就是对容器的操作。

然后,在容器中安装 Vim,示例代码如下:

使用 exit 命令退出容器之后, 该容器将默认关闭。

下面使用 docker commit 命令在 CentOS 镜像的基础上创建新的镜像,示例代码如下:

从以上示例中可以看到, 新镜像的大小是 326MB,而此前的 CentOS 镜像只有 202MB, 这是因为在安装 Vim 时还安装了许多依赖包。

然后, 查看镜像中是否已经自动安装了 Vim, 示例代码如下:

?? Dockerfile常用命令

下面介绍 Dockerfile 中常用的命令,完整说明见官方文档。

  • FROM

指定源镜像,必须是已经存在的镜像,必须是Dockerfile中第一条非注释的命令,因为其后的所有指令都使用该镜像。

  • MAINTAINER

  • RUN

在当前容器中运行指定的命令。

  • EXPOSE

指定运行容器时要使用的端口。可以使用多个EXPOSE命令。

  • CMD

指定容器启动时运行的命令,Dockerfile 可以出现多个 CMD 指令,但只有最后一个生效。CMD 可以被启动容器时添加的命令覆盖。

  • ENTRYPOINT

CMD 或容器启动时添加的命令会被当做参数传递给 ENTRYPOINT。

  • COPY

文件或目录复制到当前容器中。

  • ADD

将文件或者目录复制到当前容器中,源文件如果是归档(压缩)文件,则会被自动解压到目标位置。

  • VOLUME

为容器添加容器卷,可以存在于一个或多个目录,用来提供共享存储。该命令会在容器数据卷部分详细介绍。

  • WORKDIR

在容器内设置工作目录。

  • ENV

设置环境变量。

  • USER

指定容器以什么用户身份运行,默认是 root。

?? 运行一个Dockerfile

下面演示使用 Docker file 创建 centos/vim,示例代码如下:

添加完成之后,保存并退出。

有了 Dockerfile 文件之后即可创建新的镜像,示例代码如下:

构建完成之后,查看镜像是否构建成功,示例代码如下:

以上结论可以使用 docker history 命令验证,docker history 命令专门用来查看镜像的结构,示例代码如下:

从以上示例中可以看到,两个镜像都含有一个相同的只读层,并且这个只读层是共享的。

Docker 构建镜像时有缓存机制,如果构建镜像层时该镜像层已经存在,就直接使用,无须重新构建。

下面为先前的 Dockerfile 文件添加一点内容,安装一个 ntp 服务,重新构建一个新的镜像,示例代码如下:

在示例的第 6 行代码中可以看到,Docker 没有重新安装 Vim,而是直接使用了先前安装过的缓存。

Dockerfile 文件是从上至下依次执行的,上层依赖于下层。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。

改变先前的 Dockerfile 文件中两条 RUN 命令的上下顺序,观察 Docker 还会不会使用缓存机制,示例代码如下:

由以上验证可知,将两条 RUN 命令交换顺序导致镜像层次发生改变,Docker 会重建镜像层。由此可见 Docker 的镜像层级结构特性:只有下面的层次内容、顺序完全一致才会使用缓存机制。

如果在构建镜像时不想使用缓存,可以在 docker build 命令中添加 –no-cache 参数,否则默认使用缓存。

除了在使用 Dockerfile 构建镜像时有缓存机制,在从仓库拉取镜像时也会有缓存机制,即已经拉取到本地的镜像层可以被多个镜像共同使用,可以说是一次拉取多次使用,前提是下层镜像完全相同。

通常使用 Dockerfile 构建镜像时,如果由于某些原因镜像构建失败,我们能够得到前一个指令成功执行构建出的镜像,继而可以运行这个镜像查找指令失败的原因,这对调试 Dockerfile 有极大的帮助。

从 Docker Hub 拉取的 CentOS 镜像是最小化的,其中没有 vim 命令。下面测试错误构建 Docker 镜像的结果,示例代码如下:

在示例中,由于第三步 错,镜像没有创建成功。但也生成了一个新镜像,这个镜像是第二步操作构建的,通常可以通过这个新镜像排查错误,示例代码如下:

Docker Hub

?? docker search 命令

Docker Hub 上有许多镜像, 但并不都是 Docker 官方人员制作的, 一些镜像是由 Docker 用户上传并维护的。工作中可以通过 docker search 命令从镜像仓库中查找所需要的镜像。

示例代码如下:

  • –no-trunc

表示显示完整的镜像描述,示例代码如下:

?? 镜像推送

用户向 Docker Hub 推送镜像需要有一个 Docker Hub 账 。在 Docker 客户端使用 docker login 命令登录 Docker Hub 账 ,如图所示??

这里第 6 行出现了 错,镜像没有推送成功, 错信息显示请求资源被拒绝。这是因为 DockerHub 上的镜像都有一个 tag 标签,用户上传镜像时需要给镜像添加 tag 标签。

下面使用 docker tag 命令给镜像添加 tag 标签,示例代码如下:

可以看到,先前的镜像并没有发生改变,Docker 只是在它的基础上又创建了一个新镜像。与先前的镜像相比,新镜像只是修改了名字,镜像大小不变。

有了新镜像就可以将其推送给 Docker Hub 了,示例代码如下:

从上图可以看出,镜像已经推送成功,这个镜像在 Docker Hub 上可以供所有用户下载并使用。

尝试拉取先前推送的镜像,示例代码如下:

从以上示例中可以看出,已经成功拉取了先前推送的镜像,拉取的镜像与本地的镜像相比,ID 和镜像大小都是相同的。这就体现出了 DockerHub 官方镜像库的方便之处,用户可以将镜像上传到云端,做到随用随取,也可以将自己优秀的镜像与广大开源爱好者共享。

Docker Hub 在给用户带来便利的同时,也暴露出了极大的安全问题。任何 Docker 用户都可以随时随地上传镜像到 Docker Hub,将其推送给其他 Docker 用户,任何用户都可以拉取使用这些镜像,但谁都无法辨别所下载的镜像是否包含恶意信息。

2018 年发生了一起安全事故,一名 Docker 用户上传了 17 个带有恶意软件的镜像,攻击者利用这些恶意 Docker 镜像在受害者的计算机上安装基于 XMRig 的门罗币挖矿软件。其中一些镜像已经安装超过一百万次,还有一些则被使用了数十万次。Docker 官方 站维护人员调查确认后删除了这些恶意镜像,风波才得以平息。

在使用 Docker 镜像时要先在测试环境中安装运行,最安全的方法是尽可能使用自制的 Docker 镜像或使用经过验证的镜像。

Scratch 是一个空镜像,只能用于构建其他镜像,常用于执行一些包含了所有依赖的二进制文件。如果以 Scratch 为 base 镜像,意味着不以任何镜像为基础,下面的指令将作为镜像的第一层存在。

BusyBox 相对于 Scratch 多了一些常用的 Linux 命令,BusyBox 的官方镜像大小只有 1MB 多一点,非常适合构建小镜像。

Alpine 是一款高度精简又包含了基本工具的轻量级 Linux 发行版,官方 base 镜像只有 5MB 多一点,很适合当作 base 镜像使用。

?? Dockerfile 优化

用户在定义 Dockerfile 文件时,使用太多的 RUN 命令,会导致镜像非常靡肿,甚至超出可构建的最大层数。根据优化原则,应该将多条 RUN 命令合并为一条命令,精心设计每一个 RUN 命令,减小镜像体积,并且精心编排,最大化地利用缓存。

下面创建一个 Dockerfile 文件,示例代码如下:

从以上示例中可以看到,整个镜像构建的过程是十分烦琐的。

查看新镜像的大小与 UnionFS 的层数,示例代码如下:

在Dockerfile中使用 “&” 与 ‘’**” 将多条命令合成一条,”&&” 表示命令还没有结束,‘’**”表示换行。

下面通过优化过的 Dockerfile 构建新镜像,示例代码如下:

从以上示例中可以看到,镜像 centos/vim-portable 只有 305MB,比镜像 centos/vim-bulky 节省了 204MB 的资源,甚至比源镜像 centos/vim 还小了 21MB。

接着,查看镜像 centos/vim-portable 的 UnionFS 层数,示例代码如下:

本章小结

本章介绍了 Docker 镜像的构成,采用了层级结构,层层递进;

然后介绍了镜像的仓库,包括官方公有仓库 Docker Hub 和 私有仓库;

最后详细讲解了如何构建 Docker 镜像,并对 Dockerfile 文件进行了简单的优化。

相信大家通过本章的学习,已经掌握了镜像的原理以及操作方式。

点分享

点点赞

7a306bf50a2ca8dd90292e6e6988b2af.gif

点在看

文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树深入研究容器Collection的功能方法93566 人正在系统学习中

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年5月25日
下一篇 2022年5月25日

相关推荐