Docker 那些事儿容器为什么傲娇?全靠镜像撑腰

Posted 飞向星的客机

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Docker 那些事儿容器为什么傲娇?全靠镜像撑腰相关的知识,希望对你有一定的参考价值。

文章目录


🌟 前言

Docker 镜像是 Docker 容器的基石,容器是镜像的运行实例,有了镜像才能启动容器。
 
Docker 镜像是一个只读的模板,一个独立的文件系统,包括运行一个容器所需的数据,可以用来创建容器。

 

1. base镜像

base(基础) 镜像是指完全从零开始构建的镜像, 它不会依赖其他镜像,甚至会成为被依赖的镜像,其他镜像以它为基础进行扩展。
 
通常 base 镜像都是 Linux 的系统镜像, 如 UbuntuCentOSDebian 等。
 
下面通过 Docker 拉取一个 base 镜像并查看, 这里以 CentOS 为例, 示例代码如下:

从以上示例中可以看出,一个 CentOS 镜像大小只有 202MB,但在安装系统时,一个 CentOS 大概有几 GB,这与操作系统有关。
 
先观察 Linux 原本的操作系统结构,如图所示👇

Kernel 是内核空间。bootfs 文件系统在 Linux 启动时加载。rootfs 是包含操作命令的文件系统。
 
base 镜像的创建过程中,Kernelbootfsrootfs 都会加载,然后 bootfs 文件系统 (包括 Kernel) 被卸载掉,镜像只保留 rootfs 文件系统,供用户进行操作。bootfsKernel 将与宿主机共享。
 
另外,为了增加 Docker 的灵活性,base 镜像提供的都是最小安装的 Linux 系统。
Linux 系统不同的发行版之间最大的区别就是 rootfs 的不同,例如,Ubuntu 系统的应用程序管理器是 apt,而 CentOSyum
 
由此可见,只要提供不同的 rootfs 文件系统就可以同时支持多种操作系统,如图所示👇

从上图中可以看到,两个不同的 Linux 发行版提供了各自的 rootfs 文件系统,而它们共用的是底层宿主机的 Kernel
 
假设宿主机的系统是 Ubuntu 16.04Kernel 版本是 4.4.0,无论 base 镜像原本的发行版 Kernel 版本如何,在这台宿主机上都是 4.4.0
 
下面通过示例来验证,示例代码如下:

从上述示例中可以看出,base 镜像与宿主机的 Kernel 版本都是 3.10
 
base 镜像的 Kernel 是与宿主机共享的,其版本与宿主机一致,并且不能进行修改。

2. 镜像的本质

Docker 镜像是一个只读的文件系统,由一层一层的文件系统组成,每一层仅镜像的本质包含前一层的差异部分,这种层级文件系统被称为 UnionFS
 
大多数 Docker 镜像都在 base 镜像的基础上进行创建,每进行一次新的创建就会在镜像上构建一个新的 UnionFS
查看 ubuntu:15.04 镜像的层级结构,示例代码如下:

通常,对 Docker 的操作命令都是以 “docker” 开头。pull 是下载镜像的命令,在英文中是 “” 的意思,所以下载镜像又叫作 拉取镜像
 
以上示例中,第 5 行到第 8 行是每一层 UnionFSID 号,第 9 行是整个镜像的 ID 号,这个 ID 号可以用来操控镜像。
 
然后,查看镜像,示例代码如下:

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

这里使用 “history” 与镜像 ID 号组合的命令查看镜像构建过程,所显示的信息包括镜像 ID 号、创建时间、由什么命令创建以及镜像大小。
 
从以上示例中的信息可以看出,ubuntu:15.04 镜像由四个只读层 (Read Layer) 构建而成,每一层都是由一条命令构成的,最终得到 ID 号为 dlb55fd07600 的镜像,但以用户的视角只能看到最上层。
 
当用户将这个镜像放在容器中运行时,四层之上会创建出一个可读可写层(Read-Write Layer),用户对 Docker 的操作都通过可读可写层进行。如果用户修改了一个已存在的文件,那该文件将会从可读可写层下的只读层复制到可读可写层,该文件的只读版本仍然存在,只是已经被可读可写层中该文件的副本所隐藏。
 
可读可写层又叫作容器层,只读层又叫作镜像层,容器层之下均为镜像层,层级结构如图所示👇

镜像的这种分层机制最大的一个好处就是:共享资源
 
