Shell脚本如何配置容器优雅退出

wen 实用脚本 2

本文目录导读:

Shell脚本如何配置容器优雅退出

  1. 基础信号处理框架
  2. 完整的生产级示例
  3. Docker专用配置
  4. 多进程管理示例
  5. 最佳实践

在Shell脚本中配置容器的优雅退出,主要涉及信号处理资源清理两个核心方面,以下是完整的配置方法:

基础信号处理框架

#!/bin/bash
# 定义清理函数
cleanup() {
    echo "[INFO] 开始优雅退出..."
    # 停止子进程
    if [ -n "$CHILD_PID" ]; then
        echo "[INFO] 停止子进程 PID: $CHILD_PID"
        kill -TERM "$CHILD_PID" 2>/dev/null
        wait "$CHILD_PID"
    fi
    # 清理临时文件
    rm -f /tmp/*.tmp 2>/dev/null
    # 关闭数据库连接等
    # mysql -e "SHOW PROCESSLIST" | awk '{print $1}' | xargs kill 2>/dev/null
    echo "[INFO] 优雅退出完成"
    exit 0
}
# 注册信号处理器
trap 'cleanup' SIGTERM SIGINT SIGQUIT

完整的生产级示例

#!/bin/bash
# ============ 配置变量 ============
GRACEFUL_TIMEOUT=30  # 优雅退出超时时间(秒)
CHILD_PID=""
LOG_FILE="/var/log/app.log"
# ============ 日志函数 ============
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# ============ 清理函数 ============
cleanup() {
    local signal=$1
    log "收到信号: $signal,开始优雅退出..."
    # 1. 通知子进程
    if [ -n "$CHILD_PID" ] && kill -0 "$CHILD_PID" 2>/dev/null; then
        log "发送 SIGTERM 到子进程 $CHILD_PID"
        kill -TERM "$CHILD_PID" 2>/dev/null
        # 等待子进程退出(带超时)
        local wait_time=0
        while [ $wait_time -lt $GRACEFUL_TIMEOUT ]; do
            if ! kill -0 "$CHILD_PID" 2>/dev/null; then
                log "子进程 $CHILD_PID 已正常退出"
                break
            fi
            sleep 1
            wait_time=$((wait_time + 1))
        done
        # 超时后强制杀死
        if kill -0 "$CHILD_PID" 2>/dev/null; then
            log "子进程 $CHILD_PID 超时未退出,强制终止"
            kill -9 "$CHILD_PID" 2>/dev/null
        fi
    fi
    # 2. 清理资源
    log "清理临时文件..."
    rm -rf /tmp/app_* 2>/dev/null
    # 3. 关闭文件描述符
    exec 3>&- 2>/dev/null
    # 4. 释放其他资源
    # clean_database_connections
    log "优雅退出完成"
    exit 0
}
# ============ 注册信号处理 ============
trap 'cleanup SIGTERM' SIGTERM
trap 'cleanup SIGINT' SIGINT
trap 'cleanup SIGQUIT' SIGQUIT
trap '' SIGPIPE  # 忽略 SIGPIPE
# ============ 健康检查接口 ============
health_check() {
    if [ -n "$CHILD_PID" ] && kill -0 "$CHILD_PID" 2>/dev/null; then
        echo "healthy"
        return 0
    else
        echo "unhealthy"
        return 1
    fi
}
# ============ 主程序 ============
main() {
    log "应用启动..."
    # 启动子进程(示例:运行一个长时间的任务)
    (
        # 子进程逻辑
        while true; do
            log "应用运行中..."
            sleep 10
        done
    ) &
    CHILD_PID=$!
    log "子进程 PID: $CHILD_PID"
    # 等待子进程
    wait "$CHILD_PID"
    # 如果子进程意外退出
    log "子进程意外退出,退出码: $?"
    exit 1
}
# ============ 启动应用 ============
main

Docker专用配置

#!/bin/bash
# Docker容器优雅退出配置
set -e
# 定义清理函数
docker_cleanup() {
    echo "[Docker] 容器正在停止..."
    # 停止所有后台服务
    for service in nginx mysql redis; do
        if command -v "$service" &>/dev/null; then
            echo "[Docker] 停止 $service"
            service "$service" stop || true
        fi
    done
    # 等待所有进程退出
    sleep 2
    echo "[Docker] 容器停止完成"
    exit 0
}
# 捕获Docker发出的信号
trap 'docker_cleanup' SIGTERM SIGINT
# 启动主服务(必须以exec在前台运行)
exec nginx -g "daemon off;"

多进程管理示例

#!/bin/bash
# 管理多个子进程的优雅退出
declare -A CHILD_PIDS
cleanup_multiple() {
    echo "开始关闭所有子进程..."
    # 发送 SIGTERM 到所有子进程
    for name in "${!CHILD_PIDS[@]}"; do
        pid=${CHILD_PIDS[$name]}
        if kill -0 "$pid" 2>/dev/null; then
            echo "停止进程: $name (PID: $pid)"
            kill -TERM "$pid" 2>/dev/null
        fi
    done
    # 等待所有进程退出
    for name in "${!CHILD_PIDS[@]}"; do
        pid=${CHILD_PIDS[$name]}
        wait "$pid" 2>/dev/null || true
        echo "进程 $name 已退出"
    done
    echo "所有进程已停止"
    exit 0
}
# 启动多个子进程
start_processes() {
    # 启动Web服务
    python3 web_server.py &
    CHILD_PIDS["web"]=$!
    # 启动后台任务
    python3 background_worker.py &
    CHILD_PIDS["worker"]=$!
    # 启动定时任务
    cron -f &
    CHILD_PIDS["cron"]=$!
}
trap 'cleanup_multiple' SIGTERM SIGINT
start_processes
# 等待所有子进程
for pid in "${CHILD_PIDS[@]}"; do
    wait "$pid"
done

最佳实践

Dockerfile 配置

FROM alpine:3.18
# 安装 dumb-init 用于正确的信号处理
RUN apk add --no-cache dumb-init
COPY app.sh /app/app.sh
RUN chmod +x /app/app.sh
# 使用 dumb-init 作为 init 进程
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/app/app.sh"]
# 设置健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD /app/healthcheck.sh

docker-compose 配置

version: '3.8'
services:
  app:
    build: .
    stop_grace_period: 30s  # 给予容器更多时间优雅退出
    stop_signal: SIGTERM
    healthcheck:
      test: ["CMD", "/app/healthcheck.sh"]
      interval: 30s
      timeout: 10s
      retries: 3

关键要点

  1. 使用 exec:在启动主进程时使用 exec,确保信号直接传递给进程
  2. 设置超时:避免无限等待子进程
  3. 清理顺序:先停止子进程,再清理资源
  4. 日志记录:记录退出过程,便于故障排查
  5. 健康检查:提供健康检查接口,便于容器编排平台监控

这种配置确保容器能够优雅地处理停止信号,完成必要的清理工作,避免数据丢失或资源泄漏。

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