容器核心技术--Cgroup 与 Namespace

发表于:2020-8-25 10:55

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:小码弟    来源:简书

  容器的核心技术是Cgroup+Namespace。
  容器=cgroup+namespace+rootfs+容器引擎
  -Cgroup:资源控制
  -namespace:访问隔离
  -rootfs:文件系统隔离。镜像的本质就是一个rootfs文件
  -容器引擎:生命周期控制
  一、Cgroup
  Cgroup是Controlgroup的简称,是Linux内核提供的一个特性,用于限制和隔离一组进程对系统资源的使用。对不同资源的具体管理是由各个子系统分工完成的。
  在Cgroup出现之前,只能对一个进程做资源限制,如ulimit限制一个进程的打开文件上限、栈大小。而Cgroup可以对进程进行任意分组,如何分组由用户自定义。
  子系统介绍
  cpuset子系统
  cpuset可以为一组进程分配指定的CPU和内存节点。cpuset一开始用在高性能计算上,在NUMA(non-uniformmemoryaccess)架构的服务器上,通过将进程绑定到固定的CPU和内存节点上,来避免进程在运行时因跨节点内存访问而导致的性能下降。
  cpuset的主要接口如下:
  -cpuset.cpus:允许进程使用的CPU列表
  -cpuset.mems:允许进程使用的内存节点列表
  cpu子系统
  cpu子系统用于限制进程的CPU利用率。具体支持三个功能:
  第一,CPU比重分配。使用cpu.shares接口
  第二,CPU带宽限制。使用cpu.cfs_period_us和cpu.cfs_quota_us接口
  第三,实时进程的CPU带宽限制。使用cpu_rt_period_us和cpu_rt_quota_us接口
  cpuacct子系统
  统计各个Cgroup的CPU使用情况,有如下接口:
  -cpuacct.stat:报告这个Cgroup在用户态和内核态消耗的CPU时间,单位是赫兹
  -cpuacct.usage:报告该Cgroup消耗的总CPU时间
  -cpuacct.usage_percpu:报告该Cgroup在每个CPU上的消耗时间
  memory子系统
  限制Cgroup所能使用的内存上限。
  -memory.limit_in_bytes:设定内存上限,单位字节,默认情况下,如果使用的内存超过上限,Linux内核会试图回收内存,如果这样仍无法将内存降到限制的范围内,就会触发OOM,选择杀死该Cgroup中的某个进程
  -memory.memsw,limit_in_bytes:设定内存加上交换内存区的总量
  -memory.oom_control:如果设置为0,那么内存超过上限时,不会杀死进程,而是阻塞等待进程释放内存;同时系统会向用户态发送事件通知
  -memory.stat:报告内存使用信息。
  blkio
  限制Cgroup对阻塞IO的使用。
  -blkio.weight:设置权值,范围在[100,1000],属于比重分配,不是绝对带宽,因此只有当不同Cgroup争用同一个阻塞设备时才起作用
  -blkio.weight_device:对具体设备设置权值,它会覆盖上面的选项值
  -blkio.throttle.read_bps_device:对具体的设备,设置每秒读磁盘的带宽上限
  -blkio.throttle.write_bps_device:对具体的设备,设置每秒写磁盘的带宽上限
  -blkio.throttle.read_iops_device:对具体的设备,设置每秒读磁盘的IOPS带宽上限
  -blkio.throttle.write_iops_device:对具体的设备,设置每秒写磁盘的IOPS带宽上限
  devices子系统
  控制Cgroup的进程对哪些设备有访问权限。
  -devices.list:只读文件,显示目前允许被访问的设备列表,文件格式为类型[a|b|c]设备号[major:minor]权限[r/w/m的组合],a/b/c表示所有设备、块设备和字符设备
  -devices.allow:只写文件,以上述格式描述允许相应设备的访问列表
  -devices.deny:只写文件,以上述格式描述禁止相应设备的访问列表
  二、Namespace
  Namespace是将内核的全局资源做封装,使得每个namespace都有一份独立的资源,因此不同的进程在各自的namespace内对同一种资源的使用互不干扰。
  举个例子,执行sethostname这个系统调用会改变主机名,这个主机名就是全局资源,内核通过UTSNamespace可以将不同的进程分隔在不同的UTSNamespace中,在某个Namespace修改主机名时,另一个Namespace的主机名保持不变。
  目前,Linux内核实现了6种Namespace。
  与命名空间相关的三个系统调用:
  clone创建全新的Namespace,由clone创建的新进程就位于这个新的namespace里。创建时传入flags参数,可选值有CLONE_NEWIPC,CLONE_NEWNET,CLONE_NEWNS,CLONE_NEWPID,CLONE_NEWUTS,CLONE_NEWUSER,分别对应上面六种namespace。
  unshare为已有进程创建新的namespace。
  setns把某个进程放在已有的某个namespace里
  6种命名空间
  UTSnamespace
  UTSnamespace对主机名和域名进行隔离。为什么要隔离主机名?因为主机名可以代替IP来访问。如果不隔离,同名访问会出冲突。
  IPCnamespace
  Linux提供很多种进程通信机制,IPCnamespace针对SystemV和POSIX消息队列,这些IPC机制会使用标识符来区别不同的消息队列,然后两个进程通过标识符找到对应的消息队列。
  IPCnamespace使得相同的标识符在两个namespace代表不同的消息队列,因此两个namespace中的进程不能通过IPC来通信。
  PIDnamespace
  隔离进程号,不同namespace的进程可以使用相同的进程号。
  当创建一个PIDnamespace时,第一个进程的PID是1,即init进程。它负责回收所有孤儿进程的资源,所有发给init进程的信号都会被屏蔽。
  Mountnamespace
  隔离文件挂载点,每个进程能看到的文件系统都记录在/proc/$$/mounts里。在一个namespace里挂载、卸载的动作不会影响到其他namespace。
  Networknamespace
  隔离网络资源。每个namespace都有自己的网络设备、IP、路由表、/proc/net目录、端口号等。网络隔离可以保证独立使用网络资源,比如开发两个web应用可以使用80端口。
  新创建的Networknamespace只有loopback一个网络设备,需要手动添加网络设备。
  Usernamespace
  隔离用户和用户组。它的厉害之处在于,可以让宿主机上的一个普通用户在namespace里成为0号用户,也就是root用户。这样普通用户可以在容器内“随心所欲”,但是影响也仅限在容器内。
  最后,回到Docker上,经过上述讨论,namespace和cgroup的使用很灵活,需要注意的地方也很多。Docker通过Libcontainer来做这些脏活累活。用户只需要使用DockerAPI就可以优雅地创建一个容器。dockerexec的底层实现就是上面提过的setns。
  三、rootfs
  rootfs代表一个Docker容器在启动时(而非运行后)其内部进程可见的文件系统视角,或者叫Docker容器的根目录。
  先来看一下,Linux操作系统内核启动时,内核会先挂载一个只读的rootfs,当系统检测其完整性之后,决定是否将其切换到读写模式。
  Docker沿用这种思想,不同的是,挂载rootfs完毕之后,没有像Linux那样将容器的文件系统切换到读写模式,而是利用联合挂载技术,在这个只读的rootfs上挂载一个读写的文件系统,挂载后该读写文件系统空空如也。Docker文件系统简单理解为:只读的rootfs+可读写的文件系统。
  假设运行了一个Ubuntu镜像,其文件系统简略如下:
  在容器中修改用户视角下文件时,Docker借助COW(copy-on-write)机制节省不必要的内存分配。

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理

《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号