Shell脚本如何强制停止卡住的容器

wen 实用脚本 2

Shell脚本如何强制停止卡住的容器:3种终极方案与实战指南

目录导读

  1. 为什么容器会卡住?常见原因分析
  2. docker kill + 信号强制终止
  3. systemd-cgroup + OOM杀手
  4. 直接操作宿主机进程(终极手段)
  5. 自动化脚本实战:一键检测并强制停止
  6. 常见问题与避坑指南
  7. 何时用哪种方法最安全?

为什么容器会卡住?常见原因分析

在Docker或Podman运行环境中,容器“卡住”通常表现为:

Shell脚本如何强制停止卡住的容器

  • docker ps显示容器状态为Up,但无法响应任何命令(包括docker stop)。
  • 使用docker logs -f无输出,且docker exec报错“Container ... is not running”。
  • docker inspect显示进程PID为0或已僵尸化。

根本原因:容器内主进程可能因死锁、内存溢出、信号处理错误或挂载点故障而永久阻塞。docker stop发送的SIGTERM信号(默认15秒超时后转为SIGKILL)无法被内核正确处理,导致容器进程成为“僵尸进程”。


方案一:docker kill + 信号强制终止

Q: 为什么docker killdocker stop更彻底?
A: docker stop先发送SIGTERM(优雅终止),超时后才发SIGKILL;而docker kill直接发送指定信号(如SIGKILL)。

实战命令

# 强制立即终止容器(发送SIGKILL)
docker kill --signal=SIGKILL [容器名称或ID]
# 若容器卡在“正在停止”状态,加--force选项
docker rm --force [容器名称或ID]

脚本封装

#!/bin/bash
# force_kill_stuck_container.sh
CONTAINER_ID=$1
if docker inspect "$CONTAINER_ID" &>/dev/null; then
    echo "[INFO] 正在强制停止容器 $CONTAINER_ID..."
    docker kill --signal=SIGKILL "$CONTAINER_ID" 2>/dev/null
    sleep 2
    # 确认进程已退出,若仍有残留则强制删除
    if docker ps -a --filter "id=$CONTAINER_ID" --format "{{.Status}}" | grep -q "Up"; then
        docker rm --force "$CONTAINER_ID"
    fi
    echo "[SUCCESS] 容器已停止"
else
    echo "[ERROR] 容器不存在"
fi

适用场景:容器主进程未僵尸化,但用户层逻辑死锁(如无限循环、死锁线程)。


方案二:systemd-cgroup + OOM杀手

Q: 内存超限导致的卡住如何快速处理?
A: 使用cgroup的memory.oom_control功能,自动触发OOM Killer,或手动写入memory.force_empty

步骤

  1. 找到容器的cgroup路径:

    # Docker容器默认cgroup路径
    CGROUP_PATH=$(docker inspect --format '{{.HostConfig.CgroupParent}}' [容器ID])
    # 或直接搜索
    find /sys/fs/cgroup/memory -name "*[容器ID]*" -type d 2>/dev/null
  2. 手动触发OOM:

    # 设置oom_control为1(启用oom killer)
    echo 1 > /sys/fs/cgroup/memory/${CGROUP_PATH}/memory.oom_control
    # 强制回收内存
    echo 0 > /sys/fs/cgroup/memory/${CGROUP_PATH}/memory.force_empty

脚本整合

#!/bin/bash
# cgroup_oom_kill.sh
CONTAINER_ID=$1
CGROUP=$(docker inspect --format '{{.Id}}' "$CONTAINER_ID" | cut -c1-12)
CGROUP_PATH=$(find /sys/fs/cgroup/memory -name "*$CGROUP*" -type d 2>/dev/null | head -1)
if [ -n "$CGROUP_PATH" ]; then
    echo "触发OOM Killer..."
    echo 1 > "$CGROUP_PATH/memory.oom_control"
    echo 0 > "$CGROUP_PATH/memory.force_empty"
    echo "容器内存已被强制清理"
else
    echo "错误:未找到cgroup路径,请使用方案三"
fi

注意事项:仅适用于cgroups v1且容器内存限制开启(--memory参数)。


方案三:直接操作宿主机进程(终极手段)

Q: 如果容器完全无响应,且docker kill无效怎么办?
A: 直接使用kill -9杀掉宿主机上的容器进程,然后用docker rm清理。

查找到容器对应PID

# 方法1:docker top
docker top [容器ID]  # 输出容器内所有进程PID
# 方法2:/proc查找(适用于容器主进程)
CONTAINER_PID=$(docker inspect --format '{{.State.Pid}}' [容器ID])

强制杀进程

# 杀掉容器主进程和所有子进程
kill -9 $CONTAINER_PID 2>/dev/null
# 等待进程退出
while kill -0 $CONTAINER_PID 2>/dev/null; do sleep 1; done
# 强制删除容器
docker rm --force [容器ID]

