Docker:namespace环境隔离 CGroup资源控制

news/2024/10/28 10:30:49/

Docker:namespace环境隔离 & CGroup资源控制

    • Docker
    • namespace
      • 相关命令
        • dd
        • mkfs
        • df
        • mount
        • unshare
      • 进程隔离
      • 文件隔离
    • CGroup
      • 相关命令
        • pidstat
        • stress
        • cgroup控制
      • 内存控制
      • CPU控制


Docker

在开发中,经常会遇到环境问题,比如程序依赖某个库,库又要具体的版本,以及某些函数必须在指定平台使用,这就会为开发带来很大的麻烦。

为此,有人提出采用虚拟化技术,为软件虚拟出一个环境。就像是在一个冰天雪地的地方建了一个花房养花,花房内温度湿度都刚刚好,将花房与外部的冰雪隔离开。

这种实现环境隔离的技术,主要有虚拟机和容器两种,Docker属于容器化的隔离技术。

虚拟机

所谓虚拟机,其实就是把一台物理主机虚拟为多台逻辑上的计算机。多台虚拟机共用物理上的同一台计算机,而每个逻辑上的计算机可以运行不同的操作系统,安装不同的库,从而提供不同的环境。

在这里插入图片描述

如图,上图红色部分与蓝色部分是两个不同的虚拟机,虚拟机技术在硬件层之上,在操作系统层就开始进行隔离。虚拟机通过伪造一个硬件的抽象接口,把操作系统嫁接到硬件上。

在这里插入图片描述

在硬件层与操作系统层之间,会存在一个虚拟化层,这其实就是一个软件,该层会负责分配硬件资源。

可以看出,如果想要创建多个虚拟机,就要在一台物理主机上跑多个操作系统,这其实要不小的开销,而容器是一种更加轻量的隔离技术。


容器

容器也是一种虚拟化的实现技术,它在操作系统之上进行环境隔离,每个容器可以有自己的一套工具和库,但是它们共享操作系统的内核

在这里插入图片描述

如图,红色和蓝色区域,是两个不同的容器,它们的网络,文件系统等等都是隔离的,互不影响。

因为使用同样的操作系统内核,所以它们的系统调用接口自然就相同,但是基于相同的系统调用接口,配置了不同的库,不同的文件系统,那么最后两个容器就不同。

在这里插入图片描述

比如说上图,可以通过容积隔离技术在一个centOS操作系统上,运行不同版本的Ubuntu容器。这听起来很异想天开,但其实不然。

每个容器有自己独立的用户空间,这包括文件系统、库和用户级工具。用户操作接口是用户在容器内操作系统时接触到的命令行工具、库和应用。因此可以在上层的容器中,执行Ubuntu的命令,虽然内核是CentOS的。

相比于虚拟机技术,容器化技术非常轻量,容器相当于一个跑在操作系统上的进程,启动一个进程的速度是非常快的,通过容器技术,只需几秒钟就在主机上打开一个新的操作系统。

容器化技术,目前最流行的实现方案就是Docker

那么容器化技术是如何实现各种资源的隔离的?对Linux来说,它依赖于namespaceCGroup技术,这两个技术是由Linux内核提供的。

  • namespace:实现进程,文件系统,用户等资源隔离
  • CGroup:实现CPU,内存,网络等资源隔离

namespace

namespaceLinux 内核用来隔离内核资源的方式。通过 namespace 可以让进程只能看到与自己相关的一部分资源,不同namespace的进程感觉不到对方的存在。

具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。namespaces 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个namespace 中的系统资源只会影响当前namespace 里的进程,对其他namespace 中的进程没有影响。

常见namespace

namespace隔离资源
UTS主机名和域名
IPC进程间通信:信号量、消息队列、共享内存
PID进程
NetWork网络设备,端口等
Mount文件系统
User用户

