本文目录导读:

在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
关键要点
- 使用
exec:在启动主进程时使用exec,确保信号直接传递给进程 - 设置超时:避免无限等待子进程
- 清理顺序:先停止子进程,再清理资源
- 日志记录:记录退出过程,便于故障排查
- 健康检查:提供健康检查接口,便于容器编排平台监控
这种配置确保容器能够优雅地处理停止信号,完成必要的清理工作,避免数据丢失或资源泄漏。