1. 容器
1.1 定义
一种沙盒技术,
- 可以将应用运行在其中,与外界隔离
- 这个沙盒可以被方便地“转移”。
本质上,他就是一种特殊的进程。通过在创建容器进程的时候,指定了这个进程所需要启用的一组Namespace参数,进而让该容器进程只能看到当前Namespace所限定的资源、文件、设备、状态或者配置。--进程划分独立空间思想
一个正在运行的Linux容器,可以被一分为二地看待:
- 一组联合挂载在/var/lib/docker/aufs/mnt上的rootfs,这部分被称为“容器镜像”(Container Image),是容器的静态视图
- 一个由Namespace + Cgroups构成的隔离环境,这部分被称为“容器运行时”,是容器的动态视图
1.2 核心原理小结
在为待创建的用户进程
- 启动Linux Namespace配置
- 设置指定的Cgroups参数
- 切换进程的根目录(change root, 配合namespace mount)
切换进程的根目录的时,Docker项目会在最后一步优先使用pivot_root的系统调用,如果系统不支持,才会使用chroot。
2. 虚拟机
虚拟机在物理宿主机上存在一层Hypervisor的硬件虚拟化层,通过Hypervisor虚拟化出了CPU, 内存,I/O设备等,然后在这些虚拟设备上装了一个新的操作系统,即GuestOS,通过不同的操作系统,来实现同一个物理宿主机上的环境隔离。
容器和虚拟机的区别
# | 容器 | 虚拟机 |
---|---|---|
关键技术 | Docker Engine | Hypervisor,GuestOS |
实现原理 | Docker Engine通过在原有物理宿主机上的原操作系统,在起Docker进程的时候,通过指定各种namespace参数实现了不同Docker容器(进程)之间的进程隔离 | Hypervisor通过虚拟化出硬件设备,在硬件设备上安装若干个GuestOS,通过操作系统,实现不同操作系统内的进程互相不感知 |
本质 | 不同的Docker容器本质上是物理宿主机上的操作系统中运行的不同进程 | 不同的虚拟机本质上是Hypervisor虚拟化出来硬件上安装的不同的操作系统 |
优劣 | GuestOS会带来很大的性能消耗:1. 不做优化的场景,根据实验一个运行CentOS的KVM VM启动之后VM自身占用100~200M内存 2. VM内部用户对宿主机操作系统的调用必须经过Hypervisor这一层的拦截和处理,会对计算资源、网络和磁盘I/O的造成很大损耗 | 不需要虚拟化层导致“敏捷”“高性能”是容器相比与虚拟机的最大优势。但是正因为如此,Docker容器对应用进程隔离的不如通过Hypervisor的虚拟化工具进行的隔离要彻底。如1. 同一个宿主机上的不同Docker容器还是共享的同一个操作系统内核 2. Linux内核中,很多资源和对象是不能被NameSpace化的,比如时间,如果调用系统方法修改系统时间,那么所有Docker容器都可以感知到---定制化安全加固方案:通过Seccomp技术,对容器内部发起的系统调用进行过滤来进行安全加固,但是牺牲了一定的性能,且这种定制不具备普适性 |
因此,不能Docker engine放在和Hypervisor同样的地位,Docker Engine或任何容器管理工具都不对容器中的应用进程的隔离环境负责,而是由物理宿主机的操作系统本身直接负责的。Hypervisor是会虚拟出硬件设备,在这上面通过不同的GuestOS实现隔离的。而Docker Engine在这里扮演的角色,更多是的旁路式的辅助和管理工作。
那么如何在容器的隔离和性能之间做出平衡呢?
基于虚拟化和独立内核技术的容器
3. Docker Engine是做什么的?
#TODO
4. 容器编排
5. 集群管理
#TODO
6. 进程
6.1 定义
- 狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
- 广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
对进程来说,静态表现就是程序,未运行时存储在磁盘上;运行起来后,变成了计算机中数据和状态的总和,也是其动态表现。
容器技术的核心功能,是通过约束和修改进程的动态表现,从而为其制造出一个容器的边界。
6.2 进程和线程的区别
#TODO
7. Linux Cgroups(Linux Control Group)
7.1 解决问题
虽然Docker容器可以通过Namespace改变进程视图可见性,但是本质上容器这个进程及其内部的应用进程,都是统一归宿主机的操作系统管理,这就意味着,这个容器内部的进程和容器外部的宿主机的进程时处于平等竞争关系的。那么问题来了,这些资源占用怎么去隔离?---Cgroups
7.2 主要作用
限制一个进程族能够使用的资源上限,包括CPU,内存,磁盘,网络带宽等。此外还能够对进程进行优先级设置、审计、以及将进程挂起和恢复等操作。
详见《Linux Cgroups》
7.3 存在问题
容器中执行top指令,显示的信息是宿主机的CPU和内存数据,不是当前容器的数据。因为/proc下的存储的信息是作为top指令的主要数据来源,而/proc文件系统是感知不Cgroups限制的存在
8. Namespace技术
8.1 作用
修改进程视图的主要方法。说白了,容器正是通过在起容器进程的时候,增加namespace的配置参数,使得在该容器内部可见的进程与容器外部(物理机OS)上看到的进程“隔离”开来。但是,在物理宿主机看来这些被“隔离”的进程与其他进程并没有太大区别。
8.2 存在问题
这部分如何理解?
看容器本质就好了,容器项目通过起用户进程时候限定cgroups和namespace并切换root来起了一个“沙盒”里的应用进程,所以通过一个容器项目起来的一个应用进程就是一个容器。
9. RPC(Remote Procedure Call)
简单理解:rpc调用能够通过类似本地调用的方式实现在分布式架构中Service A调用起Service B的方法。
在上图中,以左边的Client端为例,Application就是rpc的调用方,Client Stub就是代理对象,也就是那个看起来像是Calculator(这个类是实际实现在右侧Application中的)的实现类,其实内部是通过rpc方式来进行远程调用的代理对象,至于Client Run-time Library,则是实现远程调用的工具包,比如jdk的Socket,最后通过底层网络实现实现数据的传输。
这个过程中最重要的就是序列化和反序列化了,因为数据传输的数据包必须是二进制的,你直接丢一个Java对象过去,人家可不认识,你必须把Java对象序列化为二进制格式,传给Server端,Server端接收到之后,再反序列化为Java对象。
10. 容器镜像(rootfs)
10.1 既然Docker容器是在物理宿主机上的进程,那么Docker镜像和物理宿主机的操作系统之间是什么关系?
可以简单这么理解
- 如果容器镜像os支持某硬件的驱动,但是宿主机os如果不支持该硬件驱动的话,也白搭
- 可以理解为镜像只是提供了一套镜像文件系统中的各种文件,而各种内核相关的模块或者特性支持,完全依赖于宿主机
也正是通过容器镜像,打包了整个操作系统的文件和目录(虽然没打包内核),也就意味着,应用以及他所运行所需要的全部依赖都被封装在了一起,而对于一个应用来说,操作系统本身才是他运行所需要的完整的依赖库。也正是因为这样,才有了容器的一个重要特性:一致性。
一致性
优雅地解决云端和本地服务环境不同造成在应用打包过程中使用PaaS最“痛苦”的步骤。
11. 操作系统
11.1 镜像和操作系统关系
#TODO
11.2 操作系统文件和操作系统内核的区别
为什么说容器通过mount namespace挂载不同版本的操作系统文件,但是不同的Docker还是使用的操作系统内核。(通俗理解就是不能通过容器技术在Windows宿主机上运行Linux容器,或者在低版本的Linux宿主机上运行高版本的Linux容器)
12. Copy-on-Write
#TODO
13. Volume机制
13.1 作用
是为了解决在通过rootfs机制(打包了操作系统所包含的文件、配置和目录)和mount 那么死pace建立起了一个同宿主机完全隔离的文件环境系统同时引入的两个问题:
- 容器里面的新建的文件,怎么让宿主机获取到
- 宿主机上的文件系统,怎么让容器的进程访问到
总而言之,是在隔离开宿主机和容器的文件系统之后,怎么进行文件共享操作。
13.2 是什么
允许用户将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作。
注意mount namespace在根目录下挂载rootfs和Volume机制挂载的区别
- rootfs挂载到根目录是docker仓库中打包的操作系统的文件系统
- Volume机制是把物理机上的卷挂载到通过rootfs和mount namespace隔离开的容器卷上以达到容器和宿主机的文件共享目的
13.3 用法
// mount /home on host to /test in container filesystem
$ docker run -v /home:/test
思考
- 你是否知道最新的 Docker 项目默认会为容器启用哪些Namespace?
- 如何修复容器中的top指令以及/proc文件系统中的信息?(考虑lxcfs)
参考答案:
- top 是从 /prof/stats 目录下获取数据,所以道理上来讲,容器不挂载宿主机的该目录就可以了。lxcfs就是来实现这个功能的,做法是把宿主机的 /var/lib/lxcfs/proc/memoinfo 文件挂载到Docker容器的/proc/meminfo位置后。容器中进程读取相应文件内容时,LXCFS的FUSE实现会从容器对应的Cgroup中读取正确的内存限制。从而使得应用获得正确的资源约束设定。kubernetes环境下,也能用,以ds 方式运行 lxcfs ,自动给容器注入争取的 proc 信息。
- 用的是vanilla kubernetes,遇到的主要挑战就是性能损失和多租户隔离问题,性能损失目前没想到好办法,可能的方案是用ipvs 替换iptables ,以及用 RPC 替换 rest。多租户隔离也没有很好的方法,现在是让不同的namespace调度到不同的物理机上。也许 rancher和openshift已经多租户隔离。
- 在从虚拟机向容器环境迁移应用的过程中,可能会遇到哪些容器和虚拟机不一致的问题?
- 容器通过起进程时候指明mount namespace命名空间启动,但是如果不再做其他操作,那么会导致容器内部看到的目录继承了宿主机目录,所以需要重新挂载需要挂载的目录,但是,那些没有重新挂载的是否会被自动umount?
在用户起Docker容器时,会通过mount namespace参数重新限定应用进程mount点可见视图,同时在容器的根目录下重新moun一个新的文件系统,比如Ubuntu 16.04的ISO,用来为容器进程提供隔离后执行环境的文件系统(这个文件系统也被成为容器镜像,但也有一个更专业的名字,rootfs(根文件系统),他包含一个操作系统所包含的文件、配置和目录,并不包括操作系统的内核。在Linux操作系统中,这两部分是分开存在的,操作系统只有在开机启动时才会加载指定版本的内核镜像。因此,需要注意,同一台机器上的所有容器,都共享宿主机操作系统的内核)。这样就不存在问题中的只挂载到比如/opt/ 目录,而其他挂载点需不需要再去卸载的问题了。
- 既然容器的 rootfs(比如,Ubuntu 镜像),是以只读方式挂载的,那么又如何在容器里修改 Ubuntu 镜像的内容呢?(提示:Copy-on-Write)
-
除了 AuFS,你知道 Docker 项目还支持哪些 UnionFS 实现吗?你能说出不同宿主机环境下推荐使用哪种实现吗?
5-6参考答案:
- 你在查看 Docker 容器的 Namespace 时,是否注意到有一个叫 cgroup 的 Namespace?它是 Linux 4.6 之后新增加的一个 Namespace,你知道它的作用吗?
- 如果你执行 docker run -v /home:/test 的时候,容器镜像里的 /test 目录下本来就有内容的话,你会发现,在宿主机的 /home 目录下,也会出现这些内容。这是怎么回事?为什么它们没有被绑定挂载隐藏起来呢?(提示:Docker 的“copyData”功能)
- 请尝试给这个 Python 应用加上 CPU 和 Memory 限制,然后启动它。根据我们前面介绍的 Cgroups 的知识,请你查看一下这个容器的 Cgroups 文件系统的设置,是不是跟我前面的讲解一致。
- 你在查看 Docker 容器的 Namespace 时,是否注意到有一个叫 cgroup 的 Namespace?它是 Linux 4.6 之后新增加的一个 Namespace,你知道它的作用吗?
- 如果你执行 docker run -v /home:/test 的时候,容器镜像里的 /test 目录下本来就有内容的话,你会发现,在宿主机的 /home 目录下,也会出现这些内容。这是怎么回事?为什么它们没有被绑定挂载隐藏起来呢?(提示:Docker 的“copyData”功能)
- 请尝试给这个 Python 应用加上 CPU 和 Memory 限制,然后启动它。根据我们前面介绍的 Cgroups 的知识,请你查看一下这个容器的 Cgroups 文件系统的设置,是不是跟我前面的讲解一致。(第八节 深入理解Docker)