之前有文章讨论了僵尸进程,以及脚本中出现僵尸进程时候,主进程调用wait可以一定程度上避免出现僵尸进程。但是我发现,并不是在调用完wait后,僵尸进程就一定不会存在了。
那么如何在k8s环境中,避免僵尸进程的出现呢?
本文提出两种方案来避免僵尸进程的出现。
僵尸进程产生的原理
一:操作系统中
实际上,任何一个进程都会存在僵尸进程阶段,只不过,如下两种情况就可以避免僵尸进程出现:
- 情况1: 如果父进程还存在,但是父进程没有正确调用wait/waitpid,则僵尸进程就不会消失。
- 情况2: 如果父进程早于子进程结束,那么子进程结束后,会由init进程(进程号为1)接管,init调用wait,同样不会出现僵尸进程。
二:容器环境中
容器环境中,进程号为1的进程是容器主进程,该进程不具备回收僵尸进程的能力,因此,当容器内产生孤儿进程托管给主进程(进程号为1)的进程后,就变成了没有人处理的僵尸进程
解决方法
方法一:
-
k8s 中
pause 容器
是所有容器的父容器(parent container)
,它有2个作用- 它是pod中
Linux命名空间
共享的基础 - 启用
PID 命名空间
共享,pod 中PID 1
的init 进程
有它维护,并接收收割僵尸进程
- 它是pod中
那么,如果把pause进程变成进程号为1的容器主进程,是不是就可以处理孤儿进程了呢?
答案是肯定的~~~ 有如下两种方法,能够将pause容器变成主进程
kubelet
通过配置--docker-disable-shared-pid=false
开启共享pid namespace
,也可以在 yml 中指定- 可以在yaml编排文件中指定
yaml
cat << EOF >> test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
selector:
matchLabels:
app: pod1
replicas: 1
template:
metadata:
labels:
app: pod1
spec:
shareProcessNamespace: true
containers:
- name: sshd
image: circleci/sshd:0.1
env:
- name: PUBKEY
value: "abc"
- name: pod1
image: busybox
securityContext:
capabilities:
add:
- SYS_PTRACE
stdin: true
tty: true
ports:
- name: p80
containerPort: 80
- name: p22
containerPort: 22
EOF
在编排文件的spec中,加入 shareProcessNamespace: true
开启后,可以在容器中,看到如下进程列表:
bash
[root@sc-master-1 ~]# kubectl -n milvus-1 exec -it elasticsearch-etcd-2 -- ps -ef
UID PID PPID C STIME TTY TIME CMD
65535 1 0 0 03:09 ? 00:00:00 /pause
1001 7 0 1 03:09 ? 00:03:55 etcd
1001 53340 0 0 07:46 pts/0 00:00:00 ps -ef
如上,可以看到pause容器变成了进程号为1的主进程,将会负责处理孤儿进程。
参考:www.xiexianbin.cn/kubernetes/...
方法二:
参考一: www.xiexianbin.cn/docker/dock...
参考二:cloud-atlas.readthedocs.io/zh_CN/lates...
参考三:cloud-atlas.readthedocs.io/zh_CN/lates...
tini
是一个针对容器开发的、精简的 init
服务。tini
的作用是生成子进程、避免僵尸进程生成、转发信号量(pid
为 1
时)到子进程并等待它退出。使用 tini
可以优雅的结束容器内的进程。
背景:
- Linux 中
pid
为1
的init
进程可以接受中断信号量,并将该信号量转发到所有子进程 - 非
init
需要程序实现转发信号的功能才能关闭其他程序,一般的bash
、sh
不具备上述功能 - 使用
docker stop
时,容器内pid
为1
进程将接受中断信号量
打包镜像:
dockerfile
FROM rockylinux:9.2.20230513
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
# Run your program under Tini
# or docker run your-image /your/program ...
COPY <<EOF /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
EOF
RUN dnf update -y && dnf install epel-release -y && dnf -y install s3cmd && dnf install -y wget && dnf -y install net-tools && dnf install -y telnet && dnf -y install traceroute && dnf -y install tcpdump && dnf install -y fio && dnf -y install bind-utils && dnf install -y kubectl-1.24.16-0 && dnf install -y vim && dnf install -y expect && dnf install -y git && dnf install -y procps && dnf install psmisc -y && chmod +x /tini
ENTRYPOINT ["/tini", "--"]
CMD [ "sleep","100000" ]
#docker run --rm --name app -e MX_MEM=128m -v /tmp/apps:/data -p 18080:8080 -p 17070:7070 registry.knowdee.com/tests/app:v1.0.0
#docker build -t registry.knowdee.com/library/tools:v-rock9.2 . -f ./tools.dockerfile --progress=plain
# dnf --showduplicates list kubectl
yaml编排文件:
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
labels:
name: midware
namespace: tools
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: midware
serviceName: midware
template:
metadata:
annotations:
# prometheus.io/port: "8000"
# prometheus.io/scrape: "true"
labels:
app: midware
spec:
tolerations:
- effect: NoExecute
key: taint.knowdee.io/apps
operator: Exists
containers:
- args:
- /bin/sh
- -c
- |
git config --global user.name "knowdee"
git config --global user.email "it@knowdee.com"
sleep 30000s
envFrom:
- configMapRef:
name: midware-config
volumeMounts:
- name: kubecfg
mountPath: /root/.kube
- mountPath: /data
name: data
image: registry.knowdee.com/library/tools:v-rock9.2
imagePullPolicy: IfNotPresent
name: midware
# ports:
# - containerPort: 8000
# name: http
# protocol: TCP
resources:
requests:
cpu: "1"
memory: 512Mi
dnsPolicy: ClusterFirst
volumeClaimTemplates:
- apiVersion: v1
kind: PersistentVolumeClaim
metadata:
creationTimestamp: null
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 200Mi
volumeMode: Filesystem
updateStrategy:
type: RollingUpdate
在启动后的容器中,可以看到:
bash
root 1 0 0 06:27 ? 00:00:00 /tini -- /bin/sh -c git config --global user.name "knowdee" git config --global user.email "it@knowdee.com" cp /data/.s3cfg /root/
root 7 1 0 06:27 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 30000s
root 73 0 0 06:27 pts/0 00:00:00 /bin/bash
root 793 73 0 08:32 pts/0 00:00:00 ps -ef
tini进程为进程号为1的进程,则负责孤儿进程的处理,防止出现僵尸进程。