Docker 一文带你理解容器镜像_数据

这篇文章介绍了容器与镜像的概念、容器的生命周期、项目架构以及容器与 VM 之间的区别等。希望你带着如下的问题去学习 

  1. 什么是容器与镜像?如何构建容器与镜像
  2. 容器的生命周期
  3. 容器项目的架构
  4. 容器 VS.VM

 

什么是容器?


容器,是一个视图隔离、资源可限制、独立文件系统的进程集合。

  • 视图隔离  如能看见部分进程,独立主机名等等
  • 控制资源使用率-如2G内存大小,CPU使用个数等等

Docker 一文带你理解容器镜像_数据_02

在介绍容器的具体概念之前,先简单回顾一下操作系统是如何管理进程的。

首先,当我们登录到操作系统之后,可以通过 ps 等操作看到各式各样的进程,这些进程包括系统自带的服务和用户的应用进程。那么,这些进程都有什么样的特点?

  • 第一,这些进程可以相互看到、相互通信
  • 第二,它们使用的是同一个文件系统,可以对同一个文件进行读写操作
  • 第三,这些进程会使用相同的系统资源

这样的三个特点会带来什么问题呢?

  • 因为这些进程能够相互看到并且进行通信,高级权限的进程可以攻击其他进程;
  • 因为它们使用的是同一个文件系统,因此会带来两个问题:这些进程可以对于已有的数据进行增删改查,具有高级权限的进程可能会将其他进程的数据删除掉,破坏掉其他进程的正常运行;此外,进程与进程之间的依赖可能会存在冲突,如此一来就会给运维带来很大的压力;
  • 因为这些进程使用的是同一个宿主机的资源,应用之间可能会存在资源抢占的问题,当一个应用需要消耗大量 CPU 和内存资源的时候,就可能会破坏其他应用的运行,导致其他应用无法正常地提供服务。

 针对上述的三个问题,如何为进程提供一个独立的运行环境呢?

  • 针对不同进程使用同一个文件系统所造成的问题而言,Linux 和 Unix 操作系统可以通过 chroot 系统调用将子目录变成根目录,达到视图级别的隔离;进程在 chroot 的帮助下可以具有独立的文件系统,对于这样的文件系统进行增删改查不会影响到其他进程;
  • 因为进程之间相互可见并且可以相互通信,使用 Namespace 技术来实现进程在资源的视图上进行隔离。在 chroot 和 Namespace 的帮助下,进程就能够运行在一个独立的环境下了;
  • 但在独立的环境下,进程所使用的还是同一个操作系统的资源,一些进程可能会侵蚀掉整个系统的资源。为了减少进程彼此之间的影响,可以通过 Cgroup 来限制其资源使用率,设置其能够使用的 CPU 以及内存量。

那么,应该如何定义这样的进程集合呢?

其实,容器就是一个视图隔离、资源可限制、独立文件系统的进程集合。

  • 所谓“视图隔离”就是能够看到部分进程以及具有独立的主机名等
  • 控制资源使用率则是可以对于内存大小以及 CPU 使用个数等进行限制

容器就是一个进程集合,它将系统的其他资源隔离开来,具有自己独立的资源视图。

容器具有一个独立的文件系统,因为使用的是系统的资源,所以在独立的文件系统内不需要具备内核相关的代码或者工具,我们只需要提供容器所需的二进制文件、配置文件以及依赖即可。只要容器运行时所需的文件集合都能够具备,那么这个容器就能够运行起来。

 

什么是镜像 ?


Docker 一文带你理解容器镜像_文件系统_03

 综上所述,我们将这些容器运行时所需要的所有的文件集合称之为容器镜像。

那么,一般都是通过什么样的方式来构建镜像的呢?通常情况下,我们会采用 Dockerfile 来构建镜像,这是因为 Dockerfile 提供了非常便利的语法,能够帮助我们很好地描述构建的每个步骤。当然,每个构建步骤都会对已有的文件系统进行操作,这样就会带来文件系统内容的变化,我们将这些变化称之为 changeset。当我们把构建步骤所产生的变化依次作用到一个空文件夹上,就能够得到一个完整的镜像。

changeset 的分层以及复用特点能够带来几点优势:

  • 第一,能够提高分发效率,简单试想一下,对于大的镜像而言,如果将其拆分成各个小块就能够提高镜像的分发效率,这是因为镜像拆分之后就可以并行下载这些数据;
  • 第二,因为这些数据是相互共享的,也就意味着当本地存储上包含了一些数据的时候,只需要下载本地没有的数据即可,举个简单的例子就是 golang 镜像是基于 alpine 镜像进行构建的,当本地已经具有了 alpine 镜像之后,在下载 golang 镜像的时候只需要下载本地 alpine 镜像中没有的部分即可;
  • 第三,因为镜像数据是共享的,因此可以节约大量的磁盘空间,简单设想一下,当本地存储具有了 alpine 镜像和 golang 镜像,在没有复用的能力之前,alpine 镜像具有 5M 大小,golang 镜像有 300M 大小,因此就会占用 305M 空间;而当具有了复用能力之后,只需要 300M 空间即可。

 

如何构建镜像?


如下图所示的 Dockerfile 适用于描述如何构建 golang 应用的。

Docker 一文带你理解容器镜像_docker_04

如图所示:

  1. FROM 行表示以下的构建步骤基于什么镜像进行构建,正如前面所提到的,镜像是可以复用的
  2. WORKDIR 行表示会把接下来的构建步骤都在哪一个相应的具体目录下进行,其起到的作用类似于 Shell 里面的 cd;
  3. COPY 行表示的是可以将宿主机上的文件拷贝到容器镜像内
  4. RUN 行表示在具体的文件系统内执行相应的动作。当我们运行完毕之后就可以得到一个应用了
  5. CMD 行表示使用镜像时的默认程序名字

 当有了 Dockerfile 之后,就可以通过 docker build 命令构建出所需要的应用。构建出的结果存储在本地,一般情况下,镜像构建会在打包机或者其他的隔离环境下完成。

那么,这些镜像如何运行在生产环境或者测试环境上呢?这时候就需要一个中转站或者中心存储,我们称之为 docker registry,也就是镜像仓库,其负责存储所有产生的镜像数据。我们只需要通过 docker push 就能够将本地镜像推动到镜像仓库中,这样一来,就能够在生产环境上或者测试环境上将相应的数据下载下来并运行了。

 

如何运行容器?


运行一个容器一般情况下分为三步:

  • 第一步:从镜像仓库中将相应的镜像下载下来;
  • 第二步:当镜像下载完成之后就可以通过 docker images 来查看本地镜像,这里会给出一个完整的列表,我们可以在列表中选中想要的镜像;
  • 第三步:当选中镜像之后,就可以通过 docker run 来运行这个镜像得到想要的容器,当然可以通过多次运行得到多个容器。一个镜像就相当于是一个模板,一个容器就像是一个具体的运行实例,因此镜像就具有了一次构建、到处运行的特点。(下图的run后面的top指令,因为镜像只是提供文件系统而已,我们需要指定容器对应的进程到底是什么。下面指定的是top命令也就是说这个容器对应的进程是top命令)

Docker 一文带你理解容器镜像_文件系统_05

 

小结


简单回顾一下,容器就是和系统其它部分隔离开来的进程集合,这里的其他部分包括进程、网络资源以及文件系统等。而镜像就是容器所需要的所有文件集合,其具备一次构建、到处运行的特点。