#!/bin/bash # ============================================================================ # ENHANCED DOCKER MANAGEMENT SCRIPT WITH AUTO-SCALING # Application: {{ app_name }} # Environment: {{ deploy_env }}{{ env_tag | default('') }} # Generated: {{ ansible_date_time.iso8601 }} # ============================================================================ set -eo pipefail # ============================================================================ # CONFIGURATION & ENVIRONMENT DETECTION # ============================================================================ # Extract APP_NAME and ENVIRONMENT from script name SCRIPT_NAME=$(basename "$0") SCRIPT_NAME="${SCRIPT_NAME/-worker/}" if [[ "$SCRIPT_NAME" =~ manage-(.+)-(.+)\.sh ]]; then APP_NAME="${BASH_REMATCH[1]}" ENVIRONMENT="${BASH_REMATCH[2]}" else echo "❌ Error: Could not extract APP_NAME and ENVIRONMENT from script name: $SCRIPT_NAME" echo "Expected format: manage--.sh" exit 1 fi # Default configuration FRAMEWORK={{ framework }} DEFAULT_REPLICAS={{ default_replicas | default(2) }} MAX_REPLICAS={{ max_replicas | default(5) }} HEALTH_CHECK_TIMEOUT={{ health_check_timeout | default(60) }} DEPLOYMENT_TIMEOUT={{ deployment_timeout | default(300) }} DEFAULT_LOG_LINES=100 HOME="/home/{{ server_user }}" # Environment-specific configuration CONFIG_DIR="$HOME/ci/configs/${APP_NAME}/${ENVIRONMENT}" DATA_DIR="$HOME/ci/data/${APP_NAME}/${ENVIRONMENT}" BACKUP_DIR="$HOME/ci/backups/${APP_NAME}/${ENVIRONMENT}" LOG_DIR="$HOME/ci/logs/${APP_NAME}/${ENVIRONMENT}" # Docker configuration DOCKER_REGISTRY="{{ docker_registry | default('registry.gitlab.com') }}" DOCKER_IMAGE="{{ docker_image_name }}" DOCKER_NETWORK="{{ docker_network_name }}" CONTAINER_PREFIX="{{ container_name }}" CURRENT_TAG="{{ docker_image_tag }}" # Port management BASE_PORT={{ app_port }} CONTAINER_PORT={{ container_port }} BINDING_PORT=0 # Resource configuration RESTART_POLICY="{{ container_restart_policy | default('unless-stopped') }}" MEMORY_LIMIT="{{ container_memory_limit | default('') }}" CPU_LIMIT="{{ container_cpu_limit | default('') }}" CONTAINER_MAPS_FILE="/var/lib/container_maps.json" CONTAINER_LOCK_FILE="/var/lib/container_maps.lock" declare -A IS_CONTAINER_MAP_NEW # Khởi tạo associative array declare -A container_map NGINX_CONF_DIR="/etc/nginx/conf.d" NGINX_CONF_FILE="$NGINX_CONF_DIR/$CONTAINER_PREFIX-upstream.conf" # Ensure directories exist mkdir -p "$CONFIG_DIR" "$DATA_DIR" "$BACKUP_DIR" "$LOG_DIR" IS_WORKER=0 if [[ "$CONTAINER_PREFIX" == *worker* ]]; then IS_WORKER=1 fi # ============================================================================ # UTILITY FUNCTIONS # ============================================================================ log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_DIR/manage.log" } log_error() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] ❌ ERROR: $*" | tee -a "$LOG_DIR/manage.log" >&2 } log_success() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] ✅ SUCCESS: $*" | tee -a "$LOG_DIR/manage.log" } log_warning() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] ⚠️ WARNING: $*" | tee -a "$LOG_DIR/manage.log" } log_info() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] ℹ️ INFO: $*" | tee -a "$LOG_DIR/manage.log" } # ============================================================================ # CONTAINER LOG ARCHIVAL FUNCTIONS # ============================================================================ # Archive running container logs to timestamped files archive_running_container_logs() { {% raw %} local container_logs_dir="$LOG_DIR/archives" mkdir -p "$container_logs_dir" local running_containers running_containers=$(get_running_containers) if [[ -z "$running_containers" ]]; then return 0 fi # Get current timestamp for all logs in this batch local batch_timestamp=$(date +%Y%m%d-%H%M%S) echo "$running_containers" | while read -r container; do if [[ -z "$container" ]]; then continue fi # Extract container prefix (everything before the last -) # e.g., gemba-api-1-40001 → gemba-api local container_prefix_name=$(echo "$container" | sed 's/-[0-9]*$//') # Create subdirectory per prefix local prefix_log_dir="$container_logs_dir/$container_prefix_name" mkdir -p "$prefix_log_dir" # Log file: container_name_timestamp.log local log_file="$prefix_log_dir/${container}_${batch_timestamp}.log" # Capture running logs from container docker logs "$container" > "$log_file" 2>&1 CONTAINER_ID_FULL=$(docker inspect --format='{{.Id}}' "$container" 2>/dev/null) CONTAINERLOG_PATH="/var/lib/docker/containers/${CONTAINER_ID_FULL}/${CONTAINER_ID_FULL}-json.log" sudo sh -c "cat /dev/null > $CONTAINERLOG_PATH" echo "[$(date +'%Y-%m-%d %H:%M:%S')] 📝 Container logs saved: $log_file" >> "$LOG_DIR/manage.log" done {% endraw %} } # Rotate container logs (keep only last N logs per container) rotate_container_runtime_logs() { local container_logs_dir="$LOG_DIR/archives" local max_logs_per_container=30 # Keep last 30 log files per container if [[ ! -d "$container_logs_dir" ]]; then return 0 fi # For each container prefix directory for prefix_dir in "$container_logs_dir"/*; do if [[ ! -d "$prefix_dir" ]]; then continue fi # For each container in this prefix directory for container_logs in "$prefix_dir"/*; do if [[ ! -d "$container_logs" ]]; then # Count log files for this container pattern local container_name=$(basename "$container_logs" | sed 's/_[0-9]*\.[0-9]*\.log$//') local log_count=$(ls "$prefix_dir"/${container_name}_*.log 2>/dev/null | wc -l) if [[ $log_count -gt $max_logs_per_container ]]; then local remove_count=$((log_count - max_logs_per_container)) # Remove oldest logs (keep newest) ls -t "$prefix_dir"/${container_name}_*.log 2>/dev/null | tail -n $remove_count | xargs -r rm -f fi fi done # Also cleanup at prefix level local total_logs=$(ls "$prefix_dir"/*.log 2>/dev/null | wc -l) if [[ $total_logs -gt $((max_logs_per_container * 10)) ]]; then # If too many logs, remove oldest ones local remove_count=$((total_logs - max_logs_per_container * 10)) ls -t "$prefix_dir"/*.log 2>/dev/null | tail -n $remove_count | xargs -r rm -f fi done } # Setup cron job to periodically archive container logs (one-time for all apps) setup_container_log_archive_cron() { local cron_file="/etc/cron.d/docker-container-logs-archive" local script_path="$HOME/scripts/archive-all-container-logs.sh" # Check if cron job already exists if [[ -f "$cron_file" ]]; then return 0 fi log_info "Setting up global container log archive cron job (every 15 minutes)" # Create archive script mkdir -p "$HOME/scripts" cat > "$script_path" << SCRIPT_EOF #!/bin/bash # Global container log archival script for all applications for manage_script in $HOME/scripts/manage-*.sh; do if [[ -f "\$manage_script" && -x "\$manage_script" ]]; then "\$manage_script" archive-logs 2>/dev/null || true fi done SCRIPT_EOF chmod 755 "$script_path" # Create cron file (one-time, global for all apps) sudo tee "$cron_file" > /dev/null << CRON_EOF # Global Docker container log archival (runs every 15 minutes) */15 * * * * root "$script_path" >/dev/null 2>&1 CRON_EOF sudo chmod 644 "$cron_file" log_success "Global cron job created: $cron_file" log_success "Archive script created: $script_path" } setup_container_log_archive_cron # ==== Function: Check and install jq ==== check_and_install_jq() { if ! command -v jq &>/dev/null; then echo "[INFO] jq not found. Installing..." if command -v apt-get &>/dev/null; then sudo apt-get update -y && sudo apt-get install -y jq elif command -v yum &>/dev/null; then sudo yum install -y jq elif command -v brew &>/dev/null; then brew install jq else echo "[ERROR] Could not detect package manager to install jq." exit 1 fi echo "[INFO] jq installed successfully." fi } check_and_install_jq show_active_ports() { {% raw %} mapfile -t containers < <(docker ps --format '{{.Names}}' | grep "^${CONTAINER_PREFIX}") # Nếu vẫn rỗng thì thông báo lỗi if [ ${#containers[@]} -eq 0 ]; then echo "❌ No containers found with prefix: $CONTAINER_PREFIX" >&2 return 1 fi local ports=() for c in "${containers[@]}"; do local port=$(docker port "$c" 3000/tcp | awk -F: '{print $2}' | xargs) if [ -n "$port" ]; then ports+=("$port") fi done # Nếu vẫn rỗng sau khi lấy port thì báo lỗi if [ ${#ports[@]} -eq 0 ]; then echo "❌ No ports found for containers: ${containers[*]}" >&2 return 1 fi {% endraw %} # In tất cả port cách nhau bằng dấu cách echo "${ports[@]}" } # Get port for specific replica using base port + replica number pattern get_replica_port() { local base_port=$1 local replica_number=$2 # Create port by concatenating base_port + replica_number local port="${base_port}${replica_number}" echo $port } # Get random port get_random_port() { local port local max_attempts=100 local attempt=0 while [[ $attempt -lt $max_attempts ]]; do port=$(( RANDOM % (65500 - 40000 + 1) + 40000 )) # Random từ 40000-65500 # Check if port is available (both netstat and Docker) if is_port_available "$port"; then echo "$port" return 0 fi attempt=$((attempt + 1)) done # Fallback: find next available port from 40000 log_warning "Random port selection exhausted, finding next available..." get_next_available_port 40000 } # Check if port is available is_port_available() { local port=$1 {% raw %} # Check if port is in use by network stack if netstat -tuln 2>/dev/null | grep -qE ":$port\s"; then return 1 # Port is busy fi # Check if port is reserved by any Docker container (running or stopped) if docker ps -a --format '{{.Ports}}' 2>/dev/null | grep -qE "0\.0\.0\.0:$port->|:::$port->"; then return 1 # Port is reserved by Docker fi # Double check with lsof if available if command -v lsof &>/dev/null; then if lsof -i tcp:"$port" &>/dev/null 2>&1; then return 1 fi fi {% endraw %} return 0 # Port is available } # Get next available port (fallback method) get_next_available_port() { local start_port=$1 local port=$start_port local max_port=$((start_port + 10000)) while [[ $port -lt $max_port ]]; do if is_port_available $port; then echo $port return 0 fi ((port++)) done log_error "No available port found starting from $start_port" return 1 } remove_timestamp() { [[ $1 =~ ^(.*-[0-9]+)-([0-9]+)$ ]] && echo "${BASH_REMATCH[1]}" || echo "$1"; } # Get all containers for this app get_app_containers() { docker ps -a --filter "name=${CONTAINER_PREFIX}" --format "{{ "{{" }}.Names{{ "}}" }}" | sort } # Get running containers only get_running_containers() { docker ps --filter "name=${CONTAINER_PREFIX}" --format "{{ "{{" }}.Names{{ "}}" }}" | sort } # Get container count get_container_count() { docker ps --filter "name=${CONTAINER_PREFIX}" --quiet | wc -l } # Hàm kiểm tra và giải phóng port check_and_free_port() { local port="$1" local container_old=${2:-} # Validate port if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then echo "❌ Port không hợp lệ: $port" fi # Tìm PID process đang giữ port local pids pids=$(lsof -ti tcp:"$port" 2>/dev/null | tr '\n' ' ' | xargs) if [ -n "$pids" ]; then # Kiểm tra container nào publish port này local container_name container_name=$(docker ps --format '{{ "{{" }}.Names{{ "}}" }}' --filter "publish=$port" | head -n 1) if [ -n "$container_name" ]; then container_old="$container_name" else echo "🛑 Kill process PID(s): $pids (đang dùng port $port)" kill -9 $pids 2>/dev/null || true fi fi if [ -n "$container_old" ] && [ "$container_old" != "" ]; then if docker ps -a --format '{{ "{{" }}.Names{{ "}}" }}' | grep -qx "$container_old"; then echo "🛑 Dừng container $container_old (đang dùng port $port)" docker stop "$container_old" >/dev/null 2>&1 || true docker rm "$container_old" >/dev/null 2>&1 || true fi fi } # Health check for a container health_check() { local container_name=$1 local timeout=${HEALTH_CHECK_TIMEOUT:-30} if [ "$FRAMEWORK" = "reactjs" ] || [ "$FRAMEWORK" = "laravel" ] || [ "$FRAMEWORK" = "magento" ] || [ "$FRAMEWORK" = "wordpress" ]; then timeout=180 fi local max_attempts=$((timeout / 5)) [ "$max_attempts" -gt 5 ] && max_attempts=5 local sleep_time=$((timeout / max_attempts)) if [[ "$IS_WORKER" -eq 1 ]]; then return 0 fi log_info "Performing health check for $container_name (timeout: ${timeout}s)" for ((i=1; i<=max_attempts; i++)); do # Lấy mã trạng thái HTTP từ endpoint /health health_status=$(docker exec "$container_name" curl -s -o /dev/null -w "%{http_code}" http://localhost:${CONTAINER_PORT}) # Kiểm tra nếu mã trạng thái là 200 hoặc 404 if [[ "$health_status" -eq 200 || "$health_status" -eq 404 ]]; then log_success "Health check passed for $container_name on /health with status $health_status (attempt $i/$max_attempts)" return 0 fi if [[ $i -lt $max_attempts ]]; then # Cung cấp thêm thông tin về lỗi để dễ debug log_info "Health check attempt $i/$max_attempts failed (statuses: $health_status), retrying in ${sleep_time}s..." sleep $sleep_time fi done log_error "Kiểm tra trạng thái máy chủ ở container $container_name thất bại sau $max_attempts lần thử" #local docker_logs=`docker logs --tail 10 "$container_name"` #echo $docker_logs #log_error $docker_logs return 1 } # Get current image version get_current_version() { # Tìm container đang chạy có prefix local container_name container_name=$(docker ps --filter "name=^${CONTAINER_PREFIX}" --format '{{ "{{" }}.Names{{ "}}" }}' | head -n 1) if [ -n "$container_name" ]; then docker inspect "$container_name" --format '{{ "{{" }}index .Config.Labels "version"{{ "}}" }}' 2>/dev/null || echo "unknown" else echo "none" fi } # Get available versions get_available_versions() { docker images "${DOCKER_IMAGE}" --format "{{ "{{" }}.Tag{{ "}}" }}" | grep -v "latest" | sort -V -r | head -10 } # Backup current configuration backup_config() { local backup_file="$BACKUP_DIR/config-$(date +%Y%m%d-%H%M%S).tar.gz" log_info "Creating configuration backup: $backup_file" if [[ -d "$CONFIG_DIR" ]] && [[ -n "$(ls -A "$CONFIG_DIR" 2>/dev/null)" ]]; then tar -czf "$backup_file" -C "$CONFIG_DIR" . 2>/dev/null log_success "Configuration backed up to: $backup_file" # Keep only last 10 backups ls -t "$BACKUP_DIR"/config-*.tar.gz 2>/dev/null | tail -n +11 | xargs -r rm -f else log_info "No configuration files to backup" fi } # Create Docker network if it doesn't exist ensure_network() { if ! docker network ls | grep -q "$DOCKER_NETWORK"; then log_info "Creating Docker network: $DOCKER_NETWORK" docker network create "$DOCKER_NETWORK" || { log_info "Network creation failed or network already exists" } fi } # Show port mapping information show_port_mapping() { local replicas=${1:-$DEFAULT_REPLICAS} echo "🔌 PORT MAPPING PREVIEW" echo "======================" echo "Base Port: $BASE_PORT" echo "Pattern: ${base_port}${replica_number:-1}" echo "" echo "Replica Mapping:" for ((i=1; i<=replicas; i++)); do local container_name="${CONTAINER_PREFIX}-${i}" local port=$(get_replica_port "$BASE_PORT" "$i") printf " %-25s → localhost:%-6s → container:%-6s\n" "$container_name" "$port" "$CONTAINER_PORT" done echo "" } # Start a single container start_single_container() { local container_name=$1 local image_tag=${2:-$CURRENT_TAG} local port=${3:-""} if [[ "$IS_WORKER" -ne 1 ]]; then # Get port if not provided if [[ -z "$port" ]]; then local replica_number="1" if [[ "$container_name" =~ -([0-9]+)$ ]]; then replica_number="${BASH_REMATCH[1]}" fi # Use base port + replica number pattern (e.g., 3003 + 1 = 30031) port=$(get_replica_port "$BASE_PORT" "$replica_number") fi # Check if calculated port is available if ! is_port_available "$port"; then log_warning "Calculated port $port is busy, finding alternative..." port=$(get_random_port) BINDING_PORT=$port fi fi log_info "Starting container: $container_name with image ${DOCKER_IMAGE}:${image_tag} on port $port" ensure_network # Build docker run command local docker_cmd="docker run -d --ulimit nofile=65535:65535 -e TZ=Asia/Ho_Chi_Minh" docker_cmd+=" --name $container_name" if [[ "$FRAMEWORK" == "nextjs" && "$container_name" != *rolling* ]]; then mkdir -p /var/cache/nginx/ 2>/dev/null #create volume if not exists local docker_volume="${APP_NAME}-${ENVIRONMENT}" #local docker_volume="$container_name" local docker_volume_cmd="docker volume inspect $docker_volume > /dev/null 2>&1 || docker volume create $docker_volume" eval "$docker_volume_cmd" docker_cmd+=" --mount type=volume,source=$docker_volume,target=/app/.next/cache/images" elif [[ "$APP_NAME" == "kamgift-cms" ]]; then mkdir -p /var/cache/nginx/ 2>/dev/null # Create volume for uploads local docker_volume_uploads="${APP_NAME}-${ENVIRONMENT}" local docker_volume_cmd_uploads="docker volume inspect $docker_volume_uploads > /dev/null 2>&1 || docker volume create $docker_volume_uploads" eval "$docker_volume_cmd_uploads" # Create volume for storage local docker_volume_storage="${APP_NAME}-${ENVIRONMENT}-storage" local docker_volume_cmd_storage="docker volume inspect $docker_volume_storage > /dev/null 2>&1 || docker volume create $docker_volume_storage" eval "$docker_volume_cmd_storage" docker_cmd+=" --mount type=volume,source=$docker_volume_uploads,target=/var/www/html/public/cms/backend/uploads" docker_cmd+=" --mount type=volume,source=$docker_volume_storage,target=/var/www/html/storage" elif [[ "$FRAMEWORK" == "laravel" ]]; then mkdir -p /var/cache/nginx/ 2>/dev/null #create volume if not exists local docker_volume="${APP_NAME}-${ENVIRONMENT}" #local docker_volume="$container_name" local docker_volume_cmd="docker volume inspect $docker_volume > /dev/null 2>&1 || docker volume create $docker_volume" eval "$docker_volume_cmd" docker_cmd+=" --mount type=volume,source=$docker_volume,target=/var/www/html/storage" fi docker_cmd+=" --network $DOCKER_NETWORK" docker_cmd+=" --restart $RESTART_POLICY" # Add environment file if exists if [[ -f "$CONFIG_DIR/.env" ]]; then if [[ "$FRAMEWORK" == "laravel" ]]; then docker_cmd+=" --env-file $CONFIG_DIR/.env" fi # docker_cmd+=" --env-file $CONFIG_DIR/.env" log_info "Using environment file: $CONFIG_DIR/.env" else log_warning "No .env file found in $CONFIG_DIR" fi # Add resource limits if [[ -n "$MEMORY_LIMIT" ]]; then docker_cmd+=" --memory $MEMORY_LIMIT" fi if [[ -n "$CPU_LIMIT" ]]; then docker_cmd+=" --cpus $CPU_LIMIT" fi # Add labels docker_cmd+=" --label app=$APP_NAME" docker_cmd+=" --label environment=$ENVIRONMENT" docker_cmd+=" --label version=$image_tag" # Add health check if [[ "$IS_WORKER" -ne 1 ]]; then docker_cmd+=" --health-cmd=\"curl -s -o /dev/null -w '%{http_code}' http://localhost:${CONTAINER_PORT}/ | grep -qE '^(200|404)$' || exit 1\"" docker_cmd+=" --health-interval=10s" docker_cmd+=" --health-timeout=10s" docker_cmd+=" --health-start-period=10s" docker_cmd+=" --health-retries=3" fi local docker_cmd_eval=$docker_cmd if [[ "$IS_WORKER" -ne 1 ]]; then docker_cmd_eval+=" -p ${port}:${CONTAINER_PORT}" fi docker_cmd_eval+=" ${DOCKER_IMAGE}:${image_tag}" #log_info "$docker_cmd_eval" # Stop old container (frees up original port) if [[ "$IS_WORKER" -eq 1 ]]; then container_name_original="${container_name%-*}" if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${container_name_original}$"; then docker stop "$container_name_original" &>/dev/null || true docker rm "$container_name_original" &>/dev/null || true fi fi # Execute command if eval "$docker_cmd_eval"; then log_success "Container started: $container_name on port $port" BINDING_PORT=0 return 0 else log_error "Failed to start container: $container_name" # Check if it's a port conflict error and retry with random port if docker logs "$container_name" 2>&1 | grep -q "address already in use"; then log_warning "Port conflict detected, removing failed container and retrying with random port..." docker rm -f "$container_name" &>/dev/null || true # Retry up to 3 times with random ports local retry_count=0 local max_retries=1 while [[ $retry_count -lt $max_retries ]]; do retry_count=$((retry_count + 1)) local random_port=$(get_random_port) log_info "Retry $retry_count/$max_retries with random port: $random_port" local docker_cmd_eval=$docker_cmd if [[ "$IS_WORKER" -ne 1 ]]; then docker_cmd_eval+=" -p ${random_port}:${CONTAINER_PORT}" fi docker_cmd_eval+=" ${DOCKER_IMAGE}:${image_tag}" if eval "$docker_cmd_eval"; then log_success "Container started on retry with port $random_port: $container_name" BINDING_PORT=$random_port return 0 else log_warning "Retry $retry_count failed, removing container..." docker rm -f "$container_name" &>/dev/null || true fi done log_error "All retry attempts failed for container: $container_name" fi BINDING_PORT=0 return 1 fi } # Smart deployment with auto-scaling deploy_with_auto_scale() { local target_replicas=${1:-$DEFAULT_REPLICAS} local image_tag=${2:-$CURRENT_TAG} local strategy=${3:-"rolling"} local is_restart=${4:-"0"} local restart_container=${5:-} log "" log_info "🚀 Starting smart deployment with auto-scaling" log_info "Target: $target_replicas replicas, Version: $image_tag, Strategy: $strategy" # Get current state local current_count current_count=$(get_container_count) local current_version current_version=$(get_current_version) log_info "Current state: $current_count replicas running version $current_version" if [[ "$is_restart" != "1" ]]; then # Login to registry if token available if [[ -n "{{ gitlab_ci_token | default('') }}" ]]; then log_info "Logging into GitLab Container Registry..." echo "{{ gitlab_ci_token }}" | docker login gitlab.forlife.one:5050 -u gitlab-ci-token --password-stdin >/dev/null 2>&1 fi # Pull new image first log_info "Pulling new image: ${DOCKER_IMAGE}:${image_tag}" if ! docker pull "${DOCKER_IMAGE}:${image_tag}"; then log_error "Failed to pull image: ${DOCKER_IMAGE}:${image_tag}" rm -f "$CONTAINER_LOCK_FILE" return 1 fi fi # Determine deployment strategy if [[ $current_count -eq 0 ]]; then # Fresh deployment log_info "Fresh deployment: starting $target_replicas containers" docker_start "$target_replicas" "$image_tag" elif [[ "$current_version" != "$image_tag" || "$is_restart" == "1" ]]; then # Version update with scaling log_info "Version update: $current_version → $image_tag with scaling to $target_replicas" case "$strategy" in "rolling") deploy_rolling_with_scale "$target_replicas" "$image_tag" "$is_restart" "$restart_container" ;; "blue-green") deploy_blue_green_with_scale "$target_replicas" "$image_tag" ;; "recreate") deploy_recreate_with_scale "$target_replicas" "$image_tag" ;; *) log_error "Unknown deployment strategy: $strategy" rm -f "$CONTAINER_LOCK_FILE" return 1 ;; esac elif [[ $current_count -ne $target_replicas ]]; then # Only scaling needed (same version) log_info "Scaling only: $current_count → $target_replicas replicas (same version)" docker_scale "$target_replicas" else deploy_recreate_with_scale "$target_replicas" "$image_tag" # No changes needed # log_success "No deployment needed: $current_count replicas already running version $image_tag" # return 0 fi # Verify final state local final_count final_count=$(get_container_count) local final_version final_version=$(get_current_version) #if [[ $final_count -eq $target_replicas ]] && [[ "$final_version" == "$image_tag" ]]; then if [[ "$final_version" == "$image_tag" ]]; then log_success "✅ Deployment completed successfully: $final_count replicas running $final_version" if [[ "$is_restart" != "1" ]]; then # Show final status echo "" docker_status fi rm -f "$CONTAINER_LOCK_FILE" return 0 else rm -f "$CONTAINER_LOCK_FILE" log_error "❌ Deployment verification failed: expected $target_replicas/$image_tag, got $final_count/$final_version" return 1 fi } # Rolling deployment with scaling deploy_rolling_with_scale_old() { local target_replicas=$1 local image_tag=$2 local is_restart=${3:-"0"} local restart_container=${4:-} local current_count current_count=$(get_container_count) LOCK_DIR="/tmp/container_restart_locks" && mkdir -p "$LOCK_DIR" log_info "Rolling deployment with scaling: $current_count → $target_replicas replicas" # Phase 1: Update existing containers with zero-downtime local existing_containers existing_containers=$(get_running_containers) if [[ -n "$existing_containers" ]]; then log_info "Phase 1: Rolling update of existing containers" local container_rolling_checked="?" echo "$existing_containers" | while read -r container; do if [ "$is_restart" = "1" ]; then container_rolling_checked="OK" if [ "$container" != "$restart_container" ]; then continue fi fi local timestamp=$(date +%s) # Get replica number from container name local replica_num="1" if [[ "$container" =~ -([0-9]+)$ ]]; then replica_num="${BASH_REMATCH[1]}" fi # Get original port local original_port original_port=$(get_replica_port "$BASE_PORT" "$replica_num") # Find next available port in rolling range (BASE_PORT + 2000 to BASE_PORT + 2999) local rolling_port rolling_port=$(get_next_available_port "$((BASE_PORT + 2000))") if [[ "$IS_WORKER" -ne 1 ]]; then if [[ -z "$rolling_port" ]]; then log_error "Cannot find available rolling port for $container" return 1 fi fi # Start new container with temp name on rolling port local temp_name="${container}-rolling" log_info "🔄 Rolling update: $container → $temp_name (port $rolling_port → $original_port)" if [[ "$container_rolling_checked" == "OK" ]] || start_single_container "$temp_name" "$image_tag" "$rolling_port"; then if [[ "$container_rolling_checked" == "OK" ]] || health_check "$temp_name"; then log_info "✅ New container healthy, switching traffic..." date +%s > "$LOCK_DIR/$container.lock" # Stop old container (frees up original port) if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${container}$"; then docker stop "$container" &>/dev/null || true docker rm "$container" &>/dev/null || true fi if [[ "$container_rolling_checked" != "OK" ]]; then # Stop temp container if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${temp_name}$"; then docker stop "$temp_name" &>/dev/null || true docker rm "$temp_name" &>/dev/null || true fi fi container_rolling_checked="OK" # Start new container with original name and port if start_single_container "$container" "$image_tag" "$original_port"; then if health_check "$container"; then rm -f "$LOCK_DIR/$container.lock" log_success "✅ Rolling update completed: $container" else log_error "Health check failed for final container: $container" return 1 fi else log_error "Failed to start final container: $container" return 1 fi else log_error "Không thể bắt đầu container mới, giữ nguyên container phiên bản cũ !!!" if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${temp_name}$"; then docker stop "$temp_name" &>/dev/null || true docker rm "$temp_name" &>/dev/null || true fi return 1 fi else log_error "Failed to start rolling container: $temp_name" return 1 fi log_info "⏳ Waiting before next container update..." sleep 5 # Brief pause between updates done fi # Phase 2: Scale up if needed if [[ $target_replicas -gt $current_count ]]; then log_info "Phase 2: Scaling up from $current_count to $target_replicas" for ((i=$((current_count + 1)); i<=target_replicas; i++)); do local container_name="${CONTAINER_PREFIX}-${i}" local port port=$(get_replica_port "$BASE_PORT" "$i") if start_single_container "$container_name" "$image_tag" "$port"; then if ! health_check "$container_name"; then log_error "Health check failed for new container: $container_name" return 1 fi log_success "✅ Scaled up: $container_name on port $port" else return 1 fi sleep 2 done fi # Phase 3: Scale down if needed if [[ $target_replicas -lt $current_count ]]; then log_info "Phase 3: Scaling down from $current_count to $target_replicas" local scale_down_num=$((current_count - target_replicas)) mapfile -t containers < <(docker ps --format '{% raw %}{{.Names}}{% endraw %}' | grep "^${CONTAINER_PREFIX}") for container_name in "${containers[@]}"; do if (( scale_down_num <= 0 )); then log_info "No more containers to scale down" break fi oldport=$(docker port $container_name 3000/tcp | awk -F: '{print $2}' | xargs) sed -i "\|server localhost:${oldport}|d" $NGINX_CONF_FILE nginx -t && /etc/init.d/nginx reload scale_down_num=$((scale_down_num - 1)) log_info "Removing container: $container_name" if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${container_name}$"; then docker stop "$container_name" &>/dev/null || true docker rm "$container_name" &>/dev/null || true fi done fi log_success "🎉 Rolling deployment with scaling completed successfully" } deploy_rolling_with_scale() { local target_replicas=$1 local image_tag=$2 local is_restart=${3:-"0"} local restart_container=${4:-} local current_count current_count=$(get_container_count) local current_version current_version=$(get_current_version) LOCK_DIR="/tmp/container_restart_locks" && mkdir -p "$LOCK_DIR" log_info "Rolling deployment with scaling: $current_count → $target_replicas replicas" # Scale down if [[ $target_replicas -lt $current_count ]]; then log_info "Scaling down from $current_count to $target_replicas" local scale_down_num=$((current_count - target_replicas)) mapfile -t containers < <(docker ps --format '{% raw %}{{.Names}}{% endraw %}' | grep "^${CONTAINER_PREFIX}") for container_name in "${containers[@]}"; do if (( scale_down_num <= 0 )); then log_info "No more containers to scale down" break fi oldport=$(docker port $container_name 3000/tcp | awk -F: '{print $2}' | xargs) sed -i "\|server localhost:${oldport}|d" $NGINX_CONF_FILE nginx -t && /etc/init.d/nginx reload scale_down_num=$((scale_down_num - 1)) log_info "Removing container: $container_name" if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${container_name}$"; then docker stop "$container_name" &>/dev/null || true docker rm "$container_name" &>/dev/null || true fi done fi # Update existing containers with zero-downtime local existing_containers existing_containers=$(get_running_containers) local existing_updated_count=0 if [[ -n "$existing_containers" ]]; then log_info "Rolling update of existing containers" local container_rolling_checked="?" echo "$existing_containers" | while read -r container; do existing_updated_count=$((existing_updated_count + 1)) if [[ $existing_updated_count -gt $target_replicas ]]; then if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${container}$"; then docker stop "$container" &>/dev/null || true docker rm "$container" &>/dev/null || true fi continue fi local container_old="$container" container="$(remove_timestamp "$container_old")" if [ "$is_restart" = "1" ]; then container_rolling_checked="OK" if [ "$container_old" != "$restart_container" ]; then continue fi fi # Get old port trước khi tạo container mới local oldport="" # if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${container_old}$"; then # oldport=$(docker port "$container_old" 3000/tcp 2>/dev/null | awk -F: '{print $2}' | xargs) # fi local timestamp=$(date +%s) local replica_num="1" if [[ "$container" =~ -([0-9]+)$ ]]; then replica_num="${BASH_REMATCH[1]}" fi # local original_port # original_port=$(get_replica_port "$BASE_PORT" "$replica_num") # local rolling_port # rolling_port=$(get_next_available_port "$((BASE_PORT + 2000))") local random_port=$(get_random_port) if [[ "$IS_WORKER" -ne 1 ]]; then if [[ -z "$random_port" ]]; then log_error "Cannot find available rolling port for $container" return 1 fi fi local container_new="${container}-${random_port}" date +%s > "$LOCK_DIR/$container.lock" log_info "🔄 Rolling update: $container → $container_new (new port: $random_port)" if start_single_container "$container_new" "$image_tag" "$random_port"; then if [[ "$BINDING_PORT" -ne 0 ]]; then random_port=${BINDING_PORT} fi if [[ "$IS_WORKER" -ne 1 ]]; then if health_check "$container_new"; then oldport=$(docker port $container_old 3000/tcp | awk -F: '{print $2}' | xargs) newport=${random_port} log_info "✅ New container healthy, switching traffic..." # Stop old container (frees up original port) if [[ -n "$oldport" ]]; then sed -i "\|server localhost:${oldport}|d" $NGINX_CONF_FILE fi sed -i "\|server localhost:|a\ server localhost:${newport};" $NGINX_CONF_FILE nginx -t && /etc/init.d/nginx reload # remove old container if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${container_old}$"; then docker stop "$container_old" &>/dev/null || true docker rm "$container_old" &>/dev/null || true fi log_success "✅ Rolling update completed: $container" else log_error "Health check failed for final container: $container" return 1 fi else if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${container_old}$"; then docker stop "$container_old" &>/dev/null || true docker rm "$container_old" &>/dev/null || true fi fi else rm -f "$LOCK_DIR/$container.lock" log_error "Không thể bắt đầu container mới, giữ nguyên container phiên bản cũ !!!" if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${container_new}$"; then docker stop "$container_new" &>/dev/null || true docker rm "$container_new" &>/dev/null || true fi return 1 fi log_info "⏳ Waiting before next container update..." sleep 2 done fi # Scale up if [[ $target_replicas -gt $current_count ]]; then log_info "Scaling up from $current_count to $target_replicas" i=$((current_count + 1)) while [[ $i -le $target_replicas ]]; do local port port=$(get_random_port) local timestamp timestamp=$(date +%s) local container="${CONTAINER_PREFIX}-${i}" local container_name="${container}-${port}" date +%s > "$LOCK_DIR/$container.lock" if start_single_container "$container_name" "$image_tag" "$port"; then if ! health_check "$container_name"; then log_error "Health check failed for new container: $container_name" return 1 fi log_success "✅ Scaled up: $container_name on port $port" else return 1 fi sleep 2 i=$((i + 1)) # tự tăng i done fi log_success "🎉 Rolling deployment with scaling completed successfully" } # Blue-green deployment with scaling deploy_blue_green_with_scale() { local target_replicas=$1 local image_tag=$2 log_info "Blue-green deployment with scaling: $target_replicas replicas" # Create green environment local green_containers=() log_info "Creating green environment with $target_replicas containers" for ((i=1; i<=target_replicas; i++)); do local container_name="${CONTAINER_PREFIX}-green-${i}" local port port=$(get_replica_port "$((BASE_PORT + 1000))" "$i") # Use different port range for green if start_single_container "$container_name" "$image_tag" "$port"; then if health_check "$container_name"; then green_containers+=("$container_name") log_success "✅ Green container ready: $container_name on port $port" else log_error "Health check failed for green container: $container_name" # Cleanup green containers for green_container in "${green_containers[@]}"; do if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${green_container}$"; then docker stop "$green_container" || true docker rm "$green_container" || true fi done return 1 fi else return 1 fi done # Switch traffic (rename containers) log_info "Switching traffic to green environment" # Stop and remove all blue containers local blue_containers blue_containers=$(get_running_containers) if [[ -n "$blue_containers" ]]; then echo "$blue_containers" | while read -r blue_container; do if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${blue_container}$"; then docker stop "$blue_container" || true docker rm "$blue_container" || true fi done fi # Rename green containers to blue for ((i=1; i<=target_replicas; i++)); do local green_name="${CONTAINER_PREFIX}-green-${i}" local blue_name="${CONTAINER_PREFIX}-${i}" local original_port original_port=$(get_replica_port "$BASE_PORT" "$i") # Stop green container and restart with correct port if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${green_name}$"; then docker stop "$green_name" || true docker rm "$green_name" || true fi # Start with final name and port start_single_container "$blue_name" "$image_tag" "$original_port" done log_success "Blue-green deployment with scaling completed" } # Recreate deployment with scaling deploy_recreate_with_scale() { local target_replicas=$1 local image_tag=$2 log_info "Recreate deployment with scaling: $target_replicas replicas" # Stop all existing containers docker_stop # Remove all containers local containers containers=$(get_app_containers) if [[ -n "$containers" ]]; then echo "$containers" | while read -r container; do docker rm "$container" &>/dev/null || true done fi # Start new containers docker_start "$target_replicas" "$image_tag" log_success "Recreate deployment with scaling completed" } function docker_start() { local replicas=${1:-$DEFAULT_REPLICAS} local image_tag=${2:-$CURRENT_TAG} log_info "Starting $replicas containers with image tag: $image_tag" for ((i=1; i<=replicas; i++)); do # local port=$(get_replica_port "$BASE_PORT" "$i") local port=$(get_random_port) local container_name="${CONTAINER_PREFIX}-${i}-${port}" # check_and_free_port $port $container_name if start_single_container "$container_name" "$image_tag" "$port"; then echo "" fi; sleep 5 done log_success "All containers started successfully" } function docker_stop() { local containers containers=$(get_running_containers) if [[ -z "$containers" ]]; then log_info "No running containers to stop" return 0 fi log_info "Stopping containers..." echo "$containers" | while read -r container; do log_info "Stopping container: $container" docker stop "$container" || log_warning "Failed to stop container: $container" done log_success "All containers stopped" } function docker_restart() { local replicas=${1:-$(get_container_count)} local image_tag=${2:-$(get_current_version)} local strategy=${3:-"rolling"} local restart_container=${4:-} if [[ $replicas -eq 0 ]]; then replicas=$DEFAULT_REPLICAS fi if [[ $replicas -gt $DEFAULT_REPLICAS ]]; then replicas=$DEFAULT_REPLICAS fi if [[ "$image_tag" == "unknown" || "$image_tag" == "none" ]]; then image_tag=$CURRENT_TAG log_info "Using current deployment tag: $image_tag" fi log_info "Restarting with $replicas replicas, image tag: $image_tag, strategy: $strategy" # Use smart deployment with auto-scaling if [[ "$restart_container" != "" ]]; then deploy_with_auto_scale "$replicas" "$image_tag" "$strategy" "1" "$restart_container" else deploy_with_auto_scale "$replicas" "$image_tag" "$strategy" "1" fi } function docker_restart_single() { local restart_container=${1:-} local replicas=${2:-$(get_container_count)} local image_tag=${3:-$(get_current_version)} local strategy=${4:-"rolling"} # Use smart deployment with auto-scaling docker_restart "$replicas" "$image_tag" "$strategy" "$restart_container" #docker restart "$restart_container" } function docker_scale() { local target_replicas=$1 if [[ -z "$target_replicas" || ! "$target_replicas" =~ ^[0-9]+$ ]]; then log_error "Usage: scale " return 1 fi if [[ $target_replicas -gt $MAX_REPLICAS ]]; then log_error "Cannot scale beyond maximum replicas: $MAX_REPLICAS" return 1 fi local current_count current_count=$(get_container_count) log_info "Scaling from $current_count to $target_replicas replicas" if [[ $target_replicas -eq $current_count ]]; then log_info "Already at target replica count: $target_replicas" return 0 fi local current_version current_version=$(get_current_version) if [[ "$current_version" == "none" && $target_replicas -gt 0 ]]; then log_error "No containers running, use 'start' command instead" return 1 fi if [[ $target_replicas -gt $current_count ]]; then # Scale up log_info "Scaling up from $current_count to $target_replicas" for ((i=$((current_count + 1)); i<=target_replicas; i++)); do local port=$(get_random_port) local timestamp=$(date +%s) local container="${CONTAINER_PREFIX}-${i}" local container_name="${container}-${port}" date +%s > "$LOCK_DIR/$container.lock" if start_single_container "$container_name" "$current_version" "$port"; then if ! health_check "$container_name"; then log_error "Health check failed for new container: $container_name" return 1 fi log_success "Container $container_name started on port $port" else return 1 fi done else # Scale down log_info "Scaling down from $current_count to $target_replicas" local scale_down_num=$((current_count - target_replicas)) mapfile -t containers < <(docker ps --format '{% raw %}{{.Names}}{% endraw %}' | grep "^${CONTAINER_PREFIX}") for container_name in "${containers[@]}"; do if (( scale_down_num <= 0 )); then log_info "No more containers to scale down" break fi oldport=$(docker port $container_name 3000/tcp | awk -F: '{print $2}' | xargs) sed -i "\|server localhost:${oldport}|d" $NGINX_CONF_FILE nginx -t && /etc/init.d/nginx reload scale_down_num=$((scale_down_num - 1)) log_info "Removing container: $container_name" if docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -q "^${container_name}$"; then docker stop "$container_name" &>/dev/null || true docker rm "$container_name" &>/dev/null || true fi done fi log_success "Scaling completed: $(get_container_count) replicas running" } function docker_rollback() { local target_version=${1:-""} local replicas=${2:-$(get_container_count)} if [[ $replicas -eq 0 ]]; then replicas=$DEFAULT_REPLICAS fi log_info "Rolling back application (replicas: $replicas)" if [[ -n "$target_version" ]]; then log_info "Rolling back to specific version: $target_version" # Check if version exists if ! docker images "${DOCKER_IMAGE}:${target_version}" --quiet | head -1 &>/dev/null; then log_info "Pulling rollback image: ${DOCKER_IMAGE}:${target_version}" if ! docker pull "${DOCKER_IMAGE}:${target_version}"; then log_error "Failed to pull rollback image: ${DOCKER_IMAGE}:${target_version}" return 1 fi fi else # Find previous version local available_versions available_versions=$(get_available_versions) if [[ -z "$available_versions" ]]; then log_error "No previous versions available for rollback" return 1 fi # Get the second version (first is current) target_version=$(echo "$available_versions" | sed -n '2p') if [[ -z "$target_version" ]]; then target_version=$(echo "$available_versions" | head -1) log_info "Only one version available, using: $target_version" else log_info "Rolling back to previous version: $target_version" fi fi # Create backup before rollback backup_config # Perform rollback deploy_with_auto_scale "$replicas" "$target_version" "rolling" if [[ $? -eq 0 ]]; then log_success "Rollback completed to version: $target_version" else log_error "Rollback failed" return 1 fi } function docker_status() { local containers containers=$(get_app_containers) echo "📊 APPLICATION STATUS" echo "=====================" echo "App: $APP_NAME" echo "Environment: $ENVIRONMENT" echo "Current Version: $(get_current_version)" echo "Running Containers: $(get_container_count)" echo "Base Port: $BASE_PORT (Pattern: ${BASE_PORT}${replica_number:-1})" echo "Container Port: $CONTAINER_PORT" echo "" if [[ -z "$containers" ]]; then echo "❌ No containers found" echo "" echo "📋 PORT MAPPING PREVIEW (if containers were running):" show_port_mapping 3 return 0 fi echo "📋 CONTAINER DETAILS" echo "====================" printf "%-25s %-15s %-20s %-10s %-15s\n" "NAME" "STATUS" "VERSION" "PORT" "HEALTH" echo "$(printf '%.0s-' {1..95})" echo "$containers" | while read -r container; do local status status=$(docker inspect "$container" --format '{{ "{{" }}.State.Status{{ "}}" }}' 2>/dev/null || echo "unknown") local version version=$(docker inspect "$container" --format '{{ "{{" }}index .Config.Labels "version"{{ "}}" }}' 2>/dev/null || echo "unknown") local port port=$(docker port "$container" 2>/dev/null | grep "${CONTAINER_PORT}/tcp" | cut -d':' -f2 || echo "none") local health="unknown" if [[ "$status" == "running" ]]; then if health_check "$container" 5; then health="healthy" else health="unhealthy" fi fi printf "%-25s %-15s %-20s %-10s %-15s\n" "$container" "$status" "$version" "$port" "$health" done echo "" echo "📡 LOAD BALANCER TARGETS" echo "========================" echo "$containers" | while read -r container; do local port port=$(docker port "$container" 2>/dev/null | grep "${CONTAINER_PORT}/tcp" | cut -d':' -f2 || echo "none") if [[ "$port" != "none" ]]; then echo " server localhost:$port;" fi done } function docker_logs() { local lines=${1:-$DEFAULT_LOG_LINES} local follow=${2:-false} local containers containers=$(get_running_containers) if [[ -z "$containers" ]]; then log_error "No running containers found" return 1 fi log_info "Viewing logs (last $lines lines, follow: $follow)" if [[ "$follow" == "true" ]]; then # Follow logs from all containers echo "$containers" | xargs -I {} sh -c 'echo "=== {} ===" && docker logs -f --tail '"$lines"' {} &' wait else # Show logs from all containers echo "$containers" | while read -r container; do echo "=== $container ===" docker logs --tail "$lines" "$container" 2>&1 echo "" done fi } function docker_health() { local containers containers=$(get_running_containers) if [[ -z "$containers" ]]; then log_error "No running containers found" return 1 fi local all_healthy=true echo "$containers" | while read -r container; do if health_check "$container"; then log_success "Health check passed: $container" else log_error "Health check failed: $container" all_healthy=false fi done if [[ "$all_healthy" == "true" ]]; then log_success "All containers are healthy" return 0 else log_error "Some containers are unhealthy" return 1 fi } function docker_clean() { echo "Cleaning up Docker images and containers..." # Remove stopped containers docker container prune -f # Remove unused images docker image prune -a -f # Xóa volume không còn dùng docker volume prune -f # Xóa network không còn dùng docker network prune -f # delete unused all #docker system prune -a --volumes -f # Remove old versions (keep last 5) local old_images old_images=$(docker images "${DOCKER_IMAGE}" --format "{{ "{{" }}.Tag{{ "}}" }}" | grep -v "latest" | sort -V -r | tail -n +6) if [[ -n "$old_images" ]]; then echo "$old_images" | while read -r tag; do log_info "Removing old image: ${DOCKER_IMAGE}:${tag}" docker rmi "${DOCKER_IMAGE}:${tag}" &>/dev/null || true done fi log_success "Cleanup completed" } function docker_info() { echo "🔧 DOCKER CONTAINER INFORMATION" echo "===============================" echo "- Container Base Name: $CONTAINER_PREFIX" echo "- Image: $DOCKER_IMAGE" echo "- Current Tag: $CURRENT_TAG" echo "- Host Port Base: $BASE_PORT" echo "- Port Pattern: ${BASE_PORT}${replica_number:-1}" echo "- Container Port: $CONTAINER_PORT" echo "- Memory Limit: ${MEMORY_LIMIT:-unlimited}" echo "- CPU Limit: ${CPU_LIMIT:-unlimited}" echo "- Environment: $ENVIRONMENT" echo "- Docker Network: $DOCKER_NETWORK" echo "- Default Replicas: $DEFAULT_REPLICAS" echo "- Max Replicas: $MAX_REPLICAS" echo "- Docker Version: $(docker --version)" echo "" # Show port mapping preview show_port_mapping $DEFAULT_REPLICAS echo "📋 AVAILABLE VERSIONS" echo "====================" local versions versions=$(get_available_versions) if [[ -n "$versions" ]]; then echo "$versions" else echo "No versions found" fi echo "" echo "🌐 NETWORK INFORMATION" echo "=====================" if docker network ls | grep -q "$DOCKER_NETWORK"; then docker network inspect "$DOCKER_NETWORK" --format '{{ "{{" }}.Name{{ "}}" }}: {{ "{{" }}.Driver{{ "}}" }} ({{ "{{" }}.Scope{{ "}}" }})' else echo "Network $DOCKER_NETWORK not found" fi echo "" docker_status } function show_help() { cat << EOF 🚀 Enhanced Docker Management Script with Auto-Scaling ====================================================== App: $APP_NAME Environment: $ENVIRONMENT Port Pattern: ${BASE_PORT}${replica_number:-1} USAGE: $0 [COMMAND] [OPTIONS] DEPLOYMENT COMMANDS: deploy [replicas] [version] [strategy] Smart deployment with auto-scaling (recommended for CI/CD) - replicas: Target number of replicas (default: $DEFAULT_REPLICAS) - version: Docker image version (default: $CURRENT_TAG) - strategy: rolling|blue-green|recreate (default: rolling) start [replicas] [version] [strategy] Alias for deploy command restart [replicas] [version] [strategy] Restart with smart deployment and auto-scaling restart_single [container_name] Restart with smart deployment and auto-scaling MANUAL OPERATIONS: stop Stop all running containers scale Manual scale to specified number of replicas (max: $MAX_REPLICAS) rollback [version] [replicas] Rollback to previous or specific version MONITORING COMMANDS: status Show application status and health ports [replicas] Show port mapping preview for specified replicas logs [lines] [follow] View container logs archive-logs Archive running container logs to timestamped files organized by prefix (Usually called automatically by cron job every 15 minutes) health Perform health check on all containers clean Remove old images and containers info Show detailed container and environment information help Show this help message PORT ALLOCATION: Base Port: $BASE_PORT Pattern: ${base_port}${replica_number:-1} Examples: - 1 replica: ${BASE_PORT}1 ($(get_replica_port "$BASE_PORT" "1")) - 2 replicas: ${BASE_PORT}1, ${BASE_PORT}2 ($(get_replica_port "$BASE_PORT" "1"), $(get_replica_port "$BASE_PORT" "2")) - 3 replicas: ${BASE_PORT}1, ${BASE_PORT}2, ${BASE_PORT}3 ($(get_replica_port "$BASE_PORT" "1"), $(get_replica_port "$BASE_PORT" "2"), $(get_replica_port "$BASE_PORT" "3")) EXAMPLES: $0 deploy 5 v1.2.0 rolling $0 scale 8 $0 ports 4 $0 rollback v1.1.0 2 $0 logs 100 true EOF } # ============================================================================ # MAIN SCRIPT EXECUTION # ============================================================================ function docker_manage() { case "$1" in deploy|start) local replicas=${2:-$DEFAULT_REPLICAS} local image_tag=${3:-$CURRENT_TAG} local strategy=${4:-"rolling"} date +%s > "$CONTAINER_LOCK_FILE" deploy_with_auto_scale "$replicas" "$image_tag" "$strategy" ;; stop) docker_stop ;; restart) local replicas=${2:-$(get_container_count)} local image_tag=${3:-$(get_current_version)} local strategy=${4:-"rolling"} docker_restart "$replicas" "$image_tag" "$strategy" ;; restart_single) local restart_container=${2:-} docker_restart_single "$restart_container" ;; scale) if [[ -z "$2" ]]; then log_error "Usage: $0 scale " exit 1 fi docker_scale "$2" ;; rollback) docker_rollback "$2" "$3" ;; status) docker_status ;; ports) local replicas=${2:-$DEFAULT_REPLICAS} if [[ ! "$replicas" =~ ^[0-9]+$ ]] || [[ $replicas -lt 1 ]] || [[ $replicas -gt $MAX_REPLICAS ]]; then log_error "Invalid replica count. Must be between 1 and $MAX_REPLICAS" exit 1 fi show_port_mapping "$replicas" ;; deploy_active_ports) show_active_ports ;; logs) docker_logs "$2" "$3" ;; health) docker_health ;; shell) local container_num=${2:-1} local container_name="${CONTAINER_PREFIX}-${container_num}" echo "Entering shell of $container_name container..." if docker ps --filter "name=$container_name" --quiet | head -1 &>/dev/null; then docker exec -it "$container_name" /bin/sh else log_error "Container $container_name is not running" exit 1 fi ;; clean) docker_clean ;; info) docker_info ;; archive-logs) archive_running_container_logs rotate_container_runtime_logs ;; help|--help|-h) show_help ;; *) echo "❌ Unknown command: $1" echo "" show_help exit 1 ;; esac } # Execute main function with all arguments docker_manage "$@" exit $?