chore: sync staging workspace
This commit is contained in:
66
deployment/ansible/playbooks/check-staging-logs.yml
Normal file
66
deployment/ansible/playbooks/check-staging-logs.yml
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
- name: Check Staging Container Logs
|
||||
hosts: production
|
||||
gather_facts: yes
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Check staging container status
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose ps
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: container_status
|
||||
changed_when: false
|
||||
|
||||
- name: Get staging-app logs
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose logs --tail=100 staging-app
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: staging_app_logs
|
||||
changed_when: false
|
||||
|
||||
- name: Get staging-nginx logs
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose logs --tail=100 staging-nginx
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: staging_nginx_logs
|
||||
changed_when: false
|
||||
|
||||
- name: Get staging-queue-worker logs
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose logs --tail=100 staging-queue-worker
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: staging_queue_logs
|
||||
changed_when: false
|
||||
|
||||
- name: Get staging-scheduler logs
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose logs --tail=100 staging-scheduler
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: staging_scheduler_logs
|
||||
changed_when: false
|
||||
|
||||
- name: Display container status
|
||||
debug:
|
||||
msg: "{{ container_status.stdout_lines }}"
|
||||
|
||||
- name: Display staging-app logs
|
||||
debug:
|
||||
msg: "{{ staging_app_logs.stdout_lines }}"
|
||||
|
||||
- name: Display staging-nginx logs
|
||||
debug:
|
||||
msg: "{{ staging_nginx_logs.stdout_lines }}"
|
||||
|
||||
- name: Display staging-queue-worker logs
|
||||
debug:
|
||||
msg: "{{ staging_queue_logs.stdout_lines }}"
|
||||
|
||||
- name: Display staging-scheduler logs
|
||||
debug:
|
||||
msg: "{{ staging_scheduler_logs.stdout_lines }}"
|
||||
@@ -133,31 +133,13 @@
|
||||
when: true
|
||||
register: compose_updated
|
||||
|
||||
- name: Restart application stack with new image
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ app_stack_path }}"
|
||||
state: present
|
||||
pull: always
|
||||
recreate: always
|
||||
remove_orphans: yes
|
||||
register: stack_deploy
|
||||
# Always restart when deploying, even if image already exists
|
||||
# This ensures code changes are applied even with same image tag
|
||||
when: true
|
||||
|
||||
- name: Wait for services to be healthy
|
||||
wait_for:
|
||||
timeout: 60
|
||||
changed_when: false
|
||||
|
||||
- name: Check container health status
|
||||
shell: |
|
||||
docker compose -f {{ app_stack_path }}/docker-compose.yml ps --format json | jq -r '.[] | select(.Health != "healthy" and .Health != "") | "\(.Name): \(.Health)"' || echo "All healthy or no health checks"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: health_status
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
- name: Redeploy application stack with new image
|
||||
import_role:
|
||||
name: application
|
||||
vars:
|
||||
application_sync_files: false
|
||||
application_compose_recreate: "always"
|
||||
application_remove_orphans: true
|
||||
|
||||
- name: Get deployed image information
|
||||
shell: |
|
||||
@@ -175,8 +157,9 @@
|
||||
Image Tag: {{ image_tag }}
|
||||
Deployed Image: {{ deployed_image.stdout }}
|
||||
Image Pull: {{ 'SUCCESS' if image_pull.changed else 'SKIPPED (already exists)' }}
|
||||
Stack Deploy: {{ 'UPDATED' if stack_deploy.changed else 'NO_CHANGE' }}
|
||||
Health Status: {{ health_status.stdout if health_status.stdout != '' else 'All services healthy' }}
|
||||
Stack Deploy: {{ 'UPDATED' if application_stack_changed else 'NO_CHANGE' }}
|
||||
Health Status: {{ application_health_output if application_health_output != '' else 'All services healthy' }}
|
||||
Health Check HTTP Status: {{ application_healthcheck_status }}
|
||||
dest: "{{ backups_path }}/{{ deployment_timestamp | regex_replace(':', '-') }}/deployment_metadata.txt"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
@@ -200,7 +183,9 @@
|
||||
- "Commit: {{ git_commit_sha }}"
|
||||
- "Timestamp: {{ deployment_timestamp }}"
|
||||
- "Image Pull: {{ 'SUCCESS' if image_pull.changed else 'SKIPPED' }}"
|
||||
- "Stack Deploy: {{ 'UPDATED' if stack_deploy.changed else 'NO_CHANGE' }}"
|
||||
- "Stack Deploy: {{ 'UPDATED' if application_stack_changed else 'NO_CHANGE' }}"
|
||||
- "Health Output: {{ application_health_output if application_health_output != '' else 'All services healthy' }}"
|
||||
- "Health Check HTTP Status: {{ application_healthcheck_status }}"
|
||||
- "Health Check URL: {{ health_check_url }}"
|
||||
- ""
|
||||
- "Next: Verify application is healthy"
|
||||
|
||||
96
deployment/ansible/playbooks/diagnose-staging-502.yml
Normal file
96
deployment/ansible/playbooks/diagnose-staging-502.yml
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
- name: Diagnose Staging 502 Error
|
||||
hosts: production
|
||||
gather_facts: yes
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Check Nginx error logs
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose logs --tail=50 staging-nginx 2>&1 | grep -iE "(error|502|bad gateway|php|fpm)" || echo "No errors found in nginx logs"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: nginx_errors
|
||||
changed_when: false
|
||||
|
||||
- name: Check PHP-FPM status in staging-app
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-app php-fpm-healthcheck 2>&1 || echo "PHP-FPM healthcheck failed"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: php_fpm_status
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Test PHP-FPM connection from nginx container
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-nginx sh -c "nc -zv staging-app 9000 2>&1 || echo 'Connection test failed'" || echo "Connection test failed"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: connection_test
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check if PHP-FPM is listening
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-app sh -c "netstat -tlnp 2>/dev/null | grep 9000 || ss -tlnp 2>/dev/null | grep 9000 || echo 'Port 9000 not found'" || echo "Could not check ports"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: php_fpm_port
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check staging-app process list
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-app sh -c "ps aux | grep -E '(php-fpm|nginx)' | head -20" || echo "Could not get process list"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: processes
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check network connectivity
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-nginx sh -c "ping -c 2 staging-app 2>&1 || echo 'Ping failed'" || echo "Network test failed"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: network_test
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check if /var/www/html/public/index.php exists
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-nginx sh -c "test -f /var/www/html/public/index.php && echo 'index.php exists' || echo 'index.php NOT FOUND'" || echo "Could not check file"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: index_php_check
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Display Nginx error logs
|
||||
debug:
|
||||
msg: "{{ nginx_errors.stdout_lines }}"
|
||||
|
||||
- name: Display PHP-FPM status
|
||||
debug:
|
||||
msg: "{{ php_fpm_status.stdout_lines }}"
|
||||
|
||||
- name: Display connection test
|
||||
debug:
|
||||
msg: "{{ connection_test.stdout_lines }}"
|
||||
|
||||
- name: Display PHP-FPM port check
|
||||
debug:
|
||||
msg: "{{ php_fpm_port.stdout_lines }}"
|
||||
|
||||
- name: Display processes
|
||||
debug:
|
||||
msg: "{{ processes.stdout_lines }}"
|
||||
|
||||
- name: Display network test
|
||||
debug:
|
||||
msg: "{{ network_test.stdout_lines }}"
|
||||
|
||||
- name: Display index.php check
|
||||
debug:
|
||||
msg: "{{ index_php_check.stdout_lines }}"
|
||||
71
deployment/ansible/playbooks/fix-staging-complete.yml
Normal file
71
deployment/ansible/playbooks/fix-staging-complete.yml
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
- name: Complete Fix for Staging (502 + Git)
|
||||
hosts: production
|
||||
gather_facts: yes
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Check if index.php exists in staging-app
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-app sh -c "ls -la /var/www/html/public/index.php 2>&1" || echo "index.php NOT FOUND"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: index_php_app
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check if index.php exists in staging-nginx
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-nginx sh -c "ls -la /var/www/html/public/index.php 2>&1" || echo "index.php NOT FOUND"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: index_php_nginx
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check PHP-FPM listen configuration
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-app sh -c "cat /usr/local/etc/php-fpm.d/www.conf | grep -E '(^listen|^listen.allowed_clients|^listen.owner|^listen.group|^user|^group)' | head -20" || echo "Could not read config"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: php_fpm_full_config
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Test actual HTTP request to staging-app
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-nginx sh -c "curl -v http://staging-app:9000/index.php 2>&1 | head -30" || echo "HTTP test failed"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: http_test
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Fix Git ownership permanently in staging-app entrypoint
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-app sh -c "cd /var/www/html && git config --global --add safe.directory /var/www/html && git config --global --get-all safe.directory" || echo "Git config failed"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: git_config_check
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Display index.php check in app
|
||||
debug:
|
||||
msg: "{{ index_php_app.stdout_lines }}"
|
||||
|
||||
- name: Display index.php check in nginx
|
||||
debug:
|
||||
msg: "{{ index_php_nginx.stdout_lines }}"
|
||||
|
||||
- name: Display PHP-FPM config
|
||||
debug:
|
||||
msg: "{{ php_fpm_full_config.stdout_lines }}"
|
||||
|
||||
- name: Display HTTP test
|
||||
debug:
|
||||
msg: "{{ http_test.stdout_lines }}"
|
||||
|
||||
- name: Display git config
|
||||
debug:
|
||||
msg: "{{ git_config_check.stdout_lines }}"
|
||||
83
deployment/ansible/playbooks/fix-staging-issues.yml
Normal file
83
deployment/ansible/playbooks/fix-staging-issues.yml
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
- name: Fix Staging Issues (502 Error + Git Ownership)
|
||||
hosts: production
|
||||
gather_facts: yes
|
||||
become: no
|
||||
|
||||
tasks:
|
||||
- name: Get recent nginx error logs
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose logs --tail=100 staging-nginx 2>&1
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: nginx_all_logs
|
||||
changed_when: false
|
||||
|
||||
- name: Test PHP-FPM connection with curl
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-nginx sh -c "curl -v http://staging-app:9000 2>&1 | head -20" || echo "Connection test completed"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: curl_test
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check PHP-FPM configuration
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-app sh -c "cat /usr/local/etc/php-fpm.d/www.conf | grep -E '(listen|listen.allowed_clients)' | head -10" || echo "Could not read PHP-FPM config"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: php_fpm_config
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Fix Git ownership issue in staging-app
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-app sh -c "cd /var/www/html && git config --global --add safe.directory /var/www/html && echo 'Git safe.directory configured'" || echo "Git config failed"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: git_fix
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Test if nginx can reach PHP-FPM
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-nginx sh -c "echo 'GET /index.php HTTP/1.0' | nc staging-app 9000 2>&1 | head -10" || docker compose exec -T staging-nginx sh -c "timeout 2 bash -c '</dev/tcp/staging-app/9000' && echo 'Port 9000 is reachable' || echo 'Port 9000 not reachable'" || echo "Could not test connection"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: port_test
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Check if nginx can read public directory
|
||||
shell: |
|
||||
cd ~/deployment/stacks/staging && docker compose exec -T staging-nginx sh -c "ls -la /var/www/html/public/ | head -10" || echo "Could not list public directory"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: public_dir_check
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Display all nginx logs
|
||||
debug:
|
||||
msg: "{{ nginx_all_logs.stdout_lines[-30:] }}"
|
||||
|
||||
- name: Display curl test
|
||||
debug:
|
||||
msg: "{{ curl_test.stdout_lines }}"
|
||||
|
||||
- name: Display PHP-FPM config
|
||||
debug:
|
||||
msg: "{{ php_fpm_config.stdout_lines }}"
|
||||
|
||||
- name: Display git fix result
|
||||
debug:
|
||||
msg: "{{ git_fix.stdout_lines }}"
|
||||
|
||||
- name: Display port test
|
||||
debug:
|
||||
msg: "{{ port_test.stdout_lines }}"
|
||||
|
||||
- name: Display public directory check
|
||||
debug:
|
||||
msg: "{{ public_dir_check.stdout_lines }}"
|
||||
@@ -41,284 +41,33 @@
|
||||
|
||||
# 1. Deploy Traefik (Reverse Proxy & SSL)
|
||||
- name: Deploy Traefik stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ stacks_base_path }}/traefik"
|
||||
state: present
|
||||
pull: always
|
||||
register: traefik_output
|
||||
|
||||
- name: Wait for Traefik to be ready
|
||||
wait_for:
|
||||
timeout: "{{ wait_timeout }}"
|
||||
when: traefik_output.changed
|
||||
|
||||
- name: Check Traefik logs for readiness hint
|
||||
shell: docker compose logs traefik 2>&1 | grep -Ei "(configuration loaded|traefik[[:space:]]v|Starting provider|Server configuration loaded)" || true
|
||||
args:
|
||||
chdir: "{{ stacks_base_path }}/traefik"
|
||||
register: traefik_logs
|
||||
until: traefik_logs.stdout != ""
|
||||
retries: 6
|
||||
delay: 10
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
import_role:
|
||||
name: traefik
|
||||
|
||||
# 2. Deploy PostgreSQL (Database)
|
||||
- name: Deploy PostgreSQL stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ stacks_base_path }}/postgresql"
|
||||
state: present
|
||||
pull: always
|
||||
register: postgres_output
|
||||
|
||||
- name: Wait for PostgreSQL to be ready
|
||||
wait_for:
|
||||
timeout: "{{ wait_timeout }}"
|
||||
when: postgres_output.changed
|
||||
|
||||
- name: Check PostgreSQL logs for readiness
|
||||
shell: docker compose logs postgres 2>&1 | grep -Ei "(ready to accept connections|database system is ready)" || true
|
||||
args:
|
||||
chdir: "{{ stacks_base_path }}/postgresql"
|
||||
register: postgres_logs
|
||||
until: postgres_logs.stdout != ""
|
||||
retries: 6
|
||||
delay: 10
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
import_role:
|
||||
name: postgresql
|
||||
|
||||
# 3. Deploy Docker Registry (Private Registry)
|
||||
- name: Ensure Registry auth directory exists
|
||||
file:
|
||||
path: "{{ registry_auth_path }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
become: yes
|
||||
|
||||
- name: Optionally load registry credentials from vault
|
||||
include_vars:
|
||||
file: "{{ playbook_dir }}/../secrets/production.vault.yml"
|
||||
no_log: yes
|
||||
ignore_errors: yes
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
|
||||
- name: Set registry credentials from vault or defaults
|
||||
set_fact:
|
||||
registry_username: "{{ vault_docker_registry_username | default(docker_registry_username_default) }}"
|
||||
registry_password: "{{ vault_docker_registry_password | default(docker_registry_password_default) }}"
|
||||
no_log: true
|
||||
|
||||
- name: Fail if registry password is not set
|
||||
fail:
|
||||
msg: "Registry password must be set in vault or docker_registry_password_default"
|
||||
when: registry_password is not defined or registry_password == ""
|
||||
|
||||
- name: Create Registry htpasswd file if missing
|
||||
shell: |
|
||||
if [ ! -f {{ registry_auth_path }}/htpasswd ]; then
|
||||
docker run --rm --entrypoint htpasswd httpd:2 -Bbn {{ registry_username }} {{ registry_password }} > {{ registry_auth_path }}/htpasswd
|
||||
chmod 644 {{ registry_auth_path }}/htpasswd
|
||||
fi
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become: yes
|
||||
changed_when: true
|
||||
register: registry_auth_created
|
||||
no_log: true
|
||||
|
||||
- name: Deploy Docker Registry stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ stacks_base_path }}/registry"
|
||||
state: present
|
||||
pull: always
|
||||
register: registry_output
|
||||
|
||||
- name: Wait for Docker Registry to be ready
|
||||
wait_for:
|
||||
timeout: "{{ wait_timeout }}"
|
||||
when: registry_output.changed
|
||||
|
||||
- name: Check Registry logs for readiness
|
||||
shell: docker compose logs registry 2>&1 | grep -Ei "(listening on|listening at|http server)" || true
|
||||
args:
|
||||
chdir: "{{ stacks_base_path }}/registry"
|
||||
register: registry_logs
|
||||
until: registry_logs.stdout != ""
|
||||
retries: 6
|
||||
delay: 10
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Verify Registry is accessible
|
||||
uri:
|
||||
url: "http://127.0.0.1:5000/v2/_catalog"
|
||||
user: "{{ registry_username }}"
|
||||
password: "{{ registry_password }}"
|
||||
status_code: 200
|
||||
timeout: 5
|
||||
register: registry_check
|
||||
ignore_errors: yes
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Display Registry status
|
||||
debug:
|
||||
msg: "Registry accessibility: {{ 'SUCCESS' if registry_check.status == 200 else 'FAILED - may need manual check' }}"
|
||||
import_role:
|
||||
name: registry
|
||||
|
||||
# 4. Deploy MinIO (Object Storage)
|
||||
- name: Optionally load MinIO secrets from vault
|
||||
include_vars:
|
||||
file: "{{ playbook_dir }}/../secrets/production.vault.yml"
|
||||
no_log: yes
|
||||
ignore_errors: yes
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
|
||||
- name: Set MinIO root password from vault or generate
|
||||
set_fact:
|
||||
minio_password: "{{ vault_minio_root_password | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits,punctuation')) }}"
|
||||
no_log: yes
|
||||
|
||||
- name: Set MinIO root user from vault or use default
|
||||
set_fact:
|
||||
minio_user: "{{ vault_minio_root_user | default('minioadmin') }}"
|
||||
|
||||
- name: Ensure MinIO stack directory exists
|
||||
file:
|
||||
path: "{{ stacks_base_path }}/minio"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Create MinIO stack .env file
|
||||
template:
|
||||
src: "{{ playbook_dir }}/../templates/minio.env.j2"
|
||||
dest: "{{ stacks_base_path }}/minio/.env"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: '0600'
|
||||
vars:
|
||||
minio_root_user: "{{ minio_user }}"
|
||||
minio_root_password: "{{ minio_password }}"
|
||||
minio_api_domain: "{{ minio_api_domain }}"
|
||||
minio_console_domain: "{{ minio_console_domain }}"
|
||||
no_log: yes
|
||||
|
||||
- name: Deploy MinIO stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ stacks_base_path }}/minio"
|
||||
state: present
|
||||
pull: always
|
||||
register: minio_output
|
||||
|
||||
- name: Wait for MinIO to be ready
|
||||
wait_for:
|
||||
timeout: "{{ wait_timeout }}"
|
||||
when: minio_output.changed
|
||||
|
||||
- name: Check MinIO logs for readiness
|
||||
shell: docker compose logs minio 2>&1 | grep -Ei "(API:|WebUI:|MinIO Object Storage Server)" || true
|
||||
args:
|
||||
chdir: "{{ stacks_base_path }}/minio"
|
||||
register: minio_logs
|
||||
until: minio_logs.stdout != ""
|
||||
retries: 6
|
||||
delay: 10
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Verify MinIO health endpoint
|
||||
uri:
|
||||
url: "http://127.0.0.1:9000/minio/health/live"
|
||||
method: GET
|
||||
status_code: [200, 404, 502, 503]
|
||||
timeout: 5
|
||||
register: minio_health_check
|
||||
ignore_errors: yes
|
||||
changed_when: false
|
||||
|
||||
- name: Display MinIO status
|
||||
debug:
|
||||
msg: "MinIO health check: {{ 'SUCCESS' if minio_health_check.status == 200 else 'FAILED - Status: ' + (minio_health_check.status|string) }}"
|
||||
import_role:
|
||||
name: minio
|
||||
|
||||
# 5. Deploy Gitea (CRITICAL - Git Server + MySQL + Redis)
|
||||
- name: Deploy Gitea stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ stacks_base_path }}/gitea"
|
||||
state: present
|
||||
pull: always
|
||||
register: gitea_output
|
||||
|
||||
- name: Wait for Gitea to be ready
|
||||
wait_for:
|
||||
timeout: "{{ wait_timeout }}"
|
||||
when: gitea_output.changed
|
||||
|
||||
- name: Check Gitea logs for readiness
|
||||
shell: docker compose logs gitea 2>&1 | grep -Ei "(Listen:|Server is running|Starting server)" || true
|
||||
args:
|
||||
chdir: "{{ stacks_base_path }}/gitea"
|
||||
register: gitea_logs
|
||||
until: gitea_logs.stdout != ""
|
||||
retries: 12
|
||||
delay: 10
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
import_role:
|
||||
name: gitea
|
||||
|
||||
# 6. Deploy Monitoring (Portainer + Grafana + Prometheus)
|
||||
- name: Optionally load monitoring secrets from vault
|
||||
include_vars:
|
||||
file: "{{ playbook_dir }}/../secrets/production.vault.yml"
|
||||
no_log: yes
|
||||
ignore_errors: yes
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
|
||||
- name: Set Grafana admin password from vault or generate
|
||||
set_fact:
|
||||
grafana_admin_password: "{{ vault_grafana_admin_password | default(lookup('password', '/dev/null length=25 chars=ascii_letters,digits')) }}"
|
||||
|
||||
- name: Set Prometheus password from vault or generate
|
||||
set_fact:
|
||||
prometheus_password: "{{ vault_prometheus_password | default(lookup('password', '/dev/null length=25 chars=ascii_letters,digits')) }}"
|
||||
|
||||
- name: Generate Prometheus BasicAuth hash
|
||||
shell: |
|
||||
docker run --rm httpd:alpine htpasswd -nbB admin "{{ prometheus_password }}" 2>/dev/null | cut -d ":" -f 2
|
||||
register: prometheus_auth_hash
|
||||
changed_when: false
|
||||
no_log: yes
|
||||
|
||||
- name: Set Prometheus BasicAuth string
|
||||
set_fact:
|
||||
prometheus_auth: "admin:{{ prometheus_auth_hash.stdout }}"
|
||||
|
||||
- name: Ensure monitoring stack directory exists
|
||||
file:
|
||||
path: "{{ stacks_base_path }}/monitoring"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Create monitoring stack .env file
|
||||
template:
|
||||
src: "{{ playbook_dir }}/../templates/monitoring.env.j2"
|
||||
dest: "{{ stacks_base_path }}/monitoring/.env"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: '0600'
|
||||
no_log: yes
|
||||
|
||||
- name: Deploy Monitoring stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ stacks_base_path }}/monitoring"
|
||||
state: present
|
||||
pull: always
|
||||
register: monitoring_output
|
||||
|
||||
- name: Wait for Monitoring to be ready
|
||||
wait_for:
|
||||
timeout: "{{ wait_timeout }}"
|
||||
when: monitoring_output.changed
|
||||
import_role:
|
||||
name: monitoring
|
||||
|
||||
# Verification
|
||||
- name: List all running containers
|
||||
@@ -345,180 +94,42 @@
|
||||
msg: "Gitea HTTPS check: {{ 'SUCCESS' if gitea_http_check.status == 200 else 'FAILED - Status: ' + (gitea_http_check.status|string) }}"
|
||||
|
||||
# 7. Deploy Application Stack
|
||||
- name: Optionally load application secrets from vault
|
||||
include_vars:
|
||||
file: "{{ playbook_dir }}/../secrets/production.vault.yml"
|
||||
no_log: yes
|
||||
ignore_errors: yes
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
|
||||
- name: Check if PostgreSQL .env exists
|
||||
stat:
|
||||
path: "{{ stacks_base_path }}/postgresql/.env"
|
||||
register: postgres_env_file
|
||||
changed_when: false
|
||||
|
||||
- name: Extract PostgreSQL password from .env file
|
||||
shell: "grep '^POSTGRES_PASSWORD=' {{ stacks_base_path }}/postgresql/.env 2>/dev/null | cut -d'=' -f2- || echo ''"
|
||||
register: postgres_password_from_file
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: postgres_env_file.stat.exists
|
||||
no_log: yes
|
||||
|
||||
- name: Set application database password (from file, vault, or generate)
|
||||
set_fact:
|
||||
app_db_password: "{{ postgres_password_from_file.stdout if (postgres_env_file.stat.exists and postgres_password_from_file.stdout != '') else (vault_db_root_password | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits,punctuation'))) }}"
|
||||
no_log: yes
|
||||
|
||||
- name: Set application redis password from vault or generate
|
||||
set_fact:
|
||||
app_redis_password: "{{ vault_redis_password | default(lookup('password', '/dev/null length=32 chars=ascii_letters,digits,punctuation')) }}"
|
||||
|
||||
- name: Ensure application stack directory exists
|
||||
file:
|
||||
path: "{{ stacks_base_path }}/application"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Check if application stack docker-compose.yml exists locally
|
||||
stat:
|
||||
path: "{{ playbook_dir }}/../../stacks/application/docker-compose.yml"
|
||||
register: app_compose_local
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
|
||||
- name: Copy application stack docker-compose.yml to server
|
||||
copy:
|
||||
src: "{{ playbook_dir }}/../../stacks/application/docker-compose.yml"
|
||||
dest: "{{ stacks_base_path }}/application/docker-compose.yml"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: '0644'
|
||||
when: app_compose_local.stat.exists
|
||||
|
||||
- name: Check if application stack nginx directory exists locally
|
||||
stat:
|
||||
path: "{{ playbook_dir }}/../../stacks/application/nginx/"
|
||||
register: app_nginx_local
|
||||
delegate_to: localhost
|
||||
become: no
|
||||
|
||||
- name: Copy application stack nginx configuration to server
|
||||
copy:
|
||||
src: "{{ playbook_dir }}/../../stacks/application/nginx/"
|
||||
dest: "{{ stacks_base_path }}/application/nginx/"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: '0644'
|
||||
when: app_nginx_local.stat.exists
|
||||
|
||||
- name: Create application stack .env file
|
||||
template:
|
||||
src: "{{ playbook_dir }}/../templates/application.env.j2"
|
||||
dest: "{{ stacks_base_path }}/application/.env"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: '0600'
|
||||
vars:
|
||||
db_password: "{{ app_db_password }}"
|
||||
db_user: "{{ db_user | default(db_user_default) }}"
|
||||
db_name: "{{ db_name | default(db_name_default) }}"
|
||||
redis_password: "{{ app_redis_password }}"
|
||||
app_domain: "{{ app_domain }}"
|
||||
no_log: yes
|
||||
|
||||
- name: Deploy Application stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ stacks_base_path }}/application"
|
||||
state: present
|
||||
pull: always
|
||||
register: application_output
|
||||
|
||||
- name: Wait for Application to be ready
|
||||
wait_for:
|
||||
timeout: "{{ wait_timeout }}"
|
||||
when: application_output.changed
|
||||
|
||||
- name: Wait for application containers to be healthy
|
||||
pause:
|
||||
seconds: 30
|
||||
when: application_output.changed
|
||||
|
||||
- name: Check application container health status
|
||||
shell: |
|
||||
docker compose -f {{ stacks_base_path }}/application/docker-compose.yml ps --format json | jq -r '.[] | select(.Health != "healthy" and .Health != "" and .Health != "starting") | "\(.Name): \(.Health)"' || echo "All healthy or no health checks"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: app_health_status
|
||||
changed_when: false
|
||||
ignore_errors: yes
|
||||
- name: Deploy Application Stack
|
||||
import_role:
|
||||
name: application
|
||||
|
||||
- name: Display application health status
|
||||
debug:
|
||||
msg: "Application health: {{ app_health_status.stdout if app_health_status.stdout != '' else 'All services healthy or starting' }}"
|
||||
|
||||
- name: Wait for app container to be ready before migration
|
||||
wait_for:
|
||||
timeout: 60
|
||||
when: application_output.changed
|
||||
|
||||
- name: Check if app container is running
|
||||
shell: |
|
||||
docker compose -f {{ stacks_base_path }}/application/docker-compose.yml ps app | grep -q "Up" || exit 1
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: app_container_running
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: application_output.changed
|
||||
|
||||
- name: Run database migrations
|
||||
shell: |
|
||||
docker compose -f {{ stacks_base_path }}/application/docker-compose.yml exec -T app php console.php db:migrate
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: migration_result
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
ignore_errors: yes
|
||||
when: application_output.changed and app_container_running.rc == 0
|
||||
msg: "Application health: {{ application_health_output if application_health_output != '' else 'All services healthy or starting' }}"
|
||||
|
||||
- name: Display migration result
|
||||
debug:
|
||||
msg: |
|
||||
Migration Result:
|
||||
{{ migration_result.stdout if migration_result.rc == 0 else 'Migration may have failed - check logs with: docker compose -f ' + stacks_base_path + '/application/docker-compose.yml logs app' }}
|
||||
when: application_output.changed
|
||||
|
||||
- name: Verify application accessibility via HTTPS
|
||||
uri:
|
||||
url: "{{ health_check_url }}"
|
||||
method: GET
|
||||
validate_certs: no
|
||||
status_code: [200, 404, 502, 503]
|
||||
timeout: 10
|
||||
register: app_health_check
|
||||
ignore_errors: yes
|
||||
when: application_output.changed
|
||||
{{ application_migration_stdout if application_migration_stdout != '' else 'Migration may have failed - check logs with: docker compose -f ' + application_stack_dest + '/docker-compose.yml logs app' }}
|
||||
when: application_stack_changed and application_run_migrations
|
||||
|
||||
- name: Display application accessibility status
|
||||
debug:
|
||||
msg: "Application health check: {{ 'SUCCESS (HTTP ' + (app_health_check.status|string) + ')' if app_health_check.status == 200 else 'FAILED or not ready yet (HTTP ' + (app_health_check.status|string) + ')' }}"
|
||||
when: application_output.changed
|
||||
msg: >-
|
||||
Application health check: {{
|
||||
'SUCCESS (HTTP ' + (application_healthcheck_status | string) + ')'
|
||||
if application_healthcheck_status == 200 else
|
||||
'FAILED or not ready yet (HTTP ' + (application_healthcheck_status | string) + ')'
|
||||
}}
|
||||
when: application_stack_changed and application_healthcheck_url | length > 0
|
||||
|
||||
- name: Summary
|
||||
debug:
|
||||
msg:
|
||||
- "=== Infrastructure Deployment Complete ==="
|
||||
- "Traefik: {{ 'Deployed' if traefik_output.changed else 'Already running' }}"
|
||||
- "PostgreSQL: {{ 'Deployed' if postgres_output.changed else 'Already running' }}"
|
||||
- "Docker Registry: {{ 'Deployed' if registry_output.changed else 'Already running' }}"
|
||||
- "MinIO: {{ 'Deployed' if minio_output.changed else 'Already running' }}"
|
||||
- "Gitea: {{ 'Deployed' if gitea_output.changed else 'Already running' }}"
|
||||
- "Monitoring: {{ 'Deployed' if monitoring_output.changed else 'Already running' }}"
|
||||
- "Application: {{ 'Deployed' if application_output.changed else 'Already running' }}"
|
||||
- "Traefik: {{ 'Deployed' if traefik_stack_changed else 'Already running' }}"
|
||||
- "PostgreSQL: {{ 'Deployed' if postgresql_stack_changed else 'Already running' }}"
|
||||
- "Docker Registry: {{ 'Deployed' if registry_stack_changed else 'Already running' }}"
|
||||
- "MinIO: {{ 'Deployed' if minio_stack_changed else 'Already running' }}"
|
||||
- "Gitea: {{ 'Deployed' if gitea_stack_changed else 'Already running' }}"
|
||||
- "Monitoring: {{ 'Deployed' if monitoring_stack_changed else 'Already running' }}"
|
||||
- "Application: {{ 'Deployed' if application_stack_changed else 'Already running' }}"
|
||||
- ""
|
||||
- "Next Steps:"
|
||||
- "1. Access Gitea at: https://{{ gitea_domain }}"
|
||||
|
||||
Reference in New Issue
Block a user