疯狂的Pid

疯狂的Pid

首页动作格斗女子高中僵尸模拟mod版更新时间:2024-06-20

目录

  1. 故障描述
  2. 处理经过
  3. 原因分析
  4. 故障思考

最近业务新上了一个功能,使用了chromedp

借用官方的demo,几段小小的代码,我们可以实现后台截chrome的图:

package main import ( "context" "github.com/chromedp/chromedp" "io/ioutil" "log" ) func main() { ctx, cancel := chromedp.NewContext( context.Background(), ) defer cancel() var buf []byte if err := chromedp.Run(ctx, elementScreenshot(`https://pkg.go.dev/`, `img.Homepage-logo`, &buf)); err != nil { log.Fatal(err) } if err := ioutil.WriteFile("elementScreenshot.png", buf, 0o644); err != nil { log.Fatal(err) } } func elementScreenshot(urlstr, sel string, res *[]byte) chromedp.Tasks { return chromedp.Tasks{ chromedp.Navigate(urlstr), chromedp.Screenshot(sel, res, chromedp.NodeVisible), } }

看上去很不错,小O很快帮研发同学小D制作了一个可以运行chromedp的基础镜像

# syntax=DOCKER/dockerfile:1.3-labs FROM centos:7 RUN <<"EOF" cat > /etc/yum.repos.d/google-chrome.repo [google-chrome] name=google-chrome baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch enabled=1 gpgcheck=1 gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub EOF RUN yum install -y google-chrome-stable --nogpgcheck && yum clean all

(注:上文使用了here-documents ,需大于Docker 18.09,且打开export DOCKER_BUILDKIT=1开启BuildKit mode,方可支持)

dockerfile Here Document 举例(直接书写python代码):

# syntax = docker/dockerfile:1.3-labs FROM python:3.6 RUN <<eot #!/usr/bin/env python print("hello world") eot

Bash Here Document举例(直接书写文件内容):

cat <<EOF > testfile hello world!! EOF

业务很快就跑起来了,结果半夜小O就被告警短信轰炸了

故障描述

Pid爆炸至宿主机内存使用率暴涨

处理经过

时间

处理同学

动作

17:00

小D

开发代码,上线新功能

02:17

小O

被告警吵醒,紧急规避

原因分析

在宿主机上通过top/ps命令立刻就发现了大量僵尸进程

原因很显然了,和下午上线的chromedp脱不了干系。进到容器内:

在Linux操作系统下,一个进程结束后,如果它的父进程没有通过wait系统调用等待它,则它会变成Zombie Process,即图中的defunct状态进程。

When a process ends via exit, all of the memory and resources associated with it are deallocated so they can be reused . However, the process's entry in the process table remains. The parent can read the child's exit status by executing the wait system call, whereupon the zombie is removed. ---wikipedia

wait 系统调用承担了2个功能

正常情况下,如果父进程退出,则所有僵尸进程则会被1号进程收养。1号进程负责wait僵尸进程。显然在容器内部,业务的golang进程不会去回收chrome的进程。

这时我们需要一个容器内真正能做事的1号进程:

业界有以下几个做法

Bash

bash 作为父进程,具备回收子进程的功能,但是它会不透传信号给子进程

CMD ["/bin/bash", "-c", "set -e && top"]

如果写做以下方式,则会有问题

CMD ["/bin/bash", "-c", "top"]

这种形式属于“simple command”,bash内部会做优化而舍弃fork直接exec替换掉bash进程。所以在容器内看到的结果1号进程仍然是业务进程。

Supervisord

成熟的进程管理和守护工具,较重,python实现,根据公司架构情况食用。

systemd

由于一些技术原因,目前在容器内运行systemd需要开启特权模式。

tini

一个非常轻量的init实现。是docker官方原装的init二进制。在docker命令下通过--init参数即可开启,但是在k8s情况下需要将tinit拷贝到镜像当中,在Docker中增加以下即可:

ENV TINI_VERSION v0.19.0 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod x /tini ENTRYPOINT ["/tini", "--"]

pause

k8s的基础设施pause容器的pause进程,也可以充当1号进程,只要我们和pause容器共享pid namespace即可。

业务进程回收

对于golang来说,我们可以自己动手回收:

import "golang.org/x/sys/unix" func ReapChildren() { c := make(chan os.Signal, 100) signal.Notify(c, unix.SIGCHLD) for { <-c var status unix.WaitStatus for { pid, err := unix.Wait4(-1, &status, unix.WNOHANG, nil) switch err { case nil: if pid > 0 { fmt.Println("Reap pid", pid) } case unix.ECHILD: // No more children, we are done. break case unix.EINTR: continue default: fmt.Println(err) } } } }

对c语言来说则相对简单一些

static void sigreap(int signo) { while (waitpid(-1, NULL, WNOHANG) > 0) ; } int main(int argc, char **argv) { sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap,.sa_flags = SA_NOCLDSTOP},NULL) }

大半夜的,小O只想睡好好觉。直接修改了deployment让pause来回收规避了问题。至于后续小D怎么改随他去吧。

故障思考

记录那些年我们一起处理过的故障~

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved