当前位置: 首页 > Linux

Linux容器的隔离和限制

时间:2023-04-06 04:31:31 Linux

Linux进程介绍如果要写一个计算加法的小程序,这个程序需要从一个文件中输入,计算的结果输入到另一个文件中。由于计算机只知道0和1,所以无论用哪种语言编写这段代码,都需要通过某种方式将其翻译成二进制文件,才能在计算机操作系统中运行。为了让这些代码能够正常运行,我们往往要为它们提供数据,比如我们的加法程序需要的输入文件。这些数据加上代码本身的二进制文件放在磁盘上,也就是我们通常所说的程序,也称为代码的可执行映像(executableimage)。然后,我们就可以在电脑上运行这个程序了。首先,操作系统发现程序的输入存储在一个文件中,因此将此数据加载到内存中以备使用。同时,操作系统读取指令计算加法。这时候就需要指示CPU完成加法运算。CPU和内存协同进行加法运算,用寄存器存储值,用内存栈存储执行的命令和变量。同时,电脑中有打开的文件,各种I/O设备不断调用修改自己的状态。这样,程序一旦执行,就从磁盘上的二进制文件变成了计算机内存中的数据、寄存器中的值、堆栈中的指令、打开的文件以及各种设备状态信息的集合。收集。这样一个程序运行后计算机执行环境的总和就是我们的主角:进程。因此,对于一个进程来说,它的静态表现就是一个程序,通常静静地留在磁盘上;而一旦运行起来,就变成了计算机中数据和状态的总和,这就是它的动态性能。Linux容器的隔离Docker容器本质上是Linux操作系统的进程,但是Docker通过命名空间实现了进程间的资源隔离技术。很多人觉得这个很抽象,那我们就通过实战来理解吧!首先我们创建一个容器:#dockerrun-itbusybox/bin/sh/#在容器中执行ps命令:/#psPIDUSERTIMECOMMAND1root0:00/bin/sh6root0:00ps即可查看了一下,原来我们在Docker里面最先执行的/bin/sh是这个容器里面的1号进程(PID=1),而且这个容器里面只有两个进程在运行。这意味着前面执行的/bin/sh和我们刚刚执行的ps已经被Docker隔离在了和宿主机不同的世界里。这怎么发生的?本来,每当我们在宿主机上运行/bin/sh程序时,操作系统都会给它分配一个进程号,比如PID=100。这个编号是流程的唯一标识,就像员工的胸卡一样。所以PID=100,可以大致理解为这个/bin/sh就是我们公司的100号员工,而1号员工自然是比尔盖茨这样一个掌控大局的人物。现在,我们想通过Docker在容器中运行这个/bin/sh程序。这时,Docker会在第100名员工入职时给他“视而不见”,让他永远看不到眼前的其他99名员工,更别提比尔·盖茨了。这样一来,他就会误以为自己是公司的一号员工。这种机制实际上是在操纵隔离应用程序的进程空间,让这些进程只能看到重新计算的进程号,比如PID=1。但实际上,它们仍然是宿主操作系统中原来的100号进程。这个技术就是Linux中的Namespace机制。Namespace的使用也很有意思:它其实只是Linux创建新进程的一个可选参数。我们知道Linux系统中创建线程的系统调用是clone(),例如:intpid=clone(main_function,stack_size,SIGCHLD,NULL);该系统调用将为我们创建一个新进程并返回其进程号pid。而当我们使用clone()系统调用创建新进程时,可以在参数中指定CLONE_NEWPID参数,例如:intpid=clone(main_function,stack_size,CLONE_NEWPID|SIGCHLD,NULL);这时,新创建的进程会“看到”一个全新的进程空间,其PID为1。之所以说“看到”,是因为这只是一种“盲道”。在宿主机真实的进程空间中,这个进程的PID仍然是一个真实的值,比如100。当然,我们也可以多次执行上面的clone()调用,这样就会创建多个PIDNamespace,每个Namespace中的应用进程都会认为自己是当前容器中的1号进程。到宿主机中真正的进程空间,在其他PIDNamespaces中是看不到具体情况的。除了我们刚刚使用的PIDNamespace,Linux操作系统还提供了Mount、UTS、IPC、Network、User等N??amespace,用于对各种进程上下文进行“蒙眼”操作。例如,MountNamespace用于让被隔离的进程只能看到当前Namespace中的挂载点信息;NetworkNamespace用于让被隔离的进程可以看到当前Namespace中的网络设备和配置。这是Linux容器最基本的实现原理。因此,Docker容器这个概念,听起来玄乎乎、神秘莫测,其实是在创建容器进程时指定了进程需要启用的一组Namespace参数。这样,容器就只能“看到”当前Namespace定义的资源、文件、设备、状态或配置。对于宿主和其他不相关的程序,它是完全不可见的。因此,容器实际上是一个特殊的进程。Linux容器的限制为什么要限制容器?容器中的1号进程虽然在蒙眼的干扰下只能看到容器中的情况,但是在宿主机上,作为100号进程,它仍然与平台上的所有其他进程竞争,这意味着,虽然100号进程显然是孤立的,它可以使用的资源(如CPU、内存)随时可能被宿主机上的其他进程(或其他机器)占用。当然,这个100号进程也可能会自己消耗所有资源。这些情况显然不是沙箱应该识别的合理行为。什么是LinuxCgroup?Cgroups是Linux下控制一个(或一组)进程的资源限制机制。全称是controlgroups,可以精细控制cpu、内存等资源。比如目前很多Docker都是基于Linux下cgroups提供的资源限制机制。实现资源控制;另外,开发者也可以直接参考基于cgroups的进程资源控制。比如一个web服务和一个计算服务部署在一个8核的机器上,web服务只能使用其中的6个。核心,将剩余的两个核心留给计算服务。cgroupscpulimit除了限制使用多少/哪些核外,还可以设置cpu占用率(注意占用率是各自满负荷运行时的使用率,如果一个cgroup空闲,另一个忙,那么busycgroup就有可能占据整个cpucore)。在Linux中,Cgroups暴露给用户的操作界面是文件系统,即以文件和目录的形式组织在操作系统的/sys/fs/cgroup路径下。在Centos机器里,我们可以使用mount命令将他们展示:/#mount-tcgroupcgroupon/sys/fs/cgroup/systemdtypecgroup(ro,seclabel,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)cgroupon/sys/fs/cgroup/hugetlbtypecgroup(ro,seclabel,nosuid,nodev,noexec,relatime,hugetlb)cgroupon/sys/fs/cgroup/cpu,cpuacct类型cgroup(ro,seclabel,nosuid,nodev,noexec,relatime,cpuacct,cpu)cgroupon/sys/fs/cgroup/freezer类型cgroup(ro,seclabel,nosuid,nodev,noexec,relatime,freezer)cgroupon/sys/fs/cgroup/net_cls,net_priotypecgroup(ro,seclabel,nosuid,nodev,noexec,relatime,net_prio,net_cls)cgroupon/sys/fs/cgroup/blkiotypecgroup(ro,seclabel,nosuid,nodev,noexec,relatime,blkio)cgroupon/sys/fs/cgroup/cpusettypecgroup(ro,seclabel,nosuid,nodev,noexec,relatime,cpuset)cgroupon/sys/fs/cgroup/perf_eventtypecgroup(ro,seclabel,nosuid,nodev,noexec,relatime,perf_event)cgroupon/sys/fs/cgroup/memorytypecgroup(ro,seclabel,nosuid,nodev,noexec,relatime,memory)cgroupon/sys/fs/cgroup/devicestypecgroup(ro,seclabel,nosuid,nodev,noexec,relatime,devices)cgroupon/sys/fs/cgroup/pidstypecgroup(ro,seclabel,nosuid,nodev,noexec,relatime,pids)目前/sys/fs/cgroup下有cpuset,cpu,memory等很多子目录,也叫subsystems。这些都是我的机器Cgroups目前可以限制的资源类型。而在子系统对应的资源类下,可以看到具体可以限制该类资源的方法。例如CPU子系统,我们可以看到如下配置文件:/#ls-l/sys/fs/cgroup/cpu/total0-rw-r--r--1rootroot0Aug1210:55cgroup.clone_children--w--w--w-1rootroot0Aug1210:55cgroup.event_control-rw-r--r--1rootroot0Aug1210:55cgroup.procs-rw-r--r--1rootroot0Aug1210:55cpu.cfs_period_us-rw-r--r--1rootroot0Aug1210:55cpu.cfs_quota_us-rw-r--r--1root根0Aug1210:55cpu.rt_period_us-rw-r--r--1rootroot0Aug1210:55cpu.rt_runtime_us-rw-r--r--1rootroot0Aug1210:55cpu。shares-r--r--r--1rootroot0Aug1210:55cpu.stat-r--r--r--1rootroot0Aug1210:55cpuacct.stat-rw-r--r--1rootroot0Aug1210:55cpuacct.usage-r--r--r--1rootroot0Aug1210:55cpuacct.usage_percpu-rw-r--r--1root根0Aug1210:55notify_on_release-rw-r--r--1rootroot0Aug1210:55tasks熟悉LinuxCPU管理的同学要注意,关键字cfs_period和cfs_quota需要结合使用,它可以用来限制进程在cfs_period的时间段内以cfs_quota的总量分配CPU时间。接下来,让我们使用这个配置?首先,我们需要在对应的子系统下创建一个目录:#cd/sys/fs/cgroup/cpu#mkdircontainer#cdcontainer/#lltotal0-rw-r--r--。1rootroot08月12日19:38cgroup.clone_children--w--w--w-。1rootroot08月12日19:38cgroup.event_control-rw-r--r--。1rootroot08月12日19:38cgroup.procs-r--r--r--。1rootroot08月12日19:38cpuacct.stat-rw-r--r--。1rootroot0Aug1219:38cpuacct.usage-r--r--r--。1rootroot08月12日19:38cpuacct.usage_percpu-rw-r--r--。1rootroot0Aug1219:38cpu.cfs_period_us-rw-r--r--。1rootroot08月12日19:38cpu.cfs_quota_us-rw-r--r--。1rootroot0Aug1219:38cpu.rt_period_us-rw-r--r--.1rootroot08月12日19:38cpu.rt_runtime_us-rw-r--r--。1rootroot08月12日19:38cpu.shares-r--r--r--。1rootroot08月12日19:38cpu.stat-rw-r--r--。1rootroot08月12日19:38notify_on_release-rw-r--r--。1rootroot0Aug1219:38tasks这个目录叫做控制组。你会发现操作系统会在新建的容器目录下自动生成子系统对应的资源限制文件。此时,我们执行一个无限循环脚本,将计算出的CPU消耗到100%#while:;做:;done#topPIDUSERPRNIVIRTRESSHRS%CPU%MEMTIME+COMMAND7996root2001320256212R1000.01:12.75sh通过top命令可以看到CPU使用率已经达到100%。这时我们查看容器目录下的文件,可以看到容器控制组的CPU配额没有限制(:-1)#cat/sys/fs/cgroup/cpu/container/cpu.cfs_quota_us-1接下来,我们通过修改这些文件来设置限制:向容器组中的cfs_quota文件写入20ms(20000us)echo20000>/sys/fs/cgroup/cpu/container/cpu.cfs_quota_us100ms,进程受限这个对照组只能使用20MS的CPU时间,也就是说这个进程只能使用20%的CPU带宽。被限制进程的PID写入容器组中的tasks文件,上面的设置会对进程生效#echo7996>/sys/fs/cgroup/cpu/container/tasks然后通过top查看:PIDUSERPRNIVIRTRESSHRS%CPU%MEMTIME+COMMAND7996root20011948461401652R20.30.23:45.10sh可以看到电脑的CPU占用率立马降到了20%是不是很神奇?除了CPU子系统,Cgroups的每个子系统都有独特的资源限制能力:比如blkio,为块设备设置I/O限制,一般用于磁盘等cpuset,为进程分配单独的CPU核和对应的内存节点内存,为进程设置内存使用限制。LinuxCgroups的设计是比较容易上手的。简单粗略地理解,就是一个子系统目录加上一组资源限制文件的组合。对于Docker等Linux容器项目,他们只需要在每个子系统下为每个容器创建一个控制组(即创建一个目录),然后在启动容器进程后,在对应控件的tasks文件中填写进程PID团体。至于这些控制组下的资源文件填什么值,由用户在执行dockerrun时指定参数,比如如下命令:#dockerrun-it--cpu-period=10000--cpu-quota=20000ubuntu/bin/bash启动这个容器后,我们可以在Cgroup文件系统下CPU子系统的controlgroupdocker中查看资源限制文件的内容:#cat/sys/fs/cgroup/cpu/docker/0712c3d12935b9a3f69ac976b9d70309b78cb7db9a5a5c8a612742370b7453e4/cpu.cfs_period_us10000#cat/sys/fs/cgroup/cpu/docker/0712c3d12935b9a3f69ac976b9d70309b78cb7db9a5a5c8a612742370b7453e4/cpu.cfs_quota_us20000点击"阅读原文"获取更好的阅读体验!