266 lines
7.3 KiB
Bash
266 lines
7.3 KiB
Bash
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
set -e
|
|||
|
|
|
|||
|
|
# Colors for output
|
|||
|
|
RED='\033[0;31m'
|
|||
|
|
GREEN='\033[0;32m'
|
|||
|
|
YELLOW='\033[1;33m'
|
|||
|
|
BLUE='\033[0;34m'
|
|||
|
|
NC='\033[0m' # No Color
|
|||
|
|
|
|||
|
|
# Configuration
|
|||
|
|
ENVIRONMENT=${1:-production}
|
|||
|
|
COMPOSE_FILE="docker-compose.production.yml"
|
|||
|
|
BACKUP_DIR="./backups/$(date +%Y%m%d_%H%M%S)"
|
|||
|
|
ROLLBACK_DIR="./rollback-backups"
|
|||
|
|
MAX_BACKUPS=10
|
|||
|
|
|
|||
|
|
# Function to print colored messages
|
|||
|
|
print_info() {
|
|||
|
|
echo -e "${BLUE}ℹ️ $1${NC}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print_success() {
|
|||
|
|
echo -e "${GREEN}✅ $1${NC}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print_warning() {
|
|||
|
|
echo -e "${YELLOW}⚠️ $1${NC}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print_error() {
|
|||
|
|
echo -e "${RED}❌ $1${NC}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Function to check prerequisites
|
|||
|
|
check_prerequisites() {
|
|||
|
|
print_info "Checking prerequisites..."
|
|||
|
|
|
|||
|
|
command -v docker >/dev/null 2>&1 || {
|
|||
|
|
print_error "Docker is required but not installed. Aborting.";
|
|||
|
|
exit 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
command -v docker-compose >/dev/null 2>&1 || {
|
|||
|
|
print_error "Docker Compose is required but not installed. Aborting.";
|
|||
|
|
exit 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ! docker info >/dev/null 2>&1; then
|
|||
|
|
print_error "Docker daemon is not running. Please start Docker and try again.";
|
|||
|
|
exit 1;
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
if [ ! -f "$COMPOSE_FILE" ]; then
|
|||
|
|
print_error "$COMPOSE_FILE not found. Aborting.";
|
|||
|
|
exit 1;
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
print_success "Prerequisites check passed"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Function to create backup
|
|||
|
|
create_backup() {
|
|||
|
|
print_info "Creating backup..."
|
|||
|
|
|
|||
|
|
mkdir -p "${BACKUP_DIR}"
|
|||
|
|
mkdir -p "${ROLLBACK_DIR}"
|
|||
|
|
|
|||
|
|
# Backup database
|
|||
|
|
print_info "Backing up database..."
|
|||
|
|
if docker-compose -f "$COMPOSE_FILE" ps postgres | grep -q "Up"; then
|
|||
|
|
docker-compose -f "$COMPOSE_FILE" exec -T postgres pg_dump -U "${POSTGRES_USER:-veza_user}" "${POSTGRES_DB:-veza_db}" > "${BACKUP_DIR}/database.sql" 2>/dev/null || {
|
|||
|
|
print_warning "Database backup failed (may not be critical if database is empty)"
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
print_warning "PostgreSQL container is not running, skipping database backup"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Backup Redis data
|
|||
|
|
print_info "Backing up Redis data..."
|
|||
|
|
if docker-compose -f "$COMPOSE_FILE" ps redis | grep -q "Up"; then
|
|||
|
|
docker-compose -f "$COMPOSE_FILE" exec -T redis redis-cli --rdb - > "${BACKUP_DIR}/redis.rdb" 2>/dev/null || {
|
|||
|
|
print_warning "Redis backup failed (may not be critical)"
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
print_warning "Redis container is not running, skipping Redis backup"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Save current image tags
|
|||
|
|
print_info "Saving current image tags..."
|
|||
|
|
docker-compose -f "$COMPOSE_FILE" config | grep "image:" > "${BACKUP_DIR}/image-tags.txt" || true
|
|||
|
|
|
|||
|
|
# Copy current docker-compose file
|
|||
|
|
cp "$COMPOSE_FILE" "${BACKUP_DIR}/docker-compose.production.yml"
|
|||
|
|
|
|||
|
|
# Create rollback backup
|
|||
|
|
cp -r "${BACKUP_DIR}" "${ROLLBACK_DIR}/latest"
|
|||
|
|
|
|||
|
|
print_success "Backup created at ${BACKUP_DIR}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Function to cleanup old backups
|
|||
|
|
cleanup_old_backups() {
|
|||
|
|
print_info "Cleaning up old backups (keeping last ${MAX_BACKUPS})..."
|
|||
|
|
|
|||
|
|
if [ -d "./backups" ]; then
|
|||
|
|
cd ./backups
|
|||
|
|
ls -t | tail -n +$((MAX_BACKUPS + 1)) | xargs rm -rf 2>/dev/null || true
|
|||
|
|
cd ..
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
print_success "Old backups cleaned up"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Function to pull latest images
|
|||
|
|
pull_latest_images() {
|
|||
|
|
print_info "Pulling latest images..."
|
|||
|
|
|
|||
|
|
docker-compose -f "$COMPOSE_FILE" pull || {
|
|||
|
|
print_error "Failed to pull images";
|
|||
|
|
exit 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print_success "Images pulled successfully"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Function to deploy services
|
|||
|
|
deploy_services() {
|
|||
|
|
print_info "Deploying services with zero-downtime strategy..."
|
|||
|
|
|
|||
|
|
# Deploy services one by one to minimize downtime
|
|||
|
|
SERVICES=("backend-api" "chat-server" "stream-server" "frontend")
|
|||
|
|
|
|||
|
|
for service in "${SERVICES[@]}"; do
|
|||
|
|
if docker-compose -f "$COMPOSE_FILE" ps "$service" | grep -q "Up"; then
|
|||
|
|
print_info "Updating ${service}..."
|
|||
|
|
docker-compose -f "$COMPOSE_FILE" up -d --no-deps "$service" || {
|
|||
|
|
print_error "Failed to deploy ${service}";
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
sleep 5
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
print_success "Services deployed successfully"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Function to check health
|
|||
|
|
check_health() {
|
|||
|
|
local service=$1
|
|||
|
|
local url=$2
|
|||
|
|
local name=$3
|
|||
|
|
local max_attempts=${4:-30}
|
|||
|
|
local attempt=0
|
|||
|
|
|
|||
|
|
print_info "Checking ${name} health..."
|
|||
|
|
|
|||
|
|
while [ $attempt -lt $max_attempts ]; do
|
|||
|
|
if curl -sf "$url" >/dev/null 2>&1; then
|
|||
|
|
print_success "${name} is healthy"
|
|||
|
|
return 0
|
|||
|
|
fi
|
|||
|
|
attempt=$((attempt + 1))
|
|||
|
|
sleep 2
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
print_error "${name} health check failed after ${max_attempts} attempts"
|
|||
|
|
return 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Function to verify deployment
|
|||
|
|
verify_deployment() {
|
|||
|
|
print_info "Verifying deployment..."
|
|||
|
|
|
|||
|
|
local failed=0
|
|||
|
|
|
|||
|
|
# Check container status
|
|||
|
|
if docker-compose -f "$COMPOSE_FILE" ps | grep -q "unhealthy\|Exited\|Restarting"; then
|
|||
|
|
print_error "Some containers are unhealthy or stopped"
|
|||
|
|
docker-compose -f "$COMPOSE_FILE" ps
|
|||
|
|
failed=1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Check health endpoints
|
|||
|
|
check_health "backend-api" "http://localhost:8080/health" "Backend API" 15 || failed=1
|
|||
|
|
check_health "chat-server" "http://localhost:8081/health" "Chat Server" 15 || failed=1
|
|||
|
|
check_health "stream-server" "http://localhost:8082/health" "Stream Server" 15 || failed=1
|
|||
|
|
check_health "frontend" "http://localhost:80/health" "Frontend" 15 || failed=1
|
|||
|
|
|
|||
|
|
if [ $failed -eq 1 ]; then
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
print_success "Deployment verification passed"
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Function to rollback
|
|||
|
|
rollback() {
|
|||
|
|
print_error "Deployment failed! Initiating rollback..."
|
|||
|
|
|
|||
|
|
if [ ! -d "${ROLLBACK_DIR}/latest" ]; then
|
|||
|
|
print_error "No rollback backup found. Manual intervention required."
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
print_info "Rolling back to previous version..."
|
|||
|
|
|
|||
|
|
# Stop current services
|
|||
|
|
docker-compose -f "$COMPOSE_FILE" down || true
|
|||
|
|
|
|||
|
|
# Restore docker-compose file
|
|||
|
|
if [ -f "${ROLLBACK_DIR}/latest/docker-compose.production.yml" ]; then
|
|||
|
|
cp "${ROLLBACK_DIR}/latest/docker-compose.production.yml" "$COMPOSE_FILE"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Restore database
|
|||
|
|
if [ -f "${ROLLBACK_DIR}/latest/database.sql" ]; then
|
|||
|
|
print_info "Restoring database..."
|
|||
|
|
docker-compose -f "$COMPOSE_FILE" up -d postgres
|
|||
|
|
sleep 10
|
|||
|
|
docker-compose -f "$COMPOSE_FILE" exec -T postgres psql -U "${POSTGRES_USER:-veza_user}" -d "${POSTGRES_DB:-veza_db}" < "${ROLLBACK_DIR}/latest/database.sql" || true
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Start services with previous images
|
|||
|
|
docker-compose -f "$COMPOSE_FILE" up -d
|
|||
|
|
|
|||
|
|
print_warning "Rollback completed. Please verify services manually."
|
|||
|
|
exit 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Main deployment flow
|
|||
|
|
main() {
|
|||
|
|
print_info "Starting production deployment to ${ENVIRONMENT}..."
|
|||
|
|
|
|||
|
|
# Trap errors and rollback
|
|||
|
|
trap rollback ERR
|
|||
|
|
|
|||
|
|
check_prerequisites
|
|||
|
|
create_backup
|
|||
|
|
cleanup_old_backups
|
|||
|
|
pull_latest_images
|
|||
|
|
deploy_services
|
|||
|
|
|
|||
|
|
# Wait for services to start
|
|||
|
|
print_info "Waiting for services to start..."
|
|||
|
|
sleep 30
|
|||
|
|
|
|||
|
|
# Verify deployment
|
|||
|
|
if ! verify_deployment; then
|
|||
|
|
rollback
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Disable trap on success
|
|||
|
|
trap - ERR
|
|||
|
|
|
|||
|
|
print_success "Deployment to ${ENVIRONMENT} completed successfully!"
|
|||
|
|
print_info "Backup location: ${BACKUP_DIR}"
|
|||
|
|
print_info "To rollback, run: ${ROLLBACK_DIR}/latest"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Run main function
|
|||
|
|
main
|
|||
|
|
|