例如,有很多个镜像都基于一个基础镜像构建而来,那么在本地的仓库中就只需要保存一份基础镜像,所有需要此基础镜像的容器都可以共享它,而且镜像的每一层都可以被共享,从而节省磁盘空间。
 
因为有了分层机制,本地保存的基础镜像都是只读的文件系统,不用担心对容器的操作会对镜像有什么影响。
 
为了将零星的数据整合起来,人们提出了镜像层 (Image Layer) 这个概念,如图所示👇
 
下图所示为一个镜像层,我们能够发现,一个层并不仅仅包含文件系统的改变,它还能包含其他重要的信息。

元数据 (Metadata) 就是关于这个层的额外信息,包括 Docker 运行时的信息与父镜像层的信息,并且只读层与可读可写层都包含元数据,如图所示👇

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

3. 查找本地镜像

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

从以上示例中可以看到,Docker 本地镜像储存路径是 /var/Iib/Docker
 
在本地查看镜像时,通常使用 docker images 命令,示例代码如下:

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

🍇 REPOSITORY

镜像仓库,即一些关联镜像的集合。
 
例如,Ubuntu 的每个镜像对应着不同的版本。与 Docker Registry 不同,镜像仓库提供 Docker 镜像的存储服务。
 
Docker Registry 中有很多镜像仓库,镜像仓库中有很多镜像 (相互独立)。

🍇 TAG

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

🍇 IMAGE ID

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

🍇 CREATED

镜像创建的时间。

🍇 SIZE

镜像的大小。

🍇 参数用法

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

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

  • -a

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

  • -q

表示只显示本地所有镜像 ID 号。
 
示例代码如下:

  • -no-trunc

表示使用不截断的模式显示,并显示完整的镜像 ID 号。
 
示例代码如下:

4. 构建镜像

Docker 的官方镜像库 Docker Hub 发布了成千上万的公共镜像供全球用户使用。用户可以直接拉取(下载)所需要的镜像,提高了工作效率。但是在很多工作环境中,一旦对镜像有特殊需求,就需要我们手动去构建镜像。
 
本文章将会介绍基于 docker commit 命令与 Dockerfile 两种方式来构建自己的 Docker 镜像。

🍑 使用 docker commit 命令构建镜像

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

下面我们给一个 Centos 的镜像安装一个 Vim 服务,设置开机启动,并将其构建成一个新的镜像,以免每次启动容器都要再次安装 Vim
 
首先启动一个 Centos 的容器,示例代码如下:

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

安装完成之后,退出容器,示例代码如下:

使用 exit 命令退出容器之后, 该容器将默认关闭。
下面使用 docker commit 命令在 CentOS 镜像的基础上创建新的镜像,示例代码如下:

在命令中需要用镜像 ID 号来指定基础镜像,并不需要将 ID 号都输入进去,只要输入几个字符使 ID 号与其他镜像不冲突即可。
 
此时可以看到刚刚构建的新镜像,代码如下:

从以上示例中可以看到, 新镜像的大小是 326MB,而此前的 CentOS 镜像只有 202MB, 这是因为在安装 Vim 时还安装了许多依赖包。
 
然后, 查看镜像中是否已经自动安装了 Vim, 示例代码如下:
从以上示例中可以看到,新镜像已经包含了 Vim
 
这种构建新镜像的方式在工作中并不常见,原因如下。
 
(1)效率低下,如果要给 Ubuntu 镜像也添加一个Vim,需要将上述全部过程重复一遍。
 
(2)不透明,用户使用时不知道镜像是如何构建的,难以对镜像做出正确的判断。

🍑 使用 Dockerfile 构建镜像

镜像可以基于 Dockerfile 构建。Dockerfile 是一个描述文件,包含若干条命令,每条命令都会为基础文件系统创建新的层次结构,这正好弥补了 docker commit 构建镜像效率低下的缺点。
 
Dockerfile 定义容器内部环境中发生的事情。网络接口和磁盘驱动器等资源的访问在此环境内虚拟化,与系统的其余部分隔离。
 
Dockerfile 主要使用 docker build 命令,根据 Dockerfile 文件中的指令,执行若干次 docker commit 命令构建镜像,每次执行 docker commit 命令时都会生成一个新的层,因此许多新的层会被创建,如图所示👇

🍑 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,示例代码如下:

这里在宿主机的 root 目录下创建了一个 Dockerfile 文件。
 
