简而言之,彩云使用了 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 内部实现信号量的分析。

  1. cat /proc/sys/kernel/sem 查看 Kernel 的配额, SEMMNI 对应的是 Semaphore Array 的上限,也可以通过 echo "250 32000 32 128" > /proc/sys/kernel/sem 临时修改信号量的配额。 
  2. ipcs -s 查看目前的信号量详情 
  3. 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,如果对应的进程已经不存在,就认为这个信号量已经没有被使用了。

参考资料

  1. 0xAX - Synchronization primitives in the Linux kernel. Part 3.
  2. Semaphore
  3. sem_overview
  4. dmsetup
  5. Tsung’s Blog