💧 Posted on 

Docker 系列(一):什么是容器?

容器,到底是怎么一回事?

容器其实是一种沙盒技术。顾名思义,沙盒就是能够像一个集装箱一样,把你的应用 “装” 起来的技术。这样,应用于应用之间,就因为有了边界而不至于互相干扰;而被装进集装箱的应用,也可以被方便的搬来搬去。

容器技术的核心功能,就是通过约束和修改进程(应用)的动态表现,从而为其创造一个 “边界”

动态表现:应用程序启动之后的设涉及到的数据和状态的总和。

在 Linux 中,实现容器的边界,主要有两种技术 Cgroups 和 Namespace.

  • Cgroups 用于对运行的容器进行资源的限制;
  • Namespace 则会将容器隔离起来,实现边界。

所以,容器这个听起来玄而又玄的概念,实际上是在创建容器进程时,为它加上了各种各样的 Namespace 参数。

这时,容器进程就会觉得自己是各自 PID Namespace 里的第 1 号进程,只能看到各自 Mount Namespace 里挂载的目录和文件,只能访问到各自 Network Namespace 里的网络设备,而对于宿主机以及其他不相关的程序,它就完全看不到了。

这样看来,容器只是一种被限制的了特殊进程而已。

虚拟机 vs 容器

虚拟机:指的是在一个宿主机上搭建出来的一个完全隔离的环境,这个环境的特点就是,从底层的硬件开始逐级的进行虚拟,虚拟机中的 cpu 内存 硬盘等均进行虚拟(甚至包括网卡 显卡 声卡等也是虚拟的)在这一套虚拟的硬件基础上建立一个虚拟的操作系统,然后在这个虚拟的操作系统里运行应用程序。整套环境和宿主系统完全没有关系的。(可以在 windows 上跑一个 linux)

Docker(容器技术):不会虚拟硬件层,应用软件和系统之间仅隔着一个用于任务调度的 docker engine,docker engine 就是利用 linux 内核技术中的 namespace 和 cgroup(control group) 来隔离进程和资源。进而为宿主和容器,以及容器与容器创建相对独立的环境。这里的环境指的是文件系统,cpu,网络,内存等一系列的资源。这些资源并非独立于宿主之外的一套硬件系统,而是和宿主共用的。 应用程序和宿主共用一套内核空间

虚拟机:虚拟硬件,独立环境; 容器:资源隔离,半独立环境。

虚拟机和容器,两者没有绝对的好与坏,因为两者有不同的使用场景。虚拟机更擅长于彻底隔离整个运行环境。例如,云服务提供商通常采用虚拟机技术隔离不同的用户。而 Docker通常用于隔离不同的应用 ,例如前端,后端以及数据库。

什么是 Docker

说实话关于Docker是什么并太好说,下面我通过四点向你说明Docker到底是个什么东西。

  • Docker 是世界领先的软件容器平台。
  • Docker 是 Google 公司推出的,用 Go 语言进行开发实现,基于 Linux 内核的Cgroup,Namespace,以及 UnionFS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。Docke最初实现是基于 LXC.
  • Docker 能够自动执行重复性任务,例如搭建和配置开发环境,从而解放了开发人员以便他们专注在真正重要的事情上:构建杰出的软件。
  • Docker 使用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。

Docker 是容器技术的一种实现,并不是唯一的。只不过 docker 做的太成功了。所以一提容器技术就会想到 docker 。 docker 成功的地方就在于它将应用程序进行了打包生成镜像,并且提供了一个集中的发行平台。

docker 的侧重点:如果快速部署一个应用

Docker 容器特点

  • 轻量:在一台机器上运行的多个 Docker 容器可以共享这台机器的操作系统内核;它们能够迅速启动,只需占用很少的计算和内存资源。镜像是通过文件系统层进行构造的,并共享一些公共文件。这样就能尽量降低磁盘用量,并能更快地下载镜像。
  • 标准:Docker 容器基于开放式标准,能够在所有主流 Linux 版本、Microsoft Windows 以及包括 VM、裸机服务器和云在内的任何基础设施上运行。
  • 安全:Docker 赋予应用的隔离性不仅限于彼此隔离,还独立于底层的基础设施。Docker 默认提供最强的隔离,因此应用出现问题,也只是单个容器的问题,而不会波及到整台机器。

Docker 存在的问题

  • 资源隔离不彻底(使用 namesapce 和 cgroup 来实现资源隔离,但依然存在一个无法隔离的东西,例如系统时间,如果一个容器是以一个比较高的权限在运行时,可能会将该容器中的病毒传染给宿主系统)
  • 跨平台受限(和技术实现方式有关,因为资源隔离用的是 linux 内核的技术)
  • 容器间资源抢夺

为什么要用 Docker ?

  • 一致的运行环境:Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 “这段代码在我机器上没问题啊” 这类问题。
  • 更快速的启动时间:可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。
  • 隔离性:避免公用的服务器,资源会容易受到其他用户的影响。
  • 弹性伸缩,快速扩展: 善于处理集中爆发的服务器使用压力。
  • 迁移方便:可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。
  • 持续交付和部署:使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。

Docker 概念

Docker 包括三个基本概念:

  • 镜像(Image):Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。

  • 容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

  • 仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。

  • Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。

Docker 容器通过 Docker 镜像来创建。

镜像(Image):一个特殊的文件系统

操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而Docker 镜像(Image),就相当于是一个 root 文件系统。

Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。 镜像不包含任何动态数据,其内容在构建之后也不会被改变。

Docker 设计时,就充分利用 Union FS的技术,将其设计为分层存储的架构 。 镜像实际是由多层文件系统联合组成。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。 比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

容器(Container):镜像运行时的实体

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等 。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。前面讲过镜像使用的是分层存储,容器也是如此。

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据 ,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此, 使用数据卷后,容器可以随意删除、重新 run ,数据却不会丢失。

仓库(Repository):集中存放镜像文件的地方

镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry就是这样的服务。

一个 Docker Registry中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。所以说:镜像仓库是Docker用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本 。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签.。