代码改变世界

Docker —几个概念的理解

2016-06-10 04:46  wish123  阅读(17723)  评论(1编辑  收藏  举报

本文从一种使用场景来引出docker,并讨论了什么是镜像,容器,仓库,以及docker的相关概念。

试想一种使用场景:

我的wordpress 博客网站现在部署在阿里云服务器上,但是在后期的使用中我有可能有这样一种需求,阿里云太贵,我可能实在付不起每月月租,想把我的服务迁到其他的云服务上,而又想完整的将我的wordpress服务和数据从阿里云迁出,并将其部署到另外的云服务上。那么我怎样解决这个问题?

第一种方案:在新主机上部署一套环境,然后将项目目录和mysql服务倒出,这种方法比较费力,而且由于相关依赖包比价复杂,容易出错。

第二种方案:在主机上安装一个KVM之类的虚拟机,可以将wordpress服务部署在KVM的虚拟机上,当我想迁出服务时我就将虚拟机的相关文件倒出,但是由于云服务主机的配置不是很高,才1核1G。这样就会耗费我很多的资源,我的wordpress明明可以跑在宿主机上,但是现在却要跑在虚拟机里。那么有没有什么方法可以解决我的问题?

方案四:使用Docker:Build, Ship and Run Any App, Anywhere!


什么是 Docker与Docker官方相关技术简介(可略读):

Docker是一套轻量级操作系统虚拟化解决方案,它由go语言编写。它基于Linux容器技术(LXC),Namespace,Cgroup,UnionFS(联合文件系统)等技术。

namespace(命名空间):命名空间是 Linux 内核一个强大的特性。每个容器都有自己单独的名字空间,运行在其中的应用都像是在独立的操作系统中运行一样。名字空间保证了容器之间彼此互不影响。docker实际上一个进程容器,它通过namespace实现了进程和进程所使用的资源的隔离。使不同的进程之间彼此不可见。我们可以把Docker容器想像成进程+操作系统除内核之外的一套软件。

cgroup(控制组):是 Linux 内核的一个特性,主要用来对共享资源进行隔离、限制、审计等。只有能控制分配到容器的资源,才能避免当多个容器同时运行时的对系统资源的竞争。控制组技术最早是由 Google 的程序员 2006 年起提出,Linux 内核自 2.6.24 开始支持。控制组可以提供对容器的内存、CPU、磁盘 IO 等资源的限制和审计管理。

UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对 文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。另外,不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。Docker 中使用的 AUFS(AnotherUnionFS)就是一种 Union FS。 AUFS 支持为每一个成员目录(类似 Git 的分支)设定只读(readonly)、读写(readwrite)和写出(whiteout-able)权限, 同时 AUFS 里有一个类似分层的概念, 对只读权限的分支可以逻辑上进行增量地修改(不影响只读部分的)。

Docker 目前支持的 Union 文件系统种类包括 AUFS, btrfs, vfs 和 DeviceMapper。


谈谈我的理解

1.Docker的生命周期:

Docker的生命周期包含三个部分,镜像,容器,仓库,我们可以把镜像,容器想像成java的类和对象,即容器是由镜像实例化而来的。也就是说我们想使用装有相关软件的镜像,首先要把镜像创建成容器。现在是不是对镜像,容器,仓库这些概念还存在一些困惑?那么让我们动手体验一下docker的神奇,然后慢慢理解这些概念。

 


2.Docker的安装:

对于docker的安装,docker官网已经给出了Linux各发行版所对应的安装方法,可以参照这里:https://docs.docker.com/  下面我以mac为例讲一下docker的安装:

由于docker要使用LXC,namespace,cgroup等Linux内核相关技术,而mac又是基于unix的,所以要使用boot2docker来使用docker,boot2docker实际上是一个Linux的轻量级发行版,一共24M大小,完全运行于内存中。但有一个问题,那就是下载它需要FQ,so…已将其安装文件放到了百度云盘中,想下载的可以点击这里下载。另外boot2docker需要运行在virtual Box上,所以请先下载virtualBox。安装之后直接点击如下图标:

屏幕快照 2014-11-22 下午10.39.45

此时会出现一个终端,并进行一些初始化操作,待期完成后,运行如下命令:

 

  1. bash3.2$ boot2docker ssh

 

既可以登录到boot2docker,boot2docker已经为我们预安装好docker相关环境。我们可以运行如下命令查看docker相关版本信息:

 

  1. docker@boot2docker:~$ docker version
  2. Client version: 1.3.1
  3. Client API version: 1.15
  4. Go version (client): go1.3.3
  5. Git commit (client): 4e9bbfa
  6. OS/Arch (client): linux/amd64
  7. Server version: 1.3.1
  8. Server API version: 1.15
  9. Go version (server): go1.3.3
  10. Git commit (server): 4e9bbfa

 

下面让我们在boot2docker中下载第一个docker镜像:我们下载镜像可以从docker官方为我们提供的dockerHub上下载,但由于dockerHub的下载速度非常慢,所以这里推荐个不错的国内源:docker.cn 。我们可以使用docker  pull命令来从此源下载镜像:

 

  1. docker pull docker.cn/docker/centos:centos6

 


3.理解Docker的生命周期中的镜像:

在下载的过程中我们可以看到docker的镜像好像是在一层一层的在下载,如下图:

 

  1. docker@boot2docker:~$ docker pull docker.cn/docker/centos:centos6
  2. Pulling repository docker.cn/docker/centos
  3. 70441cac1ed5: Download complete
  4. 511136ea3c5a: Download complete
  5. 5b12ef8fd570: Download complete
  6. Status: Image is up to date for docker.cn/docker/centos:centos6

 

那么docker的镜像到底是什么呢?我们来解释一下docker的镜像的概念。

docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统就是上文说到的UnionFS。在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。Docker在bootfs之上的一层是rootfs(根文件系统)。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。

docker镜像的层级结构图:

0731016

那么docker的rootfs与传统意义的rootfs不同之处到底是什么呢?

传统的Linux加载bootfs时会先将rootfs设为read-only,然后在系统自检之后将rootfs从read-only改为read-write。然后我们就可以在rootfs上进行写和读的操作了。但docker的镜像却不是这样,他在bootfs自检完毕之后并不会把rootfs的read-only改为read-write。而是利用union mount(UnionFS的一种挂载机制)将一个或多个read-only的rootfs加载到之前的read-only 的rootfs层之上。并在加载了这么多层的rootfs之后,仍然让它看起来只像一个文件系统,在docker的体系里把union mount的这些read-only层的rootfs叫做docker的镜像(image)。请注意,此时的每一层rootfs都是read-only的,也就是说我们此时还不能对其进行操作,那么我们怎样对其进行读写操作呢?

答案是将docker镜像进行实例化,就是上文说的从镜像(image)变成容器(container)的过程,当镜像被实例化为容器之后,系统会为在一层或是多层的read-only的rootfs之上分配一层空的read-write的rootfs。而这个分配的动作是由docker  run命令发起的,我们可以用如下命令创建一个容器:

 

  1. docker run t i docker.cn/docker/centos:centos6 /bin/bash

 

此时我们已经进入到容器,此处要说明一下,我们使用的docker.cn 的镜像实际上是在dockerHub提供的镜像之上进行了一些修改,来使我们可以更轻松的使用镜像,比如docker.cn 会把/etc/skel/.b* 文件拷贝到/root 目录之下等等,也就是说docker.cn是在空的那层rootfs(read-write)对底层的rootfs(read-only)进行了修改,那么问题来了,上文不是说底层的rootfs是read-only的吗?那为什么docker.cn可以对它进行修改?并且还能把修改后的镜像保存起来?这看起来好像非常的矛盾。

其实这并不矛盾,当我们将一个镜像实例化为一个容器之后,docker会在read-only 的rootfs之上分配一层空的read-write的rootfs,我们对文件系统的改变实际上是在空的这层rootfs(read-write)上发生的。打个比方,如果你想修改一个文件,系统实际上是将这个在read-only层的rootfs的文件拷贝到read-write层的rootfs之中,然后对它进行修改,但read-only层的文件并不会被修改,依然存在于read-only层之中,只不过是在read-write层下被隐藏了。这种模式被称为copy on write。这是unionFS的特性。也是docker的强大之处,为什么说强大呢?它允许镜像被继承,也就是说我们想生成一套虚拟环境不用从零开始了,而只要在一个相对完善的基础环境之上来创建我们的虚拟环境就可以了,比如我们想生成一个具有tomcat环境的镜像,只要在一个装有java环境的镜像之上来创建就可以了。这也是docker便捷性的体现。


4.何为容器

上文已经解释了什么是docker的镜像。那么什么又是容器呢?我们刚才使用docker run命令已经创建了一个docker容器,我们先来解释一下刚才那条命令:

 

  1. docker run t i docker.cn/docker/centos:centos6 /bin/bash

 

docker run:将镜像实例化成一个容器,有点像java中的new。

docker run的两个参数:

      -i参数: 是以交互模式启动容器

      -t参数: 分配一个tty终端

后面还跟了一条命令 /bin/bash,指要在容器中运行的命令

我们现在在容器中运行top命令:

 

  1. top 20:16:27 up 16:38, 0 users, load average: 0.00, 0.01, 0.05
  2. Tasks: 2 total, 1 running, 1 sleeping, 0 stopped, 0 zombie
  3. Cpu(s): 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
  4. Mem: 2056668k total, 336912k used, 1719756k free, 46280k buffers
  5. Swap: 1438672k total, 0k used, 1438672k free, 234792k cached
  6. PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
  7. 1 root 20 0 14784 3056 2704 S 0.0 0.1 0:00.06 bash
  8. 15 root 20 0 14952 1944 1744 R 0.0 0.1 0:00.14 top

 

然后我们会看到docker容器中只运行了两个进程:这与我们常看到的Linux系统进程貌似不一样,它并没有init进程,然后我们在保持原有终端不变的情况下,再开一个终端,然后登录到boot2docker中(使用命令:boot2docker ssh),然后运行如下命令:

 

  1. docker@boot2docker:~$ ps ef | grep top
  2. 2010 root top
  3. 2052 docker grep top

 

此时我们可能会比较奇怪,我们并没有在boot2docker中运行top,那么这个top进程是哪里来的呢?

这个top进程就是我们在容器中运行的top,这时你可能已经明白了,其实docker容器中运行的进程实际上就是宿主机上的进程。docker实际上使用了命名空间(namespace)来对进程进行隔离,使不同namespace的进程彼此不可见,同时使用cgroup来对彼此隔离的进程的资源进行限制,docker的容器(container)其实就是一个进程的容器,而并不是一个全虚拟化的操作系统,所以他不会有什么init进程。docker将进程、进程所需要的操作系统、运行环境称为容器。所以它比传统的基于hypervisor的虚拟机拥有更高的效率,并使用更低的资源。它实际上是一个内核级别的虚拟化技术,容器还是在使用宿主机的内核,为了证实上述内容,我们可以在容器中用如下命令查看docker的内核版本:

 

  1. [root@5b3a545077e0 /]# uname a
  2. Linux 5b3a545077e0 3.16.4tinycore64 #1 SMP Thu Oct 23 16:14:24 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

 

 你会发现docker容器使用的内核版本与宿主机相同,然后运行如下命令:

 

  1. [root@5b3a545077e0 /]# cat /etc/redhatrelease
  2. CentOS release 6.6 (Final)

 

你会发现他的发行版是centos6.6。


 5.什么是镜像仓库

很简单:它就是一个存储和共享镜像文件的地方。


6.附录

附录1.docker与传统虚拟机的对比:
       特性                         容器               虚拟机
       启动                           秒级                分钟级
     硬盘使用                    一般为MB            一般为GB
      性能                      接近原生                  弱于
   系统支持量      单机支持上千个容器         一般几十个
 
附录2.docker使用命名空间(namespace)详解:

名字空间

名字空间是 Linux 内核一个强大的特性。每个容器都有自己单独的名字空间,运行在其中的应用都像是在独立的操作系统中运行一样。名字空间保证了容器之间彼此互不影响。

pid 名字空间

不同用户的进程就是通过 pid 名字空间隔离开的,且不同名字空间中可以有相同 pid。所有的 LXC 进程在 Docker 中的父进程为Docker进程,每个 LXC 进程具有不同的名字空间。同时由于允许嵌套,因此可以很方便的实现嵌套的 Docker 容器。

net 名字空间

有了 pid 名字空间, 每个名字空间中的 pid 能够相互隔离,但是网络端口还是共享 host 的端口。网络隔离是通过 net 名字空间实现的, 每个 net 名字空间有独立的 网络设备, IP 地址, 路由表, /proc/net 目录。这样每个容器的网络就能隔离开来。Docker 默认采用 veth 的方式,将容器中的虚拟网卡同 host 上的一 个Docker 网桥 docker0 连接在一起。

ipc 名字空间

容器中进程交互还是采用了 Linux 常见的进程间交互方法(interprocess communication – IPC), 包括信号量、消息队列和共享内存等。然而同 VM 不同的是,容器的进程间交互实际上还是 host 上具有相同 pid 名字空间中的进程间交互,因此需要在 IPC 资源申请时加入名字空间信息,每个 IPC 资源有一个唯一的 32 位 id。

mnt 名字空间

类似 chroot,将一个进程放到一个特定的目录执行。mnt 名字空间允许不同名字空间的进程看到的文件结构不同,这样每个名字空间 中的进程所看到的文件目录就被隔离开了。同 chroot 不同,每个名字空间中的容器在 /proc/mounts 的信息只包含所在名字空间的 mount point。

uts 名字空间

UTS(“UNIX Time-sharing System”) 名字空间允许每个容器拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非 主机上的一个进程。

user 名字空间

每个容器可以有不同的用户和组 id, 也就是说可以在容器内用容器内部的用户执行程序而非主机上的用户。