Shell脚本如何编排容器化应用部署

wen 实用脚本 1

本文目录导读:

Shell脚本如何编排容器化应用部署

  1. 基础部署脚本结构
  2. 多容器应用编排脚本
  3. CI/CD集成脚本
  4. 使用示例
  5. 最佳实践建议

使用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

最佳实践建议

  1. 错误处理:使用 set -e 确保脚本在错误时停止
  2. 日志记录:详细记录每一步操作结果
  3. 版本管理:为每个部署打标签,支持回滚
  4. 健康检查:部署后验证服务可用性
  5. 资源限制:设置内存和CPU限制
  6. 环境分离:使用环境变量区分不同环境
  7. 并行操作:对无依赖的服务并行部署
  8. 通知机制:部署完成后发送通知

这个脚本框架可以根据实际需求进行扩展,添加更多功能如灰度发布、金丝雀部署、服务发现集成等。

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