1,三者之间的关系

    Dockerfile重塑新镜像,定义的不仅仅是镜像中的磁盘文件;Docker镜像是Dockerfile的产 物,自底之上打包软件及其环境,是软件的交付品;容器是镜像的运行态体现,一切信息来源于镜像。

    Dockerfile 是软件的原材料,Docker 镜像是软件的交付品,而 Docker 容器则可以认为是软件的运行态。从应用软件的角度来看,Dockerfile、Docker 镜像与 Docker 容器分别代表软件的三个不同阶段,Dockerfile 面向开发,Docker 镜像成为交付标准,Docker 容器则涉及部署与运维,三者缺一不可,合力充当 Docker 体系的基石。
    简单来讲,Dockerfile构建出Docker镜像,通过Docker镜像运行Docker容器。

    2,反推三者的关系

    我们可以从Docker容器的角度,来反推三者的关系。首先可以来看下图:
    Docker镜像、容器和Dockerfile - 图1

    我们假设这个容器的镜像通过以下Dockerfile构建而得:
    FROM ubuntu:14.04 ADD run.sh / VOLUME /data CMD [“./run.sh”]

    1,Dockerfile与Docker镜像

    首先,我们结合上图来看看Dockerfile与Docker镜像之间的关系。
    FROM ubuntu:14.04:设置基础镜像,此时会使用基础镜像 ubuntu:14.04 的所有镜像层,为简单起见,图中将其作为一个整体展示。
    ADD run.sh /:将 Dockerfile 所在目录的文件 run.sh 加至镜像的根目录,此时新一层的镜像只有一项内容,即根目录下的 run.sh。
    VOLUME /data:设定镜像的 VOLUME,此 VOLUME 在容器内部的路径为 /data。需要注意的是,此时并未在新一层的镜像中添加任何文件,即构建出的磁层镜像中文件为空,但更新了镜像的 json 文件,以便通过此镜像启动容器时获取这方面的信息。
    CMD [“./run.sh”]:设置镜像的默认执行入口,此命令同样不会在新建镜像中添加任何文件,仅仅在上一层镜像 json 文件的基础上更新新建镜像的 json 文件。
    因此,通过以上分析,以上的Dockerfile可以构建出一个新的镜像,包含4个镜像层,每一条命令会和一个镜像层对应,镜像之间会存在父子关系。图中很清楚的表明了这些关系。

    2,Docker镜像与Docker容器的关系

    Docker镜像是Docker容器运行的基础,没有Docker镜像,就不可能有Docker容器,这也是Docker的设计原则之一。

    可以理解的是:Docker镜像毕竟是镜像,属于静态的内容;而Docker容器就不一样了,容器属于动态的内容。动态的内容,大家很容易联想到进程,内存,CPU等之类的东西。的确,Docker容器作为动态的内容,都会包含这些。

    为了便于理解,大家可以把Docker容器,理解为一个或多个运行进程,而这些运行进程将占有相应的内存,相应的CPU计算资源,相应的虚拟网络设备以及相应的文件系统资源。而Docker容器所占用的文件系统资源,则通过Docker镜像的镜像层文件来提供。

    那么作为静态的镜像,如何才有能力转化为一个动态的Docker容器呢?此时,我们可以想象:第一,转化的依据是什么;第二,由谁来执行这个转化操作。

    其实,转化的依据是每个镜像的json文件,Docker可以通过解析Docker镜像的json的文件,获知应该在这个镜像之上运行什么样的进程,应该为进程配置怎么样的环境变量,此时也就实现了静态向动态的转变。

    谁来执行这个转化工作?答案是Docker守护进程。也许大家早就理解这样一句 话:Docker容器实质上就是一个或者多个进程,而容器的父进程就是Docker守护进程。这样的,转化工作的执行就不难理解了:Docker守护进程 手握Docker镜像的json文件,为容器配置相应的环境,并真正运行Docker镜像所指定的进程,完成Docker容器的真正创建。

    Docker容器运行起来之后,Docker镜像json文件就失去作用了。此时Docker镜像的绝大部分作用就是:为Docker容器提供一个文件系统的视角,供容器内部的进程访问文件资源。

    再次回到上图,我们再来看看容器和镜像之间的一些特殊关系。首先,之前已经提及Docker镜像是分层管理的,管理Docker容器的时候,Docker镜像仍然是分层管理的。由于此时动态的容器中已经存在进程,进程就会对文件系统视角内的文件进行读写操作,因此,就会涉及一个问题:容器是否会篡改Docker镜像的内容?

    答案自然是不会的。统一来讲,正如上图,所有的Docker镜像层对于容器来说,都是只读的,容器对于文件的写操作绝对不会作用在镜像中。

    既然如此,实现的原理就很重要,究其根本:Docker守护进程会在Docker镜像的 最上层之上,再添加一个可读写层,容器所有的写操作都会作用到这一层中。而如果Docker容器需要写底层Docker镜像中的文件,那么此时就会涉及一 个叫Copy-on-Write的机制,即aufs等联合文件系统保证:首先将此文件从Docker镜像层中拷贝至最上层的可读写层,然后容器进程再对读 写层中的副本进行写操纵。对于容器进程来讲,它只能看到最上层的文件。

    那最后我们再来说说:Docker容器的文件系统视角中,到底是不是存在一些内容,不是存储于Docker镜像中的?

    这次的答案依旧是肯定的。

    再次重申一点,Docker镜像中存储的都是一些静态文件。这些文件原则上应该和容器具体信息以及主机信息完全解藕。那么Docker容器中不存在Docker镜像中的内容主要有以下几点:

    1./proc以及/sys等虚拟文件系统的内容
    2.容器的hosts文件,hostname文件以及resolv.conf文件,这些事具体环境的信息,原则上的确不应该被打入镜像。
    3.容器的Volume路径,这部分的视角来源于从宿主机上挂载到容器内部的路径
    4.部分的设备文件

    3,Docker镜像和容器的联系

    Docker镜像、容器和Dockerfile - 图2

    1,Image Definition

    镜像(Image)就是一堆只读层(read-only layer)的统一视角,也许这个定义有些难以理解,下面的这张图能够帮助读者理解镜像的定义。
    Docker镜像、容器和Dockerfile - 图3
    从左边我们看到了多个只读层,它们重叠在一起。除了最下面一层,其它层都会有一个指针指向下一层。这些层是Docker内部的实现细节,并且能够 在主机(译者注:运行Docker的机器)的文件系统上访问到。统一文件系统(union file system)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。 我们可以在图片的右边看到这个视角的形式。

    你可以在你的主机文件系统上找到有关这些层的文件。需要注意的是,在一个运行中的容器内部,这些层是不可见的。在我的主机上,我发现它们存在于/var/lib/docker/aufs目录下。

    Docker镜像、容器和Dockerfile - 图4

    2,Container Definition

    容器(container)的定义和镜像(image)几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。
    Docker镜像、容器和Dockerfile - 图5

    要点:容器 = 镜像 + 可读层。并且容器的定义并没有提及是否要运行容器。

    3,Running Container Definition

    一个运行态容器(running container)被定义为一个可读写的统一文件系统加上隔离的进程空间和包含其中的进程。下面这张图片展示了一个运行中的容器。
    Docker镜像、容器和Dockerfile - 图6

    正是文件系统隔离技术使得Docker成为了一个前途无量的技术。一个容器中的进程可能会对文件进行修改、删除、创建,这些改变都将作用于可读写层(read-write layer)。下面这张图展示了这个行为。
    Docker镜像、容器和Dockerfile - 图7

    我们可以通过运行以下命令来验证我们上面所说的:
    docker run ubuntu touch happiness.txt

    即便是这个ubuntu容器不再运行,我们依旧能够在主机的文件系统上找到这个新文件。
    find / -name happiness.txt
    /var/lib/docker/aufs/diff/860a7b…889/happiness.txt

    4,Image Layer Definition

    为了将零星的数据整合起来,我们提出了镜像层(image layer)这个概念。下面的这张图描述了一个镜像层,通过图片我们能够发现一个层并不仅仅包含文件系统的改变,它还能包含了其他重要信息。
    Docker镜像、容器和Dockerfile - 图8
    元数据(metadata)就是关于这个层的额外信息,它不仅能够让Docker获取运行和构建时的信息,还包括父层的层次信息。需要注意,只读层和读写层都包含元数据。
    Docker镜像、容器和Dockerfile - 图9
    除此之外,每一层都包括了一个指向父层的指针。如果一个层没有这个指针,说明它处于最底层。
    Docker镜像、容器和Dockerfile - 图10
    Metadata Location:
    我发现在我自己的主机上,镜像层(image layer)的元数据被保存在名为”json”的文件中,比如说:
    /var/lib/docker/graph/e809f156dc985…/json

    e809f156dc985…就是这层的id

    一个容器的元数据好像是被分成了很多文件,但或多或少能够在/var/lib/docker/containers/目录下找到,就是一个可读层的id。这个目录下的文件大多是运行时的数据,比如说网络,日志等等。