接着,向 Docker file 文件中添加内容, 示例代码如下:

添加完成之后,保存并退出。
 
有了 Dockerfile 文件之后即可创建新的镜像,示例代码如下:

通过 docker build 命令执行 Dockerfile 文件,-t 用来指定新镜像名为 centos/vim-Dockerfile,命令行末尾的 . 表示 Dockerfile 文件在当前目录,Docker 默认从指定的目录寻找 Dockerfile 文件,也可以使用 -f 参数指定 Dockerfile 文件的位置。

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

从以上示例中可以看到,新镜像已经构建成功。
 
使用 Dockerfile 构建镜像基本可以分为以下五步。
 
(1) 选择一个基础镜像,运行一个临时容器。
 
(2) 执行一条命令,对容器做修改。
 
(3) 执行类似 docker commit 的操作,生成一个新的镜像。
 
(4) 删除临时容器,再基于刚刚构建好的新镜像运行一个临时容器。
 
(5) 重复 (2) (3) (4) 步,直到执行完 Dockerfile 中的所有指令。
 
centos/vim-DockerfileCentOS 基础镜像和 RUN yum -y install vim 构成,现在两个镜像都包含了 ID 号为 lel148e4cc2c 的只读层,如图所示👇

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

这里可以看到 CentOS 镜像中确实包含了 ID 号为 1e1148e4cc2c 的只读层。
 
接着再查看新镜像 centos/vim-Dockerfile 的结构,示例代码如下:

从以上示例中可以看到,两个镜像都含有一个相同的只读层,并且这个只读层是共享的。
 
Docker 构建镜像时有缓存机制,如果构建镜像层时该镜像层已经存在,就直接使用,无须重新构建。
 
下面为先前的 Dockerfile 文件添加一点内容,安装一个 ntp 服务,重新构建一个新的镜像,示例代码如下:

这里多加了一条安装 ntp 服务的命令。
 
添加完成后,开始创建镜像,示例代码如下:

在示例的第 6 行代码中可以看到,Docker 没有重新安装 Vim,而是直接使用了先前安装过的缓存。
 
Dockerfile 文件是从上至下依次执行的,上层依赖于下层。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。
 
改变先前的 Dockerfile 文件中两条 RUN 命令的上下顺序,观察 Docker 还会不会使用缓存机制,示例代码如下:

Dockerfile 中两条 RUN 命令的顺序互换之后,开始创建镜像,示例代码如下:

由以上验证可知,将两条 RUN 命令交换顺序导致镜像层次发生改变,Docker 会重建镜像层。由此可见 Docker 的镜像层级结构特性:只有下面的层次内容、顺序完全一致才会使用缓存机制。
 
如果在构建镜像时不想使用缓存,可以在 docker build 命令中添加 --no-cache 参数,否则默认使用缓存。
 
除了在使用 Dockerfile 构建镜像时有缓存机制,在从仓库拉取镜像时也会有缓存机制,即已经拉取到本地的镜像层可以被多个镜像共同使用,可以说是一次拉取多次使用,前提是下层镜像完全相同。
 
通常使用 Dockerfile 构建镜像时,如果由于某些原因镜像构建失败,我们能够得到前一个指令成功执行构建出的镜像,继而可以运行这个镜像查找指令失败的原因,这对调试 Dockerfile 有极大的帮助。
 
Docker Hub 拉取的 CentOS 镜像是最小化的,其中没有 vim 命令。下面测试错误构建 Docker 镜像的结果,示例代码如下:

Dockerfile 中任意一条 RUN 命令改为错误的,再开始创建镜像,示例代码如下:

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

Docker 容器技术中,编写 Dockerfile 文件是非常重要的部分,下面总结编写 Dockerfile 文件的一些小技巧,相信可以帮助大家更好地使用 DockerDockerfile
 

  • (1)容器中只运行单个应用。

从技术角度讲,在一个容器中可以实现整个 LNMP (Linux+nginx+mysql+php) 架构。但这样做有很大的弊端。首先,镜像构建的时间会非常长,每次修改都要重新构建;
 
其次,镜像文件会非常大,大大降低容器的灵活性。

  • (2)将多个 RUN 指令合并成一个。

众所周知,Docker 镜像是分层的,Dockerfile 中的每一条指令都会创建一个新的镜像层,镜像层是只读的。
 
