You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Bash脚本中SIGINT可即时捕获但SIGTERM延迟响应的问题

问题描述

在CI流水线中用Bash脚本封装Docker容器运行Ansible命令时,遇到任务中止(如收到SIGTERM信号)的场景,脚本无法及时响应,要等容器内命令执行完毕才触发清理逻辑,此时容器已停止,会抛出Cannot kill container错误,且存在容器失控继续运行的风险。

当前使用的脚本:

#!/bin/bash

CONTAINER=ansible

function kill_container() {
  echo "$0 caught $1" >&2
  docker kill ${CONTAINER}
  exit $?
}
trap 'kill_container SIGINT' SIGINT
trap 'kill_container SIGTERM' SIGTERM

function ansible_base() {
  docker run -d --rm --name ${CONTAINER} someorg/ansible:latest $@
  docker logs --follow ${CONTAINER}
}

ansible_base $@

测试复现:

  • 执行./run.sh sleep 30,在另一个终端用pkill run.sh发送SIGTERM,脚本会等待30秒后才处理信号,此时容器已停止,报错容器未运行。
  • CI环境中表现一致:进程收到SIGTERM后30秒无响应,被系统发送SIGKILL,脚本终止但Docker容器仍在运行。

问题原因

核心问题出在docker logs --follow命令上:当脚本执行到该命令时,Bash会将其作为前台进程运行,此时SIGTERM信号会被docker logs进程捕获,但它默认不会转发信号或主动退出,导致脚本的trap逻辑无法及时触发,直到docker logs因容器停止而退出后,脚本才会处理之前收到的SIGTERM信号。


解决方案

提供两种可行的修复方案,可根据场景选择:

方案1:优化后台日志跟踪逻辑

通过将docker logs后台运行+主进程wait的方式,让脚本主进程能及时响应信号,同时增加容器状态检查避免错误:

#!/bin/bash

CONTAINER=ansible

function cleanup() {
  local SIGNAL=$1
  echo "$0 caught ${SIGNAL}" >&2
  # 先检查容器是否仍在运行,再执行kill操作
  if docker ps --filter "name=${CONTAINER}" --format "{{.Names}}" | grep -q "${CONTAINER}"; then
    docker kill "${CONTAINER}"
    local KILL_EXIT=$?
    exit ${KILL_EXIT}
  fi
  exit 0
}

# 覆盖更多中止信号场景
trap 'cleanup SIGINT' SIGINT
trap 'cleanup SIGTERM' SIGTERM
trap 'cleanup SIGQUIT' SIGQUIT

function ansible_base() {
  # 启动容器并后台运行
  docker run -d --rm --name "${CONTAINER}" someorg/ansible:latest "$@"
  # 后台跟踪日志并记录进程PID
  docker logs --follow "${CONTAINER}" &
  LOGS_PID=$!
  # 主进程等待日志进程,此时能及时捕获信号
  wait ${LOGS_PID}
}

ansible_base "$@"

关键改动说明:

  • 用"$@"替代$@,避免参数含空格时出现解析错误;
  • 将docker logs后台运行,主进程通过wait等待,确保信号能被脚本主进程及时捕获;
  • 清理函数中先检查容器状态,避免容器已停止时的kill报错。

方案2:前台运行容器(更简洁)

放弃-d后台启动容器和docker logs跟踪,直接前台运行容器,配合--init参数确保信号能正确转发到容器内进程:

#!/bin/bash

CONTAINER=ansible

function cleanup() {
  local SIGNAL=$1
  echo "$0 caught ${SIGNAL}" >&2
  docker stop "${CONTAINER}"
  exit $?
}

trap 'cleanup SIGINT' SIGINT
trap 'cleanup SIGTERM' SIGTERM

function ansible_base() {
  # 前台运行容器,--init确保容器内进程能正确接收信号
  docker run --rm --name "${CONTAINER}" --init someorg/ansible:latest "$@"
}

ansible_base "$@"

方案优势:

  • 脚本逻辑更简单,无需处理后台进程和日志跟踪;
  • --init参数让Docker启动init进程作为容器PID 1,确保容器内进程能正确响应SIGTERM等信号,避免僵尸进程或不响应中止命令的情况;
  • docker stop会先向容器发送SIGTERM等待优雅停止,超时后发送SIGKILL,符合标准的中止流程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 15:05:35