From 8093c6cdefc8ae80f3abb605815a3e07ec346504 Mon Sep 17 00:00:00 2001 From: Starnop Date: Thu, 19 Jul 2018 15:00:47 +0800 Subject: [PATCH] add blogs that have been published Signed-off-by: Starnop --- ...41\344\270\216\345\256\236\347\216\260.md" | 148 ++++++ ...00\346\265\213\345\256\236\350\267\265.md" | 0 ...72\345\210\266\350\247\243\346\236\220.md" | 133 +++++ ...71\345\231\250\351\232\224\347\246\273.md" | 0 ...37\345\234\260\345\215\207\347\272\247.md" | 126 +++++ ...71\345\231\250\346\212\200\346\234\257.md" | 95 ++++ ...05\346\240\270\346\212\200\346\234\257.md" | 489 ++++++++++++++++++ 7 files changed, 991 insertions(+) create mode 100644 "blog-cn/PouchContainer CRI\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" rename "blog-cn/20180517-PouchContainer Goroutine Leak \346\243\200\346\265\213\345\256\236\350\267\265.md" => "blog-cn/PouchContainer Goroutine Leak \346\243\200\346\265\213\345\256\236\350\267\265.md" (100%) create mode 100644 "blog-cn/PouchContainer volume\346\234\272\345\210\266\350\247\243\346\236\220.md" rename "blog-cn/2018-04-11_PouchContainer-\346\224\257\346\214\201-LXCFS-\345\256\236\347\216\260\351\253\230\345\217\257\351\235\240\345\256\271\345\231\250\351\232\224\347\246\273.md" => "blog-cn/PouchContainer-\346\224\257\346\214\201-LXCFS-\345\256\236\347\216\260\351\253\230\345\217\257\351\235\240\345\256\271\345\231\250\351\232\224\347\246\273.md" (100%) create mode 100644 "blog-cn/\346\267\261\345\205\245\350\247\243\346\236\220 PouchContainer \345\246\202\344\275\225\345\256\236\347\216\260\345\256\271\345\231\250\345\216\237\345\234\260\345\215\207\347\272\247.md" create mode 100644 "blog-cn/\346\267\261\345\272\246\350\247\243\346\236\220 PouchContainer \347\232\204\345\257\214\345\256\271\345\231\250\346\212\200\346\234\257.md" create mode 100644 "blog-cn/\351\230\277\351\207\214PouchContainer\350\265\204\346\272\220\347\256\241\347\220\206\346\216\242\347\247\230\357\274\232PouchContainer\345\272\225\345\261\202\347\232\204\345\206\205\346\240\270\346\212\200\346\234\257.md" diff --git "a/blog-cn/PouchContainer CRI\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" "b/blog-cn/PouchContainer CRI\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" new file mode 100644 index 0000000..8425906 --- /dev/null +++ "b/blog-cn/PouchContainer CRI\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" @@ -0,0 +1,148 @@ +### 1. CRI简介 + +在每个Kubernetes节点的最底层都有一个程序负责具体的容器创建删除工作,Kubernetes会对其接口进行调用,从而完成容器的编排调度。我们将这一层软件称之为容器运行时(Container Runtime),大名鼎鼎的Docker就是其中的代表。 + +当然,容器运行时并非只有Docker一种,包括CoreOS的rkt,hyper.sh的runV,Google的gvisor,以及本文的主角PouchContainer,都包含了完整的容器操作,能够用来创建特性各异的容器。不同的容器运行时有着各自独特的优点,能够满足不同用户的需求,因此Kubernetes支持多种容器运行时势在必行。 + +最初,Kubernetes原生内置了对Docker的调用接口,之后社区又在Kubernetes 1.3中集成了rkt的接口,使其成为了Docker以外,另一个可选的容器运行时。不过,此时不论是对于Docker还是对于rkt的调用都是和Kubernetes的核心代码强耦合的,这无疑会带来如下两方面的问题: + +1. 新兴的容器运行时,例如PouchContainer这样的后起之秀,加入Kubernetes生态难度颇大。容器运行时的开发者必须对于Kubernetes的代码(至少是Kubelet)有着非常深入的理解,才能顺利完成两者之间的对接。 +2. Kubernetes的代码将更加难以维护,这也体现在两方面:(1)将各种容器运行时的调用接口全部硬编码进Kubernetes,会让Kubernetes的核心代码变得臃肿不堪,(2)容器运行时接口细微的改动都会引发Kubernetes核心代码的修改,增加Kubernetes的不稳定性 + +为了解决这些问题,社区在Kubernetes 1.5引入了CRI(Container Runtime Interface),通过定义一组容器运行时的公共接口将Kubernetes对于各种容器运行时的调用接口屏蔽至核心代码以外,Kubernetes核心代码只对该抽象接口层进行调用。而对于各种容器运行时,只要满足了CRI中定义的各个接口就能顺利接入Kubernetes,成为其中的一个容器运行时选项。方案虽然简单,但是对于Kubernetes社区维护者和容器运行时开发者来说,都是一种解放。 + +### 2. CRI设计概述 + + + +![cri-1.png | left | 827x299](https://cdn.yuque.com/lark/0/2018/png/103564/1527478355304-a20865ae-81b8-4f13-910d-39c9db4c72e2.png "") + + +如上图所示,左边的Kubelet是Kubernetes集群的Node Agent,它会对本节点上容器的状态进行监控,保证它们都按照预期状态运行。为了实现这一目标,Kubelet会不断调用相关的CRI接口来对容器进行同步。 + +CRI shim则可以认为是一个接口转换层,它会将CRI接口,转换成对应底层容器运行时的接口,并调用执行,返回结果。对于有的容器运行时,CRI shim是作为一个独立的进程存在的,例如当选用Docker为Kubernetes的容器运行时,Kubelet初始化时,会附带启动一个Docker shim进程,它就是Docker的CRI shime。而对于PouchContainer,它的CRI shim则是内嵌在Pouchd中的,我们将其称之为CRI manager。关于这一点,我们会在下一节讨论PouchContainer相关架构时再详细叙述。 + +CRI本质上是一套gRPC接口,Kubelet内置了一个gRPC Client,CRI shim中则内置了一个gRPC Server。Kubelet每一次对CRI接口的调用,都将转换为gRPC请求由gRPC Client发送给CRI shim中的gRPC Server。Server调用底层的容器运行时对请求进行处理并返回结果,由此完成一次CRI接口调用。 + +CRI定义的gRPC接口可划分两类,ImageService和RuntimeService:其中ImageService负责管理容器的镜像,而RuntimeService则负责对容器生命周期进行管理以及与容器进行交互(exec/attach/port-forward)。 + +### 3. CRI Manager架构设计 + + + +![yzz's pic.jpg | left | 827x512](https://cdn.yuque.com/lark/0/2018/jpeg/95844/1527582870490-a9b9591d-d529-4b7d-bc5f-69514ef115e7.jpeg "") + + +在PouchContainer的整个架构体系中,CRI Manager实现了CRI定义的全部接口,担任了PouchContainer中CRI shim的角色。当Kubelet调用一个CRI接口时,请求就会通过Kubelet的gRPC Client发送到上图的gRPC Server中。Server会对请求进行解析,并调用CRI Manager相应的方法进行处理。 + +我们先通过一个例子来简单了解一下各个模块的功能。例如,当到达的请求为创建一个Pod,那么CRI Manager会先将获取到的CRI格式的配置转换成符合PouchContainer接口要求的格式,调用Image Manager拉取所需的镜像,再调用Container Manager创建所需的容器,并调用CNI Manager,利用CNI插件对Pod的网络进行配置。最后,Stream Server会对交互类型的CRI请求,例如exec/attach/portforward进行处理。 + +值得注意的是,CNI Manager和Stream Server是CRI Manager的子模块,而CRI Manager,Container Manager以及Image Manager是三个平等的模块,它们都位于同一个二进制文件Pouchd中,因此它们之间的调用都是最为直接的函数调用,并不存在例如Docker shim与Docker交互时,所需要的远程调用开销。下面,我们将进入CRI Manager内部,对其中重要功能的实现做更为深入的理解。 + +### 4. Pod模型的实现 + +在Kubernetes的世界里,Pod是最小的调度部署单元。简单地说,一个Pod就是由一些关联较为紧密的容器构成的容器组。作为一个整体,这些“亲密”的容器之间会共享一些东西,从而让它们之间的交互更为高效。例如,对于网络,同一个Pod中的容器会共享同一个IP地址和端口空间,从而使它们能直接通过localhost互相访问。对于存储,Pod中定义的volume会挂载到其中的每个容器中,从而让每个容器都能对其进行访问。 + +事实上,只要一组容器之间共享某些Linux Namespace以及挂载相同的volume就能实现上述的所有特性。下面,我们就通过创建一个具体的Pod来分析PouchContainer中的CRI Manager是如何实现Pod模型的: + +1. 当Kubelet需要新建一个Pod时,首先会对`RunPodSandbox`这一CRI接口进行调用,而CRI Manager对该接口的实现是创建一个我们称之为"infra container"的特殊容器。从容器实现的角度来看,它并不特殊,无非是调用Container Manager,创建一个镜像为`pause-amd64:3.0`的普通容器。但是从整个Pod容器组的角度来看,它是有着特殊作用的,正是它将自己的Linux Namespace贡献出来,作为上文所说的各容器共享的Linux Namespace,将容器组中的所有容器联结到一起。它更像是一个载体,承载了Pod中所有其他的容器,为它们的运行提供基础设施。而一般我们也用infra container代表一个Pod。 +2. 在infra container创建完成之后,Kubelet会对Pod容器组中的其他容器进行创建。每创建一个容器就是连续调用`CreateContainer`和`StartContainer`这两个CRI接口。对于`CreateContainer`,CRI Manager仅仅只是将CRI格式的容器配置转换为PouchContainer格式的容器配置,再将其传递给Container Manager,由其完成具体的容器创建工作。这里我们唯一需要关心的问题是,该容器如何加入上文中提到的infra container的Linux Namespace。其实真正的实现非常简单,在Container Manager的容器配置参数中有`PidMode`, `IpcMode`以及`NetworkMode`三个参数,分别用于配置容器的Pid Namespace,Ipc Namespace和Network Namespace。笼统地说,对于容器的Namespace的配置一般都有两种模式:"None"模式,即创建该容器自己独有的Namespace,另一种即为"Container"模式,即加入另一个容器的Namespace。显然,我们只需要将上述三个参数配置为"Container"模式,加入infra container的Namespace即可。具体是如何加入的,CRI Manager并不需要关心。对于`StartContainer`,CRI Manager仅仅只是做了一层转发,从请求中获取容器ID并调用Container Manager的`Start`接口启动容器。 +3. 最后,Kubelet会不断调用`ListPodSandbox`和`ListContainers`这两个CRI接口来获取本节点上容器的运行状态。其中`ListPodSandbox`罗列的其实就是各个infra container的状态,而`ListContainer`罗列的是除了infra container以外其他容器的状态。现在问题是,对于Container Manager来说,infra container和其他container并不存在任何区别。那么CRI Manager是如何对这些容器进行区分的呢?事实上,CRI Manager在创建容器时,会在已有容器配置的基础之上,额外增加一个label,标志该容器的类型。从而在实现`ListPodSandbox`和`ListContainers`接口的时候,以该label的值作为条件,就能对不同类型的容器进行过滤。 + +综上,对于Pod的创建,我们可以概述为先创建infra container,再创建pod中的其他容器,并让它们加入infra container的Linux Namespace。 + +### 5. Pod网络配置 + +因为Pod中所有的容器都是共享Network Namespace的,因此我们只需要在创建infra container的时候,对它的Network Namespace进行配置即可。 + +在Kubernetes生态体系中容器的网络功能都是由CNI实现的。和CRI类似,CNI也是一套标准接口,各种网络方案只要实现了该接口就能无缝接入Kubernetes。CRI Manager中的CNI Manager就是对CNI的简单封装。它在初始化的过程中会加载目录`/etc/cni/net.d`下的配置文件,如下所示: + +```sh +$ cat >/etc/cni/net.d/10-mynet.conflist <PouchContainer CRI的设计与实现,是阿里巴巴-浙江大学前沿技术联合研究中心的联合研究项目,旨在帮助PouchContainer 作为一种成熟的容器运行时(container runtime),积极在生态层面拥抱 CNCF。浙江大学 SEL 实验室的卓越技术力量,有效帮助 Pouch 完成 CRI 层面的空白,未来预计在阿里巴巴以及其他使用PouchContainer的数据中心中,创造不可估量的价值。 + +### 参考文献 + +* [Introducing Container Runtime Interface (CRI) in Kubernetes](https://kubernetes.io/blog/2016/12/container-runtime-interface-cri-in-kubernetes/) +* [CRI Streaming Requests Design Doc](https://docs.google.com/document/d/1OE_QoInPlVCK9rMAx9aybRmgFiVjHpJCHI9LrfdNM_s/edit#) + diff --git "a/blog-cn/20180517-PouchContainer Goroutine Leak \346\243\200\346\265\213\345\256\236\350\267\265.md" "b/blog-cn/PouchContainer Goroutine Leak \346\243\200\346\265\213\345\256\236\350\267\265.md" similarity index 100% rename from "blog-cn/20180517-PouchContainer Goroutine Leak \346\243\200\346\265\213\345\256\236\350\267\265.md" rename to "blog-cn/PouchContainer Goroutine Leak \346\243\200\346\265\213\345\256\236\350\267\265.md" diff --git "a/blog-cn/PouchContainer volume\346\234\272\345\210\266\350\247\243\346\236\220.md" "b/blog-cn/PouchContainer volume\346\234\272\345\210\266\350\247\243\346\236\220.md" new file mode 100644 index 0000000..857acfe --- /dev/null +++ "b/blog-cn/PouchContainer volume\346\234\272\345\210\266\350\247\243\346\236\220.md" @@ -0,0 +1,133 @@ +PouchContainer volume是专门用来解决容器的数据持久化的机制,想要了解volume的机制,就需要了解PouchContainer的镜像机制。PouchContainer,和Docker一样,实现了镜像的分层机制。所谓镜像分层机制,是指容器的镜像实际上是由多个只读的镜像层(layer)叠加而成,这样不同的镜像就可以复用镜像层,大大加快了镜像分发的效率,同时也减少了容器启动时间。当容器需要启动时,pouchd(下文中提到的pouchd均指PouchContainer daemon)会在启动镜像的最上层添加一个读写层,后续容器所有的读写操作就会记录在这个读写层中。这样也引入了一个问题,那就是容器数据的持久化。假如我们将容器删除,再次通过该镜像启动时,容器之前所做的修改都丢失了,这对于有状态的应用(如数据库)是致命的。 + +volume绕过了镜像机制,让容器中的数据以正常的文件或者目录的形式存在于宿主机上,当容器停止或删除时,并不会影响到volume中的数据,从而实现了数据的持久化,而且volume数据可以在不同的container之间共享。 + +## 1. PouchContainer volume整体架构 + +该部分内容可能涉及PouchContainer的volume源码实现。 + +PouchContainer volume整体架构目前主要由以下几部分构成: + +* __VolumeManager__:该结构是volume相关操作的入口。 +* __Core__:Core是volume的核心模块,包含了volume操作的业务逻辑 +* __Store__:负责存储volume元数据,目前元数据存储在本地的boltdb文件中。 +* __Driver__:volume driver接口,抽象了volume相关驱动的基本功能 +* __Modules__:具体的volume driver,目前存在local, tmpfs, volume plugin, ceph四种volume驱动 + + + +![pouch_volume_arch.png | center | 747x624](https://cdn.yuque.com/lark/0/2018/png/108876/1526824612386-4a990eb9-77b8-4bdf-83ff-a243501a45d3.png "") + + +VolumeManager是PouchContainer中的存储组件(其他组件包括ContainerManager, ImageManager、NetworkManager等),它是所有volume操作的入口,目前提供了Create/Remove/List/Get/Attach/Detach接口。Core包含了volume操作的核心逻辑,向下负责调用底层具体的volume driver,实现volume的创建、删除、attach、detach等操作,同时调用Store,实现volume元数据管理。Store模块专门负责volume的元数据管理,volume的相关状态都会通过Store进行存储,之所以将元数据管理专门作为一个模块,是为了将来方便扩展,目前volume元数据是存储在boltdb,未来也可能存入etcd等。Driver抽象了volume driver需要实现的接口,一个具体的volume driver需要实现如下接口: + +```go +type Driver interface { +    // Name returns backend driver's name. +        Name(Context) string + +        // StoreMode defines backend driver's store model. +        StoreMode(Context) VolumeStoreMode + +        // Create a volume. +        Create(Context, *types.Volume, *types.Storage) error + +        // Remove a volume. +        Remove(Context, *types.Volume, *types.Storage) error + +        // Path returns volume's path. +        Path(Context, *types.Volume) (string, error) +} +``` +        + +## 2. PouchContainer支持的volume类型 + +目前,PouchContainer支持三种具体的volume类型,即local, tmpfs和ceph,还通过volume plugin这种通用的存储插件机制支持更多的第三方存储。 + +### 2.1 local volume + +local volume是PouchContainer默认的volume类型,适合存储需要持久化的数据,它的生命周期独立于容器的生命周期。 + +当你创建一个volume时,假如没有指定driver类型,则默认local为driver类型。local volume本质上,是由pouchd在/var/lib/pouch/volume目录下创建的一个子目录。相较于docker,PouchContainer的local volume拥有更多的实用特性,包括: + +* 指定挂载目录创建volume +* 可以指定volume大小 + +首先我们可以指定目录创建一个local volume。该特性在生产中非常实用。对于某些应用,如数据库,我们需要挂载专门的块设备,用于存储数据库数据,例如运维人员将块设备格式化后,挂载到/mnt/mysql\_data目录。执行以下命令,我们就创建了一个挂载在/mnt/mysql\_data的volume,然后可以将该volume挂载到容器指定目录,启动容器。 + +```powershell +pouch volume create --driver local --option mount=/mnt/mysql_data --name mysql_data +``` + +其次,我们可以限制volume的大小。该功能依赖于底层文件系统提供的quato功能,目前支持的底层文件系统为ext4和xfs,同时对内核版本也有要求。 + +```powershell +pouch volume create --driver local --option size=10G --name test_quota +``` + +### 2.2 tmpfs volume + +tmpfs volume的数据并不会持久化到硬盘中去,只存储于内存中 (若内存不足,则存入swap),访问速度快,但当容器停止运行时,该volume里面的所有信息都会消失,因此tmpfs volume只适合保存一些临时和敏感的数据。 + +tmpfs volume默认存储/mnt/tmpfs目录下,你也可以通过 *-o mount* 指定其挂载路径。不过指定tmpfs的挂载路径没有什么意义,因为tmpfs内容直接存储在内存中。 + +```powershell +pouch volume create --driver tmpfs --name tmpfs_test +``` + +### 2.3 ceph volume + +ceph是一种比较特殊的volume类型,ceph volume是将数据存储到ceph集群(ceph rbd 存储)中,因此可以实现volume跨物理机的迁移。 + +目前外界暂时不能使用ceph volume。从PouchContainer volume架构图可知,ceph driver和driver层之间还有一层alibaba storage controller(注意:alibaba storage controller只是一个代称),这是阿里巴巴内部的一套容器存储管理平台,后面对接了ceph/pangu/nas等诸多存储方案。PouchContainer通过与该容器存储管理平台对接,可以直接利用ceph提供volume。后期我们可能开源该容器存储管理平台。 + +### 2.4 volume plugin + +volume plugin是一种通用性的volume,准确来说它是一种volume的扩展机制。目前docker通过插件机制可以管理诸多的第三方存储,PouchContainer也实现了该volume plugin机制,可以无缝对接原先docker已经存在的volume plugin。 + +作为一个volume plugin,必须实现[volume plugin protocal](https://docs.docker.com/engine/extend/plugins_volume/#volume-plugin-protocol)。volume plugin其实本质上是一个web server,该web server实现了如下服务,所有请求均为POST请求。 + +```plain +/VolumeDriver.Create // Volume创建服务 + +/VolumeDriver.Remove // Volume删除服务 + +/VolumeDriver.Mount // Volume挂载服务 + +/VolumeDriver.Path // Volume挂载路径服务 + +/VolumeDriver.Unmount // Volume卸载服务 + +/VolumeDriver.Get // Volume Get服务 + +/VolumeDriver.List // Volume List服务 + +/VolumeDriver.Capabilities // Volume Driver能力服务 +``` + +## 3. bind mounts与volumes + +PouchContainer目前支持两种数据持久化的方式,除了上述的volumes,还可以利用bind mounts。bind mounts,顾名思义,指的是直接将宿主机的目录挂载到容器里面。 + +```powershell +pouch run -d -t -v /hostpath/data:/containerpath/data:ro ubuntu sh +``` + +上述这条命令就将宿主机上的/hostpath/data目录已只读的方式挂载到容器的/containerpath/data目录下。 + +bind mounts依赖于宿主机文件系统目录结构,而volume,在PouchContainer中有专门的机制进行管理。volumes相对于bind mounts,有以下优势: + +* volumes相对于bind mounts,更容易进行备份和管理; +* PouchContainer提供了专门的cli和api,用来管理volumes; +* volumes适合在多个容器之间安全地共享; +* volumes提供了插件机制,可以更加方便地对接第三方存储。 + +## 4. PouchContainer volume未来的发展 + +[CSI](https://github.com/container-storage-interface/spec),即Container Storage Interface(该项目定义了容器调度层和容器之间的存储接口),目前已发布了v0.2版本。Pouch未来可能增加一种通用类型的driver,用于对接已实现CSI接口的存储系统。 + +## 5. 总结 + +本文介绍了PouchContainer的volume机制, volume机制主要是为了解决pouch容器数据持久化的问题PouchContainer,目前支持local,tmpfs,ceph三种driver,同时支持以volume plugin的形式对接更多的第三方存储。 + diff --git "a/blog-cn/2018-04-11_PouchContainer-\346\224\257\346\214\201-LXCFS-\345\256\236\347\216\260\351\253\230\345\217\257\351\235\240\345\256\271\345\231\250\351\232\224\347\246\273.md" "b/blog-cn/PouchContainer-\346\224\257\346\214\201-LXCFS-\345\256\236\347\216\260\351\253\230\345\217\257\351\235\240\345\256\271\345\231\250\351\232\224\347\246\273.md" similarity index 100% rename from "blog-cn/2018-04-11_PouchContainer-\346\224\257\346\214\201-LXCFS-\345\256\236\347\216\260\351\253\230\345\217\257\351\235\240\345\256\271\345\231\250\351\232\224\347\246\273.md" rename to "blog-cn/PouchContainer-\346\224\257\346\214\201-LXCFS-\345\256\236\347\216\260\351\253\230\345\217\257\351\235\240\345\256\271\345\231\250\351\232\224\347\246\273.md" diff --git "a/blog-cn/\346\267\261\345\205\245\350\247\243\346\236\220 PouchContainer \345\246\202\344\275\225\345\256\236\347\216\260\345\256\271\345\231\250\345\216\237\345\234\260\345\215\207\347\272\247.md" "b/blog-cn/\346\267\261\345\205\245\350\247\243\346\236\220 PouchContainer \345\246\202\344\275\225\345\256\236\347\216\260\345\256\271\345\231\250\345\216\237\345\234\260\345\215\207\347\272\247.md" new file mode 100644 index 0000000..52e33aa --- /dev/null +++ "b/blog-cn/\346\267\261\345\205\245\350\247\243\346\236\220 PouchContainer \345\246\202\344\275\225\345\256\236\347\216\260\345\256\271\345\231\250\345\216\237\345\234\260\345\215\207\347\272\247.md" @@ -0,0 +1,126 @@ +# 背景 + +阿里巴巴集团内部,容器使用方式有很大一部分是富容器模式,像这种基于传统虚拟机运维模式下的富容器,其中也有一定数量容器仍然是有状态的。有状态服务的更新和升级是企业内部频率很高的一个日常操作,对于以镜像为交付的容器技术来说,服务的更新和升级,对应的容器操作实际上是两步:旧镜像容器的删除,以及新镜像容器的创建。而有状态服务的升级,则要求保证新容器必须继承旧容器所有的资源,比如网络、存储等信息。下面给出两个实际的业务案例来直观阐述富容器业务发布场景需求: + +* 客户案例一:某数据库业务,在第一次创建容器服务时,会将远程的数据下载到本地,作为数据库的初始数据。因为数据库初始化过程会比较长,所以在之后可能存在的服务升级过程中,新容器需要继承旧容器的存储数据,来降低业务发布的时间; +* 客户案例二:某中间件服务,业务采取服务注册的模式,即所有新扩容的容器 IP 必须首先注册到服务器列表中,否则新扩容业务容器不可用。在业务容器每次升级发布时,需要保证新容器继承旧容器 IP,否则会导致新发布的服务不可用。 + +现在很多企业都是使用 Moby 作为容器引擎,但 Moby 的所有 API 中并没有一个接口来对标容器升级这一操作。而组合 API 的方式,必然会增加很多 API 请求次数,比如需要请求容器的增删 API,需要请求 IP 保留的 API 等等,还可能增加升级操作失败的风险。 + +基于以上背景,PouchContainer 在容器引擎层面提供了一个 `upgrade` 接口,用于实现容器的原地升级功能。将容器升级功能下沉到容器引擎这一层来做,对于操作容器相关资源更加方便,并且减少很多 API 请求,让容器升级操作变得更加高效。 + +# Upgrade 功能具体实现 + +## 容器底层存储介绍 + +PouchContainer 底层对接的是 Containerd v1.0.3 ,对比 Moby,在容器存储架构上有很大的差别,所以在介绍 PouchContainer 如何实现容器原地升级功能之前,有必要先简单介绍一下在 PouchContainer 中一个容器的存储架构: + + +![image.png | center | 600x336.3525091799266](https://cdn.yuque.com/lark/0/2018/png/95961/1527735535637-5afc58e6-31ef-400c-984c-a9d7158fd40d.png "") + + +对比 Moby 中容器存储架构,PouchContainer 主要不一样的地方: +* PouchContainer 中没有了 GraphDriver 和 Layer 的概念,新的存储架构里引入了 Snapshotter 和 Snapshot,从而更加拥抱 CNCF 项目 containerd 的架构设计。Snapshotter 可以理解为存储驱动,比如 overlay、devicemapper、btrfs 等。Snapshot 为镜像快照,分为两种:一种只读的,即容器镜像的每一层只读数据;一种为可读写的,即容器可读写层,所有容器增量数据都会存储在可读写 Snapshot 中; +* Containerd 中容器和镜像元数据都存储在 boltdb 中,这样的好处是每次服务重启不需要通过读取宿主机文件目录信息来初始化容器和镜像数据,而是只需要初始化 boltdb。 + +## Upgrade 功能需求 + +每一个系统和功能设计之初,都需要详细调研该系统或功能需要为用户解决什么疼点。经过调研阿里内部使用容器原地升级功能的具体业务场景,我们对 `upgrade` 功能设计总结了三点要求: +* 数据一致性 +* 灵活性 +* 鲁棒性 + +数据一致性指 `upgrade` 前后需要保证一些数据不变: +* 网络:升级前后,容器网络配置要保持不变; +* 存储:新容器需要继承旧容器的所有 volume ; +* Config:新容器需要继承旧容器的某一些配置信息,比如 Env, Labels 等信息; + +灵活性指 `upgrade` 操作在旧容器的基础上,允许引入新的配置: +* 允许修改新容器的 cpu、memory 等信息; +* 对新的镜像,即要支持指定新的 `Entrypoint` ,也要允许继承旧容器的 `Entrypoint` ; +* 支持给容器增加新的 volume,新的镜像中可能会包含新的 volume 信息,在新建容器时,需要对这部分 volume 信息进行解析,并创建新的 volume。 + +鲁棒性是指在进行容器原地升级操作期间,需要对可能出现的异常情况进行处理,支持回滚策略,升级失败可以回滚到旧容器。 + +## Upgrade 功能具体实现 +### Upgrade API 定义 + +首先说明一下 `upgrade` API 入口层定义,用于定义升级操作可以对容器的哪些参数进行修改。如下 `ContainerUpgradeConfig` 的定义,容器升级操作可以对容器 `ContainerConfig` 和 `HostConfig` 都可以进行操作,如果在 PouchContainer github 代码仓库的 `apis/types` 目录下参看这两个参数的定义,可以发现实际上,`upgrade` 操作可以修改旧容器的__所有__相关配置。 +```go +// ContainerUpgradeConfig ContainerUpgradeConfig is used for API "POST /containers/upgrade". +// It wraps all kinds of config used in container upgrade. +// It can be used to encode client params in client and unmarshal request body in daemon side. +// +// swagger:model ContainerUpgradeConfig + +type ContainerUpgradeConfig struct { + ContainerConfig + + // host config + HostConfig *HostConfig `json:"HostConfig,omitempty"` +} +``` + +### Upgrade 详细操作流程 + +容器 `upgrade` 操作,实际上是在保证网络配置和原始 volume 配置不变的前提下,进行旧容器的删除操作,以及使用新镜像创建新容器的过程,如下给出了 `upgrade` 操作流程的详细说明: +* 首先需要备份原有容器的所有操作,用于升级失败之后,进行回滚操作; +* 更新容器配置参数,将请求参数中新的配置参数合并到旧的容器参数中,使新配置生效; +* 镜像 `Entrypoint` 参数特殊处理:如果新的参数中指定了 `Entrypoint` 参数,则使用新的参数;否则查看旧容器的 `Entrypoint` ,如果该参数是通过配置参数指定,而不是旧镜像中自带的,则使用旧容器的 `Entrypoint` 作为新容器的 `Entrypoint` ;如果都不是,最后使用新镜像中的 `Entrypoint` 最为新创建容器的 `Entrypoint` 。对新容器 `Entrypoint` 这样处理的原因是为了保持容器服务入口参数的连续性。 +* 判断容器的状态,如果是 running 状态的容器,首先 stop 容器;之后基于新的镜像创建一个新的 Snapshot 作为新容器读写层; +* 新的 Snapshot 创建成功之后,再次判断旧容器升级之前的状态,如果是 running 状态,则需要启动新的容器,否则不需要做任何操作; +* 最后进行容器升级清理工作,删掉旧的 Snapshot,并将最新配置进行存盘。 + +### Upgrade 操作回滚 + +`upgrade` 操作可能会出现一些异常情况,现在的升级策略是在出现异常情况时,会进行回滚操作,恢复到原来旧容器的状态,在这里我们需要首先定义一下 __升级失败情况__ : +* 给新容器创建新的资源时失败,需要执行回滚操作:当给新容器创建新的 Snapshot,Volumes 等资源时,会执行回滚操作; +* 启动新容器出现系统错误时,需要执行回滚操作:即在调用 containerd API 创建新的容器时如果失败则会执行回滚操作。如果 API 返回正常,但容器内的程序运行异常导致容器退出的情况,不会执行回滚操作。 +如下给出了回滚操作的一个基本操作: +```go +defer func() { + if !needRollback { + return + } + + // rollback to old container. + c.meta = &backupContainerMeta + + // create a new containerd container. + if err := mgr.createContainerdContainer(ctx, c); err != nil { + logrus.Errorf("failed to rollback upgrade action: %s", err.Error()) + if err := mgr.markStoppedAndRelease(c, nil); err != nil { + logrus.Errorf("failed to mark container %s stop status: %s", c.ID(), err.Error()) + } + } +}() +``` + +在升级过程中,如果出现异常情况,会将新创建的 Snapshot 等相关资源进行清理操作,在回滚阶段,只需要恢复旧容器的配置,然后用恢复后的配置文件启动一个新容器既可。 + +### Upgrade 功能演示 + +* 使用 `ubuntu` 镜像创建一个新容器: +```bash +$ pouch run --name test -d -t registry.hub.docker.com/library/ubuntu:14.04 top +43b75002b9a20264907441e0fe7d66030fb9acedaa9aa0fef839ccab1f9b7a8f + +$ pouch ps +Name ID Status Created Image Runtime +test 43b750 Up 3 seconds 3 seconds ago registry.hub.docker.com/library/ubuntu:14.04 runc +``` + +* 将 `test` 容器的镜像升级为 `busybox` : +```bash +$ pouch upgrade --name test registry.hub.docker.com/library/busybox:latest top +test +$ pouch ps +Name ID Status Created Image Runtime +test 43b750 Up 3 seconds 34 seconds ago registry.hub.docker.com/library/busybox:latest runc +``` + +如上功能演示,通过 `upgrade` 接口,直接将容器的镜像替换为新的镜像,而其他配置都没有变化。 + +# 总结 + +在企业生产环境中,容器 `upgrade` 操作和容器扩容、缩容操作一样也是的一个高频操作,但是,不管是在现在的 Moby 社区,还是 Containerd 社区都没有一个与该操作对标的 API,PouchContainer 率先实现了这个功能,解决了容器技术在企业环境中有状态服务更新发布的一个痛点问题。PouchContainer 现在也在尝试与其下游依赖组件服务如 Containerd 保持紧密的联系,所以后续也会将 `upgrade` 功能回馈给 Containerd 社区,增加 Containerd 的功能丰富度。 diff --git "a/blog-cn/\346\267\261\345\272\246\350\247\243\346\236\220 PouchContainer \347\232\204\345\257\214\345\256\271\345\231\250\346\212\200\346\234\257.md" "b/blog-cn/\346\267\261\345\272\246\350\247\243\346\236\220 PouchContainer \347\232\204\345\257\214\345\256\271\345\231\250\346\212\200\346\234\257.md" new file mode 100644 index 0000000..1c54041 --- /dev/null +++ "b/blog-cn/\346\267\261\345\272\246\350\247\243\346\236\220 PouchContainer \347\232\204\345\257\214\345\256\271\345\231\250\346\212\200\346\234\257.md" @@ -0,0 +1,95 @@ +PouchContainer 是阿里巴巴集团开源的高效、轻量级企业级富容器引擎技术,拥有隔离性强、可移植性高、资源占用少等特性。可以帮助企业快速实现存量业务容器化,同时提高超大规模下数据中心的物理资源利用率。 + +PouchContainer 源自阿里巴巴内部场景,诞生初期,在如何为互联网应用保驾护航方面,倾尽了阿里巴巴工程师们的设计心血。PouchContainer 的强隔离、富容器等技术特性是最好的证明。在阿里巴巴的体量规模下,PouchContainer 对业务的支撑得到双 11 史无前例的检验,开源之后,阿里容器成为一项普惠技术,定位于「助力企业快速实现存量业务容器化」。 +
+
+
+ +
+
+
+ + +初次接触容器技术时,阿里巴巴内部有着惊人规模的存量业务,如何通过技术快速容器化存量业务,是阿里容器技术当年在内部铺开时的重点难题。发展到今天,开源容器技术逐渐普及,面对落地,相信不少存在大量存量业务的企业,同样为这些业务的如何容器化而犯愁。云原生领域,CNCF 基金会推崇的众多先进理念,绝大多数都建立在业务容器化的基础之上。倘若企业业务在云原生的入口容器化方面没有踩准步点,后续的容器编排、Service Mesh 等行业开源技术红利更是无从谈起。 + +通过七年的实践经验,阿里巴巴容器技术 PouchContainer 用事实向行业传递这样的信息 —— 富容器是实现企业存量业务快速容器化的首选技术。 + +## 什么是富容器 + +富容器是企业打包业务应用、实现业务容器化过程中,采用的一种容器模式。此模式可以帮助企业IT技术人员打包业务应用时,几乎不费吹灰之力。通过富容器技术打包的业务应用可以达到以下两个目的: + +* 容器镜像实现业务的快速交付 +* 容器环境兼容企业原有运维体系 + +技术角度而言,富容器提供了有效路径,帮助业务在单个容器镜像中除了业务应用本身之外,还打包更多业务所需的运维套件、系统服务等;同时相比于较为简单的单进程容器,富容器在进程组织结构层面,也有着巨大的变革:容器运行时内部自动运行 systemd 等管家进程。如此一来,富容器模式下的应用,有能力在不改变任何业务代码、运维代码的情况下,像在物理机上运行一模一样。可以说,这是一种更为通用的「面向应用」的模式。 + +换言之,富容器在保障业务交付效率的同时,在开发和运维层面对应用没有任何的侵入性,从而有能力帮助 IT 人员更多聚焦业务创新。 + +## 适用场景 + +富容器的适用场景极广。可以说企业几乎所有的存量业务,都可以采纳富容器作为容器化方案首选。容器技术流行之前,有接近二十年的时间,企业 IT 服务运行在裸金属或者虚拟机中。企业业务的稳定运行,有非常大的功劳来源于运维工作,如果细分,包括「基础设施运维」以及「业务运维」。所有的应用运行,都依赖于物理资源;所有的业务稳定,都仰仗于监控系统、日志服务等运维体系。那么,我们有理由相信,在业务容器化过程中,企业坚决不能对运维体系置之不理,否则后果可想而知。 + +因此,存量业务容器化过程中,需要考虑兼容企业原有运维体系的场景,都在 PouchContainer 富容器技术的使用范围之内。 + +## 富容器技术实现 + +既然可以业务兼容原有运维体系,那么富容器技术又是通过什么样的技术来实现的呢?下图清晰的描述了富容器技术的内部情况。 + + +![image.png | center | 747x368](https://cdn.yuque.com/lark/0/2018/png/65333/1526059474453-02e322a1-f33f-4de2-a238-26e0501b3106.png "") + + +富容器技术可以完全百分百兼容社区的 OCI 镜像,容器启动时将镜像的文件系统作为容器的 rootfs。运行模式上,功能层面,除了内部运行进程,同时还包括容器启停时的钩子方法(prestart hook 和 poststop hook)。 + +### 富容器内部运行进程 + +如果从内部运行进程的角度来看待 PouchContainer 的富容器技术,我们可以把内部运行进程分为 4 类: + +* pid=1 的 init 进程 +* 容器镜像的 CMD +* 容器内部的系统 service 进程 +* 用户自定义运维组件 + +#### pid=1 的 init 进程 + +富容器技术与传统容器最明显的差异点,即容器内部运行一个 init 进程,而传统的容器(如 docker 容器等)将容器镜像中指定的 CMD 作为容器内 pid=1 的进程。PouchContainer 的富容器模式可以运行从三种 init 进程中选择: + +* systemd +* sbin/init +* dumb-init + +众所周知,传统容器作为一个独立运行环境,内部进程的管理存在一定的弊端:比如无法回收僵尸进程,导致容器消耗太多进程数、消耗额外内存等;比如无法友好管理容器内部的系统服务进程,导致一些业务应用所需要的基本能力欠缺等,比如 cron 系统服务、syslogd 系统服务等;比如,无法支持一些系统应用的正常运行,主要原因是某些系统应用需要调用 systemd 来安装 RPM 包…… + +富容器的 init 进程在运维模式上,毫无疑问可以解决以上问题,给应用带来更好的体验。init 进程在设计时就加入了可以 wait 消亡进程的能力,即可以轻松解决上图中业务进程运行过程中诞生的 Zombie 僵尸进程;同时管理系统服务也是它的本职工作之一。如果一来,一些最为基本的传统运维能力,init 进程即帮助用户解决了大半,为运维体系做好了坚实的基础。 + +#### 容器镜像的CMD + +容器镜像的 CMD,也就是传统意义上我们希望在容器内部运行的业务。比如,用户在容器化一个 Golang 的业务系统打包成镜像时,肯定会在 Dockerfile 中将该业务系统的启动命令指定为 CMD,从而保证未来通过该镜像运行容器起,会执行这条 CMD 命令运行业务系统。 + +当然,容器镜像的 CMD 代表业务应用,是整个富容器的核心部分,所有的运维适配都是为了保障业务应用更加稳定的运行。 + +#### 容器内系统 service 进程 + +服务器编程发展了数十年,很多的业务系统开发模式均基于裸金属上的 Linux 操作系统,或者虚拟化环境的下的 Linux 环境。长此以往,很多业务应用的开发范式,会非常频繁地与系统服务进程交互。比如,使用 Java 编程语言编写的应用程序,很有可能通过 log4j 来配置日志的管理方式,也可以通过 log4j.properties 配置把应用日志重定向到运行环境中的 syslogd,倘若应用运行环境中没有 syslogd 的运行,则极有可能影响业务的启动运行;再比如,业务应用需要通过 crond 来管理业务需要的周期性任务,倘若应用运行环境中没有 crond 系统守护进程,业务应用也就不可能通过 crontab 来配置周期任务;再比如,容器内部的 sshd 系统服务系统,可以快速帮助运维工程师快速进度应用运行现场,定位并解决问题等。 + +PouchContainer 的富容器模式,考虑到了行业大量有需求和系统服务交付的应用,富容器内部的 init 进程有能力非常方面的原生管理多种系统服务进程。 + +#### 用户自定义运维组件 + +系统服务的存在可以辅助业务的正常运行,但是很多情况下这还不够,企业自身针对基础设施以及应用配备的运维组件,同时起到为业务保驾护航的作用。比如,企业运维团队需要统一化的为业务应用贴近配置监控组件;运维团队必须通过自定义的日志 agent 来管理容器内部的应用日志;运维团队需要自定义自己的基础运维工具,以便要求应用运行环境符合内部的审计要求等。 + +正因为富容器内部存在 init 进程,用户自定义的运维组件,可以如往常健康稳定的运行,提供运维能力。 + +### 富容器启停执行 hook + +最终富容器内部运行的任务进程,可以保障应用的运行时稳定正常,然而对于运维团队而言,负责内容的范畴往往要比单一的运行时广得多。通俗而言,运维的职责还需要覆盖运行时之前的环境准备工作,以及运行时结束后的善后工作。对于应用而言,也就是我们通常意义上提到的 prestart hook 以及 poststop hook。 + +PouchContainer 的富容器模式,可以允许用户非常方便的指定应用的启停执行 hook: prestart hook 以及 poststop hook。 运维团队指定 prestart hook,可以帮助应用在运行之前,在容器内部做符合运维需求的一些初始化操作,比如:初始化网络路由表、获取应用执行权限、下载运行时所需的证书等。运维团队指定 poststop hook,可以帮助应用在运行结束或者异常退出之后,执行统一的善后工作,比如,对中间数据的清理以便下一次启动时的纯净环境;倘若是异常退出的话,可以即时汇报出错信息,满足运维需求等。 + +我们可以发现,富容器内部的启停 hook,对容器的运维能力又做了一层拔高,大大释放了运维团队对应用的灵活管理能力。 + +## 总结 + +经过阿里巴巴内部大量业务的锤炼,PouchContainer 已经帮助超大体量的互联网公司实现了所有在线业务的容器化。毫无疑问,富容器技术是最为实用、对应用开发以及应用运维没有任何侵入性的一项技术。开源的PouchContainer 更是希望技术可以普惠行业,帮助大量的企业在存量业务的容器化方面,赢得自己的时间,快速拥抱云原生技术,大步迈向数字化转型。 + + diff --git "a/blog-cn/\351\230\277\351\207\214PouchContainer\350\265\204\346\272\220\347\256\241\347\220\206\346\216\242\347\247\230\357\274\232PouchContainer\345\272\225\345\261\202\347\232\204\345\206\205\346\240\270\346\212\200\346\234\257.md" "b/blog-cn/\351\230\277\351\207\214PouchContainer\350\265\204\346\272\220\347\256\241\347\220\206\346\216\242\347\247\230\357\274\232PouchContainer\345\272\225\345\261\202\347\232\204\345\206\205\346\240\270\346\212\200\346\234\257.md" new file mode 100644 index 0000000..29ec275 --- /dev/null +++ "b/blog-cn/\351\230\277\351\207\214PouchContainer\350\265\204\346\272\220\347\256\241\347\220\206\346\216\242\347\247\230\357\274\232PouchContainer\345\272\225\345\261\202\347\232\204\345\206\205\346\240\270\346\212\200\346\234\257.md" @@ -0,0 +1,489 @@ +[PouchContainer](https://github.com/alibaba/pouch) 是阿里巴巴集团开源的高效、企业级容器引擎技术,拥有隔离性强、可移植性高、资源占用少等特点。可以帮助企业快速实现存量业务容器化,同时提高超大规模下数据中心的物理资源利用率。 + +资源管理是容器运行时的一个重要的部分,本文将给大家介绍PouchContainer资源管理的常用接口和其对应的底层内核接口,为了让读者加深理解,本文为部分接口提供了测试用例。 + +## 1. PouchContainer资源管理常用接口 + +| 接口 | 描述 | +| :--- | :--- | +| --blkio-weight | 块设备IO相对权重,取值范围为0到100之间的整数。 | +| --blkio-weight-device | 指定的块设备的IO相对权重 | +| --cpu-period | 完全公平算法中的period值 | +| --cpu-quota | 完全公平算法中的quota值 | +| --cpu-share | CPU份额 (相对权重) | +| --cpuset-cpus | 限制容器使用的cpu核 | +| --cpuset-mems | 限制容器使用的内存节点,该限制仅仅在NUMA系统中生效。 | +| --device-read-bps | 限制对某个设备的读取速率 ,数字需要使用正整数,单位是kb, mb, or gb中的一个。 | +| --device-read-iops | 限制对某个设备每秒IO的读取速率,数字需要使用正整数。 | +| --device-write-bps | 限制对某个设备的写速率 ,数字需要使用正整数,单位是kb, mb, or gb中的一个。 | +| --device-write-iops | 限制对某个设备每秒IO的写速率,数字需要使用正整数。 | +| -m, --memory | 内存使用限制。 数字需要使用整数,对应的单位是b, k, m, g中的一个。 | +| --memory-swap | 总内存使用限制 (物理内存 + 交换分区,数字需要使用整数,对应的单位是b, k, m, g中的一个。 | +| --memory-swappiness | 调节容器内存使用交换分区的选项,取值为0和100之间的整数(含0和100)。 | +| --memory-wmark-ratio | `用于计算low_wmark,取值范围:0到100之间的整数(包含0和100)。` | +| --oom-kill-disable | 内存耗尽时是否杀掉容器 | +| --oom-score-adj | 设置容器进程触发OOM的可能性,值越大时越容易触发容器进程的OOM。 | +| --pids-limit | 用于限制容器内部的pid数量。 | + +## 2. PouchContainer资源管理底层的内核技术 + +### 2.1 Memory资管管理 + +| 接口 | 对应的内核接口 | 内核接口描述 | +| :--- | :--- | :--- | +| -m, --memory | `cgroup/memory/memory.limit_in_bytes` | 设定内存上限,单位是字节,也可以使用k/K、m/M或者g/G表示要设置数值的单位。 | +| --memory-swap | `cgroup/memory/memory.memsw.limit_in_bytes` | 设定内存加上交换分区的使用总量。通过设置这个值,可以防止进程把交换分区用光。 | +| --memory-swappiness | cgroup/memory/memory.swappiness | 控制内核使用交换分区的倾向。取值范围是0至100之间的整数(包含0和100)。值越小,越倾向使用物理内存。 | +| --memory-wmark-ratio | `cgroup/memory/memory.wmark_ratio` | `用于计算low_wmark,low_wmark = memory.limit_in_bytes * MemoryWmarkRatio。当memory.usage_in_bytes大于low_wmark时,触发内核线程进行内存回收,当回收到memory.usage_in_bytes小于high_wmark时停止回收。` | +| --oom-kill-disable | `cgroup/memory/memory.oom_control` | 如果设置为0,那么在内存使用量超过上限时,系统不会杀死进程,而是阻塞进程直到有内存被释放可供使用时,另一方面,系统会向用户态发送事件通知,用户态的监控程序可以根据该事件来做相应的处理,例如提高内存上限等。 | +| --oom-score-adj | `/proc/$pid/oom_score_adj` | 设置进程触发OOM的可能性,值越大时越容易触发容器进程的OOM。 | + +### 2.2 cpu资管管理 +| 接口 | 对应的cgroup接口 | cgroup接口描述 | +| :--- | :--- | :--- | +| --cpu-period | `cgroup/cpu/cpu.cfs_period_us` | 负责CPU带宽限制,需要与`cpu.cfs_quota_us`搭配使用。我们可以将period设置为1秒,将quota设置为0.5秒,那么cgroup中的进程在1秒内最多只能运行0.5秒,然后就会被强制睡眠,直到下一个1秒才能继续运行。 | +| --cpu-quota | `cgroup/cpu/cpu.cfs_quota_us` | 负责CPU带宽限制,需要与`cpu.cfs_period_us`搭配使用。 | +| --cpu-share | cgroup/cpu/cpu.shares | 负责CPU比重分配的接口。假设我们在cgroupfs的根目录下创建了两个cgroup(C1和C2),并且将cpu.shares分别配置为512和1024,那么当C1和C2争用CPU时,C2将会比C1得到多一倍的CPU占用率。要注意的是,只有当它们争用CPU时CPU share才会起作用,如果C2是空闲的,那么C1可以得到全部的CPU资源。 | +| --cpuset-cpus | cgroup/cpuset/cpuset.cpus | 允许进程使用的CPU列表(例如:0-4,9)。 | +| --cpuset-mems | cgroup/cpuset/cpuset.mems | 允许进程使用的内存节点列表(例如:0-1)。 | + +### 2.3 io资管管理 +| 接口 | 对应的cgroup接口 | cgroup接口描述 | +| :--- | :--- | :--- | +| --blkio-weight | cgroup/blkio/blkio.weight | 设置权重值,取值范围是10至1000之间的整数(包含10和1000)。这跟cpu.shares类似,是比重分配,而不是绝对带宽的限制,因此只有当不同的cgroup在争用同一个块设备的带宽时,才会起作用。 | +| --blkio-weight-device | cgroup/blkio/blkio.weight_device | 对具体的设备设置权重值,这个值会覆盖上述的blkio.weight。 | +| --device-read-bps | `cgroup/blkio/blkio.throttle.read_bps_device` | 对具体的设备,设置每秒读块设备的带宽上限。 | +| --device-write-bps | `cgroup/blkio/blkio.throttle.write_bps_device` | 设置每秒写块设备的带宽上限。同样需要指定设备。 | +| --device-read-iops | `cgroup/blkio/blkio.throttle.read_iops_device` | 设置每秒读块设备的IO次数的上限。同样需要指定设备。 | +| --device-write-iops | `cgroup/blkio/blkio.throttle.write_iops_device` | 设置每秒写块设备的IO次数的上限。同样需要指定设备。 | + +### 2.4 其他资源管理接口 +| 接口 | 对应的cgroup接口 | cgroup接口描述 | +| :--- | :--- | :--- | +| --pids-limit | cgroup/pids/pids.max | 限制进程数 | + +## 3. PouchContainer资源管理接口详解与测试方法 +以下内容针对各资源管理接口做了详尽的说明。为了加深读者理解,部分接口提供有测试用例。用例中的PouchContainer版本为0.4.0。如果在你的镜像中stress命令不可用,你可以通过sudo apt-get install stress来安装stress工具。 + +### 3.1 Memory资管管理 +#### 3.1.1 -m, --memory +可以限制容器使用的内存量,对应的cgroup文件是`cgroup/memory/memory.limit_in_bytes`。 + +单位:b,k,m,g + +在默认情况下,容器可以占用无限量的内存,直至主机内存资源耗尽。 + +运行如下命令来确认容器内存的资源管理对应的cgroup文件。 + +``` +# pouch run -ti --memory 100M reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.limit_in_bytes" +104857600 +``` + +可以看到,当内存限定为100M时,对应的cgroup文件数值为104857600,该数值的单位为字节,即104857600字节等于100M。 + +本机内存环境为: + +``` +# free -m + total used free shared buff/cache available +Mem: 257755 2557 254234 1 963 254903 +Swap: 2047 0 2047 +``` + +我们使用stress工具来验证内存限定是否生效。stress是一个压力工具,如下命令将要在容器内创建一个进程,在该进程中不断的执行占用内存(malloc)和释放内存(free)的操作。在理论上如果占用的内存少于限定值,容器会工作正常。注意,如果试图使用边界值,即试图在容器中使用stress工具占用100M内存,这个操作通常会失败,因为容器中还有其他进程在运行。 + +下面尝试对一个限制内存使用为100M的容器执行一个占用150M内存的操作,但容器运行是正常的,没有出现OOM。 + +``` +# pouch run -ti --memory 100M reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04stress stress --vm 1 --vm-bytes 150M +stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd +``` + +通过以下命令看一下系统的内存使用量,你会发现Swap的内存占用量有所增加,说明--memory选项没有限制Swap内存的使用量。 + +``` +#free -m + total used free shared buff/cache available +Mem: 257755 2676 254114 1 965 254783 +Swap: 2047 41 2006 +``` + +尝试使用`swapoff -a`命令关闭Swap时我们再次执行早先的命令。从以下log中可以看到,当容器使用内存超过限制时会触发错误。 + +``` +# pouch run -ti --memory 100M reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04stress stress --vm 1 --vm-bytes 150M +stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd +stress: FAIL: [1] (422) kill error: No such process +stress: FAIL: [1] (452) failed run completed in 0s +esses +stress: FAIL: [1] (422) kill error: No such process +stress: FAIL: [1] (452) failed run completed in 0s +``` + +#### 3.1.2 --memory-swap +可以限制容器使用交换分区和内存的总和,对应的cgroup文件是`cgroup/memory/memory.memsw.limit_in_bytes`。 + +取值范围:大于内存限定值 + +单位:b,k,m,g + +运行如下命令来确认容器交换分区的资源管理对应的cgroup文件。可以看到,当memory-swap限定为1G时,对应的cgroup文件数值为1073741824,该数值的单位为字节,即1073741824B等于1G。 + +``` +# pouch run -ti -m 300M --memory-swap 1G reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/memory/memory.memsw.limit_in_bytes" +1073741824 +``` + +如下所示,当尝试占用的内存数量超过memory-swap值时,容器出现异常。 + +``` +# pouch run -ti -m 100M --memory-swap 200M reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04stress bash -c "stress --vm 1 --vm-bytes 300M" +stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd +stress: FAIL: [1] (416) <-- worker 10 got signal 9 +stress: WARN: [1] (418) now reaping child worker processes +stress: FAIL: [1] (422) kill error: No such process +stress: FAIL: [1] (452) failed run completed in 0s +``` + +#### 3.1.3 --memory-swappiness +该接口可以设定容器使用交换分区的趋势,取值范围为0至100的整数(包含0和100)。0表示容器不使用交换分区,100表示容器尽可能多的使用交换分区。对应的cgroup文件是cgroup/memory/memory.swappiness。 + +``` +# pouch run -ti --memory-swappiness=100 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c 'cat /sys/fs/cgroup/memory/memory.swappiness' +100 +``` + +#### 3.1.4 --memory-wmark-ratio +用于计算low_wmark,`low_wmark = memory.limit_in_bytes * MemoryWmarkRatio`。当`memory.usage_in_bytes`大于low_wmark时,触发内核线程进行内存回收,当回收到`memory.usage_in_bytes`小于high_wmark时停止。对应的cgroup接口是`cgroup/memory/memory.wmark_ratio`。 + +``` +# pouch run -ti --memory-wmark-ratio=60 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c 'cat /sys/fs/cgroup/memory/memory.wmark_ratio' +60 +``` +#### 3.1.5 --oom-kill-disable +当out-of-memory (OOM)发生时,系统会默认杀掉容器进程,如果你不想让容器进程被杀掉,可以使用该接口。接口对应的cgroup文件是`cgroup/memory/memory.oom_control`。 + +当容器试图使用超过限定大小的内存值时,就会触发OOM。此时会有两种情况,第一种情况是当接口--oom-kill-disable=false的时候,容器会被杀掉;第二种情况是当接口--oom-kill-disable=true的时候,容器会被挂起。 + +以下命令设置了容器的的内存使用限制为20M,将--oom-kill-disable接口的值设置为true。查看该接口对应的cgroup文件,`oom_kill_disable`的值为1。 + +``` +# pouch run -m 20m --oom-kill-disable=true reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c 'cat /sys/fs/cgroup/memory/memory.oom_control' +oom_kill_disable 1 +under_oom 0 +``` + +`oom_kill_disable`:取值为0或1,当值为1的时候表示当容器试图使用超出内存限制时(即20M),容器会挂起。 + +under_oom:取值为0或1,当值为1的时候,OOM已经出现在容器中。 + +通过`x=a; while true; do x=$x$x$x$x; done`命令来耗尽内存并强制触发OOM,log如下所示。 + +``` +# pouch run -m 20m --oom-kill-disable=false reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c 'x=a; while true; do x=$x$x$x$x; done' + +[root@r10d08216.sqa.zmf /root] +#echo $? +137 +``` + +通过上面的log可以看出,当容器的内存耗尽的时候,容器退出,退出码为137。因为容器试图使用超出限定的内存量,系统会触发OOM,容器会被杀掉,此时under_oom的值为1。我们可以通过系统中cgroup文件(`/sys/fs/cgroup/memory/docker/${container_id}/memory.oom_control`)查看`under_oom`的值(`oom_kill_disable` 1,under_oom 1)。 + +当--oom-kill-disable=true的时候,容器不会被杀掉,而是被系统挂起。 + +#### 3.1.6 --oom-score-adj +参数--oom-score-adj可以设置容器进程触发OOM的可能性,值越大时越容易触发容器进程的OOM。当值为-1000时,容器进程完全不会触发OOM。该选项对应着底层的`/proc/$pid/oom_score_adj`接口。 + +``` +# pouch run -ti --oom-score-adj=300 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04stress bash -c "cat /proc/self/oom_score_adj" +300 +``` + +### 3.2 cpu资源管理 +#### 3.2.1 --cpu-period +内核默认的Linux 调度CFS(完全公平调度器)周期为100ms,我们通过--cpu-period来设置容器对CPU的使用周期,同时--cpu-period接口需要和--cpu-quota接口一起来使用。--cpu-quota接口设置了CPU的使用值。CFS(完全公平调度器) 是内核默认使用的调度方式,为运行的进程分配CPU资源。对于多核CPU,根据需要调整--cpu-quota的值。 + +对应的cgroup文件是`cgroup/cpu/cpu.cfs_period_us`。以下命令创建了一个容器,同时设置了该容器对CPU的使用时间为50000(单位为微秒),并验证了该接口对应的cgroup文件对应的值。 + +``` +# pouch run -ti --cpu-period 50000 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpu/cpu.cfs_period_us" +50000 +``` + +以下命令将--cpu-period的值设置为50000,--cpu-quota的值设置为25000。该容器在运行时可以获取50%的cpu资源。 + +``` +# pouch run -ti --cpu-period=50000 --cpu-quota=25000 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04stress stress -c 1 +stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd +``` + +从log的最后一行中可以看出,该容器的cpu使用率约为50.0%,与预期相符。 + +``` +# top -n1 +top - 17:22:40 up 1 day, 57 min, 3 users, load average: 0.68, 0.16, 0.05 +Tasks: 431 total, 2 running, 429 sleeping, 0 stopped, 0 zombie +%Cpu(s): 0.0 us, 0.1 sy, 0.0 ni, 99.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +KiB Mem : 26354243+total, 25960588+free, 1697108 used, 2239424 buff/cache +KiB Swap: 2096636 total, 0 free, 2096636 used. 25957392+avail Mem + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND + 53256 root 20 0 7324 100 0 R 50.0 0.0 0:12.95 stress +``` + +#### 3.2.2 --cpu-quota +对应的cgroup文件是`cgroup/cpu/cpu.cfs_quota_us`。 + +``` +# pouch run -ti --cpu-quota 1600 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us" +1600 +``` + +--cpu-quota接口设置了CPU的使用值,通常情况下它需要和--cpu-period接口一起来使用。具体使用方法请参考--cpu-period选项。 + +#### 3.2.3 --cpu-share +对应的cgroup文件是cgroup/cpu/cpu.shares。 + +``` +# pouch run -ti --cpu-quota 1600 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpu/cpu.shares" +1600 +``` + +通过--cpu-shares可以设置容器使用CPU的权重,这个权重设置是针对CPU密集型的进程的。如果某个容器中的进程是空闲状态,那么其它容器就能够使用本该由空闲容器占用的CPU资源。也就是说,只有当两个或多个容器都试图占用整个CPU资源时,--cpu-shares设置才会有效。 + +我们使用如下命令来创建两个容器,它们的权重分别为1024和512。 + +``` +# pouch run -d --cpuset-cpus=0 --cpu-share 1024 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04stress stress -c 1 +c7b99f3bc4cf1af94da35025c66913d4b42fa763e7a0905fc72dce66c359c258 + +[root@r10d08216.sqa.zmf /root] +# pouch run -d --cpuset-cpus=0 --cpu-share 512 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04stress stress -c 1 +1ade73df0dd9939cc65e05117e3b0950b78079fb36f6cc548eff8b20e8f5ecb9 +``` + +从如下top命令的log可以看到,第一个容器产生的进程PID为10513,CPU占用率为65.1%,第二个容器产生进程PID为10687,CPU占用率为34.9%。两个容器CPU占用率约为2:1的关系,测试结果与预期相符。 + + +``` +#top +top - 09:38:24 up 3 min, 2 users, load average: 1.20, 0.34, 0.12 +Tasks: 447 total, 3 running, 444 sleeping, 0 stopped, 0 zombie +%Cpu(s): 3.1 us, 0.0 sy, 0.0 ni, 96.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +KiB Mem : 26354243+total, 26187224+free, 964068 used, 706120 buff/cache +KiB Swap: 2096636 total, 2096636 free, 0 used. 26052548+avail Mem + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND + 10513 root 20 0 7324 100 0 R 65.1 0.0 0:48.22 stress + 10687 root 20 0 7324 96 0 R 34.9 0.0 0:20.32 stress +``` + + +#### 3.2.4 --cpuset-cpus +该接口对应的cgroup文件是cgroup/cpuset/cpuset.cpus。 + +在多核CPU的虚拟机中,启动一个容器,设置容器只使用CPU核1,并查看该接口对应的cgroup文件会被修改为1,log如下所示。 + +``` +# pouch run -ti --cpuset-cpus 1 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpuset/cpuset.cpus" +1 +``` + +通过以下命令指定容器使用cpu核1,并通过stress命令加压。 + +``` +# pouch run -ti --cpuset-cpus 1 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 stress -c 1 +``` + +查看CPU资源的top命令的log如下所示。需要注意的是,输入top命令并按回车键后,再按数字键1,终端才能显示每个CPU的状态。 + +``` +#top +top - 17:58:38 up 1 day, 1:33, 3 users, load average: 0.51, 0.11, 0.04 +Tasks: 427 total, 2 running, 425 sleeping, 0 stopped, 0 zombie +%Cpu0 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu2 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +%Cpu3 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +``` + +从以上log得知,只有CPU核1的负载为100%,而其它CPU核处于空闲状态,结果与预期结果相符。 + +#### 3.2.5 --cpuset-mems +该接口对应的cgroup文件是cgroup/cpuset/cpuset.mems。 + +``` +# pouch run -ti --cpuset-mems=0 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/cpuset/cpuset.mems" +0 +``` + +以下命令将限制容器进程使用内存节点1、3的内存。 + +``` +# pouch run -ti --cpuset-mems="1,3" reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash +``` + +以下命令将限制容器进程使用内存节点0、1、2的内存。 + +``` +# pouch run -ti --cpuset-mems="0-2" reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash +``` + + +### 3.3 io资管管理 + +#### 3.3.1 --blkio-weight +通过--blkio-weight接口可以设置容器块设备IO的权重,有效值范围为10至1000的整数(包含10和1000)。默认情况下,所有容器都会得到相同的权重值(500)。对应的cgroup文件为cgroup/blkio/blkio.weight。以下命令设置了容器块设备IO权重为10,在log中可以看到对应的cgroup文件的值为10。 + +``` +# pouch run -ti --rm --blkio-weight 10 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.weight" +10 +``` + +通过以下两个命令来创建不同块设备IO权重值的容器。 + +``` +# pouch run -it --name c1 --blkio-weight 300 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 /bin/bash +# pouch run -it --name c2 --blkio-weight 600 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 /bin/bash +``` + +如果在两个容器中同时进行块设备操作(例如以下命令)的话,你会发现所花费的时间和容器所拥有的块设备IO权重成反比。 + +``` +# time dd if=/mnt/zerofile of=test.out bs=1M count=1024 oflag=direct +``` + +#### 3.3.2 --blkio-weight-device +通过--blkio-weight-device="设备名:权重"接口可以设置容器对特定块设备IO的权重,有效值范围为10至1000的整数(包含10和1000)。 + +对应的cgroup文件为cgroup/blkio/blkio.weight_device。 + +``` +# pouch run --blkio-weight-device "/dev/sda:1000" reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.weight_device" +8:0 1000 +``` + +以上log中的"8:0"表示sda的设备号,可以通过stat命令来获取某个设备的设备号。从以下log中可以查看到/dev/sda对应的主设备号为8,次设备号为0。 + +``` +#stat -c %t:%T /dev/sda +8:0 +``` + +如果--blkio-weight-device接口和--blkio-weight接口一起使用,那么Docker会使用--blkio-weight值作为默认的权重值,然后使用--blkio-weight-device值来设定指定设备的权重值,而早先设置的默认权重值将不在这个特定设备中生效。 + +``` +# pouch run --blkio-weight 300 --blkio-weight-device "/dev/sda:500" reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.weight_device" +8:0 500 +``` + +通过以上log可以看出,当--blkio-weight接口和--blkio-weight-device接口一起使用的时候,/dev/sda设备的权重值由--blkio-weight-device设定的值来决定。 + +#### 3.3.3 --device-read-bps +该接口用来限制指定设备的读取速率,单位可以是kb、mb或者gb。对应的cgroup文件是`cgroup/blkio/blkio.throttle.read_bps_device`。 + +``` +# pouch run -it --device /dev/sda:/dev/sda --device-read-bps /dev/sda:1mb reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.read_bps_device" +8:0 1048576 +``` + +以上log中显示8:0 1000,8:0表示/dev/sda, 该接口对应的cgroup文件的值为1048576,是1MB所对应的字节数,即1024的平方。 + +创建容器时通过--device-read-bps接口设置设备读取速度为500KB/s。从以下log中可以看出,读取速度被限定为498KB/s,与预期结果相符合。 + +``` +# pouch run -it --device /dev/sda:/dev/sda --device-read-bps /dev/sda:500k reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash +root@r10f10195:/# dd iflag=direct,nonblock if=/dev/sda of=/dev/null bs=5000k coun +1+0 records in +1+0 records out +5120000 bytes (5.1 MB) copied, 10.2738 s, 498 kB/s +``` + +#### 3.3.4 --device-write-bps +该接口用来限制指定设备的写速率,单位可以是kb、mb或者gb。对应的cgroup文件是`cgroup/blkio/blkio.throttle.write_bps_device`。 + +``` +# pouch run -it --device /dev/sda:/dev/sda --device-write-bps /dev/sda:1mB reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.write_bps_device" +8:0 1048576 +``` + +以上log中显示8:0 1000,8:0表示/dev/sda, 该接口对应的cgroup文件的值为1048576,是1MB所对应的字节数,即1024的平方。 + +创建容器时通过--device-write-bps接口设置设备写速度为1MB/s。从以下log中可以看出,读取速度被限定为1.0MB/s,与预期结果相符合。 + +限速操作: + +``` +# pouch run -it --device /dev/sdb:/dev/sdb --device-write-bps /dev/sdb:1mB reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash +root@r10d08216:/# dd oflag=direct,nonblock of=/dev/sdb if=/dev/urandom bs=10K count=1000 +1024+0 records in +1024+0 records out +10485760 bytes (10 MB) copied, 10.0022 s, 1.0 MB/s +``` + +#### 3.3.5 --device-read-iops +该接口设置了设备的IO读取速率,对应的cgroup文件是`cgroup/blkio/blkio.throttle.read_iops_device`。 + +``` +# pouch run -it --device /dev/sda:/dev/sda --device-read-iops /dev/sda:400 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.read_iops_device" +8:0 400 +``` + +可以通过"--device-read-iops /dev/sda:400"来限定sda的IO读取速率(400次/秒),log如下所示。 + +``` +# pouch run -it --device /dev/sda:/dev/sda --device-read-iops /dev/sda:400 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash +root@r10d08216:/# dd iflag=direct,nonblock if=/dev/sda of=/dev/null bs=1k count=1024 +1024+0 records in +1024+0 records out +1048576 bytes (1.0 MB) copied, 2.51044 s, 418 kB/s +root@r10d08216:/# +``` + +通过上面的log信息可以看出,容器每秒IO的读取次数为400,共需要读取1024次(log第二行:count=1024),测试结果显示执行时间为2.51044秒,与预期值2.56(1024/400)秒接近,符合预期。 + +#### 3.3.6 --device-write-iops +该接口设置了设备的IO写速率,对应的cgroup文件是`cgroup/blkio/blkio.throttle.write_iops_device`。 + +``` +# pouch run -it --device /dev/sda:/dev/sda --device-write-iops /dev/sda:400 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash -c "cat /sys/fs/cgroup/blkio/blkio.throttle.write_iops_device" +8:0 400 +``` + +可以通过"--device-write-iops /dev/sda:400"来限定sda的IO写速率(400次/秒),log如下所示。 + +``` +# pouch run -it --device /dev/sdb:/dev/sdb --device-write-iops /dev/sdb:400 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04 bash +root@r10d08216:/# dd oflag=direct,nonblock of=/dev/sdb if=/dev/urandom bs=1K count=1024 +1024+0 records in +1024+0 records out +1048576 bytes (1.0 MB) copied, 2.50754 s, 418 kB/s +``` + +通过上面的log信息可以看出,容器每秒IO的写入次数为400,共需要写1024次(log第二行:count=1024),测试结果显示执行时间为2.50754秒,与预期值2.56(1024/400)秒接近,符合预期。 + +### 3.4 其他资源管理接口 +#### --pids-limit +--pids-limit用于限制容器内部的pid数量,对应的cgroup接口是cgroup/pids/pids.max。 + +``` +# pouch run -ti --pids-limit 100 reg.docker.alibaba-inc.com/sunyuan/ubuntu:14.04stress bash -c "cat /sys/fs/cgroup/pids/pids.max" +100 +``` + +如果在容器内部不断创建新的进程,系统会提示如下错误。 + +``` +bash: fork: retry: Resource temporarily unavailable +bash: fork: retry: Resource temporarily unavailable +``` + +## 4. 总结 +PouchContainer的资源管理依赖于Linux底层的内核技术,感兴趣的读者可以根据自己兴趣补充一些有针对性的测试。关于其依赖的内核技术的实现已经远超本文的范畴。感兴趣的读者可以自行阅读内核手册。 + +## 参考资料 +[PouchContainer社区文档](https://github.com/alibaba/pouch/blob/master/docs/commandline/pouch_run.md) + +[Linux Programmer's Manual](http://man7.org/linux/man-pages/man5/proc.5.html) +