杂记:
- 容器技术的兴起源于 PaaS 技术的普及
- Docker 公司发布的 Docker 项目具有里程碑式的意义
- Docker 项目通过“容器镜像”,解决了应用打包这个根本性难题
- 容器本身没有价值,有价值的是“容器编排”
进程:
静态表现:程序
动态表现:计算机里的数据和状态总和
容器:
核心功能:通过约束和修改进程的动态表现,从而为其创造出一个“边界”
本质:一种特殊的进程
1 虚拟机与容器的对比
虚拟机:
- 是物理硬件的抽象,将一台服务器转变为多台服务器。虚拟机管理程序允许多台虚拟机在一台计算机上运行。每个VM都包含操作系统的完整副本,应用程序,必要的二进制文件和库 - 占用数十GB。虚拟机也可能很慢启动。
- ps:Hypervisor 的软件是虚拟机最主要的部分。它通过硬件虚拟化功能,模拟出了运行一个操作系统需要的各种硬件,比如 CPU、内存、I/O 设备等等。然后,它在这些虚拟的硬件上安装了一个新的操作系统,即 Guest OS。
- 优点:
- 1.硬安全边界:虚拟机在相邻系统之间提供了硬件级别的隔离,应用使用的是独立的操作系统。而在容器中,应用共享宿主机内核,在一个操作系统中运行,操作不当可能会影响整个系统。
- 2.可在模拟多种操作系统
- 3.享用更多主机资源:分配给虚拟机的资源远远超过分配给容器的资源。这就是为什么虚拟机更适合资源密集型任务。较大规模和较长生命周期的任务更适合与 VM 一起使用。
- 缺点:重量、启动时间长、代价高
- 1.不便携,重量:虚拟机是千兆字节大小的软件块,容器镜像的大小通常为几十兆
- 2.过多的无效资源:将资源分配给虚拟机,会直接占用掉本机资源,未使用的资源会被浪费;每个VM储存自己独立的操作系统文件,它还包含库、二进制文件和操作系统所需的虚拟硬件的副本。重复文件会占用服务器的大部分 RAM 和 CPU 资源。
- 更难维护的操作系统:主机上可运行多个虚拟机,各个虚拟机各自可使用不同的操作系统,需要各自更新,各自维护。
Docker容器:
- 是应用层的抽象,它将代码和依赖关系打包在一起。多个容器可以在同一台机器上运行,并与其他容器共享操作系统内核,每个容器在用户空间中作为独立进程运行。容器占用的空间比VM少(容器映像的大小通常为几十MB),可以处理更多的应用程序,并且需要更少的VM和操作系统。
- 优点:轻量化、启动时间短
- 1.便携性:容器是高度可移植的,可本机、私有云、公有云等跨环境移动
- 2.有效的资源使用:打包在容器中的代码共享运行容器所需的大部分依赖项,包括操作系统、库和框架。与虚拟机不同,每个硬件中只有一个必要文件的副本,从而更有效地使用资源。这也导致容器更轻,这意味着您可以在物理服务器中容纳更多容器。
- 3.高度可扩展性:容器编排器,如 Kubernetes 或 Docker Swarm,可自动执行大部分容器管理流程,包括扩展、网络和部署。容器化应用程序具有高度可扩展性,它们可以在需要时通过启动新节点和/或 Pod 来快速扩展和缩减。
- 缺点:
- 1.缺乏安全措施:容器提供与同一系统内的主机操作系统和容器的轻量级隔离。与虚拟机相比,这会导致较弱的安全边界。
- 2.只运行一个操作系统:容器与主机共享内核,这意味着,如果要在 Windows 宿主机上运行 Linux 容器,或者在低版本的 Linux 宿主机上运行高版本的 Linux 容器,都是行不通的。
2 Docker 项目的核心原理
Docker 项目的核心原理:为待创建的用户进程
1.启用 Linux Namespace 配置:修改进程视图
2.设置指定的 Cgroups 参数:为进程设置资源限制
3.切换进程的根目录(Change Root):
2.1 启用 Linux Namespace 配置
namespace 技术是用来修改进程视图的主要方法
1) linux 创建namespace的代码逻辑
以创建PID namespace 为例
namespace 是linux创建进程的一个可选参数,在 Linux 系统中创建进程的系统调用是 clone(),比如:
int pid = clone(main_function, stack_size, SIGCHLD, NULL);
这个系统调用就会为我们创建一个新的进程,并且返回它的进程号 pid。
而当我们用 clone() 系统调用创建一个新进程时,就可以在参数中指定 CLONE_NEWPID 参数
int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);
这时,新创建的这个进程将会“看到”一个全新的进程空间,在这个进程空间里,它的 PID 是 1,在宿主机真实的进程空间里,这个进程的 PID 还是真实的数值,比如 100。
2) linux namespace 介绍
- UTS Namespace:隔离主机名
- IPC Namespace:隔离进程间通信
- PID Namespace:隔离进程ID
- Mount Namespace:隔离进程看到的文件层级
- User Namespace:隔离用户组ID(举个例子:在宿主机上以一个非root用户运行创建一个User Namespace ,在UserNamespace里面映射成root用户)
- Network Namespace:用来隔离网络设备,IP地址端口等网络栈
3) namespace技术缺陷
- 容器只是运行在宿主机上的一种特殊的进程,那么多个容器之间使用的就还是同一个宿主机的操作系统内核。这意味着,如果要在 Windows 宿主机上运行 Linux 容器,或者在低版本的 Linux 宿主机上运行高版本的 Linux 容器,都是行不通的。
- 在 Linux 内核中,有很多资源和对象是不能被 Namespace 化的,最典型的例子就是:时间。
2.2 设置指定的 Cgroups 参数
cgroup全称是control groups。control groups:控制组,被整合在了linux内核当中,把进程(tasks)放到组里面,对组设置权限,对进程进行控制(它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等)。可以理解为用户和组的概念,用户会继承它所在组的权限。
cgroup 提供了将进程组织成控制组的能力,然后通过使用 资源控制子系统(cgroup_subsys) 来对控制组进行资源使用的控制,cgroup 支持的 资源控制子系统 有以下几种:
cpu子系统:限制 CPU 的使用。
memory子系统:限制内存使用。
cpuset子系统:可以为进程组分配单独的 CPU 或者内存节点。
cpuacct子系统:统计CPU group的使用情况。
blkio子系统:限制I/O,一般用于磁盘。
devices子系统:限制进程使用的设备。
freezer子系统:可以挂起和恢复进程组。
net_cls子系统:可以标记进程组的网络数据包,使用 tc 模块(traffic control)对数据包进行控制。
cgroup 通过把进程组织成 控制组,然后通过 资源控制子系统 来对 控制组 进行资源使用的限制,所以 cgroup 的分析可以分成两部分:cgroup框架 和 资源控制子系统。
cgroup 控制组
控制组 说白了就是一组进程(进程组),cgroup 就是用来限制 控制组 的资源使用。为了能够方便地向一个 控制组 添加或者移除进程(在命令行也能操作),内核使用了 虚拟文件系统 来进行管理 控制组。
我们可以把一个 控制组 当成是一个目录,由于目录有层级关系,所以 控制组 也有层级关系,如下图所示:
cgroup CPU限制
在cgroup里面,跟CPU相关的子系统有cpusets、cpuacct和cpu。
cpuset主要用于设置CPU的亲和性,可以限制cgroup中的进程只能在指定的CPU上运行,或者不能在指定的CPU上运行
cpuacct包含当前cgroup所使用的CPU的统计信息
cgroup Memory 限制
在上图中,我们创建了 2 个 cgroup(每个 cgroup 有 4 个进程),并且限制它们各自最多只能使用 2GB 的内存。如果使用超过 2GB 的内存,那么将会触发 OOM(Out Of Memory) 错误。
Cgroups缺陷
Cgroups 对资源的限制能力有很多不完善的地方,比如 /proc 文件系统的问题。
Linux 下的 /proc 目录存储的是记录当前内核运行状态的一系列特殊文件,用户可以通过访问这些文件,查看系统以及当前正在运行的进程的信息,比如 CPU 使用情况、内存占用率等,这些文件也是 top 指令查看系统信息的主要数据来源。
但是,你如果在容器里执行 top 指令,就会发现,它显示的信息居然是宿主机的 CPU 和内存数据,而不是当前容器的数据。
造成这个问题的原因就是,/proc 文件系统并不知道用户通过 Cgroups 给这个容器做了什么样的资源限制,即:/proc 文件系统不了解 Cgroups 限制的存在。