隔离与限制
虚拟化技术
使用虚拟化技术作为应用沙盒,就必须要由 Hypervisor 来负责创建虚拟机,这个虚拟机是真实存在的,并且它里面必须运行一个完整的 Guest OS 才能执行用户的应用进程。这就不可避免地带来了额外的资源消耗和占用。
实验,一个运行着 CentOS 的 KVM 虚拟机启动后,在不做优化的情况下,虚拟机自己就需要占用 100~200 MB 内存。此外,用户应用运行在虚拟机里面,它对宿主机操作系统的调用就不可避免地要经过虚拟化软件的拦截和处理,这本身又是一层性能损耗,尤其对计算资源、网络和磁盘 I/O 的损耗非常大。
容器化
相比之下,容器化后的用户应用,却依然还是一个宿主机上的普通进程,这就意味着这些因为虚拟化而带来的性能损耗都是不存在的;而另一方面,使用 Namespace 作为隔离手段的容器并不需要单独的 Guest OS,这就使得容器额外的资源占用几乎可以忽略不计。
优点:
“敏捷”和“高性能”是容器相较于虚拟机最大的优势,也是它能够在 PaaS 这种更细粒度的资源管理平台上大行其道的重要原因。
缺点:
基于 Linux Namespace 的隔离机制相比于虚拟化技术也有很多不足之处,其中最主要的问题就是:隔离得不彻底。
-
容器只是运行在宿主机上的一种特殊的进程,那么多个容器之间使用的就还是同一个宿主机的操作系统内核。
-
在 Linux 内核中,有很多资源和对象是不能被 Namespace 化的,最典型的例子就是:时间。
-
由于上述问题,尤其是共享宿主机内核的事实,容器给应用暴露出来的攻击面是相当大的,应用“越狱”的难度自然也比虚拟机低得多。
容器的限制
为什么还需要对容器做“限制”呢?
以 PID Namespace 为例,来给你解释这个问题。虽然容器内的第 1 号进程在“障眼法”的干扰下只能看到容器里的情况,但是宿主机上,它作为第 100 号进程与其他所有进程之间依然是平等的竞争关系。这就意味着,虽然第 100 号进程表面上被隔离了起来,但是它所能够使用到的资源(比如 CPU、内存),却是可以随时被宿主机上的其他进程(或者其他容器)占用的。当然,这个 100 号进程自己也可能把所有资源吃光。这些情况,显然都不是一个“沙盒”应该表现出来的合理行为。
Linux Cgroups 的全称是 Linux Control Group。
作用:限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。此外,Cgroups 还能够对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。
在 Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。
[15:50:24 root@lyp-node pids]#mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
结果是一系列文件系统目录。如果你在自己的机器上没有看到这些目录,那你就需要自己去挂载 Cgroups
可以看到,在、/sys/fs/cgroup
下面有很多诸如 cpuset、cpu、memory这样的子目录,也叫子系统,这些都是cgroups进行限制的资源种类。在子系统对应的资源种类下,可以看到该类资源具体可以被限制的方法。
例如cpu
[16:14:31 root@lyp-node cpu]#pwd
/sys/fs/cgroup/cpu
[16:14:45 root@lyp-node cpu]#ls -la
total 0
drwxr-xr-x 6 root root 0 Jan 26 14:42 .
drwxr-xr-x 13 root root 340 Jan 26 14:42 ..
-rw-r--r-- 1 root root 0 Jan 26 14:42 cgroup.clone_children
--w--w--w- 1 root root 0 Jan 26 14:42 cgroup.event_control
-rw-r--r-- 1 root root 0 Jan 26 14:42 cgroup.procs
-r--r--r-- 1 root root 0 Jan 26 14:42 cgroup.sane_behavior
drwxr-xr-x 2 root root 0 Jan 26 16:05 container
-r--r--r-- 1 root root 0 Jan 26 14:42 cpuacct.stat
-rw-r--r-- 1 root root 0 Jan 26 14:42 cpuacct.usage
-r--r--r-- 1 root root 0 Jan 26 14:42 cpuacct.usage_percpu
-rw-r--r-- 1 root root 0 Jan 26 14:42 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Jan 26 14:42 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Jan 26 14:42 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Jan 26 14:42 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 Jan 26 14:42 cpu.shares
-r--r--r-- 1 root root 0 Jan 26 14:42 cpu.stat
drwxr-xr-x 4 root root 0 Jan 26 14:43 kubepods
-rw-r--r-- 1 root root 0 Jan 26 14:42 notify_on_release
-rw-r--r-- 1 root root 0 Jan 26 14:42 release_agent
drwxr-xr-x 45 root root 0 Jan 26 14:57 system.slice
-rw-r--r-- 1 root root 0 Jan 26 14:42 tasks
drwxr-xr-x 2 root root 0 Jan 26 14:42 user.slice
cpu限制实验
进入 /sys/fs/cgroup/cpu 目录下:
/sys/fs/cgroup/cpu$ mkdir container
/sys/fs/cgroup/cpu$ ls container/
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_releasecgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
这个目录称为一个“控制组”。操作系统会在新创建的container目录下,自动生成该子系统对应的资源限制文件。
执行死循环命令
[16:33:55 root@lyp-node cpu]#while : ; do : ; done &
[2] 61514
htop查看当前资源
可以看到资源被打满
通过查看 container 目录下的文件,看到 container 控制组里的 CPU quota 还没有任何限制(即:-1),CPU period 则是默认的 100 ms(100000 us)
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us
-1
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us
100000
向container组里 cfs_quota文件写入20ms(20000 us):
$ echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us
意味着在每 100 ms 的时间里,被该控制组限制的进程只能使用 20 ms 的 CPU 时间,也就是说这个进程只能使用到 20% 的 CPU 带宽。
接下来,我们把限制的进程pid写入container组里tasks文件,上面设置就会对该进程生效了。
$ echo 71507 > /sys/fs/cgroup/cpu/container/tasks
使用htop查看:
可以看到,计算机的 CPU 使用率立刻降到了 20%(%Cpu0 : 20.3 us)
除 CPU 子系统外,Cgroups 的每一个子系统都有其独有的资源限制能力.
比如:
blkio,为块设备设定I/O 限制,一般用于磁盘等设备;
cpuset,为进程分配单独的 CPU 核和对应的内存节点;
memory,为进程设定内存使用的限制。
linux cgroups 简单理解:它就是一个子系统加上一组资源文件的组合
对于 Docker 等 Linux 容器项目来说,它们只需要在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录),然后在启动容器进程之后,把这个进程的 PID 填写到对应控制组的 tasks 文件中就可以了。
docker限制
$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash
启动这个容器后,我们可以通过查看cgroups文件系统下,cpu子系统中 “docker”这个控制组里的资源限制文件的内容来确认:
$ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_period_us
100000
$ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_quota_us
20000
可以看出这个docker容器, 只能使用到20%的CPU带宽。
总结:
一个正在运行的 Docker 容器,其实就是一个启用了多个 Linux Namespace 的应用进程,而这个进程能够使用的资源量,则受 Cgroups 配置的限制。
容器技术中一个非常重要的概念:“容器是一个“单进程”模型.
Cgroups 对资源的限制能力也有很多不完善的地方,被提及最多的自然是 /proc 文件系统的问题。
Linux 下的 /proc 目录存储的是记录当前内核运行状态的一系列特殊文件,用户可以通过访问这些文件,查看系统以及当前正在运行的进程的信息,比如 CPU 使用情况、内存占用率等,这些文件也是 top 指令查看系统信息的主要数据来源。
但是,你如果在容器里执行 top 指令,就会发现,它显示的信息居然是宿主机的 CPU 和内存数据,而不是当前容器的数据。造成这个问题的原因就是,/proc 文件系统并不知道用户通过 Cgroups 给这个容器做了什么样的资源限制,即:/proc 文件系统不了解 Cgroups 限制的存在。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端