update.sh•11.2 kB
#!/bin/bash
################################################################################
# Zero-Downtime Update Script for KYC MCP Server
# This script performs rolling updates with minimal service interruption
################################################################################
set -euo pipefail
# Configuration
APP_DIR="/opt/kyc-mcp-server"
BLUE_COMPOSE="docker-compose.blue.yml"
GREEN_COMPOSE="docker-compose.green.yml"
HEALTH_CHECK_TIMEOUT=60
HEALTH_CHECK_INTERVAL=5
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1"
}
# Error handler
error_exit() {
log_error "$1"
exit 1
}
# Check prerequisites
check_prerequisites() {
log_step "Checking prerequisites..."
if [ ! -f "$APP_DIR/docker-compose.yml" ]; then
error_exit "docker-compose.yml not found in $APP_DIR"
fi
if [ ! -f "$APP_DIR/.env" ]; then
error_exit ".env file not found in $APP_DIR"
fi
log_info "Prerequisites check passed"
}
# Detect current active deployment
detect_active_deployment() {
log_step "Detecting active deployment..."
if docker ps | grep -q "kyc-mcp-server-blue"; then
echo "blue"
elif docker ps | grep -q "kyc-mcp-server-green"; then
echo "green"
else
# Default to blue if no deployment exists
echo "blue"
fi
}
# Get inactive deployment
get_inactive_deployment() {
local active=$1
if [ "$active" = "blue" ]; then
echo "green"
else
echo "blue"
fi
}
# Create blue-green compose files
create_compose_files() {
log_step "Creating blue-green compose files..."
cd "$APP_DIR"
# Create blue deployment compose file
cat > "$BLUE_COMPOSE" <<'EOF'
version: '3.8'
services:
kyc-mcp-server-blue:
build:
context: .
dockerfile: Dockerfile
container_name: kyc-mcp-server-blue
environment:
- KYC_API_BASE_URL=${KYC_API_BASE_URL}
- KYC_API_KEY=${KYC_API_KEY}
- KYC_JWT_SECRET=${KYC_JWT_SECRET}
- KYC_JWT_ALGORITHM=HS256
- KYC_JWT_EXPIRY=3600
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_DB=0
- CACHE_ENABLED=true
- CACHE_DEFAULT_TTL=3600
- RATE_LIMIT_ENABLED=true
- RATE_LIMIT_PER_MINUTE=60
- RATE_LIMIT_PER_HOUR=1000
- LOG_LEVEL=INFO
- ENABLE_METRICS=true
- METRICS_PORT=9090
- MAX_RETRIES=3
- REQUEST_TIMEOUT=30
depends_on:
- redis
networks:
- kyc-network
restart: unless-stopped
stdin_open: true
tty: true
ports:
- "8080:8080"
redis:
image: redis:7-alpine
container_name: kyc-redis
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks:
- kyc-network
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
networks:
kyc-network:
driver: bridge
volumes:
redis-data:
driver: local
EOF
# Create green deployment compose file
cat > "$GREEN_COMPOSE" <<'EOF'
version: '3.8'
services:
kyc-mcp-server-green:
build:
context: .
dockerfile: Dockerfile
container_name: kyc-mcp-server-green
environment:
- KYC_API_BASE_URL=${KYC_API_BASE_URL}
- KYC_API_KEY=${KYC_API_KEY}
- KYC_JWT_SECRET=${KYC_JWT_SECRET}
- KYC_JWT_ALGORITHM=HS256
- KYC_JWT_EXPIRY=3600
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_DB=0
- CACHE_ENABLED=true
- CACHE_DEFAULT_TTL=3600
- RATE_LIMIT_ENABLED=true
- RATE_LIMIT_PER_MINUTE=60
- RATE_LIMIT_PER_HOUR=1000
- LOG_LEVEL=INFO
- ENABLE_METRICS=true
- METRICS_PORT=9091
- MAX_RETRIES=3
- REQUEST_TIMEOUT=30
depends_on:
- redis
networks:
- kyc-network
restart: unless-stopped
stdin_open: true
tty: true
ports:
- "8081:8080"
redis:
image: redis:7-alpine
container_name: kyc-redis
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks:
- kyc-network
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
networks:
kyc-network:
driver: bridge
volumes:
redis-data:
driver: local
EOF
log_info "Blue-green compose files created"
}
# Pull latest code
pull_code() {
log_step "Pulling latest code..."
cd "$APP_DIR"
if [ -d .git ]; then
git stash
git pull origin main || git pull origin master
log_info "Code updated"
else
log_warn "Not a git repository. Skipping code pull."
fi
}
# Build new version
build_new_version() {
local deployment=$1
log_step "Building new version for $deployment deployment..."
cd "$APP_DIR"
if [ "$deployment" = "blue" ]; then
docker-compose -f "$BLUE_COMPOSE" build --no-cache
else
docker-compose -f "$GREEN_COMPOSE" build --no-cache
fi
log_info "New version built"
}
# Start new deployment
start_new_deployment() {
local deployment=$1
log_step "Starting $deployment deployment..."
cd "$APP_DIR"
if [ "$deployment" = "blue" ]; then
docker-compose -f "$BLUE_COMPOSE" up -d
else
docker-compose -f "$GREEN_COMPOSE" up -d
fi
log_info "$deployment deployment started"
}
# Wait for new deployment to be ready
wait_for_deployment() {
local deployment=$1
local container_name="kyc-mcp-server-$deployment"
log_step "Waiting for $deployment deployment to be ready..."
local elapsed=0
while [ $elapsed -lt $HEALTH_CHECK_TIMEOUT ]; do
if docker ps | grep -q "$container_name"; then
# Simple check: container is running
log_info "$deployment deployment is running"
sleep 5 # Give it a bit more time to stabilize
return 0
fi
echo -n "."
sleep $HEALTH_CHECK_INTERVAL
elapsed=$((elapsed + HEALTH_CHECK_INTERVAL))
done
echo ""
log_error "$deployment deployment failed to start within ${HEALTH_CHECK_TIMEOUT}s"
return 1
}
# Health check new deployment
health_check_deployment() {
local deployment=$1
local container_name="kyc-mcp-server-$deployment"
log_step "Performing health check on $deployment deployment..."
# Check if container is running
if ! docker ps | grep -q "$container_name"; then
log_error "$deployment container is not running"
return 1
fi
# Check logs for errors
local logs=$(docker logs --tail 50 "$container_name" 2>&1)
if echo "$logs" | grep -qi "fatal\|critical"; then
log_error "Critical errors found in $deployment logs"
return 1
fi
log_info "$deployment deployment health check passed"
return 0
}
# Switch traffic to new deployment
switch_traffic() {
local new_deployment=$1
log_step "Switching traffic to $new_deployment deployment..."
# In a real scenario, this would update load balancer or reverse proxy
# For now, we'll just update nginx configuration
if [ -f /etc/nginx/sites-available/kyc-mcp.conf ]; then
local new_port
if [ "$new_deployment" = "blue" ]; then
new_port="8080"
else
new_port="8081"
fi
# Update nginx upstream
sudo sed -i "s/server localhost:[0-9]\+/server localhost:$new_port/" \
/etc/nginx/sites-available/kyc-mcp.conf
# Test nginx configuration
sudo nginx -t
# Reload nginx
sudo systemctl reload nginx
log_info "Traffic switched to $new_deployment deployment"
else
log_warn "Nginx configuration not found. Manual traffic switching required."
fi
}
# Stop old deployment
stop_old_deployment() {
local deployment=$1
log_step "Stopping $deployment deployment..."
cd "$APP_DIR"
# Wait a bit before stopping to ensure traffic has switched
log_info "Waiting 30 seconds for connections to drain..."
sleep 30
if [ "$deployment" = "blue" ]; then
docker-compose -f "$BLUE_COMPOSE" down
else
docker-compose -f "$GREEN_COMPOSE" down
fi
log_info "$deployment deployment stopped"
}
# Rollback to old deployment
rollback() {
local old_deployment=$1
log_error "Update failed. Rolling back to $old_deployment deployment..."
# Switch traffic back
switch_traffic "$old_deployment"
log_info "Rollback completed"
}
# Show update summary
show_summary() {
local active_deployment=$1
log_step "Update Summary"
echo "=========================================="
echo "Application: KYC MCP Server"
echo "Active Deployment: $active_deployment"
echo "Update Time: $(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="
# Show running containers
echo ""
echo "Running Containers:"
docker ps --filter "name=kyc" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
echo "=========================================="
log_info "Zero-downtime update completed successfully!"
echo "=========================================="
}
# Main update function
main() {
log_info "Starting zero-downtime update of KYC MCP Server..."
# Check prerequisites
check_prerequisites
# Create blue-green compose files
create_compose_files
# Detect current active deployment
ACTIVE_DEPLOYMENT=$(detect_active_deployment)
NEW_DEPLOYMENT=$(get_inactive_deployment "$ACTIVE_DEPLOYMENT")
log_info "Current active deployment: $ACTIVE_DEPLOYMENT"
log_info "New deployment will be: $NEW_DEPLOYMENT"
# Pull latest code
pull_code
# Build new version
build_new_version "$NEW_DEPLOYMENT"
# Start new deployment
start_new_deployment "$NEW_DEPLOYMENT"
# Wait for new deployment to be ready
if ! wait_for_deployment "$NEW_DEPLOYMENT"; then
docker-compose -f "docker-compose.$NEW_DEPLOYMENT.yml" down
error_exit "New deployment failed to start"
fi
# Health check new deployment
if ! health_check_deployment "$NEW_DEPLOYMENT"; then
docker-compose -f "docker-compose.$NEW_DEPLOYMENT.yml" down
error_exit "New deployment failed health check"
fi
# Switch traffic to new deployment
switch_traffic "$NEW_DEPLOYMENT"
# Stop old deployment
stop_old_deployment "$ACTIVE_DEPLOYMENT"
# Show summary
show_summary "$NEW_DEPLOYMENT"
log_info "Update completed successfully!"
}
# Run main function
main