Docker和传统虚拟机的区别
参考文章:
https://lwn.net/Articles/531114/ 操作中的命名空间详解
https://blog.yadutaf.fr/page/2/
https://ruby-china.org/topics/22004 Docker 介绍以及其相关术语、底层原理和技术
http://tiewei.github.io/cloud/Docker-Getting-Start/
主机级虚拟化
Type-I:直接在硬件平台上安装虚拟机管理器Hypervisor,在硬件上不需要再安装宿主机操作系统,而是直接安装Hypervisor,然后在Hypervisor上安装虚拟机;
Type-II:首先在物理机上安装一个主机操作系统(HostOS),即宿主机操作系统,然后在宿主机上安装VMM(Virtual Machine Manager,虚拟机管理器,例如VMware_workstation),在VMware_workstation软件上创建虚拟机。
容器级虚拟化
Control Groups(cgroups) 把系统级的资源分成多个组,然后把每一个组内的资源量分配到特定用户空间进程上
https://tech.meituan.com/2015/03/31/cgroups.html 可重点参考
https://en.wikipedia.org/wiki/Cgroups
https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html
cgroups 的全称是control groups,cgroups为每种可以控制的资源定义了一个子系统。典型的子系统介绍如下:
- cpu 子系统,主要限制进程的 cpu 使用率。
- cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
- cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
- memory 子系统,可以限制进程的 memory 使用量。
- blkio 子系统,可以限制进程的块设备 io。
- devices 子系统,可以控制进程能够访问某些设备。
- net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
- freezer 子系统,可以挂起或者恢复 cgroups 中的进程。
- 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,切换根目录。
参考:https://blog.csdn.net/zmx729618/article/details/72930474
一个用户空间主要目标是实现环境隔离的,任何进程运行在此用户空间中会认为它是唯一运行在内核上用户空间的进程。
一个用户空间应该包括的组件:
Linux支持6种Namespace:
- UTS: 主机名(本文)
- IPC: 进程间通信
- PID: "chroot"进程树
- NS: 挂载点,首次登陆Linux(
mnt
命名空间: 使用在管理挂载点 (MNT: Mount)- NET: 网络接入,包括接口
- USER: 将本地的虚拟user-id映射到真实的user-id
1、UTS namespace(主机名和域名); http://blog.lucode.net/linux/intro-Linux-namespace-1.html
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: Introduction to Linux namespaces – Part 2: IPC
每个被隔离的用户空间在运行进程的时候,必须有一个父进程,即必须有管理此用户空间的多个进程的父进程,否则在此用户空间被销毁前就无法管理此空间运行的进程了。
不同用户的进程就是通过 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 Introduction to Linux namespaces – Part 5: NET
网络隔离是通过 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 系统的交互图如下: