文章归档

cgroup.memory内核oom过程

以内核3.10.79为例。这里分析一下内核对于cgroup.memory进程组oom的过程,以及混部环境下需要什么样的oom策略。

1. 触发时机

内核对于每个memory cgroup维护一个计数器,统计当前cgroup内已经使用了的内存。每当cgroup内进程创建页面时,页面大小所占用的内存就会通过res_counter_charge_locked()函数计入计数器里。而当内存占用超过memory.limit_in_bytes所设置的阈值时,charge失败,返回ENOMEN错误。

int res_counter_charge_locked( struct res_counter *counter, unsigned long val, bool force) { int ret = 0; if (counter->usage + val > counter->limit) { counter->failcnt++; ret = -ENOMEM; if (!force) return ret; } counter->usage += val; if (counter->usage > counter->max_usage) counter->max_usage = counter->usage; return ret; }

»» 继续阅读全文

memory.usage_in_bytes与rss内存统计不一致

最近线上发现一个问题。一个用户在做resize操作的时候失败了,resize的主要动作是将内存阈值从45G调整到50G,按理来说这个调整是可行的,但实际执行时出现了问题。执行器报错如下:

Cgroups: failed to write memory.excess_mode: 16: Device or resource busy

用户的解释说,通过top统计到container内的rss进程只使用了20G左右,并没有超过45G。但是我们看memory.usage_in_bytes的统计时,container内存已经达到了90G。为什么memory.usage_in_bytes和rss会不一致呢?

原因是因为,cgroup的memory在统计内存时,把page cache也统计进去了,而top看到的rss,是不包含page cache的。

»» 继续阅读全文

Net::FTP与net_cls端口隔离

最近发现一个问题,部署脚本在下载部署包的时候总是报如下错误:

Can't call method "sockport" on an undefined value at /usr/lib/perl5/5.8.5/Net/FTP.pm line 837.

这个部署脚本是用perl语言写的,通过ftp的方式来下载数据包。经过调试后发现,原来是开启了net_cls子系统并设置端口隔离造成的。因为Net::FTP模块总是默认以主动模式来接受数据,什么是主动模式呢?

FTP有两种使用模式:主动和被动。主动模式要求客户端和服务器端同时打开并且监听一个端口以创建连接。在这种情况下,客户端由于安装了防火墙会产生一些问题。所以,创立了被动模式。被动模式只要求服务器端产生一个监听相应端口的进程,这样就可以绕过客户端安装了防火墙的问题。

Net::FTP在接收数据的时候会使用bind(0)的方式监听一个临时端口,让后把这个端口告诉服务器,服务器会与这个端口创建一个新的连接传输数据。问题在于,这个容器内设置了net_cls.bind_port_range,而端口0并不在这个范围之内,所以导致Net::FTP异常。

freezer在内核2.6.32和3.10的区别

今天看了一下内核3.10的代码,对比之前看过的2.6.32的代码,cgroup里freezer子系统的行为差别还是很大的,主要有2个:

  1. freezer处于冻结状态下进程是否仍然可以attach进来,2.6是不行的,但3.10允许
  2. freeze单个cgroup变成freeze掉cgroup以及它的所有后裔

在2.6内核里,当一个进程尝试加入某个freezer控制组时,内核会检查当前freezer cgroup的状态,如果状态为FROZEN或者FREEZING等,则不允许进程加入。但是在3.10里,这个限制去掉了,不管当前freezer cgroup的状态如何,都允许进程加入。

在接口的实现上,2.6内核只提供了can_attach()函数,3.10只提供了freezer_attach() 函数,正常流程上来说,进程在attach某个cgroup的时候,内核会先调用当前cgroup上所有子系统的can_attach()检查一下,当且仅当所有的子系统can_attach()成功时才允许进程加入当前cgroup,然后内核调用freezer子系统的freezer_attach()接口完成attach工作。

»» 继续阅读全文

netfilter + cgroup实现容器的带宽隔离

我们知道内核提供了一种机制,我们可以通过编写netfilter内核模块来实现对tcp数据包的处理,iptables的原理亦是如此:

那么,通过这种方案,能否实现进程的带宽控制呢?答案是肯定的。带宽控制的目的就是要隔离不同容器的网络需求,避免一个容器因为占用过多的网络带宽导致另一个容器饿死的现象。

尤其是在线离线混部的情况下,如果离线作业占用了过多的带宽资源,就会影响到在线服务的可用性。

实现方案如下:

  1. 扩展一个cgroup模块,或者实现一个新的cgroup子系统
  2. cgroup子系统在初始化的时候,注册netfilter钩子函数,截获所有的数据包
  3. 网络传输出发钩子函数,找到数据包所在的进程pid,找到pid相应的cgroup控制组,从而得到当前进程所在控制组的网络配额
  4. 使用类似与滑动窗口思想的算法实现带宽的限制

但是需要注意以下几个问题:

软限/硬限/整机阈值

对于每一个数据包,先检查是否到达软限,如果还没达到,则允许数据包通过。否则继续检查是否达到硬限,如果超过了硬限,则丢弃数据包。但是如果超过了软限,但是还没有超过硬限,怎么办?仍然需要判断单击带宽的物理阈值是多少。不能超过整机的网络带宽。

注意一旦超过了整机的阈值带宽,一定要先于网卡之前丢包,因为网卡无法区分此数据包是哪个容器的,超出负载后一律丢包。极端情况下:网卡丢的总是被抢占的一方的带宽资源

但是如果我们在netfilter钩子函数里判断以下当前整机带宽是否打满,提前丢包,就可以避免这种情况。

memory.usage_in_bytes内存只增不减

今天发现了一个问题,似乎memory.usage_in_bytes的内存总是在不停的增加,但很少减少,并不能说不减,只是很少。即使这个cgroup里的进程全kill了,usage_in_bytes依然纹丝不动。