Docker镜像层类似于洋葱,想要更改内层,需要将外层全部撕掉。

  • (3)基础镜像的标签尽量不要使用 latest

当镜像的标签没有指定时,默认使用 latest 标签。
 
当镜像更新时,latest 标签会指向不同的镜像,可能会对服务产生影响。

  • (4)执行 RUN 命令后删除多余文件。

假设执行了更新 yum 源的命令,会自动下载解压一些软件包,但是在运行容器的时候不需要这些包。最好将它们删除,因为这些软件包会使镜像 SIZE 变大。

  • (5)合理调整 COPYRUN 的顺序。

将变化少的部分放在 Dockerfile 文件的前面,充分利用镜像缓存机制。

  • (6)选择合适的基础镜像。

最好选择满足环境需要而且体积小巧的镜像,比如 Alpine 版本的 node 镜像。
 
Alpine 是一个极小化的 Linux 发行版,只有 5.5MB,非常适合作为基础镜像。

5. Docker Hub

🍑 docker search 命令

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

以上示例中,查找的是与 CentOS 有关的镜像,Docker Hub 中有关 CentOS 的镜像远不止这些,这里显示的只是其中一部分。
 
下面对镜像的每项信息进行讲解。
 

  • NDEX

镜像索引,这里代表镜像仓库。

  • NAME

镜像名称。

  • DESCRIPTION

关于镜像的描述,使用户可以有方向性地选择镜像。

  • STARS

镜像的星标数,反映 Docker 用户对镜像的收藏情况,值越高代表使用的用户越多。

  • OFFICIAL

有此标记的都是 Docker 官方维护的镜像,没有此标记的通常是用户上传的镜像。

  • AUTOMATED

用来区分是否为自动化构建的镜像,有此标记的为自动化构建的镜像,否则不是。

🍑 docker search 参数运用

docker search 命令添加不同的参数,就会得到不同的查询结果,下面对各参数进行解释。

  • --automated

表示只列出自动化构建的镜像,示例代码如下:

  • --no-trunc

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

  • -s

表示列出星标数不小于指定值的镜像,需要在参数后添加指定值,示例代码如下:

🍑 镜像推送

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

下面使用 docker push 命令向 Docker Hub 推送镜像,示例代码如下:

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

tag 标签添加完之后,再查看一遍镜像,确保准确无误,示例代码如下:

可以看到,先前的镜像并没有发生改变,Docker 只是在它的基础上又创建了一个新镜像。与先前的镜像相比,新镜像只是修改了名字,镜像大小不变。
 
有了新镜像就可以将其推送给 Docker Hub 了,示例代码如下:

镜像推送完成之后, 可以登录 Docker Hub 查看, 验证镜像是否推送成功, 如图所示👇

从上图可以看出,镜像已经推送成功,这个镜像在 Docker Hub 上可以供所有用户下载并使用。
 
尝试拉取先前推送的镜像,示例代码如下:

镜像拉取完成后,查看是否拉取成功,示例代码如下:

从以上示例中可以看出,已经成功拉取了先前推送的镜像,拉取的镜像与本地的镜像相比,ID 号和镜像大小都是相同的。这就体现出了 DockerHub 官方镜像库的方便之处,用户可以将镜像上传到云端,做到随用随取,也可以将自己优秀的镜像与广大开源爱好者共享。
 
Docker Hub 在给用户带来便利的同时,也暴露出了极大的安全问题。任何 Docker 用户都可以随时随地上传镜像到 Docker Hub,将其推送给其他 Docker 用户,任何用户都可以拉取使用这些镜像,但谁都无法辨别所下载的镜像是否包含恶意信息。
 
2018 年发生了一起安全事故,一名 Docker 用户上传了 17 个带有恶意软件的镜像,攻击者利用这些恶意 Docker 镜像在受害者的计算机上安装基于 XMRig 的门罗币挖矿软件。其中一些镜像已经安装超过一百万次,还有一些则被使用了数十万次。Docker 官方网站维护人员调查确认后删除了这些恶意镜像,风波才得以平息。
 
在使用 Docker 镜像时要先在测试环境中安装运行,最安全的方法是尽可能使用自制的 Docker 镜像或使用经过验证的镜像。

6. Docker镜像优化

Docker 镜像采用的是层级结构,一个镜像最多拥有 127UnionFS。每条 Dockerfile 命令都会创建一个镜像层,增加镜像大小。在生产环境中使用 Docker 容器时,要尽可能地精简 Docker 镜像,减少 UnionFS 的层数。
 
