"只想从这无边的寂寞中逃出来。"
一、什么是虚拟化、容器化?
物理机:实际的服务器或者计算机。
这是相对于虚拟机而言的对实体计算机的称呼,物理机提供虚拟机以硬件环境,有时候也称为"宿主"或"寄主"。
虚拟机: 指通过软件模拟的具有完整硬件系统功能的、运行在一个 "完全隔离环境" 中的完整计算机系统。
虚拟化: 是指通过虚拟化技术将一台计算机虚拟为多台逻辑计算机。在一台计算机上,同时运行多个逻辑计算机,每个逻辑计算机可运行不同的操作系统,并且应用程序都可以在相互独立的空间内运行而互不影响,从而显著提高计算机的工作效率。
容器化:容器化是一种虚拟化技术,又称操作系统层虚拟化。这种技术将 "操作系统内核" 虚拟化,可以允许用户空间软件实例(instances)被分割成几个独立的单元,在内核中运行,而不是只有一个单一实例运行。
这个软件实例,也被称为是一个容器(containers)。对于每一个容器拥有者都认为他们使用的服务器程序是自己一个人专用、独享的。容器技术是虚拟化的一种,docker 是现今容器技术的事实标准。
简单举例比喻:物理机就好比你的庄园,其中的房子、喷泉、花园都是你独享的,别人访问不到,也享受不到。
相反,虚拟机就像开发商的一块楼盘。一栋栋单元楼对应一户户人家,它们之间是隔离的!但其中的小区设施、花园都是共享的,所有户主都可以使用。
容器就好比 1 个房子里面,开辟出来一个又一个的胶囊公寓共享这套房子的卫生间、共享厨房、共享 WiFi,只有衣服、电脑等私人物品是你自己的。
二、为什么需要虚拟化、容器化?
从上述举的现实生活中的例子来看,虚拟化、容器化的主要目的是资源隔离。那么资源隔离有什么大的收益吗?
① 资源利用率高
将利用率较低的服务器资源进行整合,用更少硬件资源运行更多业务降低 IT 支出和
运维管理成本。
例如: 尽管你的庄园很大,非常得富丽堂皇,可是如果你是一个人住或者几个人住,土地的使用率肯定是不高的,但你付出的维护成本一定大于那栋栋楼盘。
② 环境标准化
一次构建,随处执行。实现执行环境的标准化发布,部署和运维。由于开发环境、测试环境、生产环境不一致,导致有些bug 并未在开发过程中被发现。由此,确保了应用运行环境一致性
后也就会减少这类恼人问题的出现。
③ 资源弹性伸缩
根据业务情况,动态调整计算、存储、网络等硬件及软件资源。例如开售楼盘,你可以一次性开售完,也可以先开售几个观察观察情况,可是你一旦卖出了庄园,就真的再也没有了。
容器化扩展优点:
④ 差异化环境提供
虚拟化技术可以将一台计算机虚拟为多台逻辑计算机,也就是说多台虚拟机可以配备不同的执行环境。
⑤ 沙箱安全
为避免不安全或不稳定软件对系统安全性、稳定性造成影响,可使用虚拟化技术构建虚拟执行环境。因为虚拟化技术构建虚拟执行环境具有隔离性,不会影响别人在该服务器上部署的进程、服务。
⑥ 容器对比虚拟机更轻量,启动更快
传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker 容器应用,由于直接运
行于宿主内核,无需启动完整的操作系统。大大的节约了开发、测试、部署的时间。
⑦ 维护和扩展容易
Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker 团队同各个开源项目团队一起维护了一大批高质量的 官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。
三、 虚拟化实现方式
学过操作系统的友子应该对 应用程序执行环境分层很熟悉:
(1) 虚拟化常见类别
虚拟机:
存在于 ++"硬件层和操作系统层间"++ 的虚拟化技术,虚拟机通过"伪造"一个硬件抽象接口,将一个操作系统以及操作系统层以上的层嫁接到硬件上,实现和真实物理机几乎一样的功能。
比如,我这台机器上面有一个VMWare软件,可以在WIndows系统上使用Linux Centos7虚拟机。
容器:
存在于 ++"操作系统层和函数库层"++ 之间的虚拟化技术。容器通过"伪造"操作系统的接口,将函数库层以上的功能置于操作系统上。
简单来说,如果虚拟机是把整个操作系统封装隔离,从而实现跨平台应用的话。那么容器
则是把一个个应用单独封装隔离,从而实现跨平台应用。所以容器体积比虚拟机小很多,理论上占用资源更少。
JVM 之类的虚拟机:
存在于 "函数库层和应用程序" 之间的虚拟化技术。 Java 虚拟机同样具有跨平台特性,所谓跨平台特性实际上也就是虚拟化的功劳。对下通过不同的版本适应不同的操作系统函数库,对上提供统一的运行环境交给程序和开发者,使开发者能够调用不同操作系统的函数库。
(2) 常见虚拟化实现
主机虚拟化(虚拟机)实现:
主机虚拟化的原理是通过在物理服务器上安装一个虚拟化层来实现。这个虚拟化层可以在物理服务器和客户操作系统之间建立虚拟机,使得它们可以独立运行。
从软件框架的角度上,根据虚拟化层是 "直接位于硬件之上" 还是在 "一个宿主操作系统之
上",将虚拟化划分为 Type1 和 Type2。
Type1 类的虚拟Hypervisor直接运行在硬件之上,没有宿主机操作系统,Hypervisor 直接控制硬件资源和客户机。Type2 类的Hypervisor运行在一个宿主机操作系统之上,作为宿主机操作系统中的一个应用程序,客户机就是在宿主机操作系统上的一个进程。
注: Hypervisor是一种系统软件,它充当 计算机硬件和虚拟机 之间的中介,负责有效地分配和利用由各个虚拟机使用的硬件资源,这些虚拟机在物理主机上单独工作,因此,Hypervisor 也称为虚拟机管理器。
容器虚拟化实现:
容器虚拟化实现原理: 容器虚拟化,有别于主机虚拟化,是 "操作系统层" 的虚拟化。需要通过 namespace(命名空间)进行各程序的隔离,加上 cgroups进行资源的控制,以此来进行虚拟化。
三、容器虚拟化基础之NameSpace
到这里我们总算步入正题,以上都是介绍了很多概念性的东西,当然这也是理解Docker技术的基础。
(1) 什么是Namespace(命名空间)?
你没看错,Linux系统中也有Namespace命名空间。C++中也有命名空间也是以相同的英文单词namespace,只不过它是为了保证类域名不污染的作用。那么Linux中的Namespace是啥的呢?
namespace 是 Linux 内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源放在同一namespace中。
Linux namespaces 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前namespace里的进程,对其他 namespace 中的进程没有影响。
(2) namepaces参数
Linux 提供了多个 API 用来操作 namespace,它们是 clone()、 setns() 和 unshare() 函
数,为了确定隔离的到底是哪项 namespace,在使用这些 API 时,通常需要指定一些调用参数。
|-----------|---------------|---------------|
| namespace | 系统调用给参数 | 被隔离的全局系统资源 |
| UTS | CLONE_NEWUTS | 主机名和域名 |
| IPC | CLONE_NEWIPC | 信号量、消息队列、共享内存 |
| PID | CLONE_NEWPID | 进程编号 |
| NetWork | CLONE_NEWNET | 网络设备、网络栈、端口等 |
| Mount | CLONE_NEWNS | 文件系统挂在点 |
| User | CLONE_NEWUSER | 用户和用户组 |
以上命名空间在容器环境下的隔离效果:
● UTS:每个容器能看到自己的 hostname,拥有独立的主机名和域名
● IPC:同一个 IPC namespace 的进程之间能互相通讯,不同的 IPC namespace 之间不能通信。
● PID:每个 PID namespace中的进程可以有其独立的 PID,每个容器可以有其PID为1的root进程。
● Network:每个容器用有其独立的网络设备, IP 地址, IP 路由表, /proc/net 目录,端口号。
● Mount:每个容器能看到不同的文件系统层次结构。
● User:每个 container 可以有不同的 user 和 group id。
想想如何进行隔离呢?
① 首先容器进程与进程之间需要隔离,所以需要 PID 隔离。
② 首先容器 A 进程不能读取容器 B 进程通讯内容需要隔离信号量等,所以需要 IPC隔离。
③ 首先容器 A 进程不能读取容器 B 进程的文件,所以需要 Mount 隔离。
④ Docker 允许用户在主机和容器间共享文件夹,同时不需要限制容器的访问权限,这就容易让容器突破资源限制。 需要借助用户空间来完成用户之间的隔离。
四、空间隔离实战
(1) 基础知识
dd命令详解:
Linux下的dd命令用于读取、转换并输出数据。dd 可从标准输入或文件中读取数据,根据指定的格式来转换数据,再输出到文件、设备或标准输出。
语法:
dd option
|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 参数 | 作用 |
| if=文件名;of=文件名 | 输入文件名,默认为标准输入。即指定源文件. 输出文件名,默认为标准输出。即指定目的文件. |
| ibs\obs\bs = bytes | 一次读入\输出\读入与输出 bytes 个字节. |
| cbs=bytes | 一次转换 bytes 个字节,即指定转换缓冲区大小. |
| skip=blocks | 从 输入 文件开头跳过 blocks 个块后再开始复制. |
| seek=blocks | 从 输出 文件开头跳过 blocks 个块后再开始复制. |
| count=blocks | 仅拷贝 blocks 个块,块大小等于 ibs 指定的字节数. |
| conv=<关键字> | 关键字可以有以下 11: ▪ conversion:用指定的参数转换文件。 ▪ ascii:转换 ebcdic 为 ascii ▪ ebcdic:转换 ascii 为 ebcdic ▪ ibm:转换 ascii 为 alternate ebcdic ▪ block:把每一行转换为长度为 cbs,不足部分用空格填充 ▪ unblock:使每一行的长度都为 cbs,不足部分用空格填充 ▪ lcase:把大写字符转换为小写字符 ▪ ucase:把小写字符转换为大写字符 ▪ swap:交换输入的每对字节 ▪ noerror:出错时不停止 ▪ notrunc:不截短输出文件 ▪ sync:将每个输入块填充到 ibs 个字节,不足部分用空(NUL)字符补齐。 |
示例1: 生成一个镜像文件
/dev/zero:是一个特殊的文件,当你读它的时候,它会提供无限的空字符。
bs: 8k = 8 * 1024
count: 块数 该文件的最终大小为 count * bs ≈ 80M
示例2: 将一个文件里的字符转为大写字符
mkfs命令详解:
用于在设备上创建 Linux 文件系统,俗称格式化,比如我们使用 U 盘的时候可以格式化。
语法:
mkfs [-V] [-t fstype] [fs-options] filesys [blocks]
|-------------|-------------------------|
| 参数 | 作用 |
| -V | 详细显示模式 |
| -t fstype | 指定要建立何种文件系统,例如ext3,ext4 |
| fs -options | 传递给具体文件系统的参数 |
| filesys | 指定创建文件系统的文件名 |
| blocks | 指定文件系统的磁盘块数 |
示例: 格式化镜像文件为ext4
df命令详解:
df 命令用于显示目前在 Linux 系统上的文件系统磁盘使用情况统计。
语法
df [option] ... [file] ...
|----------------------|----------------------------|
| 常见参数 | 作用 |
| -a\--all | 包含所有的具有 0 Blocks 的文件系统. |
| -h, --human-readable | 使用人类可读的格式(预设值是不加这个选项的...). |
| -H, --si 同-h相像 | 但是用 1000 为单位而不是用 1024. |
| -t, --type=TYPE | 限制列出文件系统的 TYPE. |
| -T, --print-type | 显示文件系统的形式. |
示例:查看磁盘使用情况\磁盘的系统类型
mount 命令详解:
mount 命令用于加载文件系统到指定的加载点,此命令的也常用于 "挂载光盘" ,使我们可以访问光盘中的数据,因为你将光盘插入光驱中, Linux 并不会自动挂载,必须使用mount 命令来手动完成挂载。
Linux 系统下不同目录可以 "挂载不同分区和磁盘设备" ,它的目录和磁盘分区是分离的,可以自由组合(通过挂载)。不同的目录数据可以跨越不同的磁盘分区或者不同的磁盘设备。
挂载的实质是为磁盘添加入口。
语法
mount [-l]
mount [-t vfstype] [-o options] device dir
|------------|--------------------------------------------------------------------------|
| 常见参数 | 作用 |
| -l | 显示已加载的文件系统列表 |
| -t | 加载文件系统类型支持常见系统类型的 ext3,ext4,iso9660,tmpfs,xfs 等,大部分情况可以不指定, mount 可以自己识别 |
| -o options | 主要用来描述设备或档案的挂接方式: loop:用来把一个文件当成硬盘分区挂接上系统 ro:采用只读方式挂接设备 rw:采用读写方式挂接设备 |
| device | 要挂接(mount)的设备 |
| dir | 挂载点的目录 |
示例: 将镜像文件挂载到 /mnt/目录下,需要确保挂载的目录是存在的
unshare 命令详解:
unshare 主要能力是使用与父程序不共享的名称空间运行程序。
语法
unshare [options] program [arguements]
|--------------|-------------------------------------------|
| 参数 | 作用 |
| i,--ipc | 不共享ipc空间 |
| -m,--mount | 不共享Mount空间 |
| -n,--net | 不共享Net空间 |
| -p,--pid | 不共享PID空间 |
| -u,--uts | 不共享UTS空间 |
| -U,--user | 不共享用户 |
| -V,--version | 版本查看 |
| --fork | 执行unshare的进程fork一个子进程,在子进程中执行unshare传入的参数 |
| --mount-porc | 执行子进程前,将proc挂载过去 |
示例: 实现hostname隔离
(2) 实战一 PID隔离
在主机上执行 ps -ef,可以看到进程列表如下,其中启动进程 PID 1 为 init 进程.
当我们查看该Namespace下的 /proc目录文件是这样的:
其中记录着大量与当前进程相关进程的信息。
如何实现PID隔离呢?
我们打开另外一个 shell ,执行下面命令创建一个 bash 进程并且,新建一个 PID Namespace。
unshare --fork --pid --mount-proc /bin/bash
● --fork:新建了一个 bash 进程。如果不建新进程,新的 namespace 会用 unshare的 PID 作为新的空间的父进程(pid=1),但unshare进程并不在新的namespace当中,而是在当前的namespace当中。
● --pid:表示我们的进程隔离的是 pid,而其他命名空间没有隔离。
● mount-proc:Linux 下的每个进程都有一个对应的 /proc/PID,创建一个新的 PID namespace 后,如果想让子进程中的 top、 ps 等依赖 "/proc 文件系统" 的命令工作,就需要挂载 /proc 文件系统。而文件系统隔离是 mount namespace 管理的,所以 linux特意提供了一个选项--mount-proc 来解决这个问题。如果不带这个我们看到的进程还是系统的进程信息。
执行 ps -ef 查看进程信息,我们可以看到此时进程空间内的内容已经变,而且启动进程也变成了我们的 bash 进程。说明我们已经看不到主机上的进程空间了,我们的进程空间发生了隔离。
执行 exit 退出进程。
(3) 实战二Mount 隔离
打开一个shell 窗口 A,执行命令, df -h ,查看主机默认命名空间的磁盘挂载情况。
再打开一个新的 shell 窗口 B,执行 Mount 隔离命令:
unshare --mount --fork /bin/bash
注:--fork\--mount不解释
在窗口 B 中添加新的磁盘挂载.
dd if=/dev/zero of=fdimage.img bs=8k count=1024
mkfs -t ext4 ./fdimage.img
在窗口 B 挂载的磁盘中添加文件:
echo "hello world" >> /test/tmpmount/hello.txt
分别查看窗口 A、B 中的磁盘挂载信息
A:
B:
查看窗口A、 B中的文件信息
可以看到窗口 B 中新建的文件和磁盘挂载,并不存在与A窗口,说明我们实现了文件系统的隔离。
本篇到此结束,感谢你的阅读。
祝你好运,向阳而生~