理解Docker(2):Docker 镜像
<p>本系列文章将介绍Docker的有关知识:</p><p>(1)Docker 安装及基本用法</p>
<p>(2)Docker 镜像</p>
<p>(3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境</p>
<p>(4)Docker 容器的隔离性 - 使用 cgroups 限制容器使用的资源</p>
<p>(5)Docker 网络</p>
<p> </p>
<p> 对于每个软件,除了它自身的代码以外,它的运行还需要有一个运行环境和依赖。不管这个软件是象往常一样运行在物理机或者虚机之中,还是运行在现在的容器之中,这些都是不变的。在传统环境中,软件在运行之前也需要经过 代码开发->运行环境准备 -> 安装软件 -> 运行软件 等环节,在容器环境中,中间的两个环节被镜像制作过程替代了。也就是说,镜像的制作也包括运行环境准备和安装软件等两个主要环节,以及一些其他环节。因此,Docker 容器镜像其实并没有什么新的理论,只是这过程有了新的方式而已。</p>
<p> 镜像(image)是动态的容器的静态表示(specification),包括容器所要运行的应用代码以及运行时的配置。Docker 镜像包括一个或者多个只读层( read-only layers ),因此,镜像一旦被创建就再也不能被修改了。一个运行着的Docker 容器是一个镜像的实例( instantiation )。从同一个镜像中运行的容器包含有相同的应用代码和运行时依赖。但是不像镜像是静态的,每个运行着的容器都有一个可写层( writable layer ,也成为容器层 container layer),它位于底下的若干只读层之上。运行时的所有变化,包括对数据和文件的写和更新,都会保存在这个层中。因此,从同一个镜像运行的多个容器包含了不同的容器层。</p>
<p> Docker 有两种方式来创建一个容器镜像:</p>
<div>
<ul>
<li>创建一个容器,运行若干命令,再使用 docker commit 来生成一个新的镜像。不建议使用这种方案。</li>
<li>创建一个 Dockerfile 然后再使用 docker build 来创建一个镜像。<span style="line-height: 1.5">大多人会使用 Dockerfile 来创建镜像。</span></li>
</ul>
<h2><span style="line-height: 1.5">1. 镜像有关的几个基础概念</span></h2>
<h3><span style="line-height: 1.5">1.1 Host OS VS Guest OS VS Base image</span></h3>
<p><span style="line-height: 1.5">比如,一台主机安装的是 Centos 操作系统,现在在上面跑一个 Ubuntu 容器。此时,Host OS 是 Centos,Guest OS 是 Ubuntu。Guest OS 也被成为容器的 Base Image。</span></p>
<p><span style="line-height: 1.5"><img src="https://img2018.cnblogs.com/blog/697113/201811/697113-20181102151213866-568935723.png" alt=""></span></p>
<p> </p>
<p>一些说明:</p>
<ul>
<li>关于 linux 内核和版本:所有 Linux 发行版都采用相同的 Linux 内核(kernel),然后所有发行版对内核都有轻微改动。这些改动都会上传回 linux 社区,并被合并。</li>
<li>关于Linux 容器环境:因为所有Linux发行版都包含同一个linux 内核(有轻微修改),以及不同的自己的软件,因此,会很容易地将某个 userland 软件安装在linux 内核上,来模拟不同的发行版环境。比如说,在 Ubuntu 上运行 Centos 容器,这意味着从 Centos 获取 userland 软件,运行在 Ubuntu 内核上。因此,这就像在同一个操作系统(linux 内核)上运行不同的 userland 软件(发行版的)。这就是为什么Docker 不支持在 Linux 主机上运行 FreeBSD 或者windows 容器。</li>
</ul>
<p>可见,容器的 base image 并不真的是 base OS。Base image 会远远比 base OS 更轻量。它只安装发行版特殊的部分(userland 软件)。</p>
<p><img src="https://img2018.cnblogs.com/blog/697113/201811/697113-20181102153728364-1665427677.png" alt=""></p>
<p>那为什么还需要 base image 呢?这是因为,docker 容器文件系统与 host OS 是隔离的。容器镜像中的应用软件无法看到主机文件系统,除非将主机文件系统挂载为容器的卷。因此,可以想像一下,你容器中的应用依赖于各种操作系统库,因此我们不得不将这些库打包到镜像之中。另外,base image 会让我们使用到各个发行版的包管理系统,比如 yum 和 apt-get。而且,各个linux 发行版的 base image 也不是普通的发行版,而是一个简化了的版本。而且,base image 并不带有 linux 内核,因为容器会使用主机的内核。</p>
<p>因此,需要注重理解 image 和 OS 这两个概念。之所以成为 base image,而不是 base OS,是因为 base image 中并不包括完整的 OS。而这一点,是容器与虚拟机之前的本质区别之一。那就是,容器并没有虚拟化,而是共享主机上的linux 内核。</p>
<h3>1.2 关于Container Base image</h3>
<p>从上面内容可以看出,容器把 linux 镜像从内核空间和用户空间进行了分开管理。对 host OS 来说,它更侧重于内核,再加上少量的用户空间内容;对 Guest OS 来说,它侧重于(只有)用户空间,只包括库文件、编译器、配置文件,以及用户代码。</p>
<p><img src="https://img2018.cnblogs.com/blog/697113/201811/697113-20181102154457967-378831102.png" alt=""></p>
<p>常见的容器基础镜像:</p>
<p><img src="https://img2018.cnblogs.com/blog/697113/201811/697113-20181102154656980-253048329.png" alt=""></p>
<p>因此,用户需要仔细选择容器的 base image,不仅从上表中的几个方面,还包括性能、安全性等一些因素。</p>
<p>Ubuntu 更是推出了只有 29M 的 Minimal Ubuntu 容器镜像,具体在这里 https://blog.ubuntu.com/2018/07/09/minimal-ubuntu-released。 </p>
</div>
<h2>2. docker build 生成镜像</h2>
<h3>2.1 生成过程实例</h3>
<p> 在使用 Dockerfile 创建容器之前,需要先准备一个 Dockerfile 文件,然后运行 docker build 命令来创建镜像。我们通过下面的例子来看看Docker 创建容器的过程。</p>
<div class="cnblogs_code">
<pre>FROM ubuntu:<span style="color: rgba(128, 0, 128, 1)">14.04</span><span style="color: rgba(0, 0, 0, 1)">
MAINTAINER sammy </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sammy@sammy.com</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
RUN apt</span>-<span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)"> update
RUN apt</span>-<span style="color: rgba(0, 0, 255, 1)">get</span> -<span style="color: rgba(0, 0, 0, 1)">y install ntp
EXPOSE </span><span style="color: rgba(128, 0, 128, 1)">5555</span><span style="color: rgba(0, 0, 0, 1)">
CMD [</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/usr/sbin/ntpd</span><span style="color: rgba(128, 0, 0, 1)">"</span>]</pre>
</div>
<p>这是一个非常简单的Dockerfile,它的目的是基于 Ubuntu 14.04 基础镜像安装 ntp 从而生成一个新的镜像。看看其过程:</p>
<div class="cnblogs_code">
<pre>root@devstack:/home/sammy/ntponubuntu# docker build -<span style="color: rgba(0, 0, 0, 1)">t sammy_ntp2 .
Sending build context to Docker daemon </span><span style="color: rgba(128, 0, 128, 1)">2.048</span><span style="color: rgba(0, 0, 0, 1)"> kB
<span style="color: rgba(51, 102, 255, 1)">Step </span></span><span style="color: rgba(51, 102, 255, 1)">1 : FROM ubuntu:14.04</span>
---><span style="color: rgba(0, 0, 0, 1)"> 4a725d3b3b1c
<span style="color: rgba(51, 102, 255, 1)">Step </span></span><span style="color: rgba(51, 102, 255, 1)">2 : MAINTAINER sammy "sammy@sammy.com"</span>
---><span style="color: rgba(0, 0, 0, 1)"> Using cache
</span>---><span style="color: rgba(0, 0, 0, 1)"> c4299e3f774c
<span style="color: rgba(51, 102, 255, 1)">Step </span></span><span style="color: rgba(51, 102, 255, 1)">3 : RUN apt-get update
</span>---><span style="color: rgba(0, 0, 0, 1)"> Using cache
</span>---><span style="color: rgba(0, 0, 0, 1)"> 694a19d54103
<span style="color: rgba(51, 102, 255, 1)">Step </span></span><span style="color: rgba(51, 102, 255, 1)">4 : RUN apt-get -y install ntp
</span>---> Running <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> 9bd153c65a76
Reading package lists...
</span><span style="color: rgba(0, 0, 0, 1)">...
Fetched </span><span style="color: rgba(128, 0, 128, 1)">561</span> kB <span style="color: rgba(0, 0, 255, 1)">in</span> 10s (<span style="color: rgba(128, 0, 128, 1)">51.1</span> kB/<span style="color: rgba(0, 0, 0, 1)">s)
Selecting previously unselected package libedit2:amd64.
(Reading database ... </span><span style="color: rgba(128, 0, 128, 1)">11558</span><span style="color: rgba(0, 0, 0, 1)"> files and directories currently installed.)
</span><span style="color: rgba(0, 0, 0, 1)">...
Processing triggers </span><span style="color: rgba(0, 0, 255, 1)">for</span> libc-bin (<span style="color: rgba(128, 0, 128, 1)">2.19</span>-0ubuntu6.<span style="color: rgba(128, 0, 128, 1)">9</span><span style="color: rgba(0, 0, 0, 1)">) ...
Processing triggers </span><span style="color: rgba(0, 0, 255, 1)">for</span> ureadahead (<span style="color: rgba(128, 0, 128, 1)">0.100</span>.<span style="color: rgba(128, 0, 128, 1)">0</span>-<span style="color: rgba(128, 0, 128, 1)">16</span><span style="color: rgba(0, 0, 0, 1)">) ...
</span>---><span style="color: rgba(0, 0, 0, 1)"> 9cc05cf6f48d
Removing intermediate container 9bd153c65a76
<span style="color: rgba(51, 102, 255, 1)">Step </span></span><span style="color: rgba(51, 102, 255, 1)">5 : EXPOSE 5555</span>
---> Running <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> eb4633151d98
</span>---><span style="color: rgba(0, 0, 0, 1)"> f5c96137bec9
Removing intermediate container eb4633151d98
<span style="color: rgba(51, 102, 255, 1)">Step </span></span><span style="color: rgba(51, 102, 255, 1)">6 : CMD /usr/sbin/ntpd
</span>---> Running <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> e81b1eae3678
</span>---><span style="color: rgba(0, 0, 0, 1)"> af678df648bc
Removing intermediate container e81b1eae3678
Successfully built af678df648bc</span></pre>
</div>
<p>Dockerfile 中的每个步骤都会对应每一个 docker build 输出中的 step。</p>
<p>Step 1:FROM ubuntu:14.04</p>
<p style="margin-left: 30px">获取基础镜像 ubuntu:14.04. Docker 首先会在本地查找,如果找到了,则直接利用;否则从 Docker registry 中下载。在第一次使用这个基础镜像的时候,Docker 会从 Docker Hub 中下载这个镜像,并保存在本地:</p>
<div class="cnblogs_code" style="margin-left: 30px">
<pre>Step <span style="color: rgba(128, 0, 128, 1)">1</span> : FROM ubuntu:<span style="color: rgba(128, 0, 128, 1)">14.04</span>
<span style="color: rgba(128, 0, 128, 1)">14.04</span>: Pulling <span style="color: rgba(0, 0, 255, 1)">from</span> library/<span style="color: rgba(0, 0, 0, 1)">ubuntu
862a3e9af0ae: Pull complete
6498e51874bf: Pull complete
159ebdd1959b: Pull complete
0fdbedd3771a: Pull complete
7a1f7116d1e3: Pull complete
Digest: sha256:5b5d48912298181c3c80086e7d3982029b288678fccabf2265899199c24d7f89
Status: Downloaded newer image </span><span style="color: rgba(0, 0, 255, 1)">for</span> ubuntu:<span style="color: rgba(128, 0, 128, 1)">14.04</span>
---> 4a725d3b3b1c</pre>
</div>
<p style="margin-left: 30px">以后再使用的时候就直接使用这个镜像而不再需要下载了。</p>
<p>Step 2:MAINTAINER sammy "sammy@sammy.com"</p>
<p style="margin-left: 30px">本例中依然是从 Cache 中环境新的镜像。在第一次的时候,Docker 会创建一个临时的容器 1be8f33c1846,然后运行 MAINTAINER 命令,再使用 docker commit 生成新的镜像</p>
<div class="cnblogs_code" style="margin-left: 30px">
<pre>Step <span style="color: rgba(128, 0, 128, 1)">2</span> : MAINTAINER sammy <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sammy@sammy.com</span><span style="color: rgba(128, 0, 0, 1)">"</span>
---> Running <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> 1be8f33c1846
</span>---> c4299e3f774c</pre>
</div>
<p style="margin-left: 30px">通过这个临时容器的过程(create -> commit -> destroy),生成了新的镜像 c4299e3f774c:</p>
<div class="cnblogs_code" style="margin-left: 30px">
<pre><span style="color: rgba(128, 0, 128, 1)">2016</span>-<span style="color: rgba(128, 0, 128, 1)">09</span>-16T21:<span style="color: rgba(128, 0, 128, 1)">58</span>:<span style="color: rgba(128, 0, 128, 1)">09.010886393</span>+<span style="color: rgba(128, 0, 128, 1)">08</span>:<span style="color: rgba(128, 0, 128, 1)">00</span> container create 1be8f33c18469f089d1eee8c444dad1ff0c7309be82767092082311379245358 (image=sha256:4a725d3b3b1cc18c8cbd05358ffbbfedfe1eb947f58061e5858f08e2899731ee, name=<span style="color: rgba(0, 0, 0, 1)">focused_poitras)
</span><span style="color: rgba(128, 0, 128, 1)">2016</span>-<span style="color: rgba(128, 0, 128, 1)">09</span>-16T21:<span style="color: rgba(128, 0, 128, 1)">58</span>:<span style="color: rgba(128, 0, 128, 1)">09.060071206</span>+<span style="color: rgba(128, 0, 128, 1)">08</span>:<span style="color: rgba(128, 0, 128, 1)">00</span> container commit 1be8f33c18469f089d1eee8c444dad1ff0c7309be82767092082311379245358 (comment=, image=sha256:4a725d3b3b1cc18c8cbd05358ffbbfedfe1eb947f58061e5858f08e2899731ee, name=<span style="color: rgba(0, 0, 0, 1)">focused_poitras)
</span><span style="color: rgba(128, 0, 128, 1)">2016</span>-<span style="color: rgba(128, 0, 128, 1)">09</span>-16T21:<span style="color: rgba(128, 0, 128, 1)">58</span>:<span style="color: rgba(128, 0, 128, 1)">09.071988068</span>+<span style="color: rgba(128, 0, 128, 1)">08</span>:<span style="color: rgba(128, 0, 128, 1)">00</span> container destroy 1be8f33c18469f089d1eee8c444dad1ff0c7309be82767092082311379245358 (image=sha256:4a725d3b3b1cc18c8cbd05358ffbbfedfe1eb947f58061e5858f08e2899731ee, name=focused_poitras)</pre>
</div>
<p style="margin-left: 30px">这个镜像是基于 ubuntu 14.04 基础镜像生成的,layers 没有变化,只是元数据 CMD 发生了改变:</p>
<div class="cnblogs_code" style="margin-left: 30px">
<pre><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cmd</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: [
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/bin/sh</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">-c</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">#(nop) </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">MAINTAINER sammy \"sammy@sammy.com\"</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
]</span></pre>
</div>
<p style="margin-left: 30px">因此可以认为只是镜像的元数据发生了改变。生成的新的镜像作为中间镜像会被保存在 cache 中。</p>
<p> Step 3: RUN apt-get update</p>
<p style="margin-left: 30px">本例中Docker 仍然从缓存中获取了镜像。在第一次的时候,Docker 仍然是通过创建临时容器在执行 docker commit 的方式来创建新的镜像:</p>
<div class="cnblogs_code" style="margin-left: 30px">
<pre>Step <span style="color: rgba(128, 0, 128, 1)">3</span> : RUN apt-<span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)"> update
</span>---> Running <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> 8b3b97af3bd7
Ign http:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">archive.ubuntu.com trusty InRelease</span>
Get:<span style="color: rgba(128, 0, 128, 1)">1</span> http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">archive.ubuntu.com trusty-updates InRelease </span>
<span style="color: rgba(0, 0, 0, 1)">...
</span>Get:<span style="color: rgba(128, 0, 128, 1)">22</span> http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">archive.ubuntu.com trusty/universe amd64 Packages </span><span style="color: rgba(0, 0, 0, 1)">
Fetched </span><span style="color: rgba(128, 0, 128, 1)">22.2</span> MB <span style="color: rgba(0, 0, 255, 1)">in</span> 16min 21s (<span style="color: rgba(128, 0, 128, 1)">22.6</span> kB/<span style="color: rgba(0, 0, 0, 1)">s)
Reading package lists...
</span>---><span style="color: rgba(0, 0, 0, 1)"> 694a19d54103
Removing intermediate container 8b3b97af3bd7</span></pre>
</div>
<p style="margin-left: 30px">通过以上步骤,生成了新的中间镜像 694a19d54103,它也会被保存在缓存中。你可以使用 docker inspect 694a19d54103 命令查看该中间镜像,但是无法在docker images 列表中找到它,这是因为 docker images 默认隐藏了中间状态的镜像,因此你需要使用 docker images -a 来获取它:</p>
<div class="cnblogs_code" style="margin-left: 30px">
<pre>root@devstack:/home/sammy# docker images -a |<span style="color: rgba(0, 0, 0, 1)"> grep 694a19d54103
</span><none> <none> 694a19d54103 <span style="color: rgba(128, 0, 128, 1)">11</span> hours ago <span style="color: rgba(128, 0, 128, 1)">210.1</span> MB</pre>
</div>
<p style="margin-left: 30px">该镜像和原始镜像相比,多了一个 layer,它保存的是 apt-get update 命令所带来的变化:</p>
<div class="cnblogs_code" style="margin-left: 30px">
<pre><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RootFS</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: {
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Type</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">layers</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Layers</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: [
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sha256:102fca64f92471ff7fca48e55807ae2471502822ba620292b0a06ebcab907cf4</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sha256:24fe29584c046f2a88f7f566dd0bf7b08a8c0d393dfad8370633b0748bba8cbc</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sha256:530d731d21e1b1bbe356d70d3bca4d72d76fed89e90faab271d29bd58c8ccea4</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sha256:344f56a35ff9fc747ada7d2b88bd21c49b2ec404872662cbaf0a65201873c0c6</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sha256:ffb6ddc7582aa7e2e73f102df3ffcd272e59b7cf3f7abefe08d11a7c85dea53a</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(51, 102, 255, 1)">"sha256:a1afe95c99b39c30b5c1d3e8fda451bd3f066be304616197f1046e64cf6cda93" #这一层是新加的</span><span style="color: rgba(0, 0, 0, 1)">
]
}</span></pre>
</div>
<p>Step 4: RUN apt-get -y install ntp</p>
<p style="margin-left: 30px">和上面 Step 3 过程一样,这个步骤也会通过创建临时容器,执行该命令,再使用 docker commit 命令生成一个中间镜像 9cc05cf6f48d 。和上面步骤生成的镜像相比,它又多了一层:</p>
<div class="cnblogs_code" style="margin-left: 30px">
<pre>root@devstack:/home/sammy# docker images -a |<span style="color: rgba(0, 0, 0, 1)"> grep 9cc05cf6f48d
</span><none> <none> 9cc05cf6f48d <span style="color: rgba(128, 0, 128, 1)">10</span> hours ago <span style="color: rgba(128, 0, 128, 1)">212.8</span><span style="color: rgba(0, 0, 0, 1)"> MB
root@devstack:</span>/home/sammy# docker inspect --format={{<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">.RootFS.Layers</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">}} 9cc05cf6f48d
</span></pre>
</div>
<p>Step 5: EXPOSE 5555</p>
<p style="margin-left: 30px"> 这一步和上面的 Step 2 一样,Docker 生成了一个临时容器,执行 EXPOSE 55 命令,再通过 docker commit 创建了中间镜像 f5c96137bec9。该镜像的 layers 没有变化,但是元数据发生了一些变化,包括:</p>
<div class="cnblogs_code" style="margin-left: 30px">
<pre><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ExposedPorts</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: {
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">5555/tcp</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: {}
}
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cmd</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: [
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/bin/sh</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">-c</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">#(nop) </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">EXPOSE 5555/tcp</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
]</span></pre>
</div>
<p>Step 6: CMD ["/usr/sbin/ntpd"]</p>
<p style="margin-left: 30px">这一步和上面的步骤相同,最终它创建了镜像 af678df648bc,该镜像只是修改了 CMD 元数据:</p>
<div class="cnblogs_code" style="margin-left: 30px">
<pre> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Cmd</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: [
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/bin/sh</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">-c</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">#(nop) </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">CMD [\"/usr/sbin/ntpd\"]</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
]</span></pre>
</div>
<p>该镜像也是Docker 根据本 Dockerfile 生成的最终镜像。它也出现在了 docker images 结果中:</p>
<div class="cnblogs_code">
<pre>root@devstack:/home/sammy# docker images |<span style="color: rgba(0, 0, 0, 1)"> grep af678df648bc
sammy_ntp2 latest af678df648bc </span><span style="color: rgba(128, 0, 128, 1)">11</span> hours ago <span style="color: rgba(128, 0, 128, 1)">212.8</span> MB</pre>
</div>
<p>我们可以使用 docker history 命令查看该镜像中每一层的信息:</p>
<div class="cnblogs_code">
<pre>root@devstack:/home/sammy/<span style="color: rgba(0, 0, 0, 1)">ntponubuntu# docker history af678df648bc
IMAGE CREATED CREATED BY SIZE COMMENT
af678df648bc </span><span style="color: rgba(128, 0, 128, 1)">16</span> hours ago /bin/sh -c #(nop)CMD [<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/usr/sbin/ntpd</span><span style="color: rgba(128, 0, 0, 1)">"</span>] <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)"> B
f5c96137bec9 </span><span style="color: rgba(128, 0, 128, 1)">16</span> hours ago /bin/sh -c #(nop)EXPOSE <span style="color: rgba(128, 0, 128, 1)">5555</span>/tcp <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)"> B
9cc05cf6f48d </span><span style="color: rgba(128, 0, 128, 1)">16</span> hours ago /bin/sh -c apt-<span style="color: rgba(0, 0, 255, 1)">get</span> -y install ntp <span style="color: rgba(128, 0, 128, 1)">2.679</span><span style="color: rgba(0, 0, 0, 1)"> MB
694a19d54103 </span><span style="color: rgba(128, 0, 128, 1)">16</span> hours ago /bin/sh -c apt-<span style="color: rgba(0, 0, 255, 1)">get</span> update <span style="color: rgba(128, 0, 128, 1)">22.17</span><span style="color: rgba(0, 0, 0, 1)"> MB
c4299e3f774c </span><span style="color: rgba(128, 0, 128, 1)">17</span> hours ago /bin/sh -c #(nop)MAINTAINER sammy <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sammy@sa 0 B</span>
4a725d3b3b1c <span style="color: rgba(128, 0, 128, 1)">3</span> weeks ago /bin/sh -c #(nop) CMD [<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/bin/bash</span><span style="color: rgba(128, 0, 0, 1)">"</span>] <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)"> B
</span><missing> <span style="color: rgba(128, 0, 128, 1)">3</span> weeks ago /bin/sh -c mkdir -p /run/systemd && echo <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">doc 7 B</span>
<missing> <span style="color: rgba(128, 0, 128, 1)">3</span> weeks ago /bin/sh -c sed -i <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">s/^#\s*\(deb.*universe\)$/ 1.895 kB</span>
<missing> <span style="color: rgba(128, 0, 128, 1)">3</span> weeks ago /bin/sh -c rm -rf /<span style="color: rgba(0, 0, 255, 1)">var</span>/lib/apt/lists<span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 0 B
<missing> 3 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' > /u 194.6 kB
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:ada91758a31d8de3c7 187.8 MB</span></pre>
</div>
<p>以上过程说明:</p>
<ul>
<li>容器镜像包括元数据和文件系统,其中文件系统是指对基础镜像的文件系统的修改,元数据不影响文件系统,只是会影响容器的配置</li>
<li>每个步骤都会生成一个新的镜像,新的镜像与上一次的镜像相比,要么元数据有了变化,要么文件系统有了变化而多加了一层</li>
<li>Docker 在需要执行指令时通过创建临时镜像,运行指定的命令,再通过 docker commit 来生成新的镜像</li>
<li>Docker 会将中间镜像都保存在缓存中,这样将来如果能直接使用的话就不需要再从头创建了。关于镜像缓存,请搜索相关文档。</li>
</ul>
<h3>2.2 Docker 镜像分层,COW 和 镜像大小(size)</h3>
<h4>2.2.1 镜像分层和容器层</h4>
<p><img src="https://images2015.cnblogs.com/blog/697113/201609/697113-20160917210455258-731213001.jpg" alt="" width="512" height="350"></p>
<p> 从上面例子可以看出,一个 Docker 镜像是基于基础镜像的多层叠加,最终构成和容器的 rootfs (根文件系统)。当 Docker 创建一个容器时,它会在基础镜像的容器层之上添加一层新的薄薄的可写容器层。接下来,所有对容器的变化,比如写新的文件,修改已有文件和删除文件,都只会作用在这个容器层之中。因此,通过不拷贝完整的 rootfs,Docker 减少了容器所占用的空间,以及减少了容器启动所需时间。</p>
<h4>2.2.2 COW 和镜像大小</h4>
<p> COW,copy-on-write 技术,一方面带来了容器启动的快捷,另一方也造成了容器镜像大小的增加。每一次 RUN 命令都会在镜像上增加一层,每一层都会占用磁盘空间。举个例子,在 Ubuntu 14.04 基础镜像中运行 RUN apt-get upgrade 会在保留基础层的同时再创建一个新层来放所有新的文件,而不是修改老的文件,因此,新的镜像大小会超过直接在老的文件系统上做更新时的文件大小。因此,为了减少镜像大小起见,所有文件相关的操作,比如删除,释放和移动等,都需要尽可能地放在一个 RUN 指令中进行。</p>
<p> 比如说,通过将上面的示例 Dockerfile 修改为:</p>
<div class="cnblogs_code">
<pre>FROM ubuntu:<span style="color: rgba(128, 0, 128, 1)">14.04</span><span style="color: rgba(0, 0, 0, 1)">
MAINTAINER sammy </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sammy@sammy.com</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
RUN apt</span>-<span style="color: rgba(0, 0, 255, 1)">get</span> update && apt-<span style="color: rgba(0, 0, 255, 1)">get</span> -<span style="color: rgba(0, 0, 0, 1)">y install ntp
EXPOSE </span><span style="color: rgba(128, 0, 128, 1)">5555</span><span style="color: rgba(0, 0, 0, 1)">
CMD [</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/usr/sbin/ntpd</span><span style="color: rgba(128, 0, 0, 1)">"</span>]</pre>
</div>
<p>结果产生的镜像,不仅层数少了一层(7 -> 6),而且大小减少了 0.001M :),因为这个例子比较特殊,文件都是添加,而没有更新,因此size 的下降非常小。</p>
<h4>2.2.3 使用容器需要避免的一些做法</h4>
<p>这篇文章 10 things to avoid in docker containers 列举了一些在使用容器时需要避免的做法,包括:</p>
<ul>
<li>不要在容器中保存数据(<strong>Don’t store data in containers</strong>)</li>
<li>将应用打包到镜像再部署而不是更新到已有容器(<strong>Don’t ship your application in two pieces</strong>)</li>
<li>不要产生过大的镜像 (<strong>Don’t create large images</strong>)</li>
<li>不要使用单层镜像 (<strong>Don’t use a single layer image</strong>)</li>
<li>不要从运行着的容器上产生镜像 (<strong>Don’t create images from running containers</strong> )</li>
<li>不要只是使用 “latest”标签 (<strong>Don’t use only the “latest” tag</strong>)</li>
<li>不要在容器内运行超过一个的进程 (<strong>Don’t run more than one process in a single container</strong> )</li>
<li>不要在容器内保存 credentials,而是要从外面通过环境变量传入 (<strong> Don’t store credentials in the image. Use environment variables</strong>)</li>
<li>不要使用 root 用户跑容器进程(<strong>Don’t run processes as a root user</strong> )</li>
<li>不要依赖于IP地址,而是要从外面通过环境变量传入 (<strong>Don’t rely on IP addresses</strong> )</li>
</ul>
<h3>2.3 镜像的内容</h3>
<p>容器镜像的内容,其实是一个 json 文件加上 tar 包。以非常小的镜像 kubernetes/pause 为例,我们来做个实验:</p>
<p>(1)将镜像导出为 tar 文件</p>
<div class="cnblogs_code">
<pre>root@kub-node-<span style="color: rgba(128, 0, 128, 1)">1</span>:/home/ubuntu/kub/image# docker save -o pause.tar kubernetes/<span style="color: rgba(0, 0, 0, 1)">pause:latest
root@kub</span>-node-<span style="color: rgba(128, 0, 128, 1)">1</span>:/home/ubuntu/kub/<span style="color: rgba(0, 0, 0, 1)">image# ls
pause.tar</span></pre>
</div>
<p>(2)解压 pause.tar 文件</p>
<div class="cnblogs_code">
<pre>root@kub-node-<span style="color: rgba(128, 0, 128, 1)">1</span>:/home/ubuntu/kub/image# tar -<span style="color: rgba(0, 0, 0, 1)">xf pause.tar</span></pre>
<p style="margin-left: 30px">root@kub-node-1:/home/ubuntu/kub/image/pause# ls -l<br>total 280<br>drwxr-xr-x 2 root root 4096 Jan 23 09:02 afa9f35badc97e21193ee701222d9edfc5b0f0e5c518d357eb8b016d8287cda7<br>drwxr-xr-x 2 root root 4096 Jul 192014 e0b1695ad29a961b7e28713942942786692107d7f9087d72ccf9bbc0a3ab133e<br>drwxr-xr-x 2 root root 4096 Jan 23 09:20 e3caa892ed5297d0c98916b251c5be1d26c3a50b581fe145e3a6516c00531464<br>-rw-r--r-- 1 root root 1691 Jul 192014 f9d5de0795395db6c50cb1ac82ebed1bd8eb3eefcebb1aa724e01239594e937b.json<br>-rw-r--r-- 1 root root 366 Jan11970 manifest.json<br>-rw------- 1 root root 258560 Jan 23 09:02 pause.tar<br>-rw-r--r-- 1 root root 99 Jan11970 repositories</p>
</div>
<p>其中的 repositories 文件的内容,就是镜像名称、版本、最上层的layer的名称:</p>
<div class="cnblogs_code">
<pre>root@kub-node-<span style="color: rgba(128, 0, 128, 1)">1</span>:/home/ubuntu/kub/<span style="color: rgba(0, 0, 0, 1)">image# cat repositories
{</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">kubernetes/pause</span><span style="color: rgba(128, 0, 0, 1)">"</span>:{<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">latest</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">afa9f35badc97e21193ee701222d9edfc5b0f0e5c518d357eb8b016d8287cda7</span><span style="color: rgba(128, 0, 0, 1)">"</span>}}</pre>
</div>
<p>而 manifest.json 文件则保持的是镜像的元数据,包括真正元数据 json 文件的名称及每一层的名称,tag 等:</p>
<div class="cnblogs_code">
<pre>root@kub-node-<span style="color: rgba(128, 0, 128, 1)">1</span>:/home/ubuntu/kub/<span style="color: rgba(0, 0, 0, 1)">image# cat manifest.json
[{</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Config</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">f9d5de0795395db6c50cb1ac82ebed1bd8eb3eefcebb1aa724e01239594e937b.json</span><span style="color: rgba(128, 0, 0, 1)">"</span>,<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RepoTags</span><span style="color: rgba(128, 0, 0, 1)">"</span>:[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">kubernetes/pause:latest</span><span style="color: rgba(128, 0, 0, 1)">"</span>],<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Layers</span><span style="color: rgba(128, 0, 0, 1)">"</span>:[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">e0b1695ad29a961b7e28713942942786692107d7f9087d72ccf9bbc0a3ab133e/layer.tar</span><span style="color: rgba(128, 0, 0, 1)">"</span>,<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">e3caa892ed5297d0c98916b251c5be1d26c3a50b581fe145e3a6516c00531464/layer.tar</span><span style="color: rgba(128, 0, 0, 1)">"</span>,<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">afa9f35badc97e21193ee701222d9edfc5b0f0e5c518d357eb8b016d8287cda7/layer.tar</span><span style="color: rgba(128, 0, 0, 1)">"</span>]}]</pre>
</div>
<p>f9d5de0795395db6c50cb1ac82ebed1bd8eb3eefcebb1aa724e01239594e937b.json 文件则真正包含镜像的所有元数据。</p>
<p>而剩下的3个文件夹则与该镜像的3个layers 一一对应:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">RootFS</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: {
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Type</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">layers</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Layers</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: [
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sha256:e16a89738269fec22db26ec6362823a9ec42d0163685d88ba03c4fb5d5e723f6</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
]
}</span></pre>
</div>
<p>每个文件夹中的内容为:</p>
<div class="cnblogs_code">
<pre>root@kub-node-<span style="color: rgba(128, 0, 128, 1)">1</span>:/home/ubuntu/kub/image# ls e0b1695ad29a961b7e28713942942786692107d7f9087d72ccf9bbc0a3ab133e -<span style="color: rgba(0, 0, 0, 1)">l
total </span><span style="color: rgba(128, 0, 128, 1)">12</span>
-rw-r--r-- <span style="color: rgba(128, 0, 128, 1)">1</span> root root<span style="color: rgba(128, 0, 128, 1)">393</span> Jul <span style="color: rgba(128, 0, 128, 1)">19</span><span style="color: rgba(128, 0, 128, 1)">2014</span><span style="color: rgba(0, 0, 0, 1)"> json
</span>-rw-r--r-- <span style="color: rgba(128, 0, 128, 1)">1</span> root root <span style="color: rgba(128, 0, 128, 1)">1024</span> Jul <span style="color: rgba(128, 0, 128, 1)">19</span><span style="color: rgba(128, 0, 128, 1)">2014</span><span style="color: rgba(0, 0, 0, 1)"> layer.tar
</span>-rw-r--r-- <span style="color: rgba(128, 0, 128, 1)">1</span> root root <span style="color: rgba(128, 0, 128, 1)">3</span> Jul <span style="color: rgba(128, 0, 128, 1)">19</span><span style="color: rgba(128, 0, 128, 1)">2014</span> VERSION</pre>
</div>
<p>因为 pause 镜像比较特殊,解压 layer.tar 后没有文件。如果看 nginx 镜像的某层的 layer.tar 文件,则能看到该layer中包含的文件:</p>
<div class="cnblogs_code">
<pre>root@kub-node-<span style="color: rgba(128, 0, 128, 1)">1</span>:/home/ubuntu/kub/image/nginx/2c9d2d9d91f48573ea451f8d529e88dee79d64782892def6063fdda3f127d33c# ls -<span style="color: rgba(0, 0, 0, 1)">l
total </span><span style="color: rgba(128, 0, 128, 1)">39268</span><span style="color: rgba(0, 0, 0, 1)">
drwxr</span>-xr-x<span style="color: rgba(128, 0, 128, 1)">2</span> root root <span style="color: rgba(128, 0, 128, 1)">4096</span> Jan<span style="color: rgba(128, 0, 128, 1)">8</span> <span style="color: rgba(128, 0, 128, 1)">21</span>:<span style="color: rgba(128, 0, 128, 1)">49</span><span style="color: rgba(0, 0, 0, 1)"> bin
drwxr</span>-xr-x <span style="color: rgba(128, 0, 128, 1)">13</span> root root <span style="color: rgba(128, 0, 128, 1)">4096</span> Jan<span style="color: rgba(128, 0, 128, 1)">8</span> <span style="color: rgba(128, 0, 128, 1)">21</span>:<span style="color: rgba(128, 0, 128, 1)">54</span><span style="color: rgba(0, 0, 0, 1)"> etc
</span>-rw-r--r--<span style="color: rgba(128, 0, 128, 1)">1</span> root root <span style="color: rgba(128, 0, 128, 1)">469</span> Jan<span style="color: rgba(128, 0, 128, 1)">8</span> <span style="color: rgba(128, 0, 128, 1)">23</span>:<span style="color: rgba(128, 0, 128, 1)">32</span><span style="color: rgba(0, 0, 0, 1)"> json</span><span style="color: rgba(0, 0, 0, 1)">
drwxr</span>-xr-x<span style="color: rgba(128, 0, 128, 1)">3</span> root root <span style="color: rgba(128, 0, 128, 1)">4096</span> Dec <span style="color: rgba(128, 0, 128, 1)">10</span> <span style="color: rgba(128, 0, 128, 1)">08</span>:<span style="color: rgba(128, 0, 128, 1)">00</span><span style="color: rgba(0, 0, 0, 1)"> lib
drwx</span>------<span style="color: rgba(128, 0, 128, 1)">2</span> root root <span style="color: rgba(128, 0, 128, 1)">4096</span> Jan<span style="color: rgba(128, 0, 128, 1)">8</span> <span style="color: rgba(128, 0, 128, 1)">21</span>:<span style="color: rgba(128, 0, 128, 1)">56</span><span style="color: rgba(0, 0, 0, 1)"> root
drwxr</span>-xr-x<span style="color: rgba(128, 0, 128, 1)">2</span> root root <span style="color: rgba(128, 0, 128, 1)">4096</span> Jan<span style="color: rgba(128, 0, 128, 1)">8</span> <span style="color: rgba(128, 0, 128, 1)">21</span>:<span style="color: rgba(128, 0, 128, 1)">28</span><span style="color: rgba(0, 0, 0, 1)"> run
drwxr</span>-xr-x<span style="color: rgba(128, 0, 128, 1)">2</span> root root <span style="color: rgba(128, 0, 128, 1)">4096</span> Jan<span style="color: rgba(128, 0, 128, 1)">8</span> <span style="color: rgba(128, 0, 128, 1)">21</span>:<span style="color: rgba(128, 0, 128, 1)">49</span><span style="color: rgba(0, 0, 0, 1)"> sbin
drwxr</span>-xr-x<span style="color: rgba(128, 0, 128, 1)">7</span> root root <span style="color: rgba(128, 0, 128, 1)">4096</span> Dec <span style="color: rgba(128, 0, 128, 1)">10</span> <span style="color: rgba(128, 0, 128, 1)">08</span>:<span style="color: rgba(128, 0, 128, 1)">00</span><span style="color: rgba(0, 0, 0, 1)"> usr
drwxr</span>-xr-x<span style="color: rgba(128, 0, 128, 1)">5</span> root root <span style="color: rgba(128, 0, 128, 1)">4096</span> Dec <span style="color: rgba(128, 0, 128, 1)">10</span> <span style="color: rgba(128, 0, 128, 1)">08</span>:<span style="color: rgba(128, 0, 128, 1)">00</span> <span style="color: rgba(0, 0, 255, 1)">var</span>
-rw-r--r--<span style="color: rgba(128, 0, 128, 1)">1</span> root root <span style="color: rgba(128, 0, 128, 1)">3</span> Jan<span style="color: rgba(128, 0, 128, 1)">8</span> <span style="color: rgba(128, 0, 128, 1)">23</span>:<span style="color: rgba(128, 0, 128, 1)">32</span> VERSION</pre>
</div>
<p>从以上分析可见,</p>
<ul>
<li>docker 镜像中主要就是 tar 文件包和元数据 json 文件</li>
<li>docker 镜像的打包过程,其实就是将每一层对应的文件打包过程,最后组成一个单一的 tar 文件</li>
<li>docker 镜像的使用过程,其实就是将一层层的 tar 文件接包到文件系统的过程。</li>
</ul>
<h2>3. Dockerfile 语法</h2>
<p>上面的步骤说明了 Docker 可以通过读取 Dockerfile 的内容来生成容器镜像。Dockerfile 的每一行都是 INSTRUCTION arguments 格式,即 “指令 参数”。关于 Dockerfile 的预防,请参考 https://docs.docker.com/engine/reference/builder/。下面只是就一些主要的指令做一些说明。</p>
<h3>3.1 几个主要指令</h3>
<h4>3.1.1 ADD 和 COPY</h4>
<div>Add:将 host 上的文件拷贝到或者将网络上的文件下载到容器中的指定目录</div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># Usage: ADD
ADD </span>/my_app_folder /my_app_folder</pre>
</div>
<p>例子:</p>
<div class="cnblogs_code">
<pre>FROM ubuntu:<span style="color: rgba(128, 0, 128, 1)">14.04</span><span style="color: rgba(0, 0, 0, 1)">
MAINTAINER Sammy Liu </span><sammy.liu@unknow.com><span style="color: rgba(0, 0, 0, 1)">
ADD temp dockfile
ENTRYPOINT top</span></pre>
</div>
<p>ADD 指令会将本地 temp 目录中的文件拷贝到容器的 dockfile 目录下面,从而在镜像中增加一个 layer。在未指定绝对路径的时候,会放到 WORKDIR 目录下面。</p>
<div class="cnblogs_code">
<pre>root@cc2a5605f905:/# ls dockfile/<span style="color: rgba(0, 0, 0, 1)">
dockerfile</span>-adddockerfile-cmddockerfile-envdockerfile-portsdockerfile-userdockerfile-user-<span style="color: rgba(0, 0, 0, 1)">h
root@cc2a5605f905:</span>/<span style="color: rgba(0, 0, 0, 1)"># pwd
</span>/</pre>
</div>
<p>那两者有什么区别呢?</p>
<ul>
<li>ADD 多了2个功能, 下载URL和对支持的压缩格式的包进行解压. 其他都一样。比如 <span style="font-family: "Courier New"; font-size: 12px; line-height: 1.5"><span style="font-family: "Courier New"; font-size: 12px; line-height: 1.5">ADD http://foo.com/bar.go /tmp/main.go 会将文件从因特网上方下载下来,</span></span><span style="font-family: "Courier New"; font-size: 12px; line-height: 1.5">ADD /foo.tar.gz /tmp/ 会将压缩文件解压再COPY过去</span></li>
<li>如果你不希望压缩文件拷贝到container后会被解压的话, 那么使用COPY。</li>
<li>如果需要自动下载URL并拷贝到container的话, 请使用ADD</li>
</ul>
<h4>3.1.2 CMD</h4>
</div>
<div>CMD:在容器被创建后执行的命令,和 RUN 不同,它是在构造容器时候所执行的命令</div>
<div>
<div class="cnblogs_code">
<pre># Usage <span style="color: rgba(128, 0, 128, 1)">1</span>: CMD application <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">argument</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">argument</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, ..
CMD </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">echo</span><span style="color: rgba(128, 0, 0, 1)">"</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello docker!</span><span style="color: rgba(128, 0, 0, 1)">"</span></pre>
</div>
<p>CMD 有三种格式:</p>
<ul>
<li><code>CMD ["executable","param1","param2"]</code> (like an exec, preferred form)</li>
<li><code>CMD ["param1","param2"]</code> (作为 ENTRYPOINT 的参数)</li>
<li><code>CMD command param1 param2</code> (作为 shell 运行)</li>
</ul>
<p>一个Dockerfile里只能有一个<code>CMD</code>,如果有多个,只有最后一个生效。</p>
<h4>3.1.3 ENTRYPOINT</h4>
<p>ENTRYPOINT :设置默认应用,会保证每次容器被创建后该应用都会被执行。CMD 和 ENTRYPOINT 的关系会在下面详细解释。</p>
</div>
<h4>3.1.4 ENV:设置环境变量,可以使用多次</h4>
<div>
<div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># Usage: ENV key value
ENV SERVER_WORKS </span><span style="color: rgba(128, 0, 128, 1)">4</span></pre>
</div>
</div>
</div>
<div>
<div>
<p>设置了后,后续的<code>RUN</code>命令都可以使用,并且会作为容器的环境变量。举个例子,下面是 dockfile:</p>
</div>
</div>
<div>
<div>
<div class="cnblogs_code">
<pre>FROM ubuntu:<span style="color: rgba(128, 0, 128, 1)">14.04</span><span style="color: rgba(0, 0, 0, 1)">
ENV abc</span>=<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">
ENV def</span>=<span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">
ENTRYPOINT top</span></pre>
</div>
</div>
</div>
<div>
<div>
<p>生成镜像:docker build -t envimg4 -f dockerfile-env . 其元数据包括了这两个环境变量:</p>
</div>
</div>
<div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Env</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: [
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">abc=1</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">def=2</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
],</span></pre>
</div>
</div>
</div>
<div>
<div>
<p>启动容器:docker run -it --name envc41 envimg4。也能看到:</p>
</div>
</div>
<div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Env</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: [
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">abc=1</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">def=2</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
]</span></pre>
</div>
</div>
</div>
<div>
<div>
<p>进入容器:能看到定义的 abc 和 def 变量</p>
</div>
</div>
<div>
<div>
<div class="cnblogs_code">
<pre>root@devstack:/home/sammy/ntponubuntu# docker exec -<span style="color: rgba(0, 0, 0, 1)">it envc41 bash
root@ba460e0e9dc4:</span>/<span style="color: rgba(0, 0, 0, 1)"># echo $abc
</span><span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">
root@ba460e0e9dc4:</span>/<span style="color: rgba(0, 0, 0, 1)"># echo $def
</span><span style="color: rgba(128, 0, 128, 1)">2</span></pre>
</div>
</div>
</div>
<div>
<div>
<h4>3.1.5 EXPOSE :向容器外暴露一个端口</h4>
</div>
</div>
<div>
<div>
<div>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># Usage: EXPOSE
EXPOSE </span><span style="color: rgba(128, 0, 128, 1)">8080</span></pre>
</div>
</div>
</div>
</div>
</div>
<div>
<h4>3.1.6 FROM:指定进行的基础镜像,必须是第一条指令</h4>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># Usage: FROM
FROM ubuntu</span></pre>
</div>
<h4>3.1.7 MAINTAINER:可以在任意地方使用,设置镜像的作者</h4>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># Usage: MAINTAINER
MAINTAINER authors_name</span></pre>
</div>
<h4>3.1.8 RUN:运行命令,结果会生成镜像中的一个新层</h4>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># Usage: RUN
RUN aptitude install </span>-y ntp</pre>
</div>
<h4>3.1.9 USER:设置该镜像的容器的主进程所使用的用户,以及后续 RUN, CMD 和 ENTRYPOINT 指令运行所使用的用户</h4>
<p>语法:</p>
<div class="cnblogs_code">
<pre><span># Usage: USER
USER 751</span> </pre>
</div>
<p>Dockerfile 中的默认用户是基础镜像中所使用的用户。比如,你的镜像是从一个使用非 root 用户 sammy 的镜像继承而来的,那么你的 Dockerfile 中 RUN 指定运行的命令的用户就会使用 sammy 用户。</p>
<p>举例:</p>
<p>(1)创建 dockerfile 文件</p>
<div class="cnblogs_code">
<pre>root@devstack:/home/sammy/dockerfile# cat dockerfile-<span style="color: rgba(0, 0, 0, 1)">user
FROM ubuntu:</span><span style="color: rgba(128, 0, 128, 1)">14.04</span><span style="color: rgba(0, 0, 0, 1)">
USER </span><span style="color: rgba(128, 0, 128, 1)">1000</span><span style="color: rgba(0, 0, 0, 1)">
ENTRYPOINT top</span></pre>
</div>
<p><span style="font-size: 1em; line-height: 1.5">(2)创建镜像:docker build -t dockerfile-user-1000 -f dockerfile-user .</span></p>
<p><span style="font-size: 1em; line-height: 1.5">(3)启动容器:docker run -it --name c-user-1000-3 dockerfile-user-1000 top</span></p>
<p><span style="font-size: 1em; line-height: 1.5">能看出来当前用户ID 为 1000:</span> </p>
<div class="cnblogs_code">
<pre>PID USER PRNI VIRT RES SHR S %CPU %MEM TIME+<span style="color: rgba(0, 0, 0, 1)"> COMMAND
</span><span style="color: rgba(128, 0, 128, 1)">1</span> <span style="color: rgba(128, 0, 128, 1)">1000</span> <span style="color: rgba(128, 0, 128, 1)">20</span> <span style="color: rgba(128, 0, 128, 1)">0</span> <span style="color: rgba(128, 0, 128, 1)">4440</span> <span style="color: rgba(128, 0, 128, 1)">648</span> <span style="color: rgba(128, 0, 128, 1)">548</span> S<span style="color: rgba(128, 0, 128, 1)">0.0</span><span style="color: rgba(128, 0, 128, 1)">0.0</span> <span style="color: rgba(128, 0, 128, 1)">0</span>:<span style="color: rgba(128, 0, 128, 1)">00.00</span><span style="color: rgba(0, 0, 0, 1)"> sh
</span><span style="color: rgba(128, 0, 128, 1)">5</span> <span style="color: rgba(128, 0, 128, 1)">1000</span> <span style="color: rgba(128, 0, 128, 1)">20</span> <span style="color: rgba(128, 0, 128, 1)">0</span> <span style="color: rgba(128, 0, 128, 1)">19840</span> <span style="color: rgba(128, 0, 128, 1)">1296</span> <span style="color: rgba(128, 0, 128, 1)">984</span> R<span style="color: rgba(128, 0, 128, 1)">0.0</span><span style="color: rgba(128, 0, 128, 1)">0.1</span> <span style="color: rgba(128, 0, 128, 1)">0</span>:<span style="color: rgba(128, 0, 128, 1)">00.00</span> top<span style="font-family: verdana, Arial, Helvetica, sans-serif; font-size: 14px; line-height: 1.5; background-color: rgba(255, 255, 255, 1)"> </span></pre>
</div>
<p>(4)基于该镜像再创造一个镜像,然后再启动一个容器,可以发现容器中进程所使用的用户ID 同样为 1000. </p>
<h4><span style="font-size: 1em; line-height: 1.5">3.1.10 VOLUME:允许容器访问host上某个目录</span></h4>
<div>
<div class="cnblogs_code">
<pre># Usage: VOLUME [<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/dir_1</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/dir_2</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> ..]
VOLUME [</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/my_files</span><span style="color: rgba(128, 0, 0, 1)">"</span>]</pre>
</div>
<h4>3.1.11 WORKDIR:设置 CMD 所指定命令的执行目录</h4>
<div>
<div class="cnblogs_code">
<pre># Usage: WORKDIR /<span style="color: rgba(0, 0, 0, 1)">path
WORKDIR </span>~/</pre>
</div>
<h4>3.1.12 HEALTHCHECK: 容器健康检查</h4>
<p>这是 Docker 1.12 版本中新引入的指令,其语法为 HEALTHCHECK CMD command。 来看一个例子:</p>
<div class="cnblogs_code">
<pre>FROM ubuntu:<span style="color: rgba(128, 0, 128, 1)">14.04</span><span style="color: rgba(0, 0, 0, 1)">
MAINTAINER Sammy Liu </span><sammy.liu@unknow.com><span style="color: rgba(0, 0, 0, 1)">
RUN apt</span>-<span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)"> update
RUN apt</span>-<span style="color: rgba(0, 0, 255, 1)">get</span> -<span style="color: rgba(0, 0, 0, 1)">y install curl
EXPOSE </span><span style="color: rgba(128, 0, 128, 1)">8888</span><span style="color: rgba(0, 0, 0, 1)">
CMD </span><span style="color: rgba(0, 0, 255, 1)">while</span> <span style="color: rgba(0, 0, 255, 1)">true</span>; <span style="color: rgba(0, 0, 255, 1)">do</span> echo <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hello world</span><span style="color: rgba(128, 0, 0, 1)">'</span> | nc -l -p <span style="color: rgba(128, 0, 128, 1)">8888</span><span style="color: rgba(0, 0, 0, 1)">; done
HEALTHCHECK </span>--interval=10s --timeout=2s CMD curl -f http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">localhost:8888/ || exit 1</span></pre>
</div>
<p>在启动容器后,其health 状态首先是 starting,然后在过了10秒做了第一次健康检查成功后,变为 healthy 状态。</p>
<div class="cnblogs_code">
<pre>root@devstack:/home/sammy/dockerfile# docker ps | grep c-<span style="color: rgba(0, 0, 0, 1)">health2
4c459eef1894 img</span>-health2 <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/bin/sh -c 'while tr</span><span style="color: rgba(128, 0, 0, 1)">"</span> <span style="color: rgba(128, 0, 128, 1)">7</span> seconds ago Up <span style="color: rgba(128, 0, 128, 1)">6</span> seconds (health: starting) <span style="color: rgba(128, 0, 128, 1)">8888</span>/tcp c-<span style="color: rgba(0, 0, 0, 1)">health2
root@devstack:</span>/home/sammy/dockerfile# docker ps | grep c-<span style="color: rgba(0, 0, 0, 1)">health2
4c459eef1894 img</span>-health2 <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/bin/sh -c 'while tr</span><span style="color: rgba(128, 0, 0, 1)">"</span> <span style="color: rgba(128, 0, 128, 1)">9</span> seconds ago Up <span style="color: rgba(128, 0, 128, 1)">8</span> seconds (health: starting) <span style="color: rgba(128, 0, 128, 1)">8888</span>/tcp c-<span style="color: rgba(0, 0, 0, 1)">health2
root@devstack:</span>/home/sammy/dockerfile# docker ps | grep c-<span style="color: rgba(0, 0, 0, 1)">health2
4c459eef1894 img</span>-health2 <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/bin/sh -c 'while tr</span><span style="color: rgba(128, 0, 0, 1)">"</span> <span style="color: rgba(128, 0, 128, 1)">11</span> seconds ago Up <span style="color: rgba(128, 0, 128, 1)">11</span> seconds (healthy) <span style="color: rgba(128, 0, 128, 1)">8888</span>/tcp c-health2</pre>
</div>
<p>需要注意的是 CMD 是在容器之内运行的,因此,你需要确保其命令或者脚本存在于容器之内并且可以被运行。</p>
<h3>3.2 几个比较绕的地方</h3>
<h4>3.2.1 EXPOSE 和 docker run -p -P 之间的关系</h4>
<p>容器的端口必须被发出(publish)出来后才能被外界使用。Dockerfile 中的 EXPOSE 只是“标记”某个端口会被暴露出来,只有在使用了 docker run -p 或者 -P 后,端口才会被“发出”出来,此时端口才能被使用。</p>
<p>举例:</p>
<p>(1)Dockerfile</p>
<div class="cnblogs_code">
<pre>FROM ubuntu:<span style="color: rgba(128, 0, 128, 1)">14.04</span><span style="color: rgba(0, 0, 0, 1)">
MAINTAINER Sammy Liu </span><sammy.liu@unknow.com><span style="color: rgba(0, 0, 0, 1)">
CMD </span><span style="color: rgba(0, 0, 255, 1)">while</span> <span style="color: rgba(0, 0, 255, 1)">true</span>; <span style="color: rgba(0, 0, 255, 1)">do</span> echo <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hello world</span><span style="color: rgba(128, 0, 0, 1)">'</span> | nc -l -p <span style="color: rgba(128, 0, 128, 1)">8888</span>; done</pre>
</div>
<p>(2)创建镜像:docker build -t no-exposed-ports -f dockerfile-ports .</p>
<p>(3)启动容器1:docker run -d --name no-exposed-ports1 no-exposed-ports。此容器没有 exposed 和 published 任何端口。</p>
<p>(4)启动容器2:docker run -d --name no-exposed-ports2 -p 8888:8888 no-exposed-ports</p>
<p>此时容器的 8888 端口被发布为主机上的 8888 端口:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Ports</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: {
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">8888/tcp</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: [
{
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">HostIp</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">0.0.0.0</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">HostPort</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">8888</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
]
}</span></pre>
</div>
<p>该端口会正确返回:</p>
<div class="cnblogs_code">
<pre>root@devstack:/home/sammy/dockerfile# telnet <span style="color: rgba(128, 0, 128, 1)">0.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.0</span> <span style="color: rgba(128, 0, 128, 1)">8888</span><span style="color: rgba(0, 0, 0, 1)">
Trying </span><span style="color: rgba(128, 0, 128, 1)">0.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.0</span><span style="color: rgba(0, 0, 0, 1)">...
Connected to </span><span style="color: rgba(128, 0, 128, 1)">0.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.0</span><span style="color: rgba(0, 0, 0, 1)">.
Escape character </span><span style="color: rgba(0, 0, 255, 1)">is</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">^]</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">.
hello world
Connection closed by foreign host.</span></pre>
</div>
<p>(5)使用 -P 参数:docker run -d --name no-exposed-ports3 -P no-exposed-ports</p>
<p>此时没有任何端口被 published,说明 Docker 在使用了 “-P” 情形下只是自动将 exposed 的端口 published。</p>
<p>(6)使用 -p 加上一个不存在的端口:docker run -d --name no-exposed-ports4 -p 8889:8889 no-exposed-ports</p>
<p>此时,8889 端口会被暴露,但是没法使用。说明 -p 会将没有 exposed 的端口自动 exposed 出来。</p>
<p>(7)修改 dockerfile 为:</p>
<div class="cnblogs_code">
<pre>FROM ubuntu:<span style="color: rgba(128, 0, 128, 1)">14.04</span><span style="color: rgba(0, 0, 0, 1)">
MAINTAINER Sammy Liu </span><sammy.liu@unknow.com><span style="color: rgba(0, 0, 0, 1)">
EXPOSE </span><span style="color: rgba(128, 0, 128, 1)">8888</span><span style="color: rgba(0, 0, 0, 1)">
CMD </span><span style="color: rgba(0, 0, 255, 1)">while</span> <span style="color: rgba(0, 0, 255, 1)">true</span>; <span style="color: rgba(0, 0, 255, 1)">do</span> echo <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hello world</span><span style="color: rgba(128, 0, 0, 1)">'</span> | nc -l -p <span style="color: rgba(128, 0, 128, 1)">8888</span>; done</pre>
</div>
<p>创建镜像exposed-ports, 再运行 docker run -d --name exposed-ports1 -P exposed-ports 创建一个容器,此时 8888 端口自动被 published 为主机上的 32776 端口:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Ports</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: {
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">8888/tcp</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">: [
{
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">HostIp</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">0.0.0.0</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">HostPort</span><span style="color: rgba(128, 0, 0, 1)">"</span>: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">32776</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">
}
]
}</span></pre>
</div>
<p>可见:</p>
<ul>
<li><code class="prettyprint">EXPOSE</code>或者<code class="prettyprint">--expose</code>只是为其他命令提供所需信息的元数据,或者只是告诉容器操作人员有哪些已知选择。它只是作为记录机制,也就是告诉用户哪些端口会提供服务。它保存在容器的元数据中。</li>
<li>使用 -p 发布特定端口。如果该端口已经被 exposed,则发布它;如果它还没有被 exposed,则它会被 exposed 和 published。Docker 不会检查容器端口的正确性。</li>
<li>使用 -P 时 Docker 会自动将所有已经被 exposed 的端口发出出来。</li>
</ul>
<h4>3.2.2 CMD 和 ENTRYPOINT</h4>
<p>这两个指令都指定了运行容器时所运行的命令。以下是它们共存的一些规则:</p>
<ul>
<li>Dockerfile 至少需要指定一个 CMD 或者 ENTRYPOINT 指令</li>
<li>CMD 可以用来指定 ENTRYPOINT 指令的参数</li>
</ul>
<table border="0">
<tbody>
<tr>
<td><strong> </strong></td>
<td><strong><span style="font-size: 14px">没有 ENTRYPOINT</span></strong></td>
<td><strong><span style="font-size: 14px">ENTRYPOINT exec_entry p1_entry</span></strong></td>
<td><strong><span style="font-size: 14px">ENTRYPOINT [“exec_entry”, “p1_entry”]</span></strong></td>
</tr>
<tr>
<td><span style="font-size: 14px">没有 CMD</span></td>
<td><span style="font-size: 14px">错误,不允许</span></td>
<td><span style="font-size: 14px">/bin/sh -c exec_entry p1_entry</span></td>
<td><span style="font-size: 14px"> exec_entry p1_entry</span></td>
</tr>
<tr>
<td><span style="font-size: 14px">CMD [“exec_cmd”, “p1_cmd”]</span></td>
<td><span style="font-size: 14px"> exec_cmd p1_cmd</span></td>
<td><span style="font-size: 14px"> /bin/sh -c exec_entry p1_entry exec_cmd p1_cmd</span></td>
<td><span style="font-size: 14px"> exec_entry p1_entry exec_cmd p1_cmd</span></td>
</tr>
<tr>
<td><span style="font-size: 14px">CMD [“p1_cmd”, “p2_cmd”]</span></td>
<td><span style="font-size: 14px"> p1_cmd p2_cmd</span></td>
<td><span style="font-size: 14px"> /bin/sh -c exec_entry p1_entry p1_cmd p2_cmd</span></td>
<td><span style="font-size: 14px"> exec_entry p1_entry p1_cmd p2_cmd</span></td>
</tr>
<tr>
<td><span style="font-size: 14px">CMD exec_cmd p1_cmd</span></td>
<td><span style="font-size: 14px"> /bin/sh -c exec_cmd p1_cmd</span></td>
<td><span style="font-size: 14px"> /bin/sh -c exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd</span></td>
<td><span style="font-size: 14px"> exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd</span></td>
</tr>
<tr>
<td><span style="font-size: 14px">备注</span></td>
<td><span style="font-size: 14px">只有 CMD 时,执行 CMD 定义的指令</span></td>
<td> <span style="font-size: 14px">CMD 和 ENTRYPOINT 都存在时,CMD 的指令作为 ENTRYPOINT 的参数</span></td>
<td> </td>
</tr>
</tbody>
</table>
<p> 举例:</p>
<p>(1)同时有 CMD 和 ENTRYPOINT</p>
<div class="cnblogs_code">
<pre>FROM ubuntu:<span style="color: rgba(128, 0, 128, 1)">14.04</span><span style="color: rgba(0, 0, 0, 1)">
MAINTAINER Sammy Liu </span><sammy.liu@unknow.com><span style="color: rgba(0, 0, 0, 1)">
CMD top
ENTRYPOINT ps</span></pre>
</div>
<p>此时会运行的指令为 /bin/sh -c ps /bin/sh -c top</p>
<p>但是实际上只是运行了 ps:</p>
<div class="cnblogs_code">
<pre>root@devstack:/home/sammy/dockerfile# /bin/sh -c ps /bin/sh -<span style="color: rgba(0, 0, 0, 1)">c top
PID TTY TIME CMD
</span><span style="color: rgba(128, 0, 128, 1)">10789</span> pts/<span style="color: rgba(128, 0, 128, 1)">3</span> <span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span><span style="color: rgba(0, 0, 0, 1)"> su
</span><span style="color: rgba(128, 0, 128, 1)">10790</span> pts/<span style="color: rgba(128, 0, 128, 1)">3</span> <span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span><span style="color: rgba(0, 0, 0, 1)"> bash
</span><span style="color: rgba(128, 0, 128, 1)">18479</span> pts/<span style="color: rgba(128, 0, 128, 1)">3</span> <span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span><span style="color: rgba(0, 0, 0, 1)"> sh
</span><span style="color: rgba(128, 0, 128, 1)">18480</span> pts/<span style="color: rgba(128, 0, 128, 1)">3</span> <span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span><span style="color: rgba(0, 0, 0, 1)"> ps
root@devstack:</span>/home/sammy/dockerfile# /bin/sh -<span style="color: rgba(0, 0, 0, 1)">c ps
PID TTY TIME CMD
</span><span style="color: rgba(128, 0, 128, 1)">10789</span> pts/<span style="color: rgba(128, 0, 128, 1)">3</span> <span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span><span style="color: rgba(0, 0, 0, 1)"> su
</span><span style="color: rgba(128, 0, 128, 1)">10790</span> pts/<span style="color: rgba(128, 0, 128, 1)">3</span> <span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span><span style="color: rgba(0, 0, 0, 1)"> bash
</span><span style="color: rgba(128, 0, 128, 1)">18481</span> pts/<span style="color: rgba(128, 0, 128, 1)">3</span> <span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span><span style="color: rgba(0, 0, 0, 1)"> sh
</span><span style="color: rgba(128, 0, 128, 1)">18482</span> pts/<span style="color: rgba(128, 0, 128, 1)">3</span> <span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span>:<span style="color: rgba(128, 0, 128, 1)">00</span> ps</pre>
</div>
<p>(2)CMD 作为 ENTRYPOINT 的参数</p>
<div class="cnblogs_code">
<pre>FROM ubuntu:<span style="color: rgba(128, 0, 128, 1)">14.04</span><span style="color: rgba(0, 0, 0, 1)">
MAINTAINER Sammy Liu </span><sammy.liu@unknow.com><span style="color: rgba(0, 0, 0, 1)">
CMD [</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">-n</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">10</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">]
ENTRYPOINT top</span></pre>
</div>
<p>启动容器后运行的命令为 /bin/sh -c top -n 10.</p>
</div>
</div>
</div>
</div>
</div>
<h2>4. 在 Docker hub 上创建自己的镜像</h2>
<p>当我们从docker镜像仓库中下载的镜像不能满足我们的需求时,我们可以通过以下两种方式对镜像进行更改。</p>
<ul>
<li>从已经创建的容器中更新镜像,并且提交这个镜像</li>
<li>使用 Dockerfile 指令来创建一个新的镜像</li>
</ul>
<p>通过以下步骤,采用第一种方法,在 docker hub 上创建自己的镜像:</p>
<p>(1)创建 docker hub 帐号。https://hub.docker.com/</p>
<p>(2)基于一个镜像完成某些操作。比如基于 nginx 镜像,安装 ping ifconfig 等网络工具。首先运行 docker run -it nginx /bin/bash 基于 nginx:latest 创建一个容器,然后在容器中执行 apt-get 命令安装软件,然后运行 exit 退出容器。</p>
<p>(3)将容器中的内容保存为一个镜像</p>
<div class="cnblogs_code">
<pre>docker commit -m=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">install net tools</span><span style="color: rgba(128, 0, 0, 1)">"</span> -a=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sammyliu8</span><span style="color: rgba(128, 0, 0, 1)">"</span> 3f8a4339aadd sammyliu8/nginx:v1</pre>
</div>
<p>这里的 3f8a4339aadd 为刚才容器的ID。此时,能在本地看到该镜像:</p>
<p><img src="https://images2017.cnblogs.com/blog/697113/201801/697113-20180107235209331-78522280.png" alt=""></p>
<p>(4)运行 docker login 登录 docker hub</p>
<p>(5)运行 docker push sammyliu8/nginx 将镜像上传到 docker hub。此时在 Docker hub 界面上能看到该镜像了。</p>
<p><img src="https://images2017.cnblogs.com/blog/697113/201801/697113-20180107234853221-1628694632.png" alt=""></p>
<p> </p>
<p>(6)在其他节点上,可以运行 docker pull sammyliu8/nginx 拉该镜像了。</p>
<p>(7)不过,这样做出来的新nginx有个问题,那就是nginx 服务不会自动起来。这是因为,官方的 nginx 的CMD 为 nginx -g "daemon off;",但是新的镜像的CMD 为 /bin/bash。但是,运行前面命令启动的容器又无法安装软件。因此,只能先按照上面的步骤启动一个容器,制作镜像,然后基于该镜像再不带命令地再启一个容器,在另一个窗口中,使用docker commit 将其保存为新的镜像,并上传到docker hub中。问题解决。</p>
<p> </p>
<p> </p>
<p>参考链接</p>
<ul>
<li>http://developers.redhat.com/blog/2016/03/09/more-about-docker-images-size/ </li>
<li>http://developers.redhat.com/blog/2016/02/24/10-things-to-avoid-in-docker-containers/</li>
<li>http://techknowblogs.blogspot.in/2017/12/docker-host-os-guest-os-base-image-etc.html</li>
<li>http://www.floydhilton.com/docker/2017/03/31/Docker-ContainerHost-vs-ContainerOS-Linux-Windows.html</li>
<li>http://crunchtools.com/comparison-linux-container-images/</li>
</ul>
</div>
</div><br><br>
来源:https://www.cnblogs.com/sammyliu/p/5877964.html
頁:
[1]