Docker和传统虚拟机的区别
参考⽂章:
主机级虚拟化
Type-I:直接在硬件平台上安装虚拟机管理器Hypervisor,在硬件上不需要再安装宿主机操作系统,⽽是直接安装Hypervisor,然后在Hypervisor上安装虚拟机;
Type-II:⾸先在物理机上安装⼀个主机操作系统(HostOS),即宿主机操作系统,然后在宿主机上安装VMM(Virtual Machine Manager,虚拟机管理器,例如VMware_workstation),在VMware_workstation软件上创建虚拟机。
容器级虚拟化
Control Groups(cgroups) 把系统级的资源分成多个组,然后把每⼀个组内的资源量分配到特定⽤户空间进程上
cgroups 的全称是control groups,cgroups为每种可以控制的资源定义了⼀个⼦系统。典型的⼦系统介绍如下:
1. cpu ⼦系统,主要限制进程的 cpu 使⽤率。
2. cpuacct ⼦系统,可以统计 cgroups 中的进程的 cpu 使⽤报告。
3. cpuset ⼦系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。4. memory ⼦系统,可以限制进程的 memory 使⽤量。5. blkio ⼦系统,可以限制进程的块设备 io。
6. devices ⼦系统,可以控制进程能够访问某些设备。
7. net_cls ⼦系统,可以标记 cgroups 中进程的⽹络数据包,然后可以使⽤ tc 模块(traffic control)对数据包进⾏控制。8. freezer ⼦系统,可以挂起或者恢复 cgroups 中的进程。
9. ns ⼦系统,可以使不同 cgroups 下⾯的进程使⽤不同的 namespace。
cgroups 实现了对资源的配额和度量。 cgroups 的使⽤⾮常简单,提供类似⽂件的接⼝,在 /cgroup ⽬录下新建⼀个⽂件夹即可新建⼀个group,在此⽂件夹中新建 task ⽂件,并将 pid 写⼊该⽂件,即可实现对该进程的资源控制。具体的资源配置选项可以在该⽂件夹中新建⼦subsystem ,{⼦系统前缀}.{资源项} 是典型的配置⽅法, 如 memory.usageinbytes 就定义了该 group 在 subsystem memory 中的⼀个内存限制选项。 另外,cgroups 中的 subsystem 可以随意组合,⼀个 subsystem 可以在不同的 group 中,也可以⼀个 group 包含多个subsystem - 也就是说⼀个 subsystem。
Container可以当作隔离环境管理器,在其上⾯创建⼀个个隔离环境,然后把要运⾏的隔离出来的进程运⾏在隔离环境中。内核提供的是内核空间,进程运⾏在⽤户空间,当进程运⾏在⼀个隔离环境中的话,那么隔离的就是⽤户空间,这就把⽤户空间隔离成多组。这些被隔离的⽤户进程共享底层同⼀个内核,但进程运⾏空间的边界就是被隔离的⽤户空间的边界。这些被隔离的、运⾏进程的⽤户空间就叫容器。⼀旦进程出现故障,除了感染所属容器,是否会⼲扰其他容器的进程?利⽤jail。jail被应⽤到linux上后叫vserver,vserver所实现的功能有chroot,切换根⽬录。
⼀个⽤户空间主要⽬标是实现环境隔离的,任何进程运⾏在此⽤户空间中会认为它是唯⼀运⾏在内核上⽤户空间的进程。
⼀个⽤户空间应该包括的组件:
Linux⽀持6种Namespace:
1. UTS: 主机名(本⽂)2. IPC: 进程间通信3. PID: \"chroot\"进程树
4. NS: 挂载点,⾸次登陆Linux(mnt命名空间: 使⽤在管理挂载点 (MNT: Mount)5. NET: ⽹络接⼊,包括接⼝
6. USER: 将本地的虚拟user-id映射到真实的user-id
UTS (\"UNIX Time-sharing System\") namespace 允许每个 container 拥有独⽴的 hostname 和 domain name, 使其在⽹络上可以被视作⼀个独⽴的节点⽽⾮ Host 上的⼀个进程。2、Mount namespace(根⽂件系统);
类似 chroot,将⼀个进程放到⼀个特定的⽬录执⾏。mnt namespace 允许不同 namespace 的进程看到的⽂件结构不同,这样每个 namespace 中的进程所看到的⽂件⽬录就被隔离开了。同 chroot 不同,每个 namespace 中的 container 在 /proc/mounts 的信息只包含所在 namespace 的 mount point。
3、IPC namespace:不同的⽤户隔离空间中的进程是⽆法利⽤IPC进⾏通信的,不然⽤户空间的隔离就没有任何意义了。
container 中进程交互还是采⽤ Linux 常见的进程间交互⽅法 (interprocess communication - IPC), 包括常见的信号量、消息队列和共享内存。然⽽同 VM 不同,container 的进程间交互实际上还是 host 上具有相同 pid namespace 中的进程间交互,因此需要在IPC资源申请时加⼊ namespace 信息 - 每个 IPC 资源有⼀个唯⼀的 32bit ID。4、PID namespace:
每个被隔离的⽤户空间在运⾏进程的时候,必须有⼀个⽗进程,即必须有管理此⽤户空间的多个进程的⽗进程,否则在此⽤户空间被销毁前就⽆法管理此空间运⾏的进程了。
不同⽤户的进程就是通过 pid namespace 隔离开的,且不同 namespace 中可以有相同 PID。具有以下特征:
每个 namespace 中的 pid 是有⾃⼰的 pid=1 的进程(类似 /sbin/init 进程)
每个 namespace 中的进程只能影响⾃⼰的同⼀个 namespace 或⼦ namespace 中的进程
因为 /proc 包含正在运⾏的进程,因此在 container 中的 pseudo-filesystem 的 /proc ⽬录只能看到⾃⼰ namespace 中的进程
因为 namespace 允许嵌套,⽗ namespace 可以影响⼦ namespace 的进程,所以⼦ namespace 的进程可以在⽗namespace 中看到,但是具有不同的 pid
5、USER namespace:每个⽤户空间都应该有⼀个类似与root的管理者,但这些管理者在真实的主机上是普通⽤户。
每个 container 可以有不同的 user 和 group id, 也就是说可以以 container 内部的⽤户在 container 内部执⾏程序⽽⾮ Host 上的⽤户。
6、Net namespace
⽹络隔离是通过 net namespace 实现的, 每个 net namespace 有独⽴的 network devices, IP addresses, IP routing tables,/proc/net ⽬录。这样每个 container 的⽹络就能隔离开来。 docker 默认采⽤ veth 的⽅式将 container 中的虚拟⽹卡同 host 上的⼀个 docker bridge 连接在⼀起。
Linux Namespaces
Namespaces通过系统调⽤把上述6中功能向外输出,创建进程⽤clone();
把创建好的进程放到某个namespaces中,则使⽤系统调⽤:setns(),即把内核中启⽤的进程放到容器中;unshare():此系统调⽤⽅法是是把容器中的进程拿出来;
从上述⾓度看,要想使⽤容器需要依靠linux内核级的内核资源⽤于⽀撑⽤户空间中。docker构建镜像是 分层构建、联合挂载。docker构建镜像是 分层构建、联合挂载。
⽐如centos是⼀个镜像,nginx是⼀个镜像,那么把这两者叠加在⼀起则可以实现nginx运⾏在centos上,这就叫联合挂载。当需要运⾏nginx时,把centos镜像和nginx镜像联合挂载。每⼀层镜像都是只读的,所以底层镜像是不允许修改的。
如果想在容器A中修改⽂件怎么办呢?那么可以附加⼀个新层即E层,E层是专门属容器A的层,是能读能写的,所以容器A就可以在E层进⾏读写。
但是如果容器A想要删除⼀个属于底层镜像的⽂件,怎么办?由于底层镜像是只读,所以是⽆法删除的,那么即可在底层镜像中标记为不可见即可。如果要修改底层镜像的⽂件,即把底层镜像中的⽂件复制到E层,然后在E层中进⾏修改即可。
另外⼀个问题,因为E层有数据,那么如何把容器A迁移到其他宿主机?因此在使⽤容器时,是不会在容器本地保存有效数据的,所以在需要存储数据时,就会在⽂件系统上挂载⼀个外部的存储,以后即使把容器删除,也可以再启动同样的镜像,创建同样的容器,然后挂载此数据库即可。
libcontainer
Docker 从 0.9 版本开始使⽤ libcontainer 替代 lxc,libcontainer 和 Linux 系统的交互图如下:
因篇幅问题不能全部显示,请点此查看更多更全内容