让Docker成为标准UNIX组件:信号与输出转发问题求助
Docker包装器的信号传递与标准输出适配问题
问题背景
Docker对SIGPIPE的处理存在缺陷,执行以下命令时:
$ docker run --rm ubuntu yes | head y y y y y y y y y y write /dev/stdout: broken pipe $
不仅会额外输出write /dev/stdout: broken pipe,更关键的是Docker容器内的进程不会自动终止。
为解决标准输出问题,我尝试用FIFO挂载作为输出中转,编写了如下脚本:
fifo=`basename $0`.$$ mkfifo $fifo cat $fifo & docker run --rm -v$PWD/$fifo:$PWD/$fifo ubuntu sh -c "$* >$PWD/$fifo" rm $fifo
但新问题出现:按下Ctrl-C无法终止容器,shell不会将SIGINT转发给前台的Docker进程。
于是我添加了信号捕获逻辑:
trap 'kill $!' INT TERM fifo=`basename $0`.$$ mkfifo $fifo cat $fifo & docker run --rm -v$PWD/$fifo:$PWD/$fifo ubuntu sh -c "$* >$PWD/$fifo" & wait rm $fifo
结果按下Ctrl-C后脚本退出,但Docker进程和cat仍在持续输出,完全达不到预期。
核心需求
适配UNIX工作流,用于Docker化应用的基准测试:当某实例输出特定行数后终止它,并向其他所有实例发送SIGTERM终止进程。
解决方案
1. 修复信号传递与FIFO清理问题
修改脚本,确保捕获信号时能终止所有相关进程(Docker容器、cat进程),并清理FIFO:
#!/bin/bash # 定义清理函数,终止所有子进程并删除FIFO cleanup() { # 终止Docker容器(通过docker stop,比kill更可靠) if [ -n "$DOCKER_CONTAINER_ID" ]; then docker stop "$DOCKER_CONTAINER_ID" >/dev/null 2>&1 fi # 终止cat进程 if [ -n "$CAT_PID" ]; then kill "$CAT_PID" >/dev/null 2>&1 fi # 删除FIFO if [ -e "$fifo" ]; then rm -f "$fifo" fi exit 0 } # 捕获INT、TERM信号触发清理 trap cleanup INT TERM # 创建FIFO fifo="$(basename "$0").$$" mkfifo "$fifo" # 启动cat转发输出,记录PID cat "$fifo" & CAT_PID=$! # 启动Docker容器,记录容器ID和后台进程PID cidfile="/tmp/docker_cid_$$" docker run --rm --cidfile "$cidfile" -v"$PWD/$fifo:$PWD/$fifo" ubuntu sh -c "$* >$PWD/$fifo" & DOCKER_PID=$! # 读取容器ID DOCKER_CONTAINER_ID=$(cat "$cidfile") rm -f "$cidfile" # 等待Docker进程结束,之后自动触发清理 wait "$DOCKER_PID" cleanup
2. 实现“输出特定行数后终止实例+发送SIGTERM到其他实例”
针对基准测试的需求,扩展脚本监控输出行数,达到阈值时终止当前容器,并向其他指定容器发送SIGTERM:
#!/bin/bash # 配置:目标输出行数 TARGET_LINES=10 # 其他需要终止的容器ID列表(可通过参数传入或提前获取) OTHER_CONTAINERS=("container_id_1" "container_id_2") cleanup() { # 终止当前容器 if [ -n "$DOCKER_CONTAINER_ID" ]; then docker stop "$DOCKER_CONTAINER_ID" >/dev/null 2>&1 fi # 终止cat和监控进程 if [ -n "$CAT_PID" ]; then kill "$CAT_PID" >/dev/null 2>&1 fi if [ -n "$MONITOR_PID" ]; then kill "$MONITOR_PID" >/dev/null 2>&1 fi rm -f "$fifo" exit 0 } trap cleanup INT TERM fifo="$(basename "$0").$$" mkfifo "$fifo" # 启动监控进程,统计输出行数,达到阈值时触发终止 ( line_count=0 while read -r line; do echo "$line" ((line_count++)) if [ "$line_count" -ge "$TARGET_LINES" ]; then # 终止当前容器 docker stop "$DOCKER_CONTAINER_ID" >/dev/null 2>&1 # 向其他容器发送SIGTERM for cid in "${OTHER_CONTAINERS[@]}"; do docker kill --signal=SIGTERM "$cid" >/dev/null 2>&1 done cleanup fi done < "$fifo" ) & MONITOR_PID=$! cidfile="/tmp/docker_cid_$$" docker run --rm --cidfile "$cidfile" -v"$PWD/$fifo:$PWD/$fifo" ubuntu sh -c "$* >$PWD/$fifo" & DOCKER_PID=$! DOCKER_CONTAINER_ID=$(cat "$cidfile") rm -f "$cidfile" wait "$DOCKER_PID" cleanup
关键改进点
- 使用
docker stop和docker kill --signal=SIGTERM替代直接kill,更可靠地控制Docker容器 - 记录所有相关进程的PID(
cat、监控进程、Docker容器ID),确保信号能传递到所有组件 - 统一的
cleanup函数处理所有终止和资源清理逻辑 - 监控输出行数的子进程,达到阈值时自动触发终止流程
内容的提问来源于stack exchange,提问作者Patrick Coppock
相关产品推荐
相关产品推荐

