1780 lines
62 KiB
Django/Jinja
1780 lines
62 KiB
Django/Jinja
#!/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-<APP_NAME>-<ENVIRONMENT>.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 <number_of_replicas>"
|
||
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 <replicas>
|
||
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 <number_of_replicas>"
|
||
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 $?
|