深入理解docker原理

docker文件系统思想

Linux系统至少包含两个文件系统

  • boot file system (bootfs):包含 boot loader 和 kernel。用户不会修改这个文件系统。实际上,在启动(boot)过程完成后,整个内核都会被加载进内存,此时 bootfs 会被卸载掉从而释放出所占用的内存。同时也可以看出,对于同样内核版本的不同的 Linux 发行版的 bootfs 都是一致的。

  • root file system (rootfs):包含典型的目录结构,包括 /dev, /proc, /bin, /etc, /lib, /usr, and /tmp 等再加上要运行用户应用所需要的所有配置文件,二进制文件和库文件。这个文件系统在不同的Linux 发行版中是不同的。而且用户可以对这个文件进行修改。

aufs

  • aufs使用示例
    AUFS 是一种 Union File System(联合文件系统),就是把不同物理位置的目录合并mount到同一个目录中
    比如有如下文件结构
.
├── fruits
│   ├── apple
│   └── tomato
└── vegetables
   ├── carrots
   └── tomato

使用aufs,创建一个mount目录,把水果目录和蔬菜目录union mount到 ./mnt目录中
sudo mount -t aufs -o dirs=./fruits:./vegetables none ./mnt

./mnt
├── apple
├── carrots
└── tomato

这样在./mnt目录下有三个文件,苹果apple、胡萝卜carrots和蕃茄tomato。水果和蔬菜的目录被union到了./mnt目录下了

  • 特点
  1. AUFS 是一种联合文件系统,它把若干目录按照顺序和权限 mount 为一个目录并呈现出来
  2. 默认情况下,只有第一层(第一个目录)是可写的,其余层是只读的。(也就是对./mnt/carrots的修改是有效的)
  3. 增加文件:默认情况下,新增的文件都会被放在最上面的可写层中。
  4. 删除文件:因为底下各层都是只读的,当需要删除这些层中的文件时,AUFS 使用 whiteout 机制,它的实现是通过在上层的可写的目录下建立对应的whiteout隐藏文件来实现的。

Docker 镜像的 rootfs

同一个内核版本的所有 Linux 系统的 bootfs 是相同的,而 rootfs 则是不同的。在 Docker 中,基础镜像中的 roofs 会一直保持只读模式,Docker 会利用 union mount 来在这个 rootfs 上增加更多的只读文件系统,最后它们看起来就像一个文件系统即容器的 rootfs

  1. 所有 Docker 容器都共享主机系统的 bootfs 即 Linux 内核
  2. 每个容器有自己的 rootfs,它来自不同的 Linux 发行版的基础镜像,包括 Ubuntu,Debian 和 SUSE 等
  3. 所有基于一种基础镜像的容器都共享这种 rootfs

总结docker镜像与容器原理

可写的容器层:当容器启动时,一个新的可写层被加载到镜像的顶部,这一层就叫容器层,容器层之下都叫镜像层。只有容器层是可写的,容器层下面的所有镜像层都是只读的。对容器的任何改动都只会发生在容器层中。
这里,所有的镜像层联合一起组成一个统一的文件系统,用户在容器层看到的就是一个叠加之后的文件系统。镜像层内部是有上下之分的:

  • 添加文件:在容器中创建文件时,新文件被添加到容器层中。
  • 读取文件:当在容器中读取某个文件时,Docker会从上往下依次在各镜像层中查找此文件,一旦找到打开并读入内存。
  • 修改文件:在容器中修改已存在的文件时,Docker会从上往下依次在各个镜像层中查找此文件,一旦找到立即将其复制到容器层中,然后才修改。(copy-on-write特性)
  • 删除文件:在容器中删除文件时,Docker会从上往下依次在镜像层中找,找到后,会在容器层记录下此删除操作。
    copy-on-write特性说明容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。所以镜像可以被多个容器共享。

我的理解

所有镜像都共享linux内核,主机系统的bootfs会把linux内核(kernal)加载进内存,linux内核包含管理进程,管理内存,文件系统,设备控制,网络管理等功能。镜象层是在linux内核之上,本质上就是rootfs文件系统,而这些rootfs是只读的。而我们使用的容器是在镜像层之上,相当于在只读层之上加了一层读写层。那么针对于同一镜像创建出来的多个容器,他们彼此之间不会互相影响的原因是容器的操作(比如增删改)都是针对于读写层的操作,增加文件就是在读写层增加,删除文件不会真的删掉只读层文件,而是在读写层建立一个whiteout隐藏文件实现;修改会从只读层拷贝文件到读写层,然后再修改。查找是通过只读层一层一层往下寻找。所以不同容器知识共享了只读层,彼此之间操作不影响,不同容器之间是隔离的。

docker虚拟化的本质与namespace

docker使用namespace机制进行容器隔离
Docker容器在本质上是宿主机上的一个进程。也就是常说的容器是操作系统级的虚拟化,而之前的虚拟机是硬件级别的虚拟化。容器与容器之间做了资源的隔离,所以在一个容器内部的各种操作会给人一种仿佛在独立的系统环境中的感觉。外部应用对容器进行访问时,也会有这种感觉。而做这种容器资源隔离的Linux内核机制就是namespace。使用命令sudo ls -l /proc/[pid]/ns查看pid为[pid]的进程所属的namespace,可以看到namespace共分为7种类型。分别为ipc、mnt、pid、uts、net、cgroups、user。
使用docker指令创建一个容器本质上就是使用clone()来创建一个属于新的namespace的进程 每个Docker容器其实都有一个独立存在的namespace,而用Docker exec执行一个命令,就是将该命令在该容器的namespace中运行,也就是将该命令的进程加入到一个已经存在的namespace中

docker虚拟化的本质就是每个进程都拥有独立的以下几个namespace,给人的感觉像是处在一个独立的操作系统中执行命令一样
namespace分类详述

  1. mount namespace
    mount namespace通过隔离文件系统挂载点对文件系统进行隔离。隔离之后,不同的mount namespace下的文件结构发生变化也不会互相影响
  2. cgroup namespace
    cgroup Namspace虚拟化了进程的cgroups视图。cgroups是Linux内核的一个工具,用来做资源的限制的
  3. PID namespace
    在Linux操作系统中,每一个进程的PID都是在系统中是唯一的。而在容器中,进程的PID可以和另一个容器中某进程的PID相同。这就是对PID的虚拟化。因为两个容器处于不同的PID namespace下,所以这两个容器的PID可以有重复出现。
  4. IPC namespace
    IPC namespace也是一种namespace,它隔离了IPC(进程间通信)如信号量、消息队列和共享内存。在同一个IPC namespace下的进程互相可见,不同IPC namespace下的进程互相不可见
  5. user namespace
    user namespace主要隔离安全相关的标识符和属性,包括用户ID、用户组ID、root目录、key以及特殊权限。简单来说,我们可以在Linux中用非root的用户来创建一个容器,它创建的容器进程却属于拥有超级权限的用户
  6. UTS namespace
    UTS(Unix Time-sharing System) namespace提供了主机名与域名的隔离。这样,我们每一个容器都可以拥有自己独立的主机名和域名了,在外部进行访问时好似访问了一个独立的节点

你可能感兴趣的:(深入理解docker原理)