概要
简而言之,彩云使用了 LAIN(PaaS/Docker) 部署一部分生产和开发应用,在实际使用过程中遇到了信号量泄漏 (Semaphore Leak) 的问题,于是 @siqing_yu 写了一个 Semaphore Killer 脚本清理泄漏的信号量。
semaphore_killer.sh
#!/bin/bash
SEM_IDS=`ipcs -s | egrep "0x[0-9a-f]+ [0-9]+" | cut -f2 -d " "` # Semaphores IDs
for sem_id in $SEM_IDS; do
SEM_INFO=`ipcs -s -i $sem_id`
SEM_PS=`echo -e "$SEM_INFO" | awk 'NR>8 {print $5}' | sort -u` # Semaphore's processes IDs
SEM_LEAK_FLAG=true
for ps_id in $SEM_PS; do
if `ps -p $ps_id > /dev/null`; then
SEM_LEAK_FLAG=false
fi
done
if $SEM_LEAK_FLAG ; then
echo "Delete semaphore $sem_id"
# ipcrm -s $sem_id; # Remove the semaphore identified by semid.
fi
done
问题
生产系统环境信息如下:
Kernel Version: 3.10.0-327.22.2.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
Server Version: 1.12.1
Storage Driver: devicemapper
Library Version: 1.02.135-RHEL7 (2016-09-28)
在使用 Docker 部署新容器的时候报错: devicemapper: Can't set cookie dm_task_set_cookie failed
。最终导致新版本应用不能正常部署。
解决方案
通过 Issue#85 和 Tsung's Blog 定位是信号量(Semaphore)泄漏导致的。
那么什么是信号量呢?信号量是进程或者线程用来同步的一个工具,比如多个进程需要执行一个不能同时处理的逻辑,需要循序的一个一个处理,那么就可以使用信号量来解决逻辑一个一个执行的问题。
信号量主要有一个整型数值和一个 FIFO 队列组成,等待的线程会入队,等待条件满足后,再出队运行。0xAX 有一篇 Linux Kernel 内部实现信号量的分析。
cat /proc/sys/kernel/sem
查看 Kernel 的配额, SEMMNI 对应的是 Semaphore Array 的上限,也可以通过echo "250 32000 32 128" > /proc/sys/kernel/sem
临时修改信号量的配额。ipcs -s
查看目前的信号量详情ipcs -s -i SEMID
查看某一个信号量的详细信息
SEMMSL: maximum number of semaphores per array SEMMNS: maximum semaphores system-wide
SEMOPM: maximum operations per semop call
SEMMNI: maximum number of arrays
cat /proc/sys/kernel/sem
输出示例
SEMMSL SEMMNS SEMOPM SEMMNI
250 32000 32 128
ipcs -s
输出示例
------ Semaphore Arrays --------
key semid owner perms nsems
devicemapper 是 Linux kernel 提供的一个映射虚拟块设备的框架。对于一般软件来说,机械硬盘,固态硬盘都是块设备,可以执行块设备相关操作,Docker 运行的时候需要给容器提供[虚拟]块设备读取或者存储数据使用,对于这个 LAIN 配置的是使用 devicemapper 作为存储引擎。但是在 Docker 的实际调用栈中,某些 devicemapper 操作会引发信号量泄漏。比如说需要创建容器,就需要申请一个块设备做存储,那么就要调用 devicemapper 相关的函数,调用过程中创建了一个信号量,但是调用结束后,由于某些原因没有释放信号量,实际上也不在需要使用之前申请的信号量。默认系统的上限配额是 128,由于频繁的操作,导致这个数量很快就到达了上限。于是 Docker 再创建容器的时候,devicemapper 就开始报错了。
Issue 中说可以试试 dmsetup udevcomplete_all
操作移除旧的 dm cookies
,试了一下,发现没有明显的效果。之后则尝试了手动清除无用的信号量。
ipcs -s -i SEMID
可以查询到使用信号量的进程 ID,如果对应的进程已经不存在,就认为这个信号量已经没有被使用了。