自动化脚本

#!/bin/bash
# kill_process_by_container.sh
CONTAINER_ID=$1
PID=$(docker inspect --format '{{.State.Pid}}' "$CONTAINER_ID" 2>/dev/null)
if [ -z "$PID" ] || [ "$PID" -eq 0 ]; then
    echo "错误:容器进程不存在或已僵尸"
    exit 1
fi
echo "找到容器进程PID: $PID,正在强制终止..."
kill -9 $PID 2>/dev/null
sleep 1
if docker ps -a --filter "id=$CONTAINER_ID" --format "{{.Status}}" | grep -q "Exited"; then
    echo "进程已退出,清理容器..."
    docker rm --force "$CONTAINER_ID"
else
    echo "警告:进程可能残留,请手动检查 docker ps -a"
fi

风险提示:直接kill可能造成文件描述符泄漏或容器网络未清理,建议配合docker rm --force使用。


自动化脚本实战:一键检测并强制停止

Q: 如何编写一个轮询脚本,自动检测并清理卡住的容器?
A: 结合docker events和超时机制,实现自动化处理。

#!/bin/bash
# auto_clean_stuck_containers.sh
# 功能:每隔30秒检测运行超过5分钟且无响应的容器
TIMEOUT=300  # 5分钟
CHECK_INTERVAL=30
while true; do
    RUNNING_CONTAINERS=$(docker ps --format "{{.ID}}\t{{.Names}}\t{{.RunningFor}}")
    while IFS=$'\t' read -r ID NAME RUNNING_FOR; do
        # 解析运行时间("2 hours ago")
        if echo "$RUNNING_FOR" | grep -q "minutes"; then
            MINUTES=$(echo "$RUNNING_FOR" | grep -oP '\d+(?= minutes)')
            if [ "$MINUTES" -gt 5 ]; then
                # 检查容器是否可响应(尝试执行一个简单命令)
                if ! docker exec "$ID" echo "alive" &>/dev/null; then
                    echo "[$(date)] 容器 $NAME ($ID) 卡住,正在强制停止..."
                    docker kill --signal=SIGKILL "$ID" 2>/dev/null
                    docker rm --force "$ID" 2>/dev/null
                    echo "已清理"
                fi
            fi
        fi
    done <<< "$RUNNING_CONTAINERS"
    sleep "$CHECK_INTERVAL"
done

部署建议:使用systemdcrontab后台运行,记录日志到/var/log/container_clean.log


常见问题与避坑指南

Q1: docker kill仍然无效,进程PID为0怎么办?
A: 该容器已完全僵尸化,需重启Docker daemon:

systemctl restart docker  # 谨慎操作,会中断所有运行容器

或直接重启宿主机(仅限紧急情况)。

Q2: 为什么docker stop超时后仍不自动SIGKILL?
A: 可能因为容器内进程处于 D 状态(不可中断睡眠),此时SIGKILL也被阻塞,需检查/proc/[PID]/status中的State字段,若为D,只能等待I/O操作完成。

Q3: 强制停止后,挂载点无法卸载怎么办?
A: 使用lsof查找残留进程:

lsof +D /var/lib/docker/volumes/[卷名]/_data  # 找到占用进程后kill -9

Q4: Kubernetes中如何强制停止卡住的Pod?
A: K8s不支持直接kill,需:

kubectl delete pod --grace-period=0 --force [pod名称]

或修改terminationGracePeriodSeconds为0。


何时用哪种方法最安全?

场景 推荐方法 风险等级
容器无响应但进程非D状态 docker kill --signal=SIGKILL ⭐(低)
内存泄漏导致卡死 cgroup OOM触发 ⭐⭐(中)
进程完全僵尸化(PID=0) 直接杀进程+重启Docker ⭐⭐⭐(高)
需自动化生产环境 轮询脚本+死锁检测 ⭐⭐(中)

最佳实践建议

  1. 优先尝试docker kill -s SIGKILL(5秒内解决90%问题)。
  2. 若失败,使用docker top定位PID并执行kill -9
  3. 每季度检查一次Docker版本,更新至v20.10+(修复了多处信号处理bug)。

附录:一键排查脚本

#!/bin/bash
# check_stuck_containers.sh
for CID in $(docker ps -q); do
    STATUS=$(docker inspect --format '{{.State.Status}}' "$CID")
    PID=$(docker inspect --format '{{.State.Pid}}' "$CID")
    if [ "$PID" -eq 0 ] && [ "$STATUS" == "running" ]; then
        echo "发现僵尸容器: $CID,建议强制删除"
        docker rm --force "$CID"
    fi
done

本文基于Docker v24.0+、Linux Kernel 5.15+编写,旧版本可能需调整cgroup路径。

抱歉,评论功能暂时关闭!