This commit is contained in:
Chu Văn Hải committed 2025-12-05 13:37:13 +07:00
commit 39d06135c3
29 files changed
+6840

No files matched your search

+650
View File
@@ -0,0 +1,650 @@
---
# ============================================================================
# ENHANCED DOCKER DEPLOYMENT WITH AUTO-SCALING SUPPORT
# ============================================================================
# ============================================================================
# SYSTEM VALIDATION AND REQUIREMENTS CHECK
# ============================================================================
- name: Validate deployment environment
debug:
msg:
- "=== AUTO-SCALING DEPLOYMENT VALIDATION ==="
- "Target OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- "Architecture: {{ ansible_architecture }}"
- "Available memory: {{ ansible_memtotal_mb }}MB"
- "Available disk: {{ ansible_mounts[0].size_available / 1024 / 1024 / 1024 | round(2) }}GB"
- "Target replicas: {{ docker_replicas | default(replicas | default(1)) }}"
- "Deployment strategy: {{ docker_deployment_strategy | default(deployment_strategy | default('rolling')) }}"
- name: Check minimum system requirements for auto-scaling
assert:
that:
- ansible_memtotal_mb >= (docker_replicas | default(1) | int * 512)
- (ansible_mounts[0].size_available / 1024 / 1024 / 1024) >= 5
- docker_replicas | default(1) | int <= docker_max_replicas | default(9) | int
fail_msg: |
Insufficient system resources for {{ docker_replicas | default(1) }} replicas.
Need at least {{ docker_replicas | default(1) | int * 512 }}MB RAM and 5GB disk space.
Replicas ({{ docker_replicas | default(1) }}) cannot exceed max_replicas ({{ docker_max_replicas | default(9) }}).
success_msg: "✅ System requirements met for auto-scaling deployment."
# ============================================================================
# PROJECT-SPECIFIC CONFIGURATION LOADING
# ============================================================================
- name: Load project-specific vars if exists
block:
- name: Check if project-specific variables file exists
stat:
path: "projects/{{ app_name }}/vars.yml"
register: project_vars_file
- name: Load project-specific variables
include_vars:
file: "projects/{{ app_name }}/vars.yml"
when: project_vars_file.stat.exists
- name: Check if project-specific variables by environment file exists
stat:
path: "projects/{{ app_name }}/group_vars/{{ deploy_env }}.yml"
register: project_vars_env_file
- name: Load project-specific variables by environment
include_vars:
file: "projects/{{ app_name }}/group_vars/{{ deploy_env }}.yml"
when: project_vars_env_file.stat.exists
delegate_to: localhost
run_once: true
- name: Load port registry
include_vars:
file: "{{ port_registry_file | default('group_vars/port_registry.yml') }}"
name: port_registry
delegate_to: localhost
run_once: true
when: port_registry_file is defined
# ============================================================================
# DEPLOYMENT CONFIGURATION SETUP
# ============================================================================
- name: Set deployment environment tag
set_fact:
deploy_env_tag: "{{ deploy_env }}{{ env_tag | default('') }}"
- name: Set auto-scaling configuration
set_fact:
# Auto-scaling parameters
final_replicas: "{{ docker_replicas | default(replicas | default(1)) }}"
final_max_replicas: "{{ docker_max_replicas | default(max_replicas | default(9)) }}"
final_deployment_strategy: "{{ docker_deployment_strategy | default(deployment_strategy | default('rolling')) }}"
final_health_check_timeout: "{{ docker_health_check_timeout | default(health_check_timeout | default(30)) }}"
final_deployment_timeout: "{{ docker_deployment_timeout | default(deployment_timeout | default(300)) }}"
# Port configuration
final_app_port: "{{ port_registry.projects[app_name][deploy_env_tag] | default(3000) }}"
final_container_port: "{{ container_port | default(3000) }}"
# Resource configuration
final_memory_limit: "{{ container_memory_limit | default(resource_limits[deploy_env].memory | default('1g')) }}"
final_cpu_limit: "{{ container_cpu_limit | default(resource_limits[deploy_env].cpu | default('1.0')) }}"
# Network configuration
final_docker_network: "{{ docker_network_name | default(app_name + '-' + deploy_env + '-network') }}"
final_restart_policy: "{{ container_restart_policy | default('unless-stopped') }}"
- name: Display auto-scaling deployment configuration
debug:
msg:
- "🚀 AUTO-SCALING DEPLOYMENT CONFIGURATION"
- "========================================"
- "Application: {{ app_name }}"
- "Environment: {{ deploy_env_tag }}"
- "Framework: {{ framework | default('unknown') }}"
- "Docker Image: {{ docker_image_name }}:{{ docker_image_tag }}"
- "Target Replicas: {{ final_replicas }}"
- "Max Replicas: {{ final_max_replicas }}"
- "Deployment Strategy: {{ final_deployment_strategy }}"
- "Base Port: {{ final_app_port }} (Pattern: {{ final_app_port }}{{ replica_number | default(1) }})"
- "Container Port: {{ final_container_port }}"
- "Memory Limit: {{ final_memory_limit }}"
- "CPU Limit: {{ final_cpu_limit }}"
- "Health Check Timeout: {{ final_health_check_timeout }}s"
- "Deployment Timeout: {{ final_deployment_timeout }}s"
# ============================================================================
# PRE-DEPLOYMENT VALIDATION
# ============================================================================
- name: Validate auto-scaling deployment parameters
assert:
that:
- app_name is defined and app_name != ""
- deploy_env in ['development', 'staging', 'production']
- docker_image_tag is defined and docker_image_tag != ""
- final_replicas | int >= 1
- final_replicas | int <= final_max_replicas | int
- final_deployment_strategy in ['rolling', 'blue-green', 'recreate']
- docker_registry is defined
fail_msg: "Missing or invalid auto-scaling deployment parameters"
success_msg: "✅ Auto-scaling deployment parameters validated"
tags: [validate]
# ============================================================================
# DIRECTORY AND ENVIRONMENT SETUP
# ============================================================================
- name: Create deployment directories
file:
path: "{{ item }}"
state: directory
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0755'
loop:
- "/home/{{ server_user }}/scripts"
- "/home/{{ server_user }}/ci"
- "/home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}"
- "/home/{{ server_user }}/ci/data/{{ app_name }}/{{ deploy_env }}"
- "/home/{{ server_user }}/ci/logs/{{ app_name }}/{{ deploy_env }}"
- "/home/{{ server_user }}/ci/backups/{{ app_name }}/{{ deploy_env }}"
- "/home/{{ server_user }}/ci/deployments/{{ app_name }}/{{ deploy_env }}"
- name: Create environment file from vault if secrets_dict is defined
copy:
dest: "/home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}/.env"
content: |
{% for key, value in secrets_dict.items() %}
{{ key }}={{ value }}
{% endfor %}
when: secrets_dict is defined
- name: Load project-specific env if exists
block:
- name: Check if project-specific .env template exists by environment
stat:
path: "projects/{{ app_name }}/templates/.env.{{ deploy_env }}.j2"
register: custom_env_template_by_env
delegate_to: localhost
run_once: true
- name: Create environment file from project template by environment
template:
src: "projects/{{ app_name }}/templates/.env.{{ deploy_env }}.j2"
dest: "/home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}/.env"
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0600'
backup: yes
when: env_file_enabled | default(true) and custom_env_template_by_env.stat.exists
- name: Check if project-specific .env template exists
stat:
path: "projects/{{ app_name }}/templates/.env.j2"
register: custom_env_template
delegate_to: localhost
run_once: true
- name: Create environment file from project template
template:
src: "projects/{{ app_name }}/templates/.env.j2"
dest: "/home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}/.env"
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0600'
backup: yes
when: env_file_enabled | default(true) and custom_env_template.stat.exists and not custom_env_template_by_env.stat.exists
- name: Create environment file with default template
template:
src: .env.j2
dest: "/home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}/.env"
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0600'
backup: yes
when: not custom_env_template_by_env.stat.exists and not custom_env_template.stat.exists
vars:
# Pass auto-scaling configuration to template
auto_scaling_config:
replicas: "{{ final_replicas }}"
max_replicas: "{{ final_max_replicas }}"
deployment_strategy: "{{ final_deployment_strategy }}"
base_port: "{{ final_app_port }}"
container_port: "{{ final_container_port }}"
tags: [environment]
when: secrets_dict is not defined
# ============================================================================
# DOCKER ENVIRONMENT SETUP
# ============================================================================
- name: Setup Docker environment for auto-scaling
block:
- name: Check if Docker is installed
shell: |
command -v docker
register: docker_check
failed_when: false
changed_when: false
- name: Install Docker (Debian/Ubuntu)
apt:
name: docker.io
state: present
update_cache: yes
become: yes
when:
- docker_check.rc != 0
- ansible_os_family == "Debian"
- name: Install Docker (CentOS/RHEL)
yum:
name: docker
state: present
become: yes
when:
- docker_check.rc != 0
- ansible_os_family == "RedHat"
# Kiểm tra xem Python đã có Docker SDK chưa
- name: Check if Docker SDK for Python is installed
command: python3 -c "import docker"
register: docker_sdk_check
failed_when: false
changed_when: false
# Cài Docker SDK bằng pip nếu chưa có
- name: Install Docker SDK for Python via apt (nếu không muốn dùng pip)
apt:
name: python3-docker
state: present
become: yes
when:
- ansible_os_family == "Debian"
- docker_sdk_check.rc != 0
- name: Ensure Docker service is started and enabled
service:
name: docker
state: started
enabled: true
# - name: Create Docker network for auto-scaling deployment
# docker_network:
# name: "{{ final_docker_network }}"
# state: present
# driver: bridge
# ipam_config:
# - subnet: "172.{{ (final_app_port | string)[-2:] | int + 20 }}.0.0/16"
- name: Gather Docker networks on host
shell: docker network inspect $(docker network ls -q) 2>/dev/null || echo '[]'
register: docker_networks_raw
changed_when: false
failed_when: false
- name: Parse Docker networks JSON
set_fact:
docker_networks_info: "{{ docker_networks_raw.stdout | from_json }}"
- name: Extract existing subnets (as strings) from docker networks
set_fact:
existing_subnets: >-
{{
docker_networks_info | default([])
| selectattr('IPAM', 'defined')
| selectattr('IPAM.Config', 'defined')
| map(attribute='IPAM.Config')
| select
| map('first')
| selectattr('Subnet', 'defined')
| map(attribute='Subnet')
| list
}}
- name: Debug existing subnets
debug:
var: existing_subnets
- name: Get used second-octets from existing subnets (172.X.0.0/16 only)
set_fact:
used_seconds: >-
{%- set result = [] -%}
{%- for subnet in existing_subnets -%}
{%- if subnet.startswith('172.') -%}
{%- set parts = subnet.split('.') -%}
{%- if parts | length >= 2 -%}
{%- set _ = result.append(parts[1] | int) -%}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
{{ result }}
- name: Debug used second octets
debug:
var: used_seconds
- name: Compute start octet from final_app_port (bounded 20..250)
set_fact:
start_octet: >-
{{
(((final_app_port | string)[-2:] | int) + 20)
if (((final_app_port | string)[-2:] | int) + 20) <= 250
else 250
}}
- name: Build candidate second-octet list (start -> 250 then 20 -> start-1)
set_fact:
candidate_list: >-
{{
(range(start_octet | int, 251) | list) + (range(20, start_octet | int) | list)
}}
- name: Debug candidate list
debug:
msg: "Candidates: {{ candidate_list }}, Used: {{ used_seconds }}"
- name: Select first unused second-octet
set_fact:
chosen_second: >-
{{
candidate_list
| map('int')
| reject('in', used_seconds)
| list
| first
}}
- name: Fail if couldn't find free subnet octet
fail:
msg: "No available 172.X.0.0/16 subnet found in candidate space (20..250). Existing used: {{ used_seconds }}"
when: chosen_second is not defined
- name: Build chosen subnet
set_fact:
chosen_subnet: "172.{{ chosen_second }}.0.0/16"
- name: Debug chosen subnet
debug:
msg: "Creating Docker network '{{ final_docker_network }}' with subnet {{ chosen_subnet }}"
- name: Create Docker network for auto-scaling deployment
community.docker.docker_network:
name: "{{ final_docker_network }}"
state: present
driver: bridge
ipam_config:
- subnet: "{{ chosen_subnet }}"
become: true
- name: Pull Docker image for auto-scaling deployment
docker_image:
name: "{{ docker_image_full | default(docker_image_name + ':' + docker_image_tag) }}"
source: pull
force_source: true
register: docker_pull_result
tags: [docker]
# ============================================================================
# ENHANCED DOCKER MANAGEMENT SCRIPT GENERATION
# ============================================================================
- name: Generate enhanced Docker management script with auto-scaling
template:
src: manage-docker.sh.j2
dest: "/home/{{ server_user }}/scripts/manage-{{ service_name }}.sh"
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0755'
backup: yes
vars:
# Container configuration
server_user: "{{ server_user }}"
container_name: "{{ service_name }}"
framework: "{{ framework }}"
# Auto-scaling configuration
default_replicas: "{{ final_replicas }}"
max_replicas: "{{ final_max_replicas }}"
health_check_timeout: "{{ final_health_check_timeout }}"
deployment_timeout: "{{ final_deployment_timeout }}"
# Port configuration
app_port: "{{ final_app_port }}"
container_port: "{{ final_container_port }}"
# Resource configuration
container_restart_policy: "{{ final_restart_policy }}"
container_memory_limit: "{{ final_memory_limit }}"
container_cpu_limit: "{{ final_cpu_limit }}"
# Network configuration
docker_network_name: "{{ final_docker_network }}"
# ============================================================================
# AUTO-SCALING DEPLOYMENT EXECUTION
# ============================================================================
- name: Check current deployment state
shell: |
if [ -f "/home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh" ]; then
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh status 2>/dev/null || echo "no-containers"
else
echo "no-script"
fi
become_user: "{{ server_user }}"
register: current_deployment_state
changed_when: false
- name: Display current deployment state
debug:
msg: "{{ current_deployment_state.stdout_lines }}"
when: current_deployment_state.stdout_lines is defined
- name: Ensure Docker is installed and user has permissions
block:
- name: Add user to docker group
user:
name: "{{ server_user }}"
groups: docker
append: yes
become: yes
- name: Reset SSH connection for group changes
meta: reset_connection
- name: Test Docker access
command: docker version
register: docker_test
failed_when: docker_test.rc != 0
- name: Execute smart deployment with auto-scaling
shell: |
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh deploy {{ replicas | default(1) }} {{ docker_image_tag }} rolling
async: 600
poll: 10 # Poll every 10 seconds instead of 0
register: deployment_result
become_user: "{{ server_user }}"
- name: Check deployment result
fail:
msg: "Deployment failed: {{ deployment_result.stderr | default('Unknown error') }}"
when:
- deployment_result.rc is defined
- deployment_result.rc != 0
# ============================================================================
# POST-DEPLOYMENT VERIFICATION
# ============================================================================
- name: Wait for auto-scaling deployment to settle
pause:
seconds: 5
tags: [health]
- name: Verify auto-scaling deployment completion
shell: |
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh health
become_user: "{{ server_user }}"
register: auto_scaling_health_check
retries: 5
delay: 15
until: auto_scaling_health_check.rc == 0
tags: [health]
- name: Get final auto-scaling deployment status
shell: |
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh status
become_user: "{{ server_user }}"
register: final_auto_scaling_status
changed_when: false
tags: [health]
- name: Extract auto-scaling deployment metrics
set_fact:
auto_scaling_metrics:
running_containers: "{{ final_auto_scaling_status.stdout | regex_search('Running Containers: (\\d+)', '\\1') | first | default('0') }}"
current_version: "{{ final_auto_scaling_status.stdout | regex_search('Current Version: ([^\\n]+)', '\\1') | first | default('unknown') }}"
base_port: "{{ final_auto_scaling_status.stdout | regex_search('Base Port: (\\d+)', '\\1') | first | default('0') }}"
# - name: Validate auto-scaling deployment metrics
# assert:
# that:
# - auto_scaling_metrics.running_containers | int == final_replicas | int
# - auto_scaling_metrics.current_version == docker_image_tag
# fail_msg: |
# Auto-scaling deployment validation failed:
# Expected: {{ final_replicas }} replicas with version {{ docker_image_tag }}
# Actual: {{ auto_scaling_metrics.running_containers }} replicas with version {{ auto_scaling_metrics.current_version }}
# success_msg: "✅ Auto-scaling deployment validation passed"
# ============================================================================
# LOAD BALANCER INTEGRATION
# ============================================================================
- name: Get active ports from script
shell: |
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh deploy_active_ports
become_user: "{{ server_user }}"
register: active_ports_result
when: env_tag | default('') != '-worker'
- name: Convert ports to clean list
set_fact:
active_ports_list: "{{ active_ports_result.stdout.split() }}"
when: env_tag | default('') != '-worker' and active_ports_result.stdout | length > 0
- name: Generate nginx upstream configuration
template:
src: nginx-upstream.conf.j2
dest: "/etc/nginx/conf.d/{{ app_name }}-{{ deploy_env }}-upstream.conf"
backup: yes
vars:
upstream_name: "{{ app_name | regex_replace('[^A-Za-z0-9_]', '_') }}_{{ deploy_env }}_backend"
base_port: "{{ final_app_port }}"
replica_count: "{{ final_replicas }}"
load_balancing_method: "{{ nginx_config.load_balancing_method | default('least_conn') }}"
max_fails: "{{ nginx_config.max_fails | default(1) }}"
fail_timeout: "{{ nginx_config.fail_timeout | default(10) }}"
active_ports: "{{ active_ports_list }}"
when:
- update_load_balancer | default(true)
- env_tag | default('') != '-worker'
notify:
- test nginx configuration
- reload nginx
- webapplication purge cache
tags: [loadbalancer]
# ============================================================================
# DEPLOYMENT SUCCESS SUMMARY
# ============================================================================
- name: Display auto-scaling deployment success
debug:
msg:
- "🎉 AUTO-SCALING DEPLOYMENT SUCCESS"
- "=================================="
- "✅ Successfully deployed {{ app_name }} ({{ framework | default('unknown') }})"
- "🔢 Replicas: {{ auto_scaling_metrics.running_containers }}/{{ final_replicas }}"
- "🏷️ Version: {{ auto_scaling_metrics.current_version }}"
- "🚀 Strategy: {{ final_deployment_strategy }}"
- "🌐 Base Port: {{ auto_scaling_metrics.base_port }}"
- "🏥 Health Status: All containers healthy"
- "⚖️ Load Balancer: Updated with {{ final_replicas }} targets"
- ""
- "🔧 Management Commands:"
- " Status: ./scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh status"
- " Scale: ./scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh scale <replicas>"
- " Logs: ./scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh logs"
- " Health: ./scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh health"
tags: [info]
# ============================================================================
# CLEANUP AND MAINTENANCE
# ============================================================================
- name: Cleanup old Docker images (keep last 5 versions)
shell: |
# Enhanced cleanup for auto-scaling deployment
sudo /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh clean
become_user: "{{ server_user }}"
ignore_errors: true
when: cleanup_old_images | default(true)
tags: [cleanup]
# ============================================================================
# DEPLOYMENT RECORD AND AUDIT TRAIL
# ============================================================================
- name: Create auto-scaling deployment record
copy:
content: |
# Auto-Scaling Deployment Record
timestamp: "{{ ansible_date_time.iso8601 }}"
application: "{{ app_name }}"
environment: "{{ deploy_env }}{{ env_tag | default('') }}"
version: "{{ docker_image_tag }}"
replicas: {{ final_replicas }}
max_replicas: {{ final_max_replicas }}
strategy: "{{ final_deployment_strategy }}"
base_port: "{{ final_app_port }}"
framework: "{{ framework | default('unknown') }}"
node_version: "{{ node_version | default('unknown') }}"
status: "success"
duration: "{{ auto_scaling_deployment_result.delta | default('unknown') }}"
operator: "{{ ansible_user_id }}"
git_commit: "{{ git_sha | default('unknown') }}"
git_tag: "{{ git_tag | default('unknown') }}"
memory_limit: "{{ final_memory_limit }}"
cpu_limit: "{{ final_cpu_limit }}"
docker_network: "{{ final_docker_network }}"
health_check_timeout: "{{ final_health_check_timeout }}"
deployment_timeout: "{{ final_deployment_timeout }}"
load_balancer_updated: {{ update_load_balancer | default(true) }}
management_script: "/home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh"
dest: "/home/{{ server_user }}/ci/logs/{{ app_name }}/{{ deploy_env }}/deployment-{{ ansible_date_time.epoch }}.yml"
owner: "{{ server_user }}"
group: "{{ server_user }}"
mode: '0644'
tags: [logging]
- name: Register auto-scaling deployment in system log
lineinfile:
path: "/var/log/microservices-deployments.log"
line: "{{ ansible_date_time.iso8601 }} | {{ app_name }} | {{ deploy_env }} | {{ docker_image_tag }} | {{ git_tag }} | AUTO-SCALE-SUCCESS | {{ final_replicas }}@{{ auto_scaling_metrics.base_port }} | {{ framework | default('unknown') }}"
create: true
mode: '0644'
tags: [logging]
- name: Cleanup old backups file
shell: |
ls -1t /home/{{ server_user }}/ci/configs/{{ app_name }}/{{ deploy_env }}/.env.*~ 2>/dev/null | tail -n +4 | xargs -r rm --
ls -1t /home/{{ server_user }}/scripts/manage-{{ service_name }}.sh.*~ 2>/dev/null | tail -n +4 | xargs -r rm --
ls -1t /etc/nginx/conf.d/{{ app_name }}-{{ deploy_env }}-upstream.conf.*~ 2>/dev/null | tail -n +4 | xargs -r rm --
args:
warn: false