本文目录导读:

使用Shell脚本编排容器化应用部署是一个常见的实践,特别是在小型项目或需要快速自动化的场景中,以下是一个完整、实用的Shell脚本编排方案。
基础部署脚本结构
#!/bin/bash
set -e # 遇到错误立即退出
# ========== 配置区域 ==========
APP_NAME="myapp"
IMAGE_NAME="myapp:latest"
CONTAINER_NAME="myapp-container"
HOST_PORT=8080
CONTAINER_PORT=3000
NETWORK_NAME="app-network"
VOLUME_PATH="/data/myapp"
RESTART_POLICY="unless-stopped"
LOG_DIR="/var/log/myapp"
# =============================
# 颜色输出
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
# 日志函数
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# 检查命令是否存在
check_command() {
if ! command -v $1 &> /dev/null; then
log_error "$1 未安装,请先安装"
exit 1
fi
}
# 环境准备
prepare_environment() {
log_info "准备运行环境..."
# 检查必要工具
check_command docker
check_command docker-compose # 可选
# 创建必要的目录
mkdir -p $VOLUME_PATH $LOG_DIR
# 检查Docker服务
if ! docker info &>/dev/null; then
log_error "Docker服务未运行"
exit 1
fi
}
# 清理旧容器
cleanup_old() {
log_info "清理旧容器和镜像..."
# 停止并删除同名容器
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker stop $CONTAINER_NAME 2>/dev/null || true
docker rm $CONTAINER_NAME 2>/dev/null || true
log_info "已移除旧容器: $CONTAINER_NAME"
fi
# 清理旧镜像(保留最近2个版本)
docker images $IMAGE_NAME --format "{{.Repository}}:{{.Tag}}" | \
tail -n +3 | xargs -r docker rmi 2>/dev/null || true
}
# 构建镜像
build_image() {
local tag=$1
local dockerfile=${2:-Dockerfile}
log_info "构建Docker镜像: ${IMAGE_NAME}:${tag}"
docker build \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg VERSION=$tag \
-t "${IMAGE_NAME}:${tag}" \
-f $dockerfile .
# 标记为latest
docker tag "${IMAGE_NAME}:${tag}" "${IMAGE_NAME}:latest"
log_info "镜像构建完成"
}
# 启动容器
start_container() {
local tag=${1:-latest}
log_info "启动容器: $CONTAINER_NAME"
# 确保网络存在
docker network create $NETWORK_NAME 2>/dev/null || true
# 运行容器
docker run -d \
--name $CONTAINER_NAME \
--network $NETWORK_NAME \
-p ${HOST_PORT}:${CONTAINER_PORT} \
-v ${VOLUME_PATH}:/app/data \
-v ${LOG_DIR}:/app/logs \
-e NODE_ENV=production \
-e APP_VERSION=$tag \
--restart $RESTART_POLICY \
--memory="512m" \
--cpus="0.5" \
--health-cmd="curl -f http://localhost:${CONTAINER_PORT}/health || exit 1" \
--health-interval=30s \
--health-timeout=10s \
--health-retries=3 \
${IMAGE_NAME}:${tag}
log_info "容器已启动,映射端口: ${HOST_PORT}->${CONTAINER_PORT}"
}
# 健康检查
check_health() {
local retries=12
local wait_time=5
log_info "等待服务就绪..."
for i in $(seq 1 $retries); do
if curl -sf http://localhost:${HOST_PORT}/health >/dev/null 2>&1; then
log_info "服务健康检查通过"
return 0
fi
sleep $wait_time
done
log_error "服务启动失败,查看日志: docker logs $CONTAINER_NAME"
return 1
}
# 主部署流程
deploy() {
local version=${1:-$(date +%Y%m%d%H%M%S)}
log_info "开始部署 ${APP_NAME} v${version}"
prepare_environment
cleanup_old
build_image $version
start_container $version
check_health
log_info "部署完成: http://localhost:${HOST_PORT}"
}
# 回滚到上一版本
rollback() {
log_info "执行回滚操作..."
# 获取上一个镜像版本
local prev_tag=$(docker images $IMAGE_NAME --format "{{.Tag}}" | \
grep -v "latest" | sort -r | sed -n '2p')
if [ -z "$prev_tag" ]; then
log_error "没有可回滚的版本"
exit 1
fi
log_info "回滚到版本: $prev_tag"
cleanup_old
start_container $prev_tag
check_health
log_info "回滚完成"
}
# 查看状态
status() {
echo "========== 容器状态 =========="
docker ps -a --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
echo "========== 资源使用 =========="
docker stats $CONTAINER_NAME --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
echo ""
echo "========== 健康检查 =========="
docker inspect $CONTAINER_NAME --format '{{.State.Health.Status}}' 2>/dev/null || echo "未配置健康检查"
}
# 日志查看
logs() {
docker logs --tail 100 -f $CONTAINER_NAME
}
# 停止服务
stop() {
log_info "停止服务..."
docker stop $CONTAINER_NAME 2>/dev/null || true
log_info "服务已停止"
}
# 显示帮助信息
show_help() {
cat << EOF
用法: $0 {deploy|rollback|status|logs|stop|help}
命令:
deploy [version] 部署新版本,默认使用时间戳
rollback 回滚到上一版本
status 查看服务状态
logs 查看实时日志
stop 停止服务
help 显示此帮助信息
示例:
$0 deploy # 部署最新代码
$0 deploy v1.2.3 # 部署指定版本
$0 rollback # 回滚
$0 status # 查看状态
EOF
}
# 主函数
main() {
case "${1:-help}" in
deploy)
deploy "${2:-$(date +%Y%m%d%H%M%S)}"
;;
rollback)
rollback
;;
status)
status
;;
logs)
logs
;;
stop)
stop
;;
help|*)
show_help
;;
esac
}
# 执行主函数
main "$@"
多容器应用编排脚本
对于微服务架构,可以使用以下脚本编排多个服务:
#!/bin/bash
set -e
# 服务配置
SERVICE_LIST=(
"gateway:8080:3000"
"user-service:8081:3001"
"order-service:8082:3002"
"notification-service:8083:3003"
)
# 启动所有服务
start_all_services() {
log_info "启动所有微服务..."
for service in "${SERVICE_LIST[@]}"; do
IFS=':' read -r name host_port container_port <<< "$service"
log_info "启动 ${name}..."
# 检查并创建目录
mkdir -p "/data/${name}" "/var/log/${name}"
# 启动容器
docker run -d \
--name "${name}" \
--network "microservice-network" \
-p "${host_port}:${container_port}" \
-v "/data/${name}:/app/data" \
-v "/var/log/${name}:/app/logs" \
--label "service=${name}" \
--restart unless-stopped \
"${name}:latest"
done
}
# 滚动更新
rolling_update() {
local service=$1
local new_image=$2
log_info "滚动更新 ${service}..."
# 启动新容器
docker run -d \
--name "${service}-new" \
--network "microservice-network" \
-P \
--label "service=${service}" \
"${new_image}"
sleep 5
# 检查新容器健康状态
if docker inspect "${service}-new" --format '{{.State.Status}}' | grep -q "running"; then
# 停止旧容器
docker stop "${service}" 2>/dev/null || true
docker rm "${service}" 2>/dev/null || true
# 重命名新容器
docker rename "${service}-new" "${service}"
log_info "${service} 更新完成"
else
log_error "${service} 更新失败,回滚"
docker stop "${service}-new" 2>/dev/null || true
docker rm "${service}-new" 2>/dev/null || true
fi
}
# 批量启动
batch_start() {
# 并行启动服务
for service in "${SERVICE_LIST[@]}"; do
IFS=':' read -r name _ <<< "$service"
(
log_info "启动 ${name}..."
docker start "${name}" 2>/dev/null || \
log_warn "${name} 未创建或启动失败"
) &
done
wait # 等待所有后台任务完成
}
# 数据库迁移
run_migrations() {
log_info "执行数据库迁移..."
docker run --rm \
--network "microservice-network" \
-v "$(pwd)/migrations:/migrations" \
migrate/migrate \
-path /migrations \
-database "mysql://user:password@tcp(mysql:3306)/dbname" \
up
}
CI/CD集成脚本
#!/bin/bash
set -e
# Git操作
git_operations() {
log_info "拉取最新代码..."
git fetch --all
git checkout $1 # 分支名
git pull origin $1
export GIT_COMMIT=$(git rev-parse --short HEAD)
export GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
}
# 环境变量管理
manage_env() {
local env=$1 # dev, staging, production
# 加载对应环境配置
if [ -f ".env.${env}" ]; then
set -a
source ".env.${env}"
set +a
log_info "已加载 ${env} 环境配置"
fi
# 替换模板变量
envsubst < docker-compose.template.yml > docker-compose.yml
}
# 自动化部署
auto_deploy() {
local env=$1
local branch=${2:-main}
log_info "开始自动化部署到 ${env} 环境"
# 步骤1: 代码准备
git_operations $branch
# 步骤2: 环境配置
manage_env $env
# 步骤3: 运行测试
if [ "$env" != "development" ]; then
docker-compose -f docker-compose.test.yml run --rm tests
log_info "测试通过"
fi
# 步骤4: 构建部署
docker-compose build
docker-compose up -d
# 步骤5: 健康检查
sleep 10
if curl -sf http://localhost/health; then
log_info "部署成功"
# 发送通知
send_notification "部署成功: ${env} - ${GIT_COMMIT}"
else
log_error "部署失败,执行回滚"
docker-compose down
docker-compose -f docker-compose.rollback.yml up -d
fi
}
# 通知函数
send_notification() {
local message=$1
local webhook_url=${SLACK_WEBHOOK_URL:-""}
if [ -n "$webhook_url" ]; then
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"${message}\"}" \
$webhook_url
fi
}
使用示例
# 1. 基本部署 ./deploy.sh deploy # 或指定版本 ./deploy.sh deploy v1.2.3 # 2. 查看状态 ./deploy.sh status # 3. 实时日志 ./deploy.sh logs # 4. 回滚 ./deploy.sh rollback # 5. 停止服务 ./deploy.sh stop # 6. CI/CD自动化 ./ci-deploy.sh auto_deploy staging develop
最佳实践建议
- 错误处理:使用
set -e确保脚本在错误时停止 - 日志记录:详细记录每一步操作结果
- 版本管理:为每个部署打标签,支持回滚
- 健康检查:部署后验证服务可用性
- 资源限制:设置内存和CPU限制
- 环境分离:使用环境变量区分不同环境
- 并行操作:对无依赖的服务并行部署
- 通知机制:部署完成后发送通知
这个脚本框架可以根据实际需求进行扩展,添加更多功能如灰度发布、金丝雀部署、服务发现集成等。