经过分析之后,原因如下,与memory.usage_in_bytes的统计方式有关:

  1. 内核在统计的时候,usage_in_bytes包含进程的命名页 + 匿名页,匿名页通常就是file cache
  2. file cache是谁第一次使用算到谁的头上。
  3. file cache只有在完完全全被释放掉之后才会从usage_in_bytes中减掉,即使第一次使用它的进程早已死亡。

简单来说,就是我(某进程)读文件产生的file cache你(内核)算到我的头上,我退出了你也不清空我的计数,万一这个文件正好又被别的cgroup的进程依赖了,这笔帐还是赖在我的头上。。(内核虽然这么做,但其实是不合理的)

1、charge(计入)

file cache的charge函数是mem_cgroup_cache_charge(),这个函数通常是在文件页第一次被读上来时,内核将其加到file cache中时被调用,贴一个常见的调用栈:

[<ffffffff8116019d>] ? mem_cgroup_cache_charge+0xed/0x130 [<ffffffff8110bdba>] ? add_to_page_cache_locked+0xea/0x160 [<ffffffff8119c232>] mpage_readpages+0x102/0x150 [<ffffffff8120157d>] ext4_readpages+0x1d/0x20 [<ffffffff8110c521>] do_generic_file_read.clone.23+0x271/0x450 [<ffffffff8110d1ba>] generic_file_aio_read+0x1ca/0x240 [<ffffffff81164c82>] do_sync_read+0xd2/0x110 [<ffffffff81165463>] vfs_read+0xc3/0x180 [<ffffffff81165571>] sys_read+0x51/0x90

page cache的维护是独立于进程的,所以即使cgroup里的进程退出后,其所使用的page cache不会马上被释放。

2、uncharge(计出)

file cache的计出函数是 mem_cgroup_uncharge_cache_page(),uncharge主要有三种情况:

  1. 手动触发内核回收file cache时被调用
  2. 文件被删除
  3. 内核主动回收file cache时被调用。

手动触发回收file cache,通过sysctl修改vm.drop_caches的值来实现,调用栈如下:

[<ffffffff81114bae>] __mem_cgroup_uncharge_common+0x1ab/0x1f6 [<ffffffff81114c09>] mem_cgroup_uncharge_cache_page+0x10/0x12 [<ffffffff810e0ad0>] __remove_mapping+0xd0/0xf4 [<ffffffff810e0b0a>]

»» 继续阅读全文

cpu带宽控制

一些资料

  1. https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt
  2. CPU bandwidth control for CFS

cpu带宽控制的原理是在给定周期内(cpu.cfs_period_us)内限制进程组的运行时间(cpu.cfs_quota_us),这两个参数值通过cpu子系统来设置。

cfs_rq相关的两个成员是runtime_remainingruntime_expires,前者是当前进程组的剩余时间片,后者是时间片的到期时间。

内核的做法是:

  1. 每次进程切换的时候更新当前进程的运行时间,检查进程时钟带宽是否超过阈值。
  2. 通过一个定时器,每隔固定的cpu.cfs_period_us周期,更新进程组的时钟带宽

»» 继续阅读全文

扩展net_cls子系统实现端口隔离

端口隔离的目的是要严格隔离不同容器所使用的端口段。我们通过扩展内核net_cls模块来达到此目的

  1. bind端口隔离:限制bind使用的端口范围,通过net_cls.bind_port_range配置文件设置
  2. connect端口隔离:限制进程能connect的端口范围,通过net_cls.connect_port_range配置文件设置

其中bind_port_range和connect_port_range配置文件的接口定义如下:

static struct cftype ss_files[] = {   {     .name = "bind_port_range",     .read_seq_string = port_range_read,     .write_string = port_range_write,     .max_write_len = 20U,     .private = FILE_BIND,   },   {     .name = "connect_port_range",     .read_seq_string = port_range_read,     .write_string = port_range_write,     .max_write_len = 20U,     .private = FILE_CONNECT,   },   //... }

通过port_range_read/port_range_write函数来实现配置参数的读写。我们重点关注cgrp_local_port_range()函数,这个函数取得当前进程所在的cgroup能使用的端口范围段,区分于是否是bind/connect:

/** * cgrp_local_port_range - get available local port range * * @connect: select port range */ int cgrp_local_port_range(int *low, int *high, int connect) {

»» 继续阅读全文

cgroup内核实现

cgroup模块里比较重要的数据结构是:struct cgroup, struct css_set, struct cg_cgroup_link,搞清楚这三个数据结构之间的关系,基本就可以了解内核是怎么管理cgroup的了。关于cgroup的一些资料,可以参考redhat写的介绍:

>> Relationships Between Subsystems, Hierarchies, Control Groups and Tasks

»» 继续阅读全文

hotfix内核热更新技术

最近研究了一下内核热升级技术,顺便和组内的同学做了一个分享。

https://github.com/pipul/coutune/blob/master/papers/kpatch.pdf

参考资料如下:

  1. 阿里虚拟化技术 http://pan.baidu.com/s/1eQcSPTw
  2. Safe and Automatic Live Update for Operating Systems  https://www.cs.vu.nl/~giuffrida/papers/asplos-2013.pdf
  3. LUCOS http://pan.baidu.com/s/1kTmSd4F
  4. ksplice http://www.ksplice.com/doc/ksplice.pdf
  5. 淘宝内核月报 http://kernel.taobao.org/index.php/%E5%86%85%E6%A0%B8%E6%9C%88%E6%8A%A52014-05-06
  6. kgraft http://lwn.net/Articles/596776/
  7. kpatch https://github.com/dynup/kpatch
第 3 页,共 5 页12345