解释:

  1. UTS:每个UTS namespace都可以有自己独立的主机和域名
  2. IPC:每个IPC namespace内部的进程可以进行进程间通信,但是不能跨越IPC namespace进行通信,在逻辑上这算跨主机通信
  3. PID:每个PID namespace都有自己独立的进程pid系统,不同的PID namespace可以出现相同的pid
  4. NetWork:每个NetWork namespace都有自己独立的网络设备,IP地址,路由表,端口号等
  5. Mount:每个Mount namespace有自己的文件系统,互相不能看到对方的文件
  6. User:每个User namespace都有自己独立的用户和用户组

相关命令

dd

dd可以从指定输入流读取数据,并输出到指定输出流。

参数:

  • if=文件名:从指定文件读取数据,如果不指定,则默认从标准输入读取
  • of=文件名:输出数据到指定文件,如果不指定,则默认输出到标准输出
  • bs=xxx:设定一个块block的大小
  • coun=blocks:仅仅从输入流拷贝blocks个块,结合上一个参数,可以指定要读取的数据个数

创建一个指定大小的空文件:

dd if=/dev/zero of=test.txt bs=1M count=80

以上指令,就是创建了一个80M的空文件,输入流是/dev/zero,这是一个会不断产生空白字符的文件,也就是ASCII中字符编码为0的字符。使用这个文件作为输入流,可以快速初始化一个空文件。

在这里插入图片描述

可以看到,最后创建了一个大小为83886080 byte的文件,其实就是80 M


mkfs

mkfs用于在设备上创建一个文件系统,俗称格式化。

mkfs [option] filesys [blocks]

选项:

  • -t:要创建的文件系统类型,比如ext3ext4

其中filesys是被格式化的文件,而block是文件系统的磁盘块数,可以省略。

把刚才创建的文件进行格式化:

mkfs -t ext4 test.txt

在这里插入图片描述

这样就把刚才的空文件进行了格式化,变成了一个文件系统。


df

df用于显示Linux中的文件系统磁盘使用情况。

选项:

  • -h:以更加可视化的形式输出,默认情况下数据以字节为单位,加上该选项后,会自动转化为GBMB等单位
  • -T:显示文件系统的类型

在这里插入图片描述

查看当前的文件系统,可以看到,其不包含刚才格式化的test.txt,因为他只是一个被格式化的文件,还没有被挂载。


mount

mount用于挂载文件系统,相当于给文件系统一个访问入口。

比如说你在电脑上插入一个U盘,它往往会显示为E盘或者其它盘符。这个盘符就是一个访问入口,因为U盘本身就是一个文件系统,如果想要访问这个文件系统的内容,Windows自然要提供一个入口,因此它自动分配一个E盘,让用户可以通过E盘访问U盘。

同样的,刚才格式化test.txt为一个文件系统,现在要将其挂载起来,才能访问这个文件系统。

mount [option] device dir
  • device:被挂载的文件
  • dir:挂载到的位置

选项:

  • -t:挂载文件的类型,比如ext3ext4,但是可以不填,mount会自动识别文件系统的类型

把刚才的test.txt挂载到当前/mymount目录下:

在这里插入图片描述

首先创建一个空目录/mymount,随后把test.txt挂载到这个目录下,随后可以看到,/mymount出现了新的内容。

随后通过df -t ext4查看系统的文件系统,可以看到/dev/loop0文件系统,被挂载到了/mymount下,说明成功挂载了一个文件系统。

如果想要删除这个文件系统,可以执行:

umount /mymount

以上所有命令,是在完成一个文件系统的创建,便于后续测试文件系统的隔离性。

接下来看看Linux提供的创建namespace的命令:

unshare

unshare用于执行一个进程,并且为这个进程提供一个独立的namespace

unshare [option] program 
  • program:要执行的程序

选项:

  • -i --ipc:不共享IPC空间
  • -m --mount:不共享Mount空间
  • -n --net:不共享Net空间
  • -p --pid:不共享PID空间
  • -u --uts:不共享UTS空间
  • -U --user:不共享用户空间
  • --fork:创建一个子进程执行program
  • --mount-proc:挂载一个新的/proc到命名空间内