精简镜像不仅能缩短新镜像的构建时间,还能减少磁盘用量。由于精简后的镜像更小,用户在拉取镜像时能节省时间,部署服务的效率也能得到提升。精简镜像包含的文件更少,更加不容易被攻击,提高了镜像的安全性。

🍑 base镜像优化

base 镜像优化就是在满足环境要求的前提下使用最小的 base 镜像。常用的 Linux base 镜像有 CentOSUbuntuAlpine 等,其中一些比较小的 base 镜像适合作为精简镜像的基础镜像,如 AlpineBusyBox 等。
 
下面分别拉取 AlpineBusyBox 的镜像进行对比,示例代码如下:

Scratch 是一个空镜像,只能用于构建其他镜像,常用于执行一些包含了所有依赖的二进制文件。如果以 Scratchbase 镜像,意味着不以任何镜像为基础,下面的指令将作为镜像的第一层存在。
 
BusyBox 相对于 Scratch 多了一些常用的 Linux 命令,BusyBox 的官方镜像大小只有 1MB 多一点,非常适合构建小镜像。
 
Alpine 是一款高度精简又包含了基本工具的轻量级 Linux 发行版,官方 base 镜像只有 5MB 多一点,很适合当作 base 镜像使用。

🍑 Dockerfile 优化

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

接着, 使用这个 Dockerfile 构建一个新的镜像, 示例代码如下:

从以上示例中可以看到,整个镜像构建的过程是十分烦琐的。
 
查看新镜像的大小与 UnionFS 的层数,示例代码如下:

从以上示例中可以看到,新镜像 centos/vim-bulky 的大小是 509MB,而镜像的 UnionFS 层数是 8 层。
 
这样编写 Dockerfile 导致新镜像非常庞大,既增加了构建部署的时间,也很容易出错。
 
下面对 Dockerfile 进行优化,示例代码如下:

在Dockerfile中使用 “&” 与 ‘’**" 将多条命令合成一条,"&&” 表示命令还没有结束,‘’**"表示换行。
 
下面通过优化过的 Dockerfile 构建新镜像,示例代码如下:

从以上示例可以看出,优化后的 Dockerfile 构建镜像的过程比优化前精简了一些。
 
查看并对比两个新镜像,示例代码如下:

从以上示例中可以看到,镜像 centos/vim-portable 只有 305MB,比镜像 centos/vim-bulky 节省了 204MB 的资源,甚至比源镜像 centos/vim 还小了 21MB
 
接着,查看镜像 centos/vim-portableUnionFS 层数,示例代码如下:

从以上示例中可以看到,镜像 centos/vim-portableUnionFS 只有 6 层,而先前的镜像 centos/vim-bulky8UnionFS

🍑 清理无用的文件

RUN 命令中使用 yumaptapk 等工具时,可以借助这些工具自带的参数进行优化。如执行 apt-get install-y 时添加 --no-install-recommends 选项,就可以不安装建议性的依赖包,这些依赖包都是不必要的。
 
组件的安装和清理要放置在同一条命令里面,因为 Dockerfile 的每条命令都会产生一个新的镜像层,在执行下一条命令时,上一条命令所产生的镜像层已经为只读层,不可修改。UbuntuDebian 系统使用 rm -rf/var/lib/apt/lists/* 清理镜像中的缓存文件,CentOS 等系统使用 yum clean all 命令清理。
 
Docker 社区中还有许多优化镜像的工具,如压缩镜像的工具 Docker-squash,用起来简单方便。

7. 本章小结

本章介绍了 Docker 镜像的构成,采用了层级结构,层层递进;
 
然后介绍了镜像的仓库,包括官方公有仓库 Docker Hub私有仓库
 
最后详细讲解了如何构建 Docker 镜像,并对 Dockerfile 文件进行了简单的优化。
 
相信大家通过本章的学习,已经掌握了镜像的原理以及操作方式。

以上是关于Docker 那些事儿容器为什么傲娇?全靠镜像撑腰的主要内容,如果未能解决你的问题,请参考以下文章

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

Docker那些事儿之编排工具docker-compose

Docker 那些事儿如何安全地停止删除容器

Docker 那些事儿关于容器底层技术的奥秘

Docker 那些事儿容器数据卷的本手

Docker 那些事儿容器网络的 “梦华录”(上篇)