docker作为一种将应用及其依赖进行package后发布的容器,就像可以被货轮承载和迁移的集装箱一样。现在的港口的设备当然是很强力了,不过在以前,货物达到码头后,还是需要码头工人来搬运,这似乎就是"docker"一词的原意。
作为一种基于OS的虚拟化技术,和传统的基于hypervisor的虚拟机技术一样,都是需要共享物理主机上的资源。但相比hypervisor中的Virtual Machine(以下简称VM)而言,docker的隔离度更弱,但同时资源消耗更小,相同平台可运行的container数目也更多。
而且,由于docker中的container是共享底层OS的,因此启动非常迅速。而像KVM这样的hypervisor在启动一个VM的时候,需要解压和引导VM所使用的内核,因而耗时更长。
docker最初是基于Linux设计的容器技术(现在也可以通过docker toolbox在Windows上做一个模拟层来移植到Windows上),它的实现依赖于Linux中众多的基础机制,包括用于资源限制的cgroup,用于隔离的Namespace,以及用于实现docker文件系统的Union FS等。
为什么要有group
"cgroup"代表的是"control group",这里"group"是进程的集合。“进程的集合”似乎不是一个新鲜的概念,「进程组」不就是吗?对,但「进程组」的这个集合包括的是协同工作的一组进程(比如作为整体去接收信号),而cgroup这个集合的主要目的在于控制资源的使用:多个进程作为一个整体享有资源的配额(quota),同时接受资源的限制。
在cgroup出现之前,只能对一个进程做一些资源控制,例如通过"nice"值限定对CPU的使用,或者用ulimit限制一个进程的打开文件上限、栈大小等。而cgroup可以对进程进行任意的分组,如何分组是用户自定义的,一个group即对应一个docker container。
控制什么
来看一下"/sys/fs/cgroup"下的目录结构,cgroup支持的资源种类都在这里了,它们被称为cgroup中的subsystem或者resource controller(因为不是所有subsystem都完全具备controller的属性,所以本文接下来将统称"subsystem")。
分配CPU的时间不难理解,可这里关于CPU的好像不止一个,有cpu, cpuacct和cpuset。额,可能因为CPU对一个系统来说实在太重要了吧。
那它们的区别在哪里呢?"cpu"是限制各个group对CPU的使用时间(比如50%),时间一到,就强制转入睡眠。"cpuacct"中的"acct"是"accouting"的缩写,用于统计每个group消耗的CPU时间(包括user time和system time)。
两者的关系好像非常紧密啊。是的,所以"cpu"和"cpuacct"都是link到"cpu, cpuacct"的:
而cpuset是限制SMP系统中,一个group可以使用哪几个CPU。为什么要做出这种限制,而不是让OS随意调度呢?要回答这个问题,还是先进"cpuset"的目录看一下:
你会发现有很多关于memory的条目,不是有单独的memory cgroup(简称memcg)来管理内存资源么?再想想什么时候CPU和内存在物理上的分布会是个问题?就是NUMA啦。其实,cpuset主要是用于在NUMA这种有着复杂结构的大型系统中,根据CPU和内存节点的分布情况,更加合理地调度和使用CPU和内存资源。
cgroup v1 - hierarchy
如何控制
subsystem已经是事先准备好的,那如何创建一个control group呢?很简单,因为这是sysfs文件系统,只需在对应的subsystem(比如"cpu")目录下,直接"mkdir"新建一个文件夹(比如"c1")。
然后,"c1"这个group所需的基础实施就分分钟配套完成,只是里面还没有人居住。那process怎么才可以住进来呢?把自己的pid打到"tasks"里面去就好了。
echo [pid] >> /sys/fs/cgroup/cpu,cpuacct/c1/task
不过,进了这个group的家门,就得按照这个group的规矩来(包括各种资源的限制)。当然,想换一个group也是可以的,把自己的pid移动到新的group里即可,这是process的自由。
那group有什么自由呢?subsystem是支持多层级group嵌套的,就是一个group可以再包含若干的sub-group,这样一个subsystem就形成了树形的hierarchy。至于这样设计的理由,举个例子,Linux是支持多用户的,比如你可以让root用户占其中75%的CPU资源,然后在里面再进行细分,让数据库业务占50%,其他占25%。
所以,一个group可以选择放或者不放在一个subsystem中,也可以选择放在一个subsystem的哪个层级,哪个sub-group下。比如上面示例中新建的"c1"这个group,如果它需要被限制对内存的使用,就应该放在"memory"这个subsystem中的某个结点下。不过,一个group只能放在一个subsystem hierarchy的一个节点上(要不然就乱套了)。
这就像上图所示的一个矩阵的关系,它其实是可以形成很多组合的,不过这些组合搭配并不一定都行的通,需要使用者自己去保证。cgroup的这种设计确实为应用提供了极大的灵活性,但同时也更容易出问题。
【重构 - 反对的声音】
作为cgroup的maintainer,Tejun Heo随后对cgroup的设计发起了历时多年的修改,这个新设计的版本被称为"cgroup v2"。相比起v1以subsystem为主的hierarchy结构,v2最核心的变化是以group为主导,它的层级结构如下图所示:
cgroup v2 - hierarchy
这样就不是每个subsystem有一个hierarchy,而是一个整体的unified hierarchy,对一个group可使用的CPU和内存等资源的限制,在其内部就规定好了。
(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂
更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! !
更多的变化
v1允许同一个process的不同thread分布在不同的control group,但v2取消了这个特性。在新的版本中增加新的功能可能不会受到太大的阻力,但是要减少一个曾经存在的功能,就难免引起一些争议,因为确实有实际的项目使用到这个特性。
不过,Heo最终决定做出这个改动,想必也是对需求(收益)、设计复杂度(投入)、适用场景(范围)综合考量的结果,用他的原话说就是"sometimes too much flexibility causes a hindrance"。选择恐惧症的福音啊。
此外,v2还有一个限制:既然cgroup hierarchy可以被视作一棵树,那么group就是枝干,枝干上长出叶子,而process只能添加在叶子上。也就是说:process和group不能是同级的关系,一个process不能单独和一个group去争用CPU的时间(做出这个限制的原因,主要也是为了降低实现的复杂度)。
比如下图的这个结构,在C1, C3, C4下面都是可以添加process的,但是在C2下面就不行。不过也有变通的方法,就是在一个group下只包含这一个process(比如C1这种),只是层级深了一点,但在争用CPU的时间上还是可以达到同样的效果。
2019年10月发布的Fedora 31是第一个支持v2的Linux发行版,但如果你想在上面使用docker,得手动切换回v1,这是因为当时docker使用的runc,还依赖于v1(受此限制的还有kvm使用的libvirt等)。
正是为了兼容这些虚拟化的应用,大多数发行版默认支持的依然是v1(RHEL 8可以通过修改内核启动参数的方式切换到v2,参考这篇文章)。不过来到2020年12月,随着支持v2的Docker 20.10发布,在v2模式下使用docker就再也不是个问题。
说到v2的redesign,其实对于一个公司的产品代码来说,亦可遵循这种轨迹。由于最初设计时的需求考虑不够完善等因素,经过几年的维护和修修补补,随着人员进进出出的不断变动,代码可能变的比较乱,甚至你都不知道其中的哪些代码实际是没有对应的功能的(废代码)。
这时投入相当的精力去做重构,虽然会消耗一定的人力资源,且可能对既有的业务模块造成影响,不过经历短暂的阵痛之后,更加清晰的代码结构和更加简洁的设计,也许可以让它以后走的更快和更远。
理解docker[二] - namespace
参考:
原文链接:https://zhuanlan.zhihu.com/p/143253843