虚拟化,容器化
是什么
- 物理机:指的是实际的服务器或者计算机,相对于虚拟计算机而言对实体计算机的称呼,有时候被称为'寄主'或者'宿主'。
- 虚拟机:通过虚拟化计数将一台计算机虚拟为多台逻辑计算机,在一台计算机上可以运行多个逻辑计算机,每一个逻辑计算机中都可以运行不同的操作系统,并且应用程序都可以在相互独立的空间里运行而不受外界影响。
- 容器化:容器化是一种虚拟化计数,是在操作系统层面上的虚拟化,这种计数将操作系统内核虚拟化,将用户空间软件分割为几个独立的单元,在内核中运行,而不是只有单一的实例在运行,这个软件实例被称为是一个容器。
原因
- 资源利用率高:将利用率较低的服务器资源进行整合,用更少的硬件运行更多的业务,降低管理成本。
- 环境标准化:仅需要一次构建就可以多次执行,实现执行环境的标准化发布,在开发代码的时候,由于开发环境,测试环境和生产环境不一致,会导致很多bug并未在开发的时候发现,docker镜像提供了除内核外完整的运行时环境,确保了应用运行环境的一致性。
- 资源可伸缩:根据业务情况,可以动态的调整计算,存储,网络等硬件资源,当遇到并发量高的场景,可以扩充大量的资源,在不需要使用的时候再将服务收回去。
- 差异化环境提供:一个应用只能在centos上运行,另外一个应用只能在Ubuntu上运行,但是没有预算购买两个虚拟机,容器化可以很好的解决这种场景。
- 沙箱安全:在一个容器里执行的危险操作,不会影响到其他的容器,确保安全。
- 容器更轻量:容器对比虚拟机来说,更轻量,直接运行于宿主内核,无需启动完整的操作系统,可以做到毫秒级别的启动速度。
- 维护和扩展容易:docker使用分层存储和镜像技术,使得重复部分的复用更为容易,应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得简单。
分类
虚拟机 :存在于硬件层和操作系统层间的虚拟化技术。虚拟机通过伪造一个硬件抽象接口,将一个操作系统以及操作系统层以上的层嫁接到硬件上,实现和真实物理机几乎一样的功能,比如说在一台Windows操作系统上使用安卓虚拟机,就可以使用安卓上的应用。
容器 :存在于操作系统层和函数库层之间的虚拟化技术。容器通过伪造操作系统的接口,将函数库层以上的功能置于操作系统上,以Docker为例,就是一个基于Linux操作系统的Namespace和Cgroup功能实现的隔离容器,可以模拟操作系统的功能。简单来说,如果虚拟机是把整个操作系统封装隔离,从而实现跨平台应用的话,那么容器则是把一个个应用单独封装隔离,从而实现跨平台应用。所以容器体积比虚拟机小很多,理论上占用资源更少。容器化就是应用程序级别的虚拟化技术。
JVM 之类的虚拟机:存在于函数库层和应用程序之间的虚拟化技术。Java 虚拟机同样具有跨平台特性,所谓跨平台特性实际上也就是虚拟化的功劳。我们知道 Java 语言是调用操作系统函数库的JVM就是在应用层与函数库层之间建立一个抽象层,对下通过不同的版本适应不同的操作系统函数库,比如说在Windows上运行,调用的是Windows上的API,在Linux上运行,调用的就是Linux上的API。
实现
主机虚拟化实现

