作者:夏志培
本文为原创文章,转载请注明作者及出处
背景:
C/C++ 应用程序进程由于各种原因会崩溃,人为上去的迅速干预处理也会造成一定的业务影响,于是运维的同事将应用程序全部接入到 supervisor,通过supervisor 来防止应用进程宕掉短时间无人处理。
经过一段时间后,supervisor 带来一些收效,但同时引入一些问题。
譬如应用程序崩溃后,在系统日志和内核的环形日志中留下一些报错信息(例如指令指针地址)。
虽说通过指令指针地址信息找到对应的出错行,但是由于该应用经过多次版本发布,即使通过指令指针地址找到了对应的代码行,也无法确定该出错的代码行就是当前代码仓库最新版本中的代码行,仍然不好定位 bug,形成较大的困扰。
所以业务方仍然需要应用产生的 core 信息。于是介入参与,解决这个问题。(备注:环境信息:centos6.5 x64,supervisord-2.1-9)
过程和原因:
-
运维已经下线该 Serve ,保留了现场,于是登上目标服务器上查看,发现应用崩溃后在系统日志中仅存在少量的报错日志,(指令地址信息 ,文件的装载地址信息), 但是没有 core 文件。先检查 core 文件的名字生成规则有没有被修改。
\#sysctl -a |grep core_pattern确认 core 的文件格式以 core 结尾。然后检查系统参数 core file size 是 unlimited ,系统配置层未发现问题。
-
然后进行故障重现,在终端中手工启动该应用程序,崩溃后可以产生 core文件。(向目标进程发 SIGUSR2 信号)
-
开始检查和对比 supervisor 运行环境变量和终端中执行程序的环境变量 (可以借助 gdb,查看进程相关的内存)
-
对比环境后确实存在差异, supervisor 接管后进程的 core file size 为 0, 终端中执行的进程中的 core file size 为 ulimited, 其中 0 是不能产生 core 文件,ulimit 是可以产生 core 文件。
-
为了所辖排查范围,用 python 写一个类似 supervider 的程序和产生 core 的 C 程序,并且放入到 crontab 中运行, 代码如下:
5.1 产生 core 的测试 C 代码: #cd /tmp/ && gcc test.c
#include<stdio.h>
int main()
{
char *str="this is a Segmentation fault";
str[0]='f';
return 0;
}5.2 测试的 python 程序(类似 supervisor 作用)
#!/usr/bin/env python
#coding=utf8
from time import sleep
import os,signal
def child_handler(signum,stackframe):
while 1:
try:
result = os.waitpid(-1,os.WNOHANG)
except:
break
print "Reaped child process %d" % result[0]
signal.signal(signal.SIGCHLD,child_handler)
signal.signal(signal.SIGCHLD,child_handler)
try:
pid = os.fork()
if pid == 0:
os.execv("/tmp/a.out",[])
except OSError, e:
pass
sleep(30) -
经过测试,C 程序可以产生 core 文件,此时原因就是 supervidoer 程序本身了。顺着 /usr/bin/supervisord 去查看源代码, 发现 supervisord 有一个set_rlimits 函数,专门处理环境变量这一块的参数, 但 supervisord 的 set_rlimits 只会获取系统环境中两个变量:
RLIMIT_NOFILE 最大打开文件句柄数
RLIMIT_NOPROC 用户最大的进程数,
其他全部使用 python.resource 模块设置的默认值, 具体看 https://pymotw.com/2/resource/,为了验证这一点, 查看了当前未被 supervisord 接管某个应用进程的 max locked memory 和 supervisor接管进 程的 max locked memory, 正如预期中的一样,发现明显的差异。
supervisor 中由于core file size 这个参数也是使用 python.resource 模块设 置的默认值 (0),所以造成应用进程无法产生 core 文件。
解决办法
-
不重启应用进程的3种解决办法:
1.1 借助gdb 直接修改ulimit的一些参数【不推荐】【内核>2.6】
1.2 直接修改/proc/xx/limits的参数,echo -n ‘Max open files=unlimited:unlimited’ > /proc/15533/limits 【内核>2.6】
1.3 借助prlimit工具 【内核>2.6.36】,prlimit –pid 25622 – nofile=10240:10240
-
根本解决办法, 修改 supvisor 模块中 options.py 文件第 1293 行下面加入以下代码
soft, hard = resource.getrlimit(resource.RLIMIT_CORE)
resource.setrlimit(resource.RLIMIT_CORE, (-1, hard))
示例: #vim /usr/lib/python2.6/site-packages/supervisor/options.py
if hasattr(resource, 'RLIMIT_NPROC'):
limits.append(
{
'msg':('The minimum number of available processes required '
'to run this program is %(min)s as per the "minprocs" '
'command-line argument or config file setting. '
'The current environment will only allow you '
'to open %(hard)s processes. Either raise '
'the number of usable processes in your '
'environment (see README.rst) or lower the '
'minprocs setting in the config file to allow '
'the program to start.'),
'min':self.minprocs,
'resource':resource.RLIMIT_NPROC,
'name':'RLIMIT_NPROC',
})
###增加一下2行代码
soft, hard = resource.getrlimit(resource.RLIMIT_CORE)
resource.setrlimit(resource.RLIMIT_CORE, (-1, hard))
今天的故障分析到此啦。
End
活动推荐
点击下方图片即可阅读
沪江 x 饿了么联手举办的运维专场技术沙龙即将来袭!赶快报名上车!


