容器虚拟化实现
容器虚拟化实现原理
容器虚拟化,有别于主机虚拟化,是操作系统层的虚拟化。通过 namespace 进行各程序的隔离,加上 cgroups 进行资源的控制,以此来进行虚拟化。
容器虚拟化基础之 NameSpace
什么是 Namespace(命名空间)
namespace 是 Linux 内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。
Linux namespaces 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前namespace 里的进程,对其他 namespace 中的进程没有影响。
Linux 提供了多个 API 用来操作 namespace,它们是 clone()、setns() 和 unshare() 函数,为了确定隔离的到底是哪项 namespace,在使用这些 API 时,通常需要指定一些调用参数:CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWUSER、CLONE_NEWUTS 和CLONE_NEWCGROUP。
如果要同时隔离多个 namespace,可以使用 | (按位或)组合这些参数。
举个例子
三年一班的小明和三年二班的小明,虽说他们名字是一样的,但是所在班级不一样,那么,在全年级排行榜上面,即使出现两个名字一样的小明,也会通过各自的学号来区分。对于学校来说,每个班级就相当于是一个命名空间,这个空间的名称是班级号。班级号用于描述逻辑上的学生分组信息,至于什么学生分配到 1 班,什么学生分配到2 班,那就由学校层面来统一调度。
namespace | 系统调用参数 | 被隔离的全局系统资源 | 引入内核版本 |
---|---|---|---|
UTS | CLONE_NEWUTS | 主机名和域名 | 2.6.19 |
IPC | CLONE_NEWIPC | 信号量、消息队列和共享内存(进程间通信) | 2.6.19 |
PID | CLONE_NEWPID | 进程编号 | 2.6.24 |
Network | CLONE_NEWNET | 网络设备、网络栈、端口等 | 2.6.29 |
Mount | CLONE_NEWNS | 文件系统挂载点 | 2.4.19 |
User | CLONE_NEWUSER | 用户和用户组 | 3.8 |
以上命名空间在容器环境下的隔离效果:
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。
想想以下如果我们要隔离两个进程需要怎么办?
(1)首先容器进程与进程之间需要隔离,所以需要 PID 隔离
(2)首先容器 A 进程不能读取容器 B 进程通讯内容需要隔离信号量等,所以需要 IPC隔离
(3)首先容器 A 进程不能读取容器 B 进程的文件,所以需要 Mount 隔离
(4)首先容器 A 进程不能读取容器 B 进程的 socket,所以需要网络隔离、主机隔离
(5)Docker 允许用户在主机和容器间共享文件夹,同时不需要限制容器的访问权限,
这就容易让容器突破资源限制。需要借助用户空间来完成用户之间的隔离。
NameSpace 隔离实战
实战目的
了解隔离能力并不是 docker 提供的,而是操作系统内核提供基本能力。
基础知识
dd 命令
Linux dd 命令用于读取、转换并输出数据。
dd 可从标准输入或文件中读取数据,根据指定的格式来转换数据,再输出到文件、设备或标准输出。
• 语法
dd OPTION
• 参数
- if=文件名:输入文件名,默认为标准输入。即指定源文件。
- of=文件名:输出文件名,默认为标准输出。即指定目的文件。
- ibs=bytes:一次读入 bytes 个字节,即指定一个块大小为 bytes 个字节。
obs=bytes:一次输出 bytes 个字节,即指定一个块大小为 bytes 个字节。
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)字符补齐。 - ○ --help:显示帮助信息
- ○ --version:显示版本信息
案例
# 生成 1 个镜像文件
dd if=/dev/zero of=fdimage.img bs=8k count=10240
#将 testfile 文件中的所有英文字母转换为大写,然后转成为 testfile_1 文件
dd if=testfile of=testfile_1 conv=ucase
hx@LAPTOP-H2EI4I6A:~/first$ echo "hello world!" > test.txt
hx@LAPTOP-H2EI4I6A:~/first$ cat test.txt
hello world!
hx@LAPTOP-H2EI4I6A:~/first$ dd if=test.txt of=test2.txt conv=ucase
0+1 records in
0+1 records out
13 bytes copied, 7.902e-05 s, 165 kB/s
hx@LAPTOP-H2EI4I6A:~/first$ ll
total 16
drwxr-xr-x 2 hx hx 4096 Jun 25 13:38 ./
drwxr-xr-x 7 hx hx 4096 Jun 25 13:36 ../
-rw-r--r-- 1 hx hx 13 Jun 25 13:37 test.txt
-rw-r--r-- 1 hx hx 13 Jun 25 13:38 test2.txt
hx@LAPTOP-H2EI4I6A:~/first$ cat test2.txt
HELLO WORLD!
mkfs 命令详解
用于在设备上创建 Linux 文件系统,俗称格式化,比如我们使用 U 盘的时候可以格式化。
• 语法
mkfs [-V] [-t fstype] [fs-options] filesys [blocks]
• 参数
-t fstype:指定要建立何种文件系统;如 ext3,ext4
filesys :指定要创建的文件系统对应的设备文件名;
blocks:指定文件系统的磁盘块数。
-V : 详细显示模式
fs-options:传递给具体的文件系统的参数
实例
#将 sda6 分区格式化为 ext4 格式
mkfs -t ext4 /dev/sda6
#格式化镜像文件为 ext4
mkfs -t ext4 ./fdimage.img
df 命令详解
Linux df(英文全拼:disk free) 命令用于显示目前在 Linux 系统上的文件系统磁盘使用情况统计。
• 语法
df [OPTION]... [FILE]...
• 常见参数
○ -a, --all 包含所有的具有 0 Blocks 的文件系统
○ -h, --human-readable 使用人类可读的格式(预设值是不加这个选项的…)
○ -H, --si 很像 -h, 但是用 1000 为单位而不是用 1024
○ -t, --type=TYPE 限制列出文件系统的 TYPE
○ -T, --print-type 显示文件系统的形式
• 案例
#查看磁盘使用情况
df -h
#查看磁盘的系统类型
df -Th
hx@LAPTOP-H2EI4I6A:/dev$ df -h
Filesystem Size Used Avail Use% Mounted on
none 3.8G 4.0K 3.8G 1% /mnt/wsl
drivers 100G 85G 16G 85% /usr/lib/wsl/drivers
none 3.8G 0 3.8G 0% /usr/lib/modules
none 3.8G 0 3.8G 0% /usr/lib/modules/5.15.153.1-microsoft-standard-WSL2
/dev/sdc 251G 2.4G 236G 1% /
none 3.8G 76K 3.8G 1% /mnt/wslg
none 3.8G 0 3.8G 0% /usr/lib/wsl/lib
rootfs 3.8G 2.1M 3.8G 1% /init
none 3.8G 0 3.8G 0% /dev
none 3.8G 872K 3.8G 1% /run
none 3.8G 0 3.8G 0% /run/lock
none 3.8G 0 3.8G 0% /run/shm
tmpfs 3.8G 0 3.8G 0% /sys/fs/cgroup
none 3.8G 76K 3.8G 1% /mnt/wslg/versions.txt
none 3.8G 76K 3.8G 1% /mnt/wslg/doc
C:\ 100G 85G 16G 85% /mnt/c
D:\ 376G 221G 155G 59% /mnt/d
snapfuse 64M 64M 0 100% /snap/core20/1891
snapfuse 92M 92M 0 100% /snap/lxd/24061
snapfuse 54M 54M 0 100% /snap/snapd/19122
hx@LAPTOP-H2EI4I6A:/dev$ df -Th
Filesystem Type Size Used Avail Use% Mounted on
none tmpfs 3.8G 4.0K 3.8G 1% /mnt/wsl
drivers 9p 100G 85G 16G 85% /usr/lib/wsl/drivers
none tmpfs 3.8G 0 3.8G 0% /usr/lib/modules
none overlay 3.8G 0 3.8G 0% /usr/lib/modules/5.15.153.1-microsoft-standard-WSL2
/dev/sdc ext4 251G 2.4G 236G 1% /
none tmpfs 3.8G 76K 3.8G 1% /mnt/wslg
none overlay 3.8G 0 3.8G 0% /usr/lib/wsl/lib
rootfs rootfs 3.8G 2.1M 3.8G 1% /init
none devtmpfs 3.8G 0 3.8G 0% /dev
none tmpfs 3.8G 872K 3.8G 1% /run
none tmpfs 3.8G 0 3.8G 0% /run/lock
none tmpfs 3.8G 0 3.8G 0% /run/shm
tmpfs tmpfs 3.8G 0 3.8G 0% /sys/fs/cgroup
none overlay 3.8G 76K 3.8G 1% /mnt/wslg/versions.txt
none overlay 3.8G 76K 3.8G 1% /mnt/wslg/doc
C:\ 9p 100G 85G 16G 85% /mnt/c
D:\ 9p 376G 221G 155G 59% /mnt/d
snapfuse fuse.snapfuse 64M 64M 0 100% /snap/core20/1891
snapfuse fuse.snapfuse 92M 92M 0 100% /snap/lxd/24061
snapfuse fuse.snapfuse 54M 54M 0 100% /snap/snapd/19122
在WSL2 中安装的Ubuntu甚至还能看到C盘和D盘的使用情况
mount 命令详解
mount 命令用于加载文件系统到指定的加载点。此命令的也常用于挂载光盘,使我们可以访问光盘中的数据,因为你将光盘插入光驱中,Linux 并不会自动挂载,必须使用Linux mount 命令来手动完成挂载。
Linux 系统下不同目录可以挂载不同分区和磁盘设备,它的目录和磁盘分区是分离的,可以自由组合(通过挂载)
不同的目录数据可以跨越不同的磁盘分区或者不同的磁盘设备。
挂载的实质是为磁盘添加入口(挂载点)。
• mount 常见用法
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: 挂载点的目录
• 案例
#将 /dev/hda1 挂在 /mnt 之下。
mount /dev/hda1 /mnt
#将镜像挂载到/mnt/testext4 下面,需要确保挂载点也就是目录存在
mkdir -p /mnt/testext4
mount ./fdimage.img /mnt/testext4
unshare 命令详解
unshare 主要能力是使用与父程序不共享的名称空间运行程序。
• 语法
unshare [options] program [arguments]
• 常用参数
案例
#hostname 隔离
root@LAPTOP-H2EI4I6A:~# unshare -u /bin/bash
root@LAPTOP-H2EI4I6A:~# hostname test1
root@LAPTOP-H2EI4I6A:~# hostname
test1
root@LAPTOP-H2EI4I6A:~# exit
exit
root@LAPTOP-H2EI4I6A:~# hostname
LAPTOP-H2EI4I6A
实战操作一(PID 隔离)
- 在主机上执行 ps -ef,可以看到进程列表如下,其中启动进程 PID 1 为 init 进程
- 我们打开另外一个 shell ,执行下面命令创建一个 bash 进程,并且新建一个 PID Namespace:
–fork 新建了一个 bash 进程,是因为如果不建新进程,新的 namespace 会用 unshare的 PID 作为新的空间的父进程,而这个 unshare 进程并不在新的 namespace 中,所以会报个错 Cannot allocate memory
–pid 表示我们的进程隔离的是 pid,而其他命名空间没有隔离
–mount-proc 是因为 Linux 下的每个进程都有一个对应的 /proc/PID 目录,该目录包含了大量的有关当前进程的信息。 对一个 PID namespace 而言,/proc 目录只包含当前namespace 和它所有子孙后代 namespace 里的进程的信息。创建一个新的 PID namespace 后,如果想让子进程中的 top、ps 等依赖 /proc 文件系统的命令工作,还需要挂载 /proc 文件系统。而文件系统隔离是 mount namespace 管理的,所以 linux特意提供了一个选项–mount-proc 来解决这个问题。如果不带这个我们看到的进程还是系统的进程信息。
unshare --fork --pid --mount-proc /bin/bash
- 执行 ps -ef 查看进程信息,我们可以看到此时进程空间内的内容已经变了,而且启动进程也变成了我们的 bash 进程。
说明我们已经看不到主机上的进程空间了,我们的进程空间发生了隔离。
4.执行 exit 退出进程
exit
实战操作二(Mount 隔离)
- 打开第一个 shell 窗口 A,执行命令, df -h ,查看主机默认命名空间的磁盘挂载情况
- 打开一个新的 shell 窗口 B,执行 Mount 隔离命令
# --mount 表示我们要隔离 Mount 命名空间了
# --fork 表示新建进程
unshare --mount --fork /bin/bash
mkdir -p /data/tmpmount
- 在窗口 B 中添加新的磁盘挂载
dd if=/dev/zero of=fdimage.img bs=8k count=10240
mkfs -t ext4 ./fdimage.img
mount ./fdimage.img /data/tmpmount
- 在窗口 B 挂载的磁盘中添加文件
echo "Hello world!" > /data/tmpmount/hello.txt
- 查看窗口 B 中的磁盘挂载信息
- 查看窗口 A 中的磁盘挂载信息
- 查看窗口 B 中的文件信息
root@LAPTOP-H2EI4I6A:/home/hacha# ll /data/tmpmount/
total 28
drwxr-xr-x 3 root root 4096 Oct 18 12:59 ./
drwxr-xr-x 3 root root 4096 Oct 18 12:58 ../
-rw-r--r-- 1 root root 13 Oct 18 12:59 hello.txt
drwx------ 2 root root 16384 Oct 18 12:58 lost+found/
root@LAPTOP-H2EI4I6A:/home/hacha# cat /data/tmpmount/hello.txt
Hello world!
- 查看窗口 A 中的文件信息,可以看到窗口 B 中新建的文件和磁盘挂载在主机的窗口中并没有,说明我们实现了文件系统隔离
hacha@LAPTOP-H2EI4I6A:~$ ll /data/tmpmount/
total 8
drwxr-xr-x 2 root root 4096 Oct 18 12:58 ./
drwxr-xr-x 3 root root 4096 Oct 18 12:58 ../
hacha@LAPTOP-H2EI4I6A:~$ cat /data/tmpmount/hello.txt
cat: /data/tmpmount/hello.txt: No such file or directory
- 窗口 B 执行 exit,退出
exit
容器虚拟化基础之 cgroups
什么是 cgroups
cgroups(Control Groups) 是 linux 内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。
简单说,cgroups 可以限制、记录任务组所使用的物理资源。本质上来说,cgroups 是内核附加在程序上的一系列钩子(hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。
为什么使用 cgroups
其可以做到对 cpu,内存等资源实现精细化的控制,目前越来越火的轻量级容器
Docker 及 k8s 中的 pod 就使用了 cgroups 提供的资源限制能力来完成 cpu,内存等部分的资源控制。
比如在一个既部署了前端 web 服务,也部署了后端计算模块的八核服务器上,可以使用 cgroups 限制 web server 仅可以使用其中的六个核,把剩下的两个核留给后端计算模块。
cgroups 的用途
Resource limitation: 限制资源使用,例:内存使用上限/cpu 的使用限制
Prioritization: 优先级控制,例:CPU 利用/磁盘 IO 吞吐
Accounting: 一些审计或一些统计
Control: 挂起进程/恢复执行进程
cgroups 可以控制的子系统
名称 | 描述 |
---|---|
blkio | 对块设备的 IO 进行限制。 |
cpu | 限制 CPU 时间片的分配。 |
cpuacct | 生成 cgroup 中的任务占用 CPU 资源的报告,与 cpu 挂载在同一目录。 |
cpuset | 给 cgroup 中的任务分配独立的 CPU(多处理器系统) 和内存节点。 |
devices | 限制设备文件的创建,和对设备文件的读写。 |
freezer | 暂停/恢复 cgroup 中的任务。 |
memory | 对 cgroup 中的任务的可用内存进行限制,并自动生成资源占用报告。 |
perf_event | 允许 perf 观测 cgroup 中的 task。 |
net_cls | cgroup 中的任务创建的数据报文的类别标识符,这让 Linux 流量控制器(tc 指令)可以识别来自特定 cgroup 任务的数据包,并进行网络限制。 |
hugetlb | 限制使用的内存页数量。 |
pids | 限制任务的数量。 |
rdma | 限制 RDMA 资源(Remote Direct Memory Access,远程直接数据存取)。 |
资源控制实战
CGroups 资源控制实战
实战目的
能够查看 cgroups 基本信息,了解容器化中,操作系统是真正的资源控制层,对 cpu和内存的控制有一定的理解。
基础知识
pidstat
• 概述
pidstat 是 sysstat 的一个命令,用于监控全部或指定进程的 CPU、内存、线程、设备IO 等系统资源的占用情况。Pidstat 第一次采样显示自系统启动开始的各项统计信息,后续采样将显示自上次运行命令后的统计信息。用户可以通过指定统计的次数和时间来获得所需的统计信息。
• 语法
pidstat [ 选项 ] [ <时间间隔> ] [ <次数> ]
• 参数
-u:默认参数,显示各进程的 CPU 使用统计
-r:显示各进程的内存使用统计
-d:显示各进程的 IO 使用情况
-p:指定进程号,ALL 表示所有进程
-C:指定命令
-l:显示命令名和所有参数
• 安装
Ubuntu 安装
#卸载
apt remove sysstat -y
#安装
apt install sysstat -y
CentOS 安装
#卸载
yum remove sysstat -y
#安装
yum install sysstat -y
stress
• 概述
stress 是 Linux 的一个压力测试工具,可以对 CPU、Memory、IO、磁盘进行压力测试。
• 语法
stress [OPTION [ARG]]
• 参数
-c, --cpu N
:产生 N 个进程,每个进程都循环调用 sqrt 函数产生 CPU 压力。
-i, --io N
:产生 N 个进程,每个进程循环调用 sync 将内存缓冲区内容写到磁盘上,产生 IO 压力。通过系统调用 sync 刷新内存缓冲区数据到磁盘中,以确保同步。如果缓冲区内数据较少,写到磁盘中的数据也较少,不会产生 IO 压力。在 SSD 磁盘环境中尤为明显,很可能 iowait 总是 0,却因为大量调用系统调用 sync,导致系统 CPU 使用率 sys 升高。
-m, --vm N
:产生 N 个进程,每个进程循环调用 malloc/free 函数分配和释放内存。
--vm-bytes B
:指定分配内存的大小
--vm-keep
:一直占用内存,区别于不断的释放和重新分配(默认是不断释放并重新分配内存)
-d, --hdd N
:产生 N 个不断执行 write 和 unlink 函数的进程(创建文件,写入内容,删除文件)
--hdd-bytes B
:指定文件大小
-t, --timeout N
:在 N 秒后结束程序
-q, --quiet
:程序在运行的过程中不输出信息
• 安装
Ubuntu:
#卸载
apt remove stress -y
#安装
apt install stress -y
CentOS:
#卸载
yum remove stress -y
#安装
yum install stress -y
实战操作一(cgroups 信息查看)
cgroups 版本查看
hacha@LAPTOP-H2EI4I6A:~$ cat /proc/filesystems |grep cgroup
nodev cgroup
nodev cgroup2
如果看到 cgroup2,表示支持 cgroup v2
cgroups 子系统查看
cgroups 挂载信息查看
可以看到默认存储位置为 /sys/fs/cgroup
查看一个进程上的 cgroup 限制
- 以当前 shell 进程为例,查看进程的 cgroup
- 比如 cpu 在 user.slice,我们可以找到这个目录,里面有对 init 进程的详细限制信息
实战操作二(使用 cgroups 对内存进行控制)
- 创建内存的 cgroup,很简单我们进入到 cgroup 的内存控制目录
/sys/fs/cgroup/memory,我们创建目录 test_memory
hacha@LAPTOP-H2EI4I6A:/sys/fs/cgroup/memory$ sudo mkdir test_memory
- 可以看到内存限制文件已经自动在 test_memory 中创建完成了,cgroups 文件系统会在创建文件目录的时候自动创建相应的配置文件
- 配置 cgroup 的策略为最大使用 20M 内存
root@LAPTOP-H2EI4I6A:/sys/fs/cgroup/memory# expr 20 \* 1024 \* 1024
20971520
root@LAPTOP-H2EI4I6A:/sys/fs/cgroup/memory# cat test_memory/memory.limit_in_bytes
9223372036854771712
root@LAPTOP-H2EI4I6A:/sys/fs/cgroup/memory# echo "20971520" > test_memory/memory.limit_in_bytes
root@LAPTOP-H2EI4I6A:/sys/fs/cgroup/memory# cat test_memory/memory.limit_in_bytes
20971520
- 启动 1 个消耗内存的进程,每个进程占用 50M 内存
root@LAPTOP-H2EI4I6A:/sys/fs/cgroup# stress -m 1 --vm-bytes 50M
stress: info: [23901] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
- 打开一个新的 shell 窗口 B 窗口,使用 pidstat 查看状态,红色为进程 id
root@LAPTOP-H2EI4I6A:/home/hacha# pidstat -r -C stress -p ALL 1 10000
Linux 5.15.153.1-microsoft-standard-WSL2 (LAPTOP-H2EI4I6A) 10/18/24 _x86_64_ (16 CPU)
14:28:51 UID PID minflt/s majflt/s VSZ RSS %MEM Command
14:28:52 0 23901 0.00 0.00 3864 960 0.01 stress
14:28:52 0 23902 73410.00 0.00 55068 26788 0.34 stress
14:28:52 UID PID minflt/s majflt/s VSZ RSS %MEM Command
14:28:53 0 23901 0.00 0.00 3864 960 0.01 stress
14:28:53 0 23902 72348.00 0.00 55068 2212 0.03 stress
- 打开一个新的 shell C 窗口,将进程 id 【这里我的进程ID是23902】移动到我们的 cgroup 策略
cd /sys/fs/cgroup/memory
echo 23902 >> test_memory/tasks
- 可以看到进程无法申请到足够内存退出
可以看到进程消失了
实战操作三(使用 cgroups 对 cpu 进行控制)
- 创建内存的 cgroup,很简单我们进入到 cgroup 的内存控制目录/sys/fs/cgroup/cpu,我们创建目录 test_cpu,
可以看到系统会自动为我们创建 cgroup的 cpu 策略
- 打开新的 shell 窗口 B 窗口,使用 stress 模拟一个任务,cpu 使用率为 100
root@139-159-150-152:/sys/fs/cgroup# stress -c 1
stress: info: [62576] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
- 可以看到 cpu 的使用率为 100%
- 打开新的 shell 窗口 C 窗口,我们设置 cproup 的 cpu 使用率为 30%,cpu 使用率的计算公式 cfs_quota_us/cfs_period_us
1)cfs_period_us:cfs_period_us 表示一个 cpu 带宽,单位为微秒。系统总 CPU 带宽 ,默认值 100000。
2)cfs_quota_us:cfs_quota_us 表示 Cgroup 可以使用的 cpu 的带宽,单位为微秒。
cfs_quota_us 为-1,表示使用的 CPU 不受 cgroup 限制。cfs_quota_us 的最小值为1ms(1000),最大值为 1s。
所以我们将 cfs_quota_us 的值设置为 30000 ,从理论上讲就可以限制 test_cpu 控制的进程的 cpu 利用率最多是 30%
cd /sys/fs/cgroup/cpu
echo 30000 > test_cpu/cpu.cfs_quota_us
- 我们可以看到进程的 PID 为 62577,我们将该进程放到 tasks 文件进行控制
cd /sys/fs/cgroup/cpu
echo 62577 > test_cpu/tasks
- B 窗口中可以看到我们监控的 cpu 的使用率由 100%降低为 30%
至此我们成功的模拟了对内存和 cpu 的使用控制,而 docker 本质也是调用这些的 API来完成对资源的管理,只不过 docker 的易用性和镜像的设计更加人性化,所以 docker才能风靡全球,经过docker的学习后我们会看下 docker 如何对资源控制对比这种控制可以说简单不止一倍。
容器虚拟化基础之 LXC
LXC 是什么?
LXC(LinuX Containers)Linux 容器,一种操作系统层虚拟化技术,为 Linux 内核容器功能的一个用户空间接口。它将应用软件系统打包成一个软件容器(Container),内含应用软件本身的代码,以及所需要的操作系统核心和库。透过统一的名字空间和共享 API 来分配不同软件容器的可用硬件资源,创造出应用程序的独立沙箱运行环境,使得 Linux 用户可以容易的创建和管理系统或应用容器。
LXC 是最早一批真正把完整的容器技术用一组简易使用的工具和模板来极大的简化了容器技术使用的一个方案
LXC 虽然极大的简化了容器技术的使用,但比起直接通过内核调用来使用容器技术,其复杂程度其实并没有多大降低,因为我们必须要学会 LXC 的一组命令工具,且由于内核的创建都是通过命令来实现的,通过批量命令实现数据迁移并不容易。其隔离性也没有虚拟机那么强大。
后来就出现了 docker,所以从一定程度上来说,docker 就是 LXC 的增强版。
LXC 容器操作实战
实战目的
通过 lxc 来完成容器的创建,体会容器,并了解 docker 并不是容器的唯一实现。自docker 0.9 版本起,docker 除了继续支持 LXC 外,还开始引入自家的 libcontainer,试图打造更通用的底层容器虚拟化库。如今的 docker 基本上都已经是使用libcontainer 而非 LXC 了。
基础知识
LXC 的常用命令如下:
lxc-checkconfig
检查系统环境是否满足容器使用要求;
格式:lxc-checkconfig
lxc-create
创建 lxc 容器;
格式:lxc-create -n NAME -t TEMPLATE_NAME [-- template-options]
lxc-start
启动容器;
格式:lxc-start -n NAME -d
lxc-ls
列出所有容器,-f 表示打印常用的信息 ;
格式:lxc-ls -f
lxc-info
查看容器相关的信息;
格式:lxc-info -n NAME
lxc-attach
进入容器执行命令;
格式:lxc-attach --name=NAME [-- COMMAND]
lxc-stop
停止容器;
格式:lxc-stop -n NAME
lxc-destory
删除处于停机状态的容器;
格式:lxc-destory -n NAME
安装 LXC
Ubuntu 安装
安装前执行检查看下是否需要卸载,如果需要卸载,执行下面的命令完成卸载,不需要直接到第 2 步
# 一、检查是否安装。清理资源
systemctl status lxc
lxc-stop -n xxx # lxc-ls -f 遍历所有容器,停止运行的容器
lxc-destroy -n xxx # 删除对应的容器
# 二、 卸载软件
apt-get purge --auto-remove lxc lxc-templates
# 三、 检查服务已经没有该服务了
systemctl status lxc
没有安装的话,执行下面的命令完成安装
#一、安装
#lxc 主程序包
#lxc-templates lxc 的配置模板
#bridge-utils 网桥管理工具
apt install lxc lxc-templates bridge-utils -y
#二、检查服务是否正常运行
systemctl status lxc
CentOS 安装
安装前执行检查看下是否需要卸载,如果需要卸载,执行下面的命令完成卸载,不需要直接到第 2 步
# 一、检查是否安装。清理资源
systemctl status lxc #检查是否安装
lxc-stop -n xxx # lxc-ls -f 遍历所有容器,停止对应的容器
lxc-destroy -n xxx #删除对应的容器
# 二、 卸载软件
yum remove lxc lxc-templates lxc-libs lxc-extra libvirt debootstrap
# 三、检查,提示服务不存在
systemctl status lxc
CentOS 安装 LXC,如果已经安装,可以检查下是否需要卸载,如果需要卸载执行Centos 卸载 LXC
# 一、 配置源
yum -y install epel-release #这个软件包里包含 epel yum 源和GPG 的配置
# 二、 安装程序
# lxc 主程序包
# lxc-templates lxc 的配置模板
# bridge-utils 网桥管理工具 lxc-libs lxc 所需的库文件
# libcgroup cgroup 安装包
# libvirt 管理 Linux 的虚拟化功能所需的服务器端守护程序。 需要针对特定驱
动程序的管理程序。
# debootstrap debootstrap 是 Debian 引导程序,它允许您将 Debian 基本系统
(例如 Debian 或 Ubuntu)安装到当前正在运行的系统的目录中。
yum -y install lxc lxc-templates bridge-utils lxc-libs libcgroup
libvirt lxc-extra debootstrap
#三、启动和检查
#如果未运行输入以下命令完成启动
systemctl start lxc #启动 lxc 服务
systemctl start libvirtd #启动虚拟机监控服务
systemctl status lxc
systemctl status libvirtd
LXC 容器操作实战
- 检查 lxc 是否运行
- 检查 lxc 的功能支持情况
- 查看 lxc 提供的容器模板
- 创建一个 lxc 虚拟主机,这个命令就会下载安装指定环境下的软件包,创建新容器。
整个过程需要时间较长,与容器的类型有关。
#创建 LXC 主机,-t 指定模板容器,-n 指定要创建的主机名,下面创建的是
ubuntu,centos 的命令类似(lxc-create -t centos --name centos1 -- --release 7 --arch x86_64)
root@LAPTOP-H2EI4I6A:/home/hacha# lxc-create -t ubuntu --name lxchost1 -- -r xenial -a amd64
#
#创建完成显示
##
# The default user is 'ubuntu' with password 'ubuntu'!
# Use the 'sudo' command to run tasks as root in the container.
##
- 下载安装完所有软件包后,LXC 容器镜像就创建完成了,你可以看到默认的登录界面。容器被放到 /var/lib/lxc/<容器名> 这个目录下,容器的根文件系统放在/var/lib/lxc/<容器名>/rootfs 目录下。创建过程中下载的软件包保存在 /var/cache/lxc 目录下面,当你想另外建一个一样的容器时,可以省去很多下载时间。
root@LAPTOP-H2EI4I6A:/home/hacha# ll /var/lib/lxc/lxchost1/
total 16
drwxrwx--- 3 root root 4096 Oct 18 15:28 ./
drwx------ 3 root root 4096 Oct 18 15:28 ../
-rw-r----- 1 root root 155 Oct 18 15:28 config
---------- 1 root root 0 Oct 18 15:28 partial
drwxr-xr-x 2 root root 4096 Oct 18 15:28 rootfs/
root@LAPTOP-H2EI4I6A:/home/hacha# ll /var/cache/lxc/
total 12
drwx------ 3 root root 4096 Oct 18 15:28 ./
drwxr-xr-x 13 root root 4096 Oct 18 15:23 ../
drwxr-xr-x 2 root root 4096 Oct 18 15:44 xenial/
- 查看创建的容器信息。
root@LAPTOP-H2EI4I6A:/home/hacha# lxc-ls -f
NAME STATE AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED
lxchost1 STOPPED 0 - - - false
- 启动容器,我们可以看到容器状态为运行中
root@139-159-150-152:/var/run/docker/netns# lxc-start -n lxchost1 -d
root@139-159-150-152:/var/run/docker/netns# lxc-ls -f
NAME STATE AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED
lxchost1 RUNNING 0 - 10.0.3.248 - false
- 查看容器的详细信息
root@139-159-150-152:/var/run/docker/netns# lxc-info -n lxchost1
Name: lxchost1
State: RUNNING
PID: 282127
IP: 10.0.3.248
CPU use: 0.59 seconds
BlkIO use: 29.45 MiB
Memory use: 59.52 MiB
KMem use: 6.82 MiB
Link: vethbg8LKH
TX bytes: 1.73 KiB
RX bytes: 6.61 KiB
Total bytes: 8.33 KiB
- 容器 ip 为 10.0.3.248,我们通过 ssh 进入容器,查看 ip 地址,磁盘挂载信息,目录信息和宿主机都不一样
root@139-159-150-152:/var/run/docker/netns# ssh ubuntu@10.0.3.248
ubuntu@10.0.3.248's password:
ubuntu@lxchost1:~$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state
UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0@if562: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc
noqueue state UP group default qlen 1000
link/ether 00:16:3e:71:d8:3d brd ff:ff:ff:ff:ff:ff linknetnsid 0
inet 10.0.3.248/24 brd 10.0.3.255 scope global dynamic eth0
valid_lft 2844sec preferred_lft 2844sec
inet6 fe80::216:3eff:fe71:d83d/64 scope link
valid_lft forever preferred_lft forever
ubuntu@lxchost1:~$ uname -a
Linux lxchost1 5.4.0-100-generic #113-Ubuntu SMP Thu Feb 3
18:43:29 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
ubuntu@lxchost1:~$ ll /
total 52
drwxr-xr-x 17 root root 4096 Mar 17 03:51 ./
drwxr-xr-x 17 root root 4096 Mar 17 03:51 ../
lrwxrwxrwx 1 root root 7 Mar 17 03:40 bin -> usr/bin/
drwxr-xr-x 2 root root 4096 Apr 15 2020 boot/
drwxr-xr-x 7 root root 540 Mar 17 03:51 dev/
drwxr-xr-x 63 root root 4096 Mar 17 03:51 etc/
drwxr-xr-x 3 root root 4096 Mar 17 03:44 home/
lrwxrwxrwx 1 root root 7 Mar 17 03:40 lib -> usr/lib/
lrwxrwxrwx 1 root root 9 Mar 17 03:40 lib32 -> usr/lib32/
lrwxrwxrwx 1 root root 9 Mar 17 03:40 lib64 -> usr/lib64/
lrwxrwxrwx 1 root root 10 Mar 17 03:40 libx32 -> usr/libx32/
drwxr-xr-x 2 root root 4096 Mar 17 03:40 media/
drwxr-xr-x 2 root root 4096 Mar 17 03:40 mnt/
drwxr-xr-x 2 root root 4096 Mar 17 03:40 opt/
dr-xr-xr-x 225 root root 0 Mar 17 03:51 proc/
drwx------ 2 root root 4096 Mar 17 03:40 root/
drwxr-xr-x 13 root root 420 Mar 17 04:04 run/
lrwxrwxrwx 1 root root 8 Mar 17 03:40 sbin -> usr/sbin/
drwxr-xr-x 2 root root 4096 Mar 17 03:40 srv/
dr-xr-xr-x 13 root root 0 Mar 17 03:51 sys/
drwxrwxrwt 9 root root 4096 Mar 17 04:04 tmp/
drwxr-xr-x 13 root root 4096 Mar 17 03:40 usr/
drwxr-xr-x 11 root root 4096 Mar 17 03:40 var/
ubuntu@lxchost1:~$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 21G 17G 57% /
none 492K 4.0K 488K 1% /dev
tmpfs 992M 0 992M 0% /dev/shm
tmpfs 199M 108K 199M 1% /run
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 992M 0 992M 0% /sys/fs/cgroup
tmpfs 199M 0 199M 0% /run/user/1000
ubuntu@lxchost1:~$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 03:51 ? 00:00:00 /sbin/init
root 44 1 0 03:51 ? 00:00:00
/lib/systemd/systemd-journald
systemd+ 72 1 0 03:51 ? 00:00:00
/lib/systemd/systemd-networkd
root 76 1 0 03:51 ? 00:00:00 /usr/sbin/cron
-f
message+ 77 1 0 03:51 ? 00:00:00 /usr/bin/dbusdaemon --system --address=systemd: --nofork --nopidfile --systemd-
activation --syslog-only
root 79 1 0 03:51 ? 00:00:00
/usr/bin/python3 /usr/bin/networkd-dispatcher --run-startuptriggers
syslog 80 1 0 03:51 ? 00:00:00
/usr/sbin/rsyslogd -n -iNONE
root 81 1 0 03:51 ? 00:00:00
/lib/systemd/systemd-logind
systemd+ 82 1 0 03:51 ? 00:00:00
/lib/systemd/systemd-resolved
root 88 1 0 03:51 pts/0 00:00:00 /sbin/agetty -
o -p -- \u --noclear --keep-baud console 115200,38400,9600 vt220
root 89 1 0 03:51 ? 00:00:00 sshd:
/usr/sbin/sshd -D [listener] 0 of 10-100 startups
root 163 89 0 04:03 ? 00:00:00 sshd: ubuntu
[priv]
ubuntu 166 1 0 04:04 ? 00:00:00
/lib/systemd/systemd --user
ubuntu 167 166 0 04:04 ? 00:00:00 (sd-pam)
ubuntu 182 163 0 04:04 ? 00:00:00 sshd:
ubuntu@pts/5
ubuntu 183 182 0 04:04 pts/5 00:00:00 -bash
ubuntu 196 183 0 04:04 pts/5 00:00:00 ps -ef
- 在容器外面执行命令
root@139-159-150-152:/var/run/docker/netns# lxc-attach -n lxchost1
--clear-env -- echo "Hello bit"
Hello bit
- 停止容器
root@139-159-150-152:/var/run/docker/netns# lxc-stop -n lxchost1
root@139-159-150-152:/var/run/docker/netns# lxc-ls -f
NAME STATE AUTOSTART GROUPS IPV4 IPV6 UNPRIVILEGED
lxchost1 STOPPED 0 - - - false
- 删除容器
root@139-159-150-152:/var/run/docker/netns# lxc-destroy -n lxchost1
root@139-159-150-152:/var/run/docker/netns# lxc-ls -f
root@139-159-150-152:/var/run/docker/netns#