此处这个--fork有一点点绕,因为ushare这个命令,本身也是一个进程,是在宿主机环境运行的。

如果直接执行unshare,流程如下:

  1. unshare 创建一个新的命名空间
  2. unshare 执行 program,但是没有创建新的进程,而是直接用 program 替换了 unshare 本身

在这里插入图片描述

以上unshare不带有--fork参数,执行了/bin/bash进程。进入namespace后,可以看到,/bin/bash的父进程是-bash。这个-bash就是宿主机的bash进程。因为unshare是在bash中执行的,所以unshare的父进程是-bash。最后将/bin/bash替换unshare/bin/bash的父进程就是原先的父进程-bash

这种情况下,看不到unshare进程,因为/bin/bash就是原先的unshare

如果加上--fork参数,流程如下:

  1. unshare 创建一个新的命名空间
  2. unshare 在新的命名空间中创建一个新的子进程来执行 program
  3. unshare本身不退出

在这里插入图片描述

加上--fork参数后,/bin/bash的父进程就变成了unshareunshare的父进程是-bash。也就是说unshare创建了一个子进程来执行program,而不是亲自执行program

如果你跟着操作了,此时还处于namespace中,要通过exit来退出,不然会影响后续操作。


进程隔离

现在尝试进行进程隔离,也就是隔离,通过--pid选项完成:

unshare --fork --pid --mount-proc /bin/bash

以上命令,用于创建一个新的PID命名空间,并执行bash进程,也就是执行一个新的命令行。

此处要加上--fork选项,因为要进行进程隔离,而unshare本身是宿主机的进程,如果直接让unshare本身去执行program,那么program就不在新的namespace中,导致错误。

在这里插入图片描述

以上示例中,因为没有加上--fork,报错了。

如果只加上--fork选项,此时还是不能观察到进程隔离。因为topps等进程监控的命令,是依赖于/proc/PID这个目录的。但是命令没有进行文件系统隔离,所以还是会使用宿主机的/proc/PID目录,导致namespace内部可以看到外部的进程。

为此,unshare命令专门提供了一个参数--mount-proc,在新的namespace挂载一个独立的/proc目录,方便进行进程的监控。

进程隔离结果:

在这里插入图片描述

可以看到,创建了新的namespace后,ps -aux只能查到两个进程,一个是bash,一个是grep。这就将namespace内部的进程与宿主机的进程隔离开了。


文件隔离

想要进行文件隔离,创建一个新的namespace,然后在里面创建一个文件系统并挂载。再在外部的宿主机查看是否可以看到这个文件系统。

  1. 创建一个新的文件隔离命名空间
unshare --mount --fork /bin/bash
  1. 创建一个指定大小的空文件
dd if=/dev/zero of=data.img bs=1M count=80
  1. 格式化文件为文件系统
mkfs -t ext4 data.img
  1. 挂载文件系统
mkdir /mymount
mount -t ext4 data.img /mymount

在这里插入图片描述

最后通过df -t ext4,可以看到文件系统已经挂载成功了。

打开一个新的终端,执行df -t ext4

在这里插入图片描述

此时左右终端看到的文件系统不同,左侧的namespace内挂载的新文件系统,右侧终端看不到了,这就是文件隔离。

此时在namespace中执行exit,就会退出这个bash,进而退出namespace,在其内部创建的文件,挂载的文件系统都会自动销毁。

在这里插入图片描述

exit退出后,再次查看文件系统,也找不到刚才挂载的文件系统了。这和刚才两个终端的情况不同,之前是不同namespace之间的文件隔离,而此处是退出namespace后,文件系统已经被销毁了。


CGroup

cgroup的可以把一系列任务,也就是进程划分到一个任务组,并且限制一个任务组的资源占用。比如可以限制一系列任务最多占用多少CPU,占用多少内存等等。

也就是说,namespace完成了不同容器之间环境的隔离,而cgroup完成了每个容器资源的访问限制。

相关命令

为了方便测试CPU与内存压力,此处使用两个工具分别完成产生压力以及压力检测。

