问题背景:基于golang 实现了一个自助化配置的通用命令任务管理模块,这样运营人员可以直接通过修改配置,完成 if do 的命令逻辑:当磁盘满时,做什么?当网卡down 时做什么;并且可以支持各类环境 和规则。

确定僵尸进程问题
但是灰度后不久,命令执行报错: fork exec resource temporarily unavailable;
进一步查看syslog 报错 fork rejected by pids controller in xxx.service,cat pid limits 查看上限,也就是说,进程fork 出错 service 控制上限了
执行 ps -ef |grep -c pid 发现service 创建了太多进程 ,大部分都变成了Z状态,另外通过top 也可以看到 zombile 个数

我们知道僵尸进程是子进程退出,父进程没有wait 捕捉状态;配置了多个命令,每分钟 每30秒执行一次,为什么大部分僵尸进程是 wc 和grep 呢?即:不是所有命令执行变成了僵尸进程,而是某一部分命令变成了僵尸。
通过分析这些特殊的命令有助于缩小命令调用变僵尸进程的bug代码范围:

确定僵尸进程的命令特征
一种方式是:注释配置 使用排除法,但是效率太低;
另一种方式使用动态追踪方式 来查看具体僵尸进程(不断在产生),以grep僵尸进程为例:
python execsnoop.py -n grep -T |grep 父进程 -B 1
execsnoop 通过getppid,/proc/pid/status 中的ppid 来获取父进程,但是有时候命令马上执行完就退出了,难以获取祖父进程等,另外获取打印参数也是一个问题;实际上 有时候我们需要追踪到最开始的进程,因此稍作修改:

    for (int i = 0; curr_pid != 1 && i < MAX; i++) {
        curr = curr->real_parent;
        curr_pid = curr->tgid;
        data.ppid[i] = curr_pid;
        bpf_probe_read(&c, sizeof(c), curr->comm);
        comm_cache.update(&curr_pid, &c);
    }

cmd pipeline 的问题
从上一步 可以明确是 cmd pipeline 的问题;当pipe line 的命令都执行成功时,不会出现Z,但是当前面有进程异常退出时,后面的wc 等命令出现了Z
https://stackoverflow.com/questions/36050503/golang-child-processes-become-zombies

func RunCmds(maxtime int, cmds []*exec.Cmd) error {

	// start processes in descending order
	for i := len(cmds) - 1; i > 0; i-- {
		cmds[i].SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
		if maxtime == 0 {
			maxtime = 600
		}

		time.AfterFunc(time.Duration(maxtime)*time.Second, func() {
			if cmds[i].Process != nil {
				syscall.Kill(-cmds[i].Process.Pid, syscall.SIGINT)
				syscall.Kill(-cmds[i].Process.Pid, syscall.SIGKILL)
			}
		})

		if err := cmds[i].Start(); err != nil {
			return err
		} 
	}

	// run the first process
	if err := cmds[0].Run(); err != nil {
		return err
	}

	// wait on processes in ascending order
	for i := 1; i < len(cmds); i++ {
		if err := cmds[i].Wait(); err != nil {
			return err
		}
	}
	return nil
}

仔细阅读:start run wait 接口:
start 会调用os.StartProcess 创建一个新的进程 也是cmd.Process, 而wait 相当于等待子进程退出 清理资源,wait 等待子进程退出 或者 stdin stdout copy complete. 所以类似wc -l 等命令得以继续等待stdin 而不退出,但是直接wait 会 使得wc -l 退出(待确认)

所以一定要 wait start后的进程,否则会产生僵尸进程,pipeline 更好实现方式可以参考 上面stackoverflow 连接

如何通过扩展自己的知识面来提升工作效率;单纯的动态追踪命令,仅在排查谁调用了命令时使用,想不到在排查命令导致的僵尸进程时正好可以用到