You need to enable JavaScript to run this app. Quarkus部署于Kubernetes集群 如何规避持续产生僵尸进程的问题
优惠活动
大模型
产品
解决方案
定价
更多

Quarkus部署于Kubernetes集群 如何规避持续产生僵尸进程的问题

Kubernetes集群Quarkus应用持续产生僵尸进程的排查与解决

问题现象

在Kubernetes集群中部署的Quarkus应用出现僵尸进程持续增长异常,增量速度为每1-2秒1个,节点累计僵尸进程峰值曾超过12000个。
按照应用代码逻辑,仅在收到特定操作指令时,才会通过Runtime调用Shell命令创建子进程。当前业务场景未触发该类进程创建逻辑,但僵尸进程仍持续产生。
相关的进程调用、回收逻辑代码如下:

String command = "helm install release xxx";
LOGGER.debug("handle Install request : command [{}]", command);
waitForNormalTermination(Runtime.getRuntime().exec(command), INSTALL_TIMEOUT, TimeUnit.SECONDS, name);

private void waitForNormalTermination(Process process, int timeout, TimeUnit unit, String release) throws Exception {
try {
    if (!process.waitFor(timeout, unit)) {
        throw new TimeoutException("Timeout while executing " + process.info().commandLine().orElse(null));
    }

    if (process.exitValue() != 0) {
        String errorStreamOutput = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8);
        if (errorStreamOutput != null && errorStreamOutput.contains("release: not found")) {
            throw new ReleaseNotFoundException(release);
        }

        throw new Exception("Process termination was abnormal, exit value: [" + process.exitValue() + "], command:[" + process.info().commandLine().orElse(null) + "] error returned:[" + errorStreamOutput + "]");
    }
} finally {
   pr.destroy();   // 后续补充该调用用于简化进程销毁逻辑,确保方法退出前所有关联进程被销毁
}
}

已开展的排查与修复操作

  • 在代码中新增pr.destroy()调用逻辑,重新构建镜像并推送至镜像仓库
  • 手动删除集群中运行的异常Pod,等待Pod基于新镜像自动重建完成
  • 登录应用所在的调度节点手动清理存量僵尸进程,清理前节点累计僵尸进程超12000个,清理后剩余4200个存量
  • 循环执行 ps aux | grep 'Z' | wc -l 观测僵尸进程增量,确认僵尸数量仍在持续增长,观测时节点统计值已达4487
  • 持续跟踪应用日志,确认业务侧仅存在其他服务发起的少量周期性REST轮询调用,未触发子进程创建相关逻辑
  • 编写节点侧排查脚本,统计各父进程关联的僵尸进程数量、关联容器PID,脚本内容如下:
#!/bin/bash
ps -eo ppid,comm | grep "<defunct>" | awk '{print $1}' | sort | uniq -c > /tmp/zombie.file
Files="/tmp/zombie.file"
Lines=$(cat $Files | tr -s ' ' | cut -d ' ' -f2,3)

i=0;

for Line in $Lines
do
   if [[ $i -eq 0 ]]
     then
       echo "Zombies found = $Line"
       i=1
   else
       ps -f $Line
       i=0
   fi
done

echo " "
echo " "

echo "Running docker containers are "

# 该行用于筛选私有仓库下的自有容器
#docker ps | grep private-repository

echo " "
echo " "

echo "the PID of those docker containers"
for value in $(docker ps | grep private-repository  | cut -d ' ' -f1); do
  docker inspect --format '{{ .State.Pid }}' $value
done
  • 将节点容器运行时从Docker切换为Containerd后,僵尸进程增长问题仍然存在,初步判断问题与Kubernetes的exec探针机制相关

标准解决方案

  • 配置轻量init进程作为容器1号进程
    Kubernetes容器内如果没有指定专属init进程,Java应用会直接作为容器内的1号进程。Linux内核规则下,1号进程不会自动继承回收孤儿子进程,所有退出后未被父进程调用wait()回收的子进程都会成为僵尸进程。无论是应用自身创建的子进程、还是kubelet执行exec探针派生的子进程,都会因为无法被回收持续累积。
    可在应用镜像启动命令中引入tini或dumb-init作为前置init进程,负责自动回收所有退出的子进程,示例Dockerfile配置:
# 安装tini
RUN apk add --no-cache tini
# 将tini设为1号进程
ENTRYPOINT ["/sbin/tini", "--", "java", "-jar", "/app/quarkus-app/quarkus-run.jar"]

Quarkus框架本身也提供内置的进程适配逻辑,可在配置文件中开启自动子进程回收能力,无需额外引入外部init进程。

  • 修正原有进程调用的代码缺陷
    原代码中finally块的pr.destroy()存在变量不匹配问题:方法入参定义的是Process process,但finally中调用的pr属于未定义的无效变量,编译运行时该销毁逻辑根本未实际执行,所有运行完成的子进程都未被主动回收。需将代码修改为process.destroyForcibly(),同时补充进程关联的输入流、错误流的关闭逻辑,避免进程句柄泄漏。
  • 优化exec探针配置
    调整Liveness/Readiness探针的命令格式,避免通过sh -c包裹探针命令派生多余的shell中间进程;合理设置探针的超时时间、执行频率,减少kubelet创建的exec子进程残留。

内容的提问来源于stack exchange,提问作者Sebastien Dionne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.01 10:54:00