pidstat

pidstat用于检测一个进程的CPU、内存、IO、线程等等资源的占用情况。

需要下载:

apt install sysstat

语法:

pidstat [option] [时间间隔] [次数]

选项:

  • -u:检测CPU使用情况,默认就是该选项
  • -r:检测内存使用情况
  • -d:检测IO使用情况
  • -p:指定进程pid,如果指定ALL则监视所有进程
  • -C:检测通过指定命令启动的进程

直接执行pidstat

在这里插入图片描述

此时会输出所有进程,默认输出CPU占用情况,也就是%CPU这一栏。

通过-p指定进程:

在这里插入图片描述

通过-C指定进程:

在这里插入图片描述

此处指定bash进程。

通过-r检测内存:

在这里插入图片描述

此处%MEM栏就是内存占用情况。

指定检测次数与频率:

在这里插入图片描述

此处的1 3表示:每隔一秒检测一次,一共检测三次。


stress

stress是一个压力测试工具,可以对CPU、内存IO等进行压力测试。

这个工具也要下载:

apt install stress

语法:

stress [option]

参数:

  • -c --cpu N:产生N个进程,每个进程都循环调用sqrt函数产生CPU压力
  • -m --vm N:产生N个进程,每个进程都循环调用malloc free函数,产生内存压力

示例:

在这里插入图片描述

左侧使用stress创建了一个进程进行CPU压力输出,右侧检测stress产生的压力,结果一个进程占满了100%的CPU资源。


cgroup控制

接下来看看如何操作cgroup,它并没有现成的指令来控制,而是需要操控配置文件来完成。

/proc/cgroups查看cgroup支持的资源控制:

在这里插入图片描述

/proc/cgroups文件中,包含了cgroup所支持的资源控制的类型,比如CPU、内存等。

查看cgroup挂载信息:

mount | grep cgroup

在这里插入图片描述

这里就是每一个资源的控制目录,比如在/sys/fs/cgroup/cpu目录下,就是控制CPU资源的配置文件。


内存控制

创建一个内存的控制组很简单,跳转到目录/sys/fs/cgroup/memory,然后创建一个目录:

在这里插入图片描述

此处创建了一个test_memory目录,这就算创建了一个test_memory内存控制组。进入目录后,可以看到这个目录被初始化了很多文件,其中memory.limit_in_bytes这个文件,就是这个cgroup可以使用的最大内存数目,以字节为单位。

想要限制这个cgroup的最大内存数量,直接往文件写入数据即可:

echo "20971520" > memory.limit_in_bytes

此处20971520其实就是20 mb,此后这个cgroup的最大内存占用就不会超过20 mb

那么要如何把一个进程加入控制组?这里有一个task文件,只需要把进程的PID写入到这个文件中,那么一个进程就算加入了这个cgroup

在这里插入图片描述

如图,创建一个进程,占用50m的内存:

stress -m 1 --vm-bytes 50m

随后在另一个端口通过pidstat查看stressPID,为1507015069,其中15069是控制进程,15070是真正在产生内存压力的进程。将15070写入tasks文件中,让其加入cgroup

结果左侧的stress退出了,无法产生50m的压力。这是因为一开始就限制了cgroup只能占用最多20 m的内存。一旦进程加入后,就会受到限制,从而崩溃。


CPU控制

创建一个CPU的控制组也一样,跳转到目录/sys/fs/cgroup/cpu,然后创建一个目录:

在这里插入图片描述

此处创建了一个test_cpu目录,也就死和创建了一个test_cpuCPU控制组。这个目录同样被初始化了很多文件。

其中控制CPU占用的是cpu.cfs_period_uscpu.cfs_quota_us,这两个文件共同完成CPU资源限制。其中cpu.cfs_period_us作为分母,cpu.cfs_quota_us作为分子,以百分比的形式限制CPU。

