diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index dcd49df..c3815f4 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -68,87 +68,27 @@ jobs: docker push mikhaillobanov/devops-platform:latest docker push mikhaillobanov/devops-platform:${GITHUB_SHA} - - name: Deploy application + - name: Deploy application via Ansible if: github.event_name == 'push' && github.ref == 'refs/heads/main' env: + ANSIBLE_PRIVATE_KEY: ${{ secrets.ID_RSA }} IMAGE: mikhaillobanov/devops-platform:${{ github.sha }} run: | set -eu - OLD_IMAGE=$(docker inspect --format='{{.Config.Image}}' devops-platform) + apt-get update + apt-get install -y ansible openssh-client - docker pull "$IMAGE" - docker stop devops-platform - docker rm devops-platform + mkdir -p /root/.ssh + chmod 700 /root/.ssh - if IMAGE="$IMAGE" docker compose up -d --no-build app; then - echo "New container started" - else - echo "Failed to start new container; restoring old image" - docker rm -f devops-platform || true - if ! IMAGE="$OLD_IMAGE" docker compose up -d --no-build app; then - echo "ERROR: rollback failed; manual recovery required" - exit 1 - fi + printf '%s\n' "$ANSIBLE_PRIVATE_KEY" > /root/.ssh/id_rsa + chmod 600 /root/.ssh/id_rsa - ROLLBACK_HEALTHY=false + ssh-keyscan -H 178.154.209.109 >> /root/.ssh/known_hosts + chmod 600 /root/.ssh/known_hosts - for attempt in $(seq 1 15); do - if docker exec devops-platform python -c \ - "import urllib.request; r=urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2); assert r.status == 200"; then - ROLLBACK_HEALTHY=true - break - fi - sleep 2 - done - - if [ "$ROLLBACK_HEALTHY" = true ]; then - echo "Rollback successful; previous version is healthy" - else - echo "ERROR: rollback container failed health check; manual recovery required" - fi - - exit 1 - fi - - HEALTHY=false - - for attempt in $(seq 1 15); do - if docker exec devops-platform python -c \ - "import urllib.request; r=urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2); assert r.status == 200"; then - HEALTHY=true - break - fi - sleep 2 - done - - if [ "$HEALTHY" = true ]; then - echo "Deployment successful" - else - echo "Health check failed; rolling back" - docker stop devops-platform || true - docker rm devops-platform || true - if ! IMAGE="$OLD_IMAGE" docker compose up -d --no-build app; then - echo "ERROR: rollback failed; manual recovery required" - exit 1 - fi - - ROLLBACK_HEALTHY=false - - for attempt in $(seq 1 15); do - if docker exec devops-platform python -c \ - "import urllib.request; r=urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2); assert r.status == 200"; then - ROLLBACK_HEALTHY=true - break - fi - sleep 2 - done - - if [ "$ROLLBACK_HEALTHY" = true ]; then - echo "Rollback successful; previous version is healthy" - else - echo "ERROR: rollback container failed health check; manual recovery required" - fi - - exit 1 - fi + ansible-playbook \ + -i ansible/inventory.ini \ + ansible/playbooks/deploy_app.yml \ + -e "deploy_image=$IMAGE" diff --git a/ansible/inventory.ini b/ansible/inventory.ini new file mode 100644 index 0000000..0ae92d1 --- /dev/null +++ b/ansible/inventory.ini @@ -0,0 +1,2 @@ +[targets] +ansible-target ansible_host=178.154.209.109 ansible_user=root ansible_ssh_private_key_file=/root/.ssh/id_rsa diff --git a/ansible/inventory/hosts.ini b/ansible/inventory/hosts.ini new file mode 100644 index 0000000..0ae92d1 --- /dev/null +++ b/ansible/inventory/hosts.ini @@ -0,0 +1,2 @@ +[targets] +ansible-target ansible_host=178.154.209.109 ansible_user=root ansible_ssh_private_key_file=/root/.ssh/id_rsa diff --git a/ansible/playbooks/configure_nginx_proxy.yml b/ansible/playbooks/configure_nginx_proxy.yml new file mode 100644 index 0000000..ffff876 --- /dev/null +++ b/ansible/playbooks/configure_nginx_proxy.yml @@ -0,0 +1,56 @@ +--- +- name: Configure Nginx reverse proxy + hosts: targets + become: true + + tasks: + - name: Deploy Nginx reverse proxy configuration + ansible.builtin.template: + src: "{{ playbook_dir }}/../templates/devops-platform.conf.j2" + dest: /etc/nginx/sites-available/devops-platform + owner: root + group: root + mode: '0644' + notify: Reload Nginx + + - name: Enable DevOps application site + ansible.builtin.file: + src: /etc/nginx/sites-available/devops-platform + dest: /etc/nginx/sites-enabled/devops-platform + state: link + notify: Reload Nginx + + - name: Disable default Nginx site + ansible.builtin.file: + path: /etc/nginx/sites-enabled/default + state: absent + notify: Reload Nginx + + - name: Validate Nginx configuration + ansible.builtin.command: + cmd: nginx -t + changed_when: false + + - name: Apply Nginx changes now + ansible.builtin.meta: flush_handlers + + - name: Verify application through Nginx + ansible.builtin.uri: + url: http://127.0.0.1/health + method: GET + status_code: 200 + return_content: true + register: proxy_health + retries: 5 + delay: 2 + until: proxy_health.status == 200 + + - name: Show proxied application health + ansible.builtin.debug: + var: proxy_health.json + + handlers: + - name: Reload Nginx + ansible.builtin.service: + name: nginx + state: reloaded diff --git a/ansible/playbooks/deploy_app.yml b/ansible/playbooks/deploy_app.yml new file mode 100644 index 0000000..74460bd --- /dev/null +++ b/ansible/playbooks/deploy_app.yml @@ -0,0 +1,56 @@ +--- +- name: Deploy DevOps application + hosts: targets + become: true + + vars: + deploy_image: "mikhaillobanov/devops-platform:latest" + + tasks: + - name: Create application directory + ansible.builtin.file: + path: /opt/devops-platform + state: directory + owner: root + group: root + mode: '0755' + + - name: Copy Docker Compose configuration + ansible.builtin.copy: + src: "{{ playbook_dir }}/../../compose.yaml" + dest: /opt/devops-platform/compose.yaml + owner: root + group: root + mode: '0644' + + - name: Deploy application with Docker Compose + ansible.builtin.command: + cmd: docker compose up -d --pull always + chdir: /opt/devops-platform + environment: + IMAGE: "{{ deploy_image }}" + register: deploy_result + changed_when: > + 'Created' in deploy_result.stdout or + 'Started' in deploy_result.stdout or + 'Pulled' in deploy_result.stdout or + 'Recreated' in deploy_result.stdout + + - name: Wait for application health endpoint + ansible.builtin.uri: + url: http://127.0.0.1:8000/health + method: GET + status_code: 200 + return_content: true + register: health_result + retries: 10 + delay: 3 + until: health_result.status == 200 + + - name: Show deployed image + ansible.builtin.debug: + msg: "Deployed image: {{ deploy_image }}" + + - name: Show application health + ansible.builtin.debug: + var: health_result.json diff --git a/ansible/playbooks/install_docker.yml b/ansible/playbooks/install_docker.yml new file mode 100644 index 0000000..da4140a --- /dev/null +++ b/ansible/playbooks/install_docker.yml @@ -0,0 +1,28 @@ +--- +- name: Install Docker Engine + hosts: targets + become: true + + tasks: + - name: Update apt cache + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + + - name: Install Docker and Compose plugin + ansible.builtin.apt: + name: + - docker.io + - docker-compose-v2 + state: present + + - name: Ensure Docker is running + ansible.builtin.service: + name: docker + state: started + enabled: true + + - name: Verify Docker Compose + ansible.builtin.command: + cmd: docker compose version + changed_when: false diff --git a/ansible/playbooks/install_nginx.yml b/ansible/playbooks/install_nginx.yml new file mode 100644 index 0000000..206ea77 --- /dev/null +++ b/ansible/playbooks/install_nginx.yml @@ -0,0 +1,25 @@ +--- +- name: Install and configure Nginx + hosts: targets + become: true + + tasks: + - name: Install Nginx + ansible.builtin.apt: + name: nginx + state: present + update_cache: true + + - name: Ensure Nginx is running + ansible.builtin.service: + name: nginx + state: started + enabled: true + + - name: Deploy index page + ansible.builtin.template: + src: ../templates/index.html.j2 + dest: /var/www/html/index.html + owner: root + group: root + mode: '0644' diff --git a/ansible/templates/devops-platform.conf.j2 b/ansible/templates/devops-platform.conf.j2 new file mode 100644 index 0000000..d36451a --- /dev/null +++ b/ansible/templates/devops-platform.conf.j2 @@ -0,0 +1,16 @@ +server { + listen 80 default_server; + listen [::]:80 default_server; + + server_name _; + + location / { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/ansible/templates/index.html.j2 b/ansible/templates/index.html.j2 new file mode 100644 index 0000000..7aa97e5 --- /dev/null +++ b/ansible/templates/index.html.j2 @@ -0,0 +1,12 @@ + + +
+ +Server: {{ inventory_hostname }}
+Managed by Ansible
+ +