主机虚拟化的原理是通过在物理服务器上安装一个虚拟化层来实现。这个虚拟化层可以在物理服务器和客户操作系统之间建立虚拟机,使得它们可以独立运行。从软件框架的角度上,根据虚拟化层是直接位于硬件之上还是在一个宿主操作系统之上,将虚拟化划分为Type1和Type2。
- Type1类的Hypervisor(Hypervisor是一种系统软件,它充当计算机硬件和虚拟机之间的中介,负责有效地分配和利用由各个虚拟机使用的硬件资源,这些虚拟机在物理主机上单独工作。
- Type2类的Hypervisor运行在一个宿主机操作系统之上或者系统里面,Hypervisor作为宿主机操作系统中的一个应用程序,客户机就是在宿主机操作系统上的一个进程。
容器虚拟化实现
原理
容器虚拟化是操作系统层面的虚拟化,有区别于主机的虚拟化,通过namespace进行各程序的隔离,加上cgroups进行资源的控制,以此来进行虚拟化。
也就是说,在启动一个进程的时候,将此进程需要的操作系统文件及进程的文件打包在一起,就是容器的虚拟化,根据配置文件来在主机上配置不同的系统,不同的资源,比如说进程A配置Ubuntu操作系统,规定只能使用2G内存,进程B配置centos操作系统,规定只能使用4G内存,这两个容器底层共用同一个内核。
namespace
namespace 是 Linux 内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。

隔离
命令
dd命令:dd命令用于从文件或者标准输入中读取数据,根据指定的格式转换数据,再将数据输出到文件,设备或者标准输出。
bash
dd option
比如说我想创建一个8M大小的文件:

mkfs命令:此命令用于在设备上创建Linux文件系统,也就是俗称的格式化。
bash
mkfs [-V] [-t fstype] [fs-options] filesys [blocks]
比如说我们想把一个文件格式化为一个磁盘:

df命令:此命令用于显示目前在Linux系统上文件系统磁盘使用情况统计。
bash
df [option]... [file]...
mount命令:用于加载文件系统到指定的加载点,比如说我们要往windows电脑上插入一个U盘,文管对话框里就会多出一个磁盘,这就是挂载,实质就是为磁盘添加入口,Linux上无法自动挂载。
bash
mount -l
mount [-t vfstype] [-o options] device dir
mount命令参数

unshare命令:主要能力是使用与父程序不共享命名空间运行程序。
bash
unshare [programs] option [argument]
-i, --ipc # 隔离 IPC 命名空间
-m, --mount # 隔离挂载命名空间
-n, --net # 隔离网络命名空间
-p, --pid # 隔离 PID 命名空间
-u, --uts # 隔离 UTS 命名空间
-U, --user # 隔离用户命名空间
-f, --fork # 创建子进程执行目标程序
--mount-proc # 在新 PID 命名空间中挂载 /proc 文件系统
-r, --map-root-user # 将当前用户映射为命名空间内的 root 用户
比如说修改一个主机名,子进程的主机名修改后,外部的主机名不会变化。

操作
pid隔离
- 在主机上执行ps -ef可以看到进程的列表如下。

- 打开另外一个shell,执行命令创建一个bash进程,并且新建一个namespace。
bash
unshare --fork --pid --mount-proc /bin/bash
--fork创建了一个bash进程,因为如果不创建新进程,创建的namespace就会用unshare的pid作为新的空间的父进程,而这个unshare进程并不会在namespace中,就会出现报错。

--pid表示我们隔离的是进程的pid,而其他的命名空间没有隔离。
--mount-proc用于给容器挂载/proc文件系统,因为Linux下每个进程都有一个/proc/PID目录,包含大量当前进程相关的信息,没有挂载这个目录,top/ps等依赖于/proc文件系统命令工作的命令就无法工作。
执行ps -ef可以发现,此时/proc下的进程内容已经变了,启动进程也变为bash进程,和主机上的进程空间隔离开了。

mount隔离
- 在shell窗口执行df -h,查看主机默认命名空间磁盘挂载情况。

- 执行mount隔离命令,和隔离pid相同的,这里也需要--fork为新的命名空间分配一个祖先进程。
bash
unshare --mount --fork /bin/bash
- 添加新的磁盘挂载。
bash
dd if=/dev/zero of=test.img bs=8k count=1024
mkfs -t ext4 ./test.img
mount ./test.img /data/tmpmount

可以看到在新的namespace里已经挂载上了/data/tmpmount,在执行echo "hello world" > /data/tmpmount/test.txt以后,在两个窗口执行ls指令,看到的文件系统是不一样的,表示mount已经发生了隔离。


cgroup
控制
命令
pidstat:pidstat用于监控全部或者指定进程的CPU,内存,线程,设备IO等系统资源占用的情况。
bash
pidstat [选项] [<时间间隔>] [<次数>]
如果需要安装此命令,可以使用以下命令:
bash
#卸载
apt remove sysstat -y
#安装
apt install sysstat -y
pidstat语法及参数
stress:是Linux的一个压力测试根据,可以对CPU,内存,IO和磁盘进行压力测试。
bash
stress [option [arg]]
操作
查看cgroups信息
- 查看cgroups版本。
bash
cat /proc/filesystems | grep cgroup

- cgroupv1和cgroupv2的查看
bash
#查看cgroupv1
cat /proc/cgroups
#查看cgroupv2
cat /sys/fs/cgroup/cgroup.controllers

- cgroups挂载信息
bash
cd /sys/fs/cgroup
mount | grep cgroup

- 查看一个进程上的cgroup限制
bash
#查看当前进程的cgroup
cat/proc/$$/cgroup
#找到自己cpu的目录,里面有对init进程的详细限制信息
ll /sys/fs/cgroup/user.slice/user-1001.slice/session-25400.scope

使用cgroup对内存进行控制
- 查看当前控制,确认memory是支持的。
bash
cat /sys/fs/cgroup/cgroup.controllers

- 创建cgroup子目录,目录里会自动分配需要的文件。

- 配置cgroup的策略为最大使用20M内存。

- 启动一个消耗内存的进程,分配50M的内存,将此进程的pid移动到cgroup策略。
bash
#启动进程
stress -m 1 --vm-bytes 50M
#查看进程id及状态,-r表示监控内存,-C stress表示监控进程名为stress的进程
#-p ALL表示监控的进程范围,1表示时间间隔为1s,10000表示持续10000次
pidstat -r -C stress -p ALL 1 10000
#将进程的id移动到cgroup策略
echo pid >> cgroup.procs

可以看到进程无法申请到足够内存退出

LXC
命令
lxc-checkconfig :用于检查系统环境是否满足容器的使用要求,没有参数。
lxc-create:用于创建lxc容器,-n表示容器的唯一名称,-t表示创建容器的模板名称,例如Ubuntu,debian等,option表示参数,例如模板的专属配置项,例如Ubuntu模板对应的参数可以是--release 22.04。
bash
lxc-create -n NAME -t TEMPLATE_NAME [--template-options]
lxc-start:用于启动容器,-n表示名字,-d表示后台启动容器。
bash
lxc-start -n NAME -d
lxc-ls:列出所有容器,-f表示打印常用的信息。
bash
lxc-ls -f
lxc-info:查看容器相关的信息,-n表示容器名字。
bash
lxc-info -n NAME
lxc-attach:用于进入对应的容器。
bash
lxc-attach -name=NAME[-Command]
lxc-stop:停止容器。
bash
lxc-stop -n NAME
lxc-destroy:删除处于停机状态的容器。
bash
lxc-destroy -n NAME
安装LXC
Ubuntu下安装:
bash
#检查是否安装,清理资源
systemctl status lxc
lxc-stop -n xxx
lxc-destroy -n xxx
#卸载软件
apt-get purge --auto-remove lxc lxc-templates
#安装软件
apt install lxc lxc-templates bridge-utils -y
#检查服务是否正常运行
systemctl status lxc
操作
- 检查lxc功能支持情况。
bash
lxc-checkconfig

- 查看lxc提供的容器模板
bash
ls /usr/share/lxc/templates

- 创建一个lxc虚拟主机。
bash
lxc-create -t ubuntu -name lxcname1 -- -r xenial -a amd64
#启动容器
lxc-start -n lxcname1

- 查看创建的容器信息。
bash
lxc-ls -f
- 查看容器的详细信息。
bash
lxc-info -n lxcname1

- 查看到容器的ip,通过ssh进入容器,查看对应的ip地址,磁盘挂载信息,目录信息等,和宿主机都不一样。
bash
ssh ubuntu@ip

- 在容器外执行命令。
bash
lxc-attach -n lxcname1 --clear-env --echo "hello world"

- 停止容器
bash
lxc-stop -n lxcname1
lxc-ls -f
- 删除容器
bash
lxc-destroy -n lxcname1
lxc-ls -f

