本文目录导读:

下面我来介绍如何使用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% → 5% → 10% → 50% → 100%
- 自动回滚:设置错误率阈值自动触发回滚
- A/B测试:可以结合请求头/用户ID进行精细化分流
- 日志对比:收集金丝雀和稳定版本的日志对比分析
- 系统资源监控:监控CPU、内存、网络等指标
这个脚本提供了一个完整的金丝雀发布框架,你可以根据实际业务需求调整配置和监控逻辑。