本文目录导读:

配置容器补丁更新的Shell脚本需要考虑容器运行时的安全性、最小化停机时间和回滚能力,以下是几种常见场景的配置方案:
基础容器更新脚本(适用于测试环境)
#!/bin/bash
# 容器补丁更新脚本 - 基础版
set -euo pipefail
# 配置变量
CONTAINER_NAME="my-app"
IMAGE_NAME="my-registry/my-app"
BACKUP_DIR="/backup/containers"
LOG_FILE="/var/log/container-update.log"
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 创建备份
backup_container() {
log "开始备份容器 $CONTAINER_NAME"
mkdir -p "$BACKUP_DIR"
# 导出当前容器配置
docker inspect "$CONTAINER_NAME" > "$BACKUP_DIR/${CONTAINER_NAME}_config_$(date +%Y%m%d_%H%M%S).json"
# 如果容器正在运行,创建镜像备份
if docker ps -q -f name="$CONTAINER_NAME" > /dev/null 2>&1; then
docker commit "$CONTAINER_NAME" "${IMAGE_NAME}:backup-$(date +%Y%m%d_%H%M%S)"
log "容器镜像备份完成"
fi
}
# 拉取最新镜像
pull_update() {
log "拉取最新镜像: $IMAGE_NAME:latest"
docker pull "$IMAGE_NAME:latest"
if [ $? -eq 0 ]; then
log "镜像拉取成功"
else
log "错误: 镜像拉取失败"
exit 1
fi
}
# 执行更新
perform_update() {
log "停止旧容器: $CONTAINER_NAME"
docker stop "$CONTAINER_NAME" || true
docker rm "$CONTAINER_NAME" || true
log "启动新容器"
docker run -d \
--name "$CONTAINER_NAME" \
--restart unless-stopped \
-p 8080:80 \
"$IMAGE_NAME:latest"
# 检查容器是否成功启动
sleep 5
if docker ps -q -f name="$CONTAINER_NAME" > /dev/null 2>&1; then
log "容器更新成功"
return 0
else
log "错误: 容器启动失败"
return 1
fi
}
# 健康检查
health_check() {
log "执行健康检查..."
# 替换为实际健康检查命令
curl -f http://localhost:8080/health || return 1
# 检查容器日志是否有错误
docker logs --tail 50 "$CONTAINER_NAME" | grep -i "error" && return 1
return 0
}
# 回滚函数
rollback() {
log "执行回滚操作..."
local backup_image=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep "${IMAGE_NAME}:backup" | sort | tail -1)
if [ -n "$backup_image" ]; then
docker stop "$CONTAINER_NAME" || true
docker rm "$CONTAINER_NAME" || true
docker run -d \
--name "$CONTAINER_NAME" \
--restart unless-stopped \
-p 8080:80 \
"$backup_image"
log "回滚完成,使用镜像: $backup_image"
else
log "没有找到可回滚的备份镜像"
exit 1
fi
}
# 主执行流程
main() {
log "开始容器补丁更新流程"
backup_container
pull_update
if perform_update; then
if health_check; then
log "更新成功完成"
else
log "健康检查失败,执行回滚"
rollback
fi
else
log "更新失败,执行回滚"
rollback
fi
}
# 执行
main
无停机更新脚本(蓝绿部署)
#!/bin/bash
# 蓝绿部署更新脚本
set -euo pipefail
# 配置变量
APP_NAME="my-app"
IMAGE="$1" # 从参数获取新镜像
BLUE_PORT=8081
GREEN_PORT=8082
ACTIVE_COLOR_FILE="/tmp/active_color.txt"
# 获取当前活跃环境
get_active_color() {
if [ -f "$ACTIVE_COLOR_FILE" ]; then
cat "$ACTIVE_COLOR_FILE"
else
echo "blue"
fi
}
# 切换负载均衡
switch_traffic() {
local new_color=$1
local new_port
if [ "$new_color" = "blue" ]; then
new_port=$BLUE_PORT
else
new_port=$GREEN_PORT
fi
# 更新负载均衡配置(示例使用Nginx)
cat > /etc/nginx/conf.d/${APP_NAME}.conf << EOF
upstream ${APP_NAME} {
server 127.0.0.1:${new_port};
}
server {
listen 80;
server_name ${APP_NAME}.example.com;
location / {
proxy_pass http://${APP_NAME};
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
}
EOF
nginx -s reload
echo "$new_color" > "$ACTIVE_COLOR_FILE"
echo "已切换到 $new_color 环境"
}
# 部署新版本到备用环境
deploy_staging() {
local active_color=$(get_active_color)
local staging_color
local staging_port
# 确定备用环境
if [ "$active_color" = "blue" ]; then
staging_color="green"
staging_port=$GREEN_PORT
else
staging_color="blue"
staging_port=$BLUE_PORT
fi
echo "部署到 $staging_color 环境"
# 启动新版本容器
docker run -d \
--name "${APP_NAME}-${staging_color}" \
-p ${staging_port}:80 \
"$IMAGE"
# 等待容器就绪
sleep 10
# 验证新版本
if curl -f http://localhost:${staging_port}/health; then
echo "新版本验证成功"
switch_traffic "$staging_color"
# 停止旧版本
docker stop "${APP_NAME}-${active_color}" || true
docker rm "${APP_NAME}-${active_color}" || true
else
echo "新版本验证失败,保持当前环境"
docker stop "${APP_NAME}-${staging_color}" || true
docker rm "${APP_NAME}-${staging_color}" || true
exit 1
fi
}
# 执行部署
deploy_staging
生产环境滚动更新脚本
#!/bin/bash
# 生产环境滚动更新脚本
set -euo pipefail
# 配置
CONTAINER_PREFIX="web-app"
INSTANCE_COUNT=3
IMAGE_NAME="$1"
ROLLBACK_IMAGE="${IMAGE_NAME}:previous"
# 滚动更新函数
rolling_update() {
local success_count=0
for i in $(seq 1 $INSTANCE_COUNT); do
local container_name="${CONTAINER_PREFIX}-${i}"
echo "更新容器 $container_name"
# 优雅停止
docker exec "$container_name" /usr/sbin/nginx -s quit || true
sleep 2
# 启动新容器
docker run -d \
--name "${container_name}_new" \
--network app-network \
"$IMAGE_NAME"
# 等待就绪
sleep 5
# 健康检查
if docker exec "${container_name}_new" curl -f http://localhost/health; then
# 切换流量
docker network disconnect app-network "$container_name" || true
docker network connect app-network "${container_name}_new"
# 停止旧容器
docker stop "$container_name" || true
docker rm "$container_name" || true
# 重命名新容器
docker rename "${container_name}_new" "$container_name"
((success_count++))
echo "容器 $container_name 更新成功"
else
echo "容器 ${container_name}_new 健康检查失败"
docker stop "${container_name}_new" || true
docker rm "${container_name}_new" || true
return 1
fi
done
echo "滚动更新完成,成功更新 $success_count/$INSTANCE_COUNT 个实例"
}
# 执行滚动更新
rolling_update
使用Docker Compose的更新脚本
#!/bin/bash
# Docker Compose更新脚本
set -euo pipefail
COMPOSE_FILE="docker-compose.yml"
PROJECT_NAME="my-project"
BACKUP_COMPOSE="docker-compose.backup.yml"
# 备份当前配置
backup_compose() {
echo "备份当前Docker Compose配置"
cp "$COMPOSE_FILE" "$BACKUP_COMPOSE"
# 保存当前镜像标签
docker-compose images > "images_backup_$(date +%Y%m%d).txt"
}
# 更新服务
update_services() {
echo "拉取最新镜像"
docker-compose pull
echo "重建并启动服务(零停机)"
docker-compose up -d --no-deps --build service1 service2
echo "等待服务就绪..."
sleep 10
# 健康检查
for service in service1 service2; do
if docker-compose ps "$service" | grep -q "Up"; then
echo "$service 运行正常"
else
echo "$service 启动失败,开始回滚"
rollback
exit 1
fi
done
}
# 回滚函数
rollback() {
echo "执行回滚..."
if [ -f "$BACKUP_COMPOSE" ]; then
cp "$BACKUP_COMPOSE" "$COMPOSE_FILE"
docker-compose down
docker-compose up -d
echo "回滚完成"
else
echo "没有可用的备份配置"
exit 1
fi
}
# 执行更新
backup_compose
update_services
自动化更新脚本(带通知和监控)
#!/bin/bash
# 完整的自动化更新脚本
set -euo pipefail
# 配置
NOTIFY_EMAIL="admin@example.com"
SLACK_WEBHOOK="https://hooks.slack.com/services/xxx"
PROMETHEUS_URL="http://prometheus:9090"
# 通知函数
notify() {
local message=$1
local level=${2:-INFO}
# 邮件通知
echo "$message" | mail -s "[$level] 容器更新通知" "$NOTIFY_EMAIL"
# Slack通知
curl -X POST -H "Content-Type: application/json" \
-d "{\"text\": \"[$level] $message\"}" \
"$SLACK_WEBHOOK" 2>/dev/null || true
}
# 监控指标检查
check_metrics() {
local query="avg(rate(container_cpu_usage_seconds_total[5m]))"
local result=$(curl -s "$PROMETHEUS_URL/api/v1/query?query=$query" | jq '.data.result[0].value[1]')
if (( $(echo "$result > 0.8" | bc -l) )); then
notify "CPU使用率过高: $result" "WARNING"
return 1
fi
return 0
}
# 预检查
pre_check() {
echo "执行更新前检查..."
# 检查磁盘空间
local disk_usage=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$disk_usage" -gt 80 ]; then
notify "磁盘使用率超过80%: $disk_usage%" "WARNING"
fi
# 检查内存
local mem_usage=$(free | grep Mem | awk '{print $3/$2 * 100.0}' | cut -d. -f1)
if [ "$mem_usage" -gt 80 ]; then
notify "内存使用率超过80%: $mem_usage%" "WARNING"
fi
# 检查当前容器状态
if ! docker ps --format "{{.Names}}" | grep -q "my-app"; then
notify "容器 my-app 不在运行状态" "ERROR"
exit 1
fi
echo "预检查通过"
}
# 后检查
post_check() {
echo "执行更新后检查..."
# 检查关键指标
check_metrics || true
# 检查API响应
local response_time=$(curl -o /dev/null -s -w "%{time_total}" http://localhost:8080/api/health)
if (( $(echo "$response_time > 2.0" | bc -l) )); then
notify "API响应时间过高: ${response_time}s" "WARNING"
fi
echo "后检查完成"
}
# 主流程
main() {
notify "开始容器更新流程" "INFO"
pre_check
# 执行更新(调用之前的更新函数)
if perform_update; then
post_check
notify "容器更新成功完成" "INFO"
else
notify "容器更新失败,开始回滚" "ERROR"
rollback
fi
}
# 执行主流程
main
使用建议
-
选择合适的更新策略:
- 测试环境:使用基础更新脚本
- 生产环境:使用蓝绿部署或滚动更新
- 高可用要求:使用滚动更新,逐个替换容器
-
安全考虑:
# 使用TLS验证镜像签名 export DOCKER_CONTENT_TRUST=1 # 使用只读文件系统 docker run --read-only ... # 限制容器资源 docker run --memory="512m" --cpus="0.5" ...
-
错误处理:
# 设置超时 timeout 120 docker-compose pull # 幂等性操作 docker stop container_name 2>/dev/null || true docker rm container_name 2>/dev/null || true
-
监控和日志:
# 发送更新事件到监控系统 curl -X POST "http://monitoring:8086/write?db=deployments" \ --data-binary "container_update,app=my-app,status=success value=1" # 集中日志收集 exec > >(tee -a /var/log/container-update.log | logger -t container-update)
定时更新脚本(cron示例)
# crontab -e # 每周日凌晨3点执行更新 0 3 * * 0 /usr/local/bin/container-update.sh >> /var/log/container-update.cron.log 2>&1 # 每天检查更新 0 0 * * * /usr/local/bin/check-updates.sh
选择哪种方案取决于你的具体需求:测试环境用简单方案,生产环境建议使用蓝绿部署或滚动更新,配合完整的监控和通知机制。