比如cpu.cfs_period_us内填入5000cpu.cfs_quota_us内填入2000。那么该cgroup最多可以占用2000/5000也就是40%的CPU资源。要注意的是,这两个文件的最小值都是1000,不允许使用比1000更小的数字进行配置。另外的cpu.cfs_quota_us的默认值为-1,表示可以占用100%的CPU。

另一个就是tasks文件,同样的只要把PID写入这个文件,就算加入了这个CPU控制组。

启动一个stress进程:

在这里插入图片描述

由于stress本身就会尽可能占满CPU,右侧输出窗口每隔一秒输出stress的状态,其一直保持100%的CPU资源占用。

左下角窗口先限制了test_cpu这个控制组的CPU最大占用率是2000/10000,也就是20%

随后把stress的PID20849写入tasks

在这里插入图片描述

写入后,从右边的窗口可以看出,stress的CPU占用率立马下降,100%37%最后稳定在20%

可以cgroup成功对进程的CPU进行了限制。


容器化技术在Linux中基于namespacecgroup实现,namespace完成不同容器之间的环境隔离,而cgroup完成多个容器对资源的占用限制,合理分配资源。这就是容器化技术,以及docker的最基本原理,也是底层依赖。



http://www.ppmy.cn/news/1542572.html

相关文章

Qt元对象系统 —— 属性系统

目录 属性系统 进一步:使用上面的阐述详细讨论如何对对象的属性读写 一个例子 Detailed Descriptions of settings 我们下面要讨论的是Qt元对象系统的第二部分:那就是Qt的属性系统。我们知道大部分的类其实都在现实生活中表达一种属性——我们希望通过…

加密DNS有什么用?

在当今数字化高速发展的时代,网络安全和隐私保护成为人们日益关注的焦点。而加密 DNS作为一种新兴的技术手段,正逐渐发挥着重要的作用。 首先我们先来了解下什么是加密DNS,它究竟是什么? 加密DNS(Domain Name System…

Qt的简单布局管理说明

目录 Qt的几个Layout QBoxLayout 一个例子: 一些常见的API QGridLayout 一个例子: API QFormLayout 例子 API QStackedLayout 一些例子 API 其他 QSplitter 一些例子 API 附注 这里不是具体的好看的界面设计教程,但是打算说…

DDei 在线设计器 V1.2.41 版发布

​ DDei 在线设计器 V1.2.41 版本带来了新功能、提升了性能、增强了稳定性、扩展了多个回调函数。 提供了简版布局与之配套的简版控件工具箱和顶部菜单,并支持通过 vue 来扩展菜单。 2.由canvas渲染改为canvshtml混合渲染,提升清晰度和渲染性能&#xf…

Leetcode 3336. Find the Number of Subsequences With Equal GCD

Leetcode 3336. Find the Number of Subsequences With Equal GCD 1. 解题思路2. 代码实现 题目链接:3336. Find the Number of Subsequences With Equal GCD 1. 解题思路 这一题没能自力搞定,挺伤心的,看大佬的代码之后发现思路上是一个非…

使用 python中 pandas 将 Excel 转换为 CSV 文件

在数据处理和分析中,我们经常需要将 Excel 文件转换为 CSV 格式。CSV 文件因其简单、易于处理的特点,广泛用于数据交换。本文将介绍如何使用 wxPython 创建一个简单的图形用户界面(GUI),结合 pandas 库,实现…

每天五分钟深度学习框架pytorch:从底层实现一元线性回归模型

本文重点 本节课程我们继续搭建一元线性回归模型,不同的是这里我们不使用pytorch框架已经封装好的一些东西,我们做这个目的是为了更加清楚的看到pytorch搭建模型的本质,为了更好的理解,当然实际中我们还是使用pytorch封装好的一些东西,不要重复造轮子。 模型搭建 #定义…

NLP自然语言处理中的Attention机制原理揭秘

NLP自然语言处理中的Attention机制原理揭秘 引言 在自然语言处理(NLP)领域,Attention机制已经成为提升模型性能和效果的重要技术之一。它模仿了人类在处理信息时的注意力机制,使得模型能够在处理输入数据时,动态地聚…