Shell脚本如何金丝雀发布容器应用

wen 实用脚本 1

本文目录导读:

Shell脚本如何金丝雀发布容器应用

  1. 核心思路
  2. 基础架构假设
  3. Shell脚本实现
  4. 使用示例
  5. Docker Compose版本
  6. 高级特性(可选扩展)
  7. 最佳实践建议

下面我来介绍如何使用Shell脚本实现容器应用的金丝雀发布(Canary Release)。

核心思路

金丝雀发布的核心是逐步替换实例,通过流量控制实现风险控制,以下是完整的Shell脚本实现方案。

基础架构假设

  • 容器编排:Docker / Docker Compose
  • 负载均衡:Nginx / HAProxy
  • 服务发现:Consul / 静态配置

Shell脚本实现

#!/bin/bash
set -euo pipefail
# ============ 全局配置 ============
APP_NAME="myapp"
NAMESPACE="production"
REGISTRY="registry.example.com"
IMAGE_TAG="${1:-latest}"  # 新版本镜像标签
CANARY_PERCENT="${2:-10}"  # 金丝雀流量比例(默认10%)
ROLLOUT_INTERVAL="${3:-30}"  # 每步等待时间(秒)
# ============ 颜色输出 ============
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
info()  { echo -e "${GREEN}[INFO]${NC} $1"; }
warn()  { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
# ============ 函数定义 ============
# 检查必选命令是否安装
check_dependencies() {
    local deps=("docker" "curl" "jq")
    for cmd in "${deps[@]}"; do
        if ! command -v "$cmd" &> /dev/null; then
            error "Missing required command: $cmd"
        fi
    done
}
# 拉取新版本镜像
pull_image() {
    info "Pulling new image: ${REGISTRY}/${APP_NAME}:${IMAGE_TAG}"
    docker pull "${REGISTRY}/${APP_NAME}:${IMAGE_TAG}" || error "Failed to pull image"
}
# 获取稳定版本健康实例数
get_stable_instances() {
    docker ps --filter "name=${APP_NAME}-stable" --filter "status=running" --format "{{.Names}}" | wc -l
}
# 启动金丝雀实例
start_canary() {
    local canary_count=$(( $(get_stable_instances) * CANARY_PERCENT / 100 ))
    canary_count=$(( canary_count < 1 ? 1 : canary_count ))
    info "Starting ${canary_count} canary instance(s)..."
    for i in $(seq 1 $canary_count); do
        docker run -d \
            --name "${APP_NAME}-canary-${i}" \
            --label "version=canary" \
            --label "canary=true" \
            --network "app-network" \
            -e "APP_VERSION=${IMAGE_TAG}" \
            "${REGISTRY}/${APP_NAME}:${IMAGE_TAG}"
    done
    info "Canary instances started"
}
# 金丝雀健康检查
health_check() {
    local retries=5
    local delay=3
    for i in $(seq 1 $retries); do
        local healthy_instances=0
        local total_instances=0
        for container in $(docker ps --filter "label=canary=true" --format "{{.Names}}"); do
            total_instances=$((total_instances + 1))
            # 假设应用有健康检查端点
            if curl -sf "http://${container}:8080/health" &> /dev/null; then
                healthy_instances=$((healthy_instances + 1))
            fi
        done
        if [ "$healthy_instances" -ge "$total_instances" ]; then
            info "All canary instances healthy"
            return 0
        fi
        warn "Health check attempt $i: ${healthy_instances}/${total_instances} healthy"
        sleep "$delay"
    done
    return 1
}
# 更新负载均衡配置(Nginx示例)
update_load_balancer() {
    local strategy="${1:-canary}"  # canary / full
    # 生成新的Nginx配置
    local nginx_conf="/etc/nginx/conf.d/${APP_NAME}.conf"
    cat > "$nginx_conf" << EOF
upstream app_backend {
    # 稳定版本实例
EOF
    for container in $(docker ps --filter "label=version=stable" --format "{{.Names}}"); do
        local ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container")
        echo "    server ${ip}:8080 weight=100;" >> "$nginx_conf"
    done
    if [ "$strategy" = "canary" ]; then
        echo "" >> "$nginx_conf"
        echo "    # 金丝雀实例(低权重)" >> "$nginx_conf"
        for container in $(docker ps --filter "label=canary=true" --format "{{.Names}}"); do
            local ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container")
            echo "    server ${ip}:8080 weight=${CANARY_PERCENT};" >> "$nginx_conf"
        done
    fi
    echo "}" >> "$nginx_conf"
    # 重载Nginx配置
    nginx -s reload && info "Nginx configuration reloaded"
}
# 监控金丝雀指标
monitor_canary() {
    local monitor_interval=10
    local max_attempts=60  # 最多监控10分钟
    info "Monitoring canary for ${max_attempts} intervals..."
    for i in $(seq 1 $max_attempts); do
        local error_rate=0
        local response_time=0
        # 这里可以集成Prometheus查询或日志分析
        # 示例:检查错误日志
        error_rate=$(docker logs "${APP_NAME}-canary-1" --since "10s" 2>&1 | grep -c "ERROR")
        if [ "$error_rate" -gt 5 ]; then
            warn "High error rate detected in canary: ${error_rate}"
            return 1
        fi
        sleep "$monitor_interval"
    done
    return 0
}
# 回滚金丝雀
rollback_canary() {
    warn "Rolling back canary deployment..."
    # 停止并删除金丝雀实例
    for container in $(docker ps -a --filter "label=canary=true" --format "{{.Names}}"); do
        docker stop "$container" && docker rm "$container"
    done
    # 恢复Nginx配置(只保留稳定实例)
    update_load_balancer "stable"
    info "Canary rollback completed"
}
# 全量发布
full_rollout() {
    info "Starting full rollout..."
    local stable_count=$(get_stable_instances)
    # 逐步替换稳定实例为金丝雀镜像
    for i in $(seq 1 $stable_count); do
        local old_container="${APP_NAME}-stable-${i}"
        local new_container="${APP_NAME}-stable-${i}"
        info "Upgrading instance ${i}/${stable_count}"
        # 停止旧实例
        docker stop "$old_container" > /dev/null 2>&1 || true
        # 用新镜像启动
        docker run -d \
            --name "$new_container" \
            --label "version=stable" \
            --network "app-network" \
            -e "APP_VERSION=${IMAGE_TAG}" \
            "${REGISTRY}/${APP_NAME}:${IMAGE_TAG}"
        # 健康检查
        sleep 5
        if ! docker ps | grep -q "$new_container"; then
            error "Failed to start new instance"
        fi
        # 更新负载均衡
        update_load_balancer "full"
        # 等待稳定
        sleep "$ROLLOUT_INTERVAL"
    done
    info "Full rollout completed"
}
# 清理旧版本
cleanup_old_versions() {
    info "Cleaning up old version images..."
    docker image prune -a --force --filter "until=24h"
}
# ============ 主流程 ============
main() {
    echo "========================================="
    echo "  Canary Deployment Script"
    echo "  App: ${APP_NAME}"
    echo "  Image: ${IMAGE_TAG}"
    echo "  Canary: ${CANARY_PERCENT}%"
    echo "========================================="
    check_dependencies
    # Step 1: 拉取新镜像
    pull_image
    # Step 2: 启动金丝雀实例
    start_canary
    # Step 3: 更新负载均衡(添加金丝雀)
    update_load_balancer "canary"
    # Step 4: 健康检查
    if ! health_check; then
        error "Health check failed for canary"
    fi
    # Step 5: 监控金丝雀
    if ! monitor_canary; then
        rollback_canary
        error "Canary monitoring failed, rolled back"
    fi
    # Step 6: 全量发布
    full_rollout
    # Step 7: 清理
    cleanup_old_versions
    # Step 8: 移除金丝雀标签
    for container in $(docker ps -a --filter "label=canary=true" --format "{{.Names}}"); do
        docker rm "$container" 2> /dev/null || true
    done
    info "Canary deployment completed successfully!"
}
# 执行主函数
main "$@"

使用示例

# 基础用法 - 10%金丝雀流量部署v2.1.0
./canary-deploy.sh v2.1.0
# 自定义金丝雀比例(20%)
./canary-deploy.sh v2.1.0 20
# 自定义间隔时间(60秒)
./canary-deploy.sh v2.1.0 10 60

Docker Compose版本

# docker-compose.canary.yml
version: '3.8'
services:
  app-stable:
    image: registry.example.com/myapp:${STABLE_TAG:-latest}
    deploy:
      replicas: 5
    labels:
      - "version=stable"
    networks:
      - app-network
  app-canary:
    image: registry.example.com/myapp:${CANARY_TAG:-latest}
    deploy:
      replicas: ${CANARY_REPLICAS:-1}
    labels:
      - "version=canary"
      - "canary=true"
    networks:
      - app-network
networks:
  app-network:
    driver: bridge

高级特性(可选扩展)

灰度比例动态调整

# 动态调整金丝雀权重
update_canary_weight() {
    local weight=$1
    update_load_balancer "custom" "$weight"
    info "Updated canary weight to ${weight}%"
}

集成监控告警

# 集成Prometheus指标查询
check_prometheus_metrics() {
    local query='rate(app_errors_total[5m])'
    local result=$(curl -s "http://prometheus:9090/api/v1/query?query=${query}")
    local error_rate=$(echo "$result" | jq '.data.result[0].value[1]')
    if (( $(echo "$error_rate > 0.01" | bc -l) )); then
        return 1
    fi
    return 0
}

最佳实践建议

  1. 渐进式发布:从1% → 5% → 10% → 50% → 100%
  2. 自动回滚:设置错误率阈值自动触发回滚
  3. A/B测试:可以结合请求头/用户ID进行精细化分流
  4. 日志对比:收集金丝雀和稳定版本的日志对比分析
  5. 系统资源监控:监控CPU、内存、网络等指标

这个脚本提供了一个完整的金丝雀发布框架,你可以根据实际业务需求调整配置和监控逻辑。

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