cfile read 最大读取限制_云平台的容器线程数量限制机制

adca3f79ea364172af7b74e4ac33cec9.gif 58云计算平台(以下简称云平台)是TEG-架构线基于Kubernetes+ docker的私有云(Kubernetes简写为K8S),旨在为集团内部提供一套业务实例管理平台。 58云平台具有简单,轻量的特点,能够高效利用物理资源,更快的部署和统一规范的标准化运行环境,通过云平台,实现服务标准化,上线流程规范化,资源利用合理化。 docker对于容器的CPU,内存做了限制,云平台对于容器的网络进行了限制,但是缺少对于线程数量的限制,本文针对如何限制容器线程数量进行讨论。

1、问题描述

目前云平台对容器的CPU,内存以及网络都做了限制,但是缺少对于线程数量的限制,这可能会引入一些问题。比如某些容器上的服务,大量的创建线程,会耗光系统资源,导致系统无法新建线程,CPU负载过高等问题。

通过跟进社区的releasenotes,发现K8S1.10+docker1.11之后开始支持限制容器创建的进程数, 社区采用的方式是使用pids子系统限制创建的进程数量,但是没有实现实时感知的机制, 而K8S 1.7+docker1.10没有该功能的支持。如果部署在容器上的应用,代码编写不规范,大量的创建线程,可能会导致系统的pid(Linux上的线程可以认为是使用进程模拟的)耗尽,进而导致宿主机无法创建子进程的问题,使得整个宿主机处于一个很危险的状态。

针对上述问题,我们计划设计一个通用的方案,能够对K8S1.10+docker1.11之前的版本提供限制容器线程数量的功能,同时提供实时感知容器线程数触达上线的机制。

2、问题复现

import time

import os

from multiprocessing import    Process

s = []

def func(i):

        print 'hello', i

        time.sleep(1000)

def main():

        for i in range(50000):

                p = Process(target=func,    args=(i,))

                s.append(p)

        for p in s:

                p.start()

        time.sleep(2000)

main()

基于1中的调研,我们找了两台机器做实验,上面设置的kernel.pid_max = 40960,即系统允许创建的最大进程数是40960,我们又写了一个python脚本不断去创建进程,看宿主机是会出现1中描述的问题。

通过运行上面的脚本,发现当进程创建到27000的时候,执行docker命令就会很卡,当进程数达到kernel.pid_max定义上限的时候,已经耗尽系统的pid资源,同时报出报出fork:retry: Resource temporarily unavailable的错误。

3、解决方案

针对2中潜在的问题,我们讨论了该问题的可行方案,最终确定如下:

1)调大kernel.pid_max,允许系统创建更多的进程。

2)限制单个容器创建的线程数量。

3.1 kernel.pid_max参数

kernel.pid_max是内核允许系统创建的最大进程数,这个值在64位机上最大可设置为 4194304(4M)。目前从4.18的代码来看,调大后的影响是内核在维护PID时要多用一些内存,分配PID时要花更多一点时间,这些与进程本身的消耗相比,可以忽略。 内核的推荐值是cpu数*1024,也就是说内核认为一个CPU最多可以应对1024个进程,如果一台机器有40个CPU,那 40*1024 = 40960。但根据当前云平台宿主机的实际情况,内核推荐的设置已经不适用于58云平台宿主机。所以从稳定性的角度,结合58云平台的业务,建议将宿主机的kernel.pid_max设置成1048576 (1M)。 kernel.pid_max仅仅是调大了系统允许创建的进程/线程数,这并没有从根本上解决问题,单个容器依然可能创建很多的进程/线程,我们需要通过cgroup的pids子系统限制每个容器启动的最大进程/线程数。 3.2 解决方案设计 经过调研,我们采用cgroup pids子系统 + inotify的方式,限制容器启动的进程/线程数量。
3.2.1 cgroup pids子系统
CGroup 是 Control Groups 的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组 (process groups) 所使用的物理资源 (如cpu, memory, I/O 等等) 的机制,它是容器限制资源的基础。 CGroup 是将任意进程进行分组化管理的 Linux 内核功能。CGroup 本身是提供将进程进行分组化管理的功能和接口的基础结构,I/O 或内存的分配控制等具体的资源管理功能是通过这个功能来实现的。这些具体的资源管理功能称为 CGroup 子系统或控制器。 在Linux Kernel 4.3中,引入了一个新的cgroups子系统pids,通过这个子系统,可以实现对某个CGroup中进程和线程的总数进行限制。如下图所示:

397da217028e688c30b98e207ca1df5c.png

其中,pids.max控制该组中最多可以拥有的进程数(也可以用来限制线程数)。pids.current存储了当前CGroup的进程(线程)总数。cgroup.procs是需要限制的进程pid列表。pid.events记录CGroup触发进程上限的次数。 facebook在2016年向内核提交了一个patch(135b8b),实现了pid.events的功能。当CGroup尝试fork新进程的时候,会调用pids_can_fork判断是否可以fork,如果不能,会通过cgroup_file_notify触发pid.events的事件。 内核推荐的宿主机机进程数上限为:CPU核数*1024,云平台的容器也将使用这一内核推荐值,即随着核数的增加,允许的最大线程上限也随之增加。考虑到部分云实例的CPU核数较少,如按照上述方式配置,部分服务会受影响,容器核数不足6核的,按照6核计算。 通过cgroup pids子系统可以有效的限制容器启动的线程数量,但是当容器创建的线程触达上限的时候,会使得容器处于不可预知的状态,我们需要通过inotify实时检测pids.events文件的变化,感知容器线程数量是否触达上限。
3.2.2 inotify
inotify是Linux中用于监控文件系统变化的一个框架,不同于前一个框架dnotify, inotify可以实现基于inode的文件监控。 也就是说监控对象不再局限于目录,也包含了文件。 不仅如此,在事件的通知方面,inotify摈弃了dnotify的信号方式,采用在文件系统的处理函数中放置hook函数的方式实现。 inotify 提供一个简单的 API,使用最小的文件描述符,并且允许细粒度监控。 与inotify 的通信是通过系统调用实现。 主要的API如下: inotify_init 用于创建一个 inotify 实例的系统调用,并返回一个指向该实例的文件描述符。 inotify_add_watch 增加对文件或者目录的监控,并指定需要监控哪些事件。 标志用于控制是否将事件添加到已有的监控中,是否只有路径代表一个目录才进行监控,是否要追踪符号链接,是否进行一次性监控,当首次事件出现后就停止监控。 inotify_rm_watch 从监控列表中移出监控项目。 read 读取包含一个或者多个事件信息的缓存。 close 关闭文件描述符,并且移除所有在该描述符上的所有监控。 当关于某实例的所有文件描述符都关闭时,资源和下层对象都将释放,以供内核再次使用。 由3.2.1中pids子系统的机制可知,我们可以通过inotify_add_watch和read监控pid.events文件的变化,可以感知CGroup的进程是否触达pid.max定义的上限,并进行后续的处理。

4、总结

总体来说,上述方案很好的解决了限制容器启动线程数量的问题,并能够提供实时感知容器触达线程上限的机制。从本次方案设计的过程中,我们发现平台还存在一些潜在的问题。我们需要改进监控和报警系统,尽早发现潜在的问题并解决。我们也会持续关注社区演进,同时会将我们比较好的功能提交到社区。

49a77e9ebb7e18f14156f921068d1a97.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值