D4.3 Deployment Workflows 🔄
Τα 4 βασικά Workflows
┌─────────────────────────────────────────────────────────┐
│ 1. Fresh Deploy → πρώτη εγκατάσταση │
│ 2. Update → νέα έκδοση εφαρμογής │
│ 3. Config Update → αλλαγή ρυθμίσεων │
│ 4. Rollback → επιστροφή σε παλιά έκδοση │
└─────────────────────────────────────────────────────────┘
Workflow 1: Fresh Deploy
# tasks/deploy_fresh.yml
---
# ============================================================
# Fresh Deployment — Πρώτη εγκατάσταση
# ============================================================
- name: Verify Docker is running
ansible.builtin.service_facts:
- name: Assert Docker running
ansible.builtin.assert:
that:
- ansible_facts.services['docker.service'].state == 'running'
fail_msg: "❌ Docker is not running!"
- name: Create directory structure
ansible.builtin.file:
path: "{{ item }}"
state: directory
mode: '0755'
owner: root
group: docker
loop:
- "{{ app_base_dir }}"
- "{{ app_config_dir }}"
- "{{ app_config_dir }}/nginx"
- "{{ app_logs_dir }}"
- name: Render compose files
ansible.builtin.template:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode | default('0644') }}"
loop:
- src: docker-compose.yml.j2
dest: "{{ app_base_dir }}/docker-compose.yml"
- src: nginx_compose.conf.j2
dest: "{{ app_config_dir }}/nginx/nginx.conf"
no_log: false
- name: Render .env file
ansible.builtin.template:
src: app.env.j2
dest: "{{ app_config_dir }}/.env"
mode: '0600'
no_log: true
- name: Pull all images
community.docker.docker_compose_v2:
project_src: "{{ app_base_dir }}"
project_name: "{{ app_name }}"
pull: always
state: present
register: deploy_result
no_log: true
- name: Wait for services healthy
ansible.builtin.command:
cmd: >
docker compose
-f {{ app_base_dir }}/docker-compose.yml
ps --format json
register: services_status
changed_when: false
retries: 15
delay: 10
until: >
services_status.stdout | from_json |
selectattr('Health', 'equalto', 'unhealthy') |
list | length == 0
- name: Fresh deploy complete
ansible.builtin.debug:
msg: "✅ Fresh deployment complete!"
Workflow 2: Update (Rolling Update)
# tasks/deploy_update.yml
---
# ============================================================
# Update Deployment — Νέα έκδοση
# ============================================================
# ── Pre-update checks ─────────────────────────
- name: Get current version
ansible.builtin.command:
cmd: >
docker compose
-f {{ app_base_dir }}/docker-compose.yml
ps --format json
register: current_state
changed_when: false
ignore_errors: true
- name: Show current state
ansible.builtin.debug:
msg: "Current: {{ current_state.stdout_lines }}"
# ── Backup current compose file ───────────────
- name: Backup current docker-compose.yml
ansible.builtin.copy:
src: "{{ app_base_dir }}/docker-compose.yml"
dest: "{{ app_base_dir }}/docker-compose.yml.bak"
remote_src: true
mode: '0644'
ignore_errors: true
# ── Backup current .env ───────────────────────
- name: Backup current .env
ansible.builtin.copy:
src: "{{ app_config_dir }}/.env"
dest: "{{ app_config_dir }}/.env.bak"
remote_src: true
mode: '0600'
ignore_errors: true
no_log: true
# ── Render new compose file ───────────────────
- name: Render updated docker-compose.yml
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ app_base_dir }}/docker-compose.yml"
mode: '0644'
# ── Render updated .env ───────────────────────
- name: Render updated .env
ansible.builtin.template:
src: app.env.j2
dest: "{{ app_config_dir }}/.env"
mode: '0600'
no_log: true
# ── Validate new compose file ─────────────────
- name: Validate new docker-compose.yml
ansible.builtin.command:
cmd: docker compose -f {{ app_base_dir }}/docker-compose.yml config
register: validate_result
changed_when: false
# ── Pull new images ───────────────────────────
- name: Pull new images
community.docker.docker_compose_v2:
project_src: "{{ app_base_dir }}"
project_name: "{{ app_name }}"
pull: always
state: present
register: update_result
no_log: true
# ── Wait for healthy ──────────────────────────
- name: Wait for services healthy
ansible.builtin.command:
cmd: >
docker compose
-f {{ app_base_dir }}/docker-compose.yml
ps --format json
register: post_update_status
changed_when: false
retries: 15
delay: 10
until: >
post_update_status.stdout | from_json |
selectattr('Health', 'equalto', 'unhealthy') |
list | length == 0
# ── Verify HTTP ───────────────────────────────
- name: Verify HTTP after update
ansible.builtin.uri:
url: "http://localhost/health"
status_code: 200
register: post_update_http
retries: 5
delay: 5
# ── Cleanup backup αν OK ──────────────────────
- name: Remove backup files (update OK)
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- "{{ app_base_dir }}/docker-compose.yml.bak"
- "{{ app_config_dir }}/.env.bak"
when: not post_update_http.failed
- name: Update complete
ansible.builtin.debug:
msg:
- "✅ Update complete!"
- "Version: {{ app_version }}"
Workflow 3: Config Update
# tasks/deploy_config.yml
---
# ============================================================
# Config Update — Μόνο αλλαγές ρυθμίσεων
# ============================================================
- name: Render nginx config
ansible.builtin.template:
src: nginx_compose.conf.j2
dest: "{{ app_config_dir }}/nginx/nginx.conf"
mode: '0644'
register: nginx_config_changed
- name: Reload nginx αν άλλαξε config
ansible.builtin.command:
cmd: >
docker compose
-f {{ app_base_dir }}/docker-compose.yml
-p {{ app_name }}
exec nginx nginx -s reload
when: nginx_config_changed.changed
register: nginx_reload
ignore_errors: true
- name: Render .env αν άλλαξε
ansible.builtin.template:
src: app.env.j2
dest: "{{ app_config_dir }}/.env"
mode: '0600'
no_log: true
register: env_changed
- name: Restart app αν άλλαξε .env
ansible.builtin.command:
cmd: >
docker compose
-f {{ app_base_dir }}/docker-compose.yml
-p {{ app_name }}
restart app
when: env_changed.changed
- name: Config update result
ansible.builtin.debug:
msg:
- "Nginx config: {{ 'Changed ✅' if nginx_config_changed.changed else 'Unchanged' }}"
- "Env file : {{ 'Changed ✅' if env_changed.changed else 'Unchanged' }}"
Workflow 4: Rollback
# tasks/deploy_rollback.yml
---
# ============================================================
# Rollback — Επιστροφή σε παλιά έκδοση
# ============================================================
- name: Check backup exists
ansible.builtin.stat:
path: "{{ app_base_dir }}/docker-compose.yml.bak"
register: backup_stat
- name: Assert backup exists
ansible.builtin.assert:
that:
- backup_stat.stat.exists
fail_msg: >
❌ No backup found!
Cannot rollback without backup.
Manual intervention required.
- name: Show backup info
ansible.builtin.debug:
msg:
- "Backup found: {{ backup_stat.stat.path }}"
- "Backup date : {{ backup_stat.stat.mtime | int | strftime('%Y-%m-%d %H:%M') }}"
- name: Restore docker-compose.yml από backup
ansible.builtin.copy:
src: "{{ app_base_dir }}/docker-compose.yml.bak"
dest: "{{ app_base_dir }}/docker-compose.yml"
remote_src: true
mode: '0644'
- name: Restore .env από backup
ansible.builtin.copy:
src: "{{ app_config_dir }}/.env.bak"
dest: "{{ app_config_dir }}/.env"
remote_src: true
mode: '0600'
no_log: true
ignore_errors: true
- name: Rollback deployment
community.docker.docker_compose_v2:
project_src: "{{ app_base_dir }}"
project_name: "{{ app_name }}"
state: present
recreate: always
no_log: true
- name: Wait for rollback healthy
ansible.builtin.command:
cmd: >
docker compose
-f {{ app_base_dir }}/docker-compose.yml
ps --format json
register: rollback_status
changed_when: false
retries: 12
delay: 10
until: >
rollback_status.stdout | from_json |
selectattr('Health', 'equalto', 'unhealthy') |
list | length == 0
- name: Verify rollback
ansible.builtin.uri:
url: "http://localhost/health"
status_code: 200
register: rollback_http
retries: 5
delay: 5
- name: Rollback result
ansible.builtin.debug:
msg: >
{{ '✅ Rollback successful!' if not rollback_http.failed
else '❌ Rollback failed! Manual intervention needed!' }}
Master Deployment Playbook
cat > ~/ansible/playbooks/compose-deploy.yml << 'EOF'
---
# ============================================================
# Docker Compose Deployment Playbook
# Υποστηρίζει: fresh, update, config, rollback
# Χρήση: -e "deploy_mode=update" -e "app_version=2.0.0"
# ============================================================
- name: Docker Compose Deployment
hosts: "{{ target | default('all_managed') }}"
become: true
gather_facts: true
vars:
# ── Deployment mode ───────────────────────
deploy_mode: "{{ deploy_mode | default('update') }}"
# Επιλογές: fresh, update, config, rollback
# ── App settings ──────────────────────────
app_name: mywebapp
app_version: "{{ app_version | default('1.0.0') }}"
app_env: "{{ app_env | default('production') }}"
app_port: 3000
# ── Paths ─────────────────────────────────
app_base_dir: "/opt/{{ app_name }}"
app_config_dir: "/opt/{{ app_name }}/config"
app_logs_dir: "/opt/{{ app_name }}/logs"
# ── Images ────────────────────────────────
nginx_image: nginx:1.25.3-alpine
postgres_image: postgres:15.4-alpine
redis_image: redis:7.2-alpine
app_image: "nginx:1.25.3-alpine" # ← placeholder
# ── Database ──────────────────────────────
postgres_db: "{{ app_name }}_db"
postgres_user: "{{ app_name }}_user"
postgres_port: 5432
# ── Redis ─────────────────────────────────
redis_port: 6379
redis_maxmem: "128mb"
# ── Nginx ─────────────────────────────────
nginx_host_port: 80
nginx_rate_limit: true
monitoring_enabled: false
pre_tasks:
# ── Validation ────────────────────────────
- name: Validate deploy_mode
ansible.builtin.assert:
that:
- deploy_mode in ['fresh', 'update', 'config', 'rollback']
fail_msg: >
❌ Invalid deploy_mode: {{ deploy_mode }}
Valid options: fresh, update, config, rollback
tags: always
- name: Deployment info
ansible.builtin.debug:
msg:
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- "Docker Compose Deployment"
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- "Mode : {{ deploy_mode | upper }}"
- "App : {{ app_name }} v{{ app_version }}"
- "Env : {{ app_env }}"
- "Host : {{ inventory_hostname }}"
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
tags: always
- name: Verify Docker
ansible.builtin.service_facts:
tags: always
- name: Assert Docker running
ansible.builtin.assert:
that:
- ansible_facts.services['docker.service'].state == 'running'
fail_msg: "❌ Docker is not running!"
tags: always
tasks:
# ════════════════════════════════════════
# FRESH DEPLOY
# ════════════════════════════════════════
- name: Fresh deployment
ansible.builtin.include_tasks:
file: "{{ playbook_dir }}/../roles/myapp/tasks/deploy_fresh.yml"
when: deploy_mode == 'fresh'
tags: [fresh, deploy]
# ════════════════════════════════════════
# UPDATE
# ════════════════════════════════════════
- name: Update deployment
ansible.builtin.include_tasks:
file: "{{ playbook_dir }}/../roles/myapp/tasks/deploy_update.yml"
when: deploy_mode == 'update'
tags: [update, deploy]
# ════════════════════════════════════════
# CONFIG UPDATE
# ════════════════════════════════════════
- name: Config update
ansible.builtin.include_tasks:
file: "{{ playbook_dir }}/../roles/myapp/tasks/deploy_config.yml"
when: deploy_mode == 'config'
tags: [config]
# ════════════════════════════════════════
# ROLLBACK
# ════════════════════════════════════════
- name: Rollback deployment
ansible.builtin.include_tasks:
file: "{{ playbook_dir }}/../roles/myapp/tasks/deploy_rollback.yml"
when: deploy_mode == 'rollback'
tags: [rollback]
post_tasks:
# ── Status report ─────────────────────────
- name: Get final stack status
ansible.builtin.command:
cmd: >
docker compose
-f {{ app_base_dir }}/docker-compose.yml
-p {{ app_name }}
ps
register: final_status
changed_when: false
ignore_errors: true
tags: always
- name: Final HTTP check
ansible.builtin.uri:
url: "http://localhost/health"
status_code: 200
register: final_http
ignore_errors: true
tags: always
- name: Deployment Summary
ansible.builtin.debug:
msg:
- "╔══════════════════════════════════════╗"
- "║ DEPLOYMENT SUMMARY ║"
- "╠══════════════════════════════════════╣"
- "║ Mode : {{ deploy_mode | upper }}"
- "║ App : {{ app_name }} v{{ app_version }}"
- "║ Host : {{ inventory_hostname }}"
- "║ HTTP : {{ '✅ OK' if not final_http.failed else '❌ FAILED' }}"
- "╠══════════════════════════════════════╣"
- "║ Services: ║"
- "{{ final_status.stdout | default('N/A') }}"
- "╚══════════════════════════════════════╝"
tags: always
EOF
Εκτέλεση όλων των Workflows
# ── Fresh deployment ──────────────────────────
ansible-playbook playbooks/compose-deploy.yml \
--limit nextcloud \
-e "deploy_mode=fresh" \
-e "app_version=1.0.0" \
-v
# ── Update σε νέα έκδοση ──────────────────────
ansible-playbook playbooks/compose-deploy.yml \
--limit nextcloud \
-e "deploy_mode=update" \
-e "app_version=1.1.0" \
-v
# ── Config update μόνο ────────────────────────
ansible-playbook playbooks/compose-deploy.yml \
--limit nextcloud \
-e "deploy_mode=config" \
-v
# ── Rollback αν κάτι πήγε στραβά ─────────────
ansible-playbook playbooks/compose-deploy.yml \
--limit nextcloud \
-e "deploy_mode=rollback" \
-v
# ── Dry run πριν update ───────────────────────
ansible-playbook playbooks/compose-deploy.yml \
--limit nextcloud \
-e "deploy_mode=update" \
-e "app_version=2.0.0" \
--check --diff
Σύνοψη D4.3
Deployment Workflows
├── Fresh Deploy:
│ create dirs → render templates
│ → pull images → deploy → verify
│
├── Update:
│ backup → render new templates
│ → validate → pull → deploy
│ → verify → cleanup backup
│
├── Config Update:
│ render config → reload nginx
│ → restart app αν .env άλλαξε
│
├── Rollback:
│ check backup → restore files
│ → redeploy → verify
│
└── Master Playbook:
-e "deploy_mode=fresh|update|config|rollback"
-e "app_version=X.Y.Z"