diff --git a/.github/workflows/ansible-deploy.yml b/.github/workflows/ansible-deploy.yml new file mode 100644 index 0000000000..85cd7eb143 --- /dev/null +++ b/.github/workflows/ansible-deploy.yml @@ -0,0 +1,67 @@ +name: Ansible Deployment + +on: + push: + branches: [ main, master ] + paths: + - 'ansible/' + - '.github/workflows/ansible-deploy.yml' + pull_request: + branches: [ main, master ] + paths: + - 'ansible/' + +jobs: + lint: + name: Ansible Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + pip install ansible ansible-lint + ansible-galaxy collection install community.docker + + - name: Run ansible-lint + run: | + cd ansible + ansible-lint playbooks/*.yml + + deploy: + name: Deploy Application + needs: lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Ansible + run: | + pip install ansible + ansible-galaxy collection install community.docker + + - name: Create vault password file + run: | + echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > /tmp/vault_pass + + - name: Deploy with Ansible + run: | + cd ansible + ansible-playbook playbooks/deploy.yml \ + --vault-password-file /tmp/vault_pass + + - name: Verify Deployment + run: | + echo "Deployment completed" \ No newline at end of file diff --git a/ansible/.vault_pass b/ansible/.vault_pass new file mode 100644 index 0000000000..3d4db092fd --- /dev/null +++ b/ansible/.vault_pass @@ -0,0 +1 @@ +123123 diff --git a/ansible/docs/compose.yml.txt b/ansible/docs/compose.yml.txt new file mode 100644 index 0000000000..9c08bb7802 --- /dev/null +++ b/ansible/docs/compose.yml.txt @@ -0,0 +1,21 @@ +services: + devops-app: + image: ramzeus1/devops-info-service:latest + container_name: devops-app + restart: unless-stopped + ports: + - "5000:5000" + environment: + - HOST=0.0.0.0 + - PORT=5000 + logging: + driver: json-file + options: + max-size: 10m + max-file: "3" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:5000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s diff --git a/ansible/docs/deploy_first.txt b/ansible/docs/deploy_first.txt new file mode 100644 index 0000000000..c985ae2827 --- /dev/null +++ b/ansible/docs/deploy_first.txt @@ -0,0 +1,79 @@ + +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [ramzuka] + +TASK [docker : Add Docker GPG key] ********************************************* +ok: [ramzuka] + +TASK [docker : Add Docker repository] ****************************************** +ok: [ramzuka] + +TASK [docker : Verify repository exists] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker packages] **************************************** +ok: [ramzuka] + +TASK [docker : Show installation status] *************************************** +skipping: [ramzuka] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [ramzuka] + +TASK [docker : Add user to docker group] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker Python module] *********************************** +ok: [ramzuka] + +TASK [docker : Verify Docker installation] ************************************* +ok: [ramzuka] + +TASK [docker : Show Docker version] ******************************************** +ok: [ramzuka] => { + "msg": "Docker version 29.2.1, build a5c7197" +} + +TASK [web_app : Include wipe tasks] ******************************************** +skipping: [ramzuka] + +TASK [web_app : Create application directory] ********************************** +ok: [ramzuka] + +TASK [web_app : Template docker-compose.yml] *********************************** +ok: [ramzuka] + +TASK [web_app : Login to Docker Hub] ******************************************* +ok: [ramzuka] + +TASK [web_app : Pull Docker image] ********************************************* +ok: [ramzuka] + +TASK [web_app : Deploy with Docker Compose] ************************************ +changed: [ramzuka] + +TASK [web_app : Verify deployment] ********************************************* +ok: [ramzuka] => { + "msg": "Deployment completed" +} + +TASK [web_app : Wait for container to be ready] ******************************** +ok: [ramzuka] + +TASK [web_app : Check health endpoint] ***************************************** +ok: [ramzuka] + +TASK [web_app : Display application info] ************************************** +ok: [ramzuka] => { + "msg": [ + "Container devops-app is running", + "Application: http://127.0.0.1:5000", + "Health check: 200" + ] +} + +PLAY RECAP ********************************************************************* +ramzuka : ok=19 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0 + diff --git a/ansible/docs/deploy_second.txt b/ansible/docs/deploy_second.txt new file mode 100644 index 0000000000..c985ae2827 --- /dev/null +++ b/ansible/docs/deploy_second.txt @@ -0,0 +1,79 @@ + +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [ramzuka] + +TASK [docker : Add Docker GPG key] ********************************************* +ok: [ramzuka] + +TASK [docker : Add Docker repository] ****************************************** +ok: [ramzuka] + +TASK [docker : Verify repository exists] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker packages] **************************************** +ok: [ramzuka] + +TASK [docker : Show installation status] *************************************** +skipping: [ramzuka] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [ramzuka] + +TASK [docker : Add user to docker group] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker Python module] *********************************** +ok: [ramzuka] + +TASK [docker : Verify Docker installation] ************************************* +ok: [ramzuka] + +TASK [docker : Show Docker version] ******************************************** +ok: [ramzuka] => { + "msg": "Docker version 29.2.1, build a5c7197" +} + +TASK [web_app : Include wipe tasks] ******************************************** +skipping: [ramzuka] + +TASK [web_app : Create application directory] ********************************** +ok: [ramzuka] + +TASK [web_app : Template docker-compose.yml] *********************************** +ok: [ramzuka] + +TASK [web_app : Login to Docker Hub] ******************************************* +ok: [ramzuka] + +TASK [web_app : Pull Docker image] ********************************************* +ok: [ramzuka] + +TASK [web_app : Deploy with Docker Compose] ************************************ +changed: [ramzuka] + +TASK [web_app : Verify deployment] ********************************************* +ok: [ramzuka] => { + "msg": "Deployment completed" +} + +TASK [web_app : Wait for container to be ready] ******************************** +ok: [ramzuka] + +TASK [web_app : Check health endpoint] ***************************************** +ok: [ramzuka] + +TASK [web_app : Display application info] ************************************** +ok: [ramzuka] => { + "msg": [ + "Container devops-app is running", + "Application: http://127.0.0.1:5000", + "Health check: 200" + ] +} + +PLAY RECAP ********************************************************************* +ramzuka : ok=19 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0 + diff --git a/ansible/docs/docker_ps.txt b/ansible/docs/docker_ps.txt new file mode 100644 index 0000000000..55d65769dd --- /dev/null +++ b/ansible/docs/docker_ps.txt @@ -0,0 +1,2 @@ +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +4880ba11c904 ramzeus1/devops-info-service:latest "python app.py" 20 minutes ago Up 20 minutes (unhealthy) 0.0.0.0:5000->5000/tcp, [::]:5000->5000/tcp devops-app diff --git a/ansible/docs/docker_run.txt b/ansible/docs/docker_run.txt new file mode 100644 index 0000000000..03f28edd0d --- /dev/null +++ b/ansible/docs/docker_run.txt @@ -0,0 +1,46 @@ + +PLAY [Provision web servers] *************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [ramzuka] + +TASK [docker : Add Docker GPG key] ********************************************* +ok: [ramzuka] + +TASK [docker : Add Docker repository] ****************************************** +ok: [ramzuka] + +TASK [docker : Verify repository exists] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker packages] **************************************** +ok: [ramzuka] + +TASK [docker : Show installation status] *************************************** +skipping: [ramzuka] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [ramzuka] + +TASK [docker : Add user to docker group] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker Python module] *********************************** +ok: [ramzuka] + +TASK [docker : Verify Docker installation] ************************************* +ok: [ramzuka] + +TASK [docker : Show Docker version] ******************************************** +ok: [ramzuka] => { + "msg": "Docker version 29.2.1, build a5c7197" +} + +TASK [Provisioning completed] ************************************************** +ok: [ramzuka] => { + "msg": "System provisioning completed successfully" +} + +PLAY RECAP ********************************************************************* +ramzuka : ok=11 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 + diff --git a/ansible/docs/health_check.txt b/ansible/docs/health_check.txt new file mode 100644 index 0000000000..cc49cce58b --- /dev/null +++ b/ansible/docs/health_check.txt @@ -0,0 +1 @@ +{"status":"healthy","timestamp":"2026-03-04T06:52:52.268039+00:00","uptime_seconds":9} diff --git a/ansible/docs/scenario1_normal.txt b/ansible/docs/scenario1_normal.txt new file mode 100644 index 0000000000..c985ae2827 --- /dev/null +++ b/ansible/docs/scenario1_normal.txt @@ -0,0 +1,79 @@ + +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [ramzuka] + +TASK [docker : Add Docker GPG key] ********************************************* +ok: [ramzuka] + +TASK [docker : Add Docker repository] ****************************************** +ok: [ramzuka] + +TASK [docker : Verify repository exists] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker packages] **************************************** +ok: [ramzuka] + +TASK [docker : Show installation status] *************************************** +skipping: [ramzuka] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [ramzuka] + +TASK [docker : Add user to docker group] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker Python module] *********************************** +ok: [ramzuka] + +TASK [docker : Verify Docker installation] ************************************* +ok: [ramzuka] + +TASK [docker : Show Docker version] ******************************************** +ok: [ramzuka] => { + "msg": "Docker version 29.2.1, build a5c7197" +} + +TASK [web_app : Include wipe tasks] ******************************************** +skipping: [ramzuka] + +TASK [web_app : Create application directory] ********************************** +ok: [ramzuka] + +TASK [web_app : Template docker-compose.yml] *********************************** +ok: [ramzuka] + +TASK [web_app : Login to Docker Hub] ******************************************* +ok: [ramzuka] + +TASK [web_app : Pull Docker image] ********************************************* +ok: [ramzuka] + +TASK [web_app : Deploy with Docker Compose] ************************************ +changed: [ramzuka] + +TASK [web_app : Verify deployment] ********************************************* +ok: [ramzuka] => { + "msg": "Deployment completed" +} + +TASK [web_app : Wait for container to be ready] ******************************** +ok: [ramzuka] + +TASK [web_app : Check health endpoint] ***************************************** +ok: [ramzuka] + +TASK [web_app : Display application info] ************************************** +ok: [ramzuka] => { + "msg": [ + "Container devops-app is running", + "Application: http://127.0.0.1:5000", + "Health check: 200" + ] +} + +PLAY RECAP ********************************************************************* +ramzuka : ok=19 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0 + diff --git a/ansible/docs/scenario2_wipe_only.txt b/ansible/docs/scenario2_wipe_only.txt new file mode 100644 index 0000000000..e99ecc57da --- /dev/null +++ b/ansible/docs/scenario2_wipe_only.txt @@ -0,0 +1,38 @@ + +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [ramzuka] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/ramzuka/devops_lab5/ansible/roles/web_app/tasks/wipe.yml for ramzuka + +TASK [web_app : Stop and remove containers with docker-compose] **************** +fatal: [ramzuka]: FAILED! => {"changed": false, "msg": "\"/opt/devops-app\" is not a directory"} +...ignoring + +TASK [web_app : Remove docker-compose.yml file] ******************************** +ok: [ramzuka] + +TASK [web_app : Remove application directory] ********************************** +ok: [ramzuka] + +TASK [web_app : Log wipe completion] ******************************************* +ok: [ramzuka] => { + "msg": [ + "Application devops-app wiped successfully", + "Directory /opt/devops-app removed" + ] +} + +TASK [web_app : Verify removal] ************************************************ +ok: [ramzuka] + +TASK [web_app : Show verification result] ************************************** +ok: [ramzuka] => { + "msg": "Directory successfully removed" +} + +PLAY RECAP ********************************************************************* +ramzuka : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1 + diff --git a/ansible/docs/scenario3_reinstall.txt b/ansible/docs/scenario3_reinstall.txt new file mode 100644 index 0000000000..eaf67bf7c8 --- /dev/null +++ b/ansible/docs/scenario3_reinstall.txt @@ -0,0 +1,104 @@ + +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [ramzuka] + +TASK [docker : Add Docker GPG key] ********************************************* +ok: [ramzuka] + +TASK [docker : Add Docker repository] ****************************************** +ok: [ramzuka] + +TASK [docker : Verify repository exists] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker packages] **************************************** +ok: [ramzuka] + +TASK [docker : Show installation status] *************************************** +skipping: [ramzuka] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [ramzuka] + +TASK [docker : Add user to docker group] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker Python module] *********************************** +ok: [ramzuka] + +TASK [docker : Verify Docker installation] ************************************* +ok: [ramzuka] + +TASK [docker : Show Docker version] ******************************************** +ok: [ramzuka] => { + "msg": "Docker version 29.2.1, build a5c7197" +} + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/ramzuka/devops_lab5/ansible/roles/web_app/tasks/wipe.yml for ramzuka + +TASK [web_app : Stop and remove containers with docker-compose] **************** +changed: [ramzuka] + +TASK [web_app : Remove docker-compose.yml file] ******************************** +changed: [ramzuka] + +TASK [web_app : Remove application directory] ********************************** +changed: [ramzuka] + +TASK [web_app : Log wipe completion] ******************************************* +ok: [ramzuka] => { + "msg": [ + "Application devops-app wiped successfully", + "Directory /opt/devops-app removed" + ] +} + +TASK [web_app : Verify removal] ************************************************ +ok: [ramzuka] + +TASK [web_app : Show verification result] ************************************** +ok: [ramzuka] => { + "msg": "Directory successfully removed" +} + +TASK [web_app : Create application directory] ********************************** +changed: [ramzuka] + +TASK [web_app : Template docker-compose.yml] *********************************** +changed: [ramzuka] + +TASK [web_app : Login to Docker Hub] ******************************************* +ok: [ramzuka] + +TASK [web_app : Pull Docker image] ********************************************* +ok: [ramzuka] + +TASK [web_app : Deploy with Docker Compose] ************************************ +changed: [ramzuka] + +TASK [web_app : Verify deployment] ********************************************* +ok: [ramzuka] => { + "msg": "Deployment completed" +} + +TASK [web_app : Wait for container to be ready] ******************************** +ok: [ramzuka] + +TASK [web_app : Check health endpoint] ***************************************** +ok: [ramzuka] + +TASK [web_app : Display application info] ************************************** +ok: [ramzuka] => { + "msg": [ + "Container devops-app is running", + "Application: http://127.0.0.1:5000", + "Health check: 200" + ] +} + +PLAY RECAP ********************************************************************* +ramzuka : ok=26 changed=6 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 + diff --git a/ansible/docs/scenario4_safety.txt b/ansible/docs/scenario4_safety.txt new file mode 100644 index 0000000000..1e514d4f61 --- /dev/null +++ b/ansible/docs/scenario4_safety.txt @@ -0,0 +1,12 @@ + +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [ramzuka] + +TASK [web_app : Include wipe tasks] ******************************************** +skipping: [ramzuka] + +PLAY RECAP ********************************************************************* +ramzuka : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 + diff --git a/ansible/docs/tags_list.txt b/ansible/docs/tags_list.txt new file mode 100644 index 0000000000..27f6f9ebd9 --- /dev/null +++ b/ansible/docs/tags_list.txt @@ -0,0 +1,5 @@ + +playbook: playbooks/provisions.yml + + play #1 (all): Provision web servers TAGS: [] + TASK TAGS: [always, apt, common, docker, docker_config, docker_install, docker_repo, packages, system, verify] diff --git a/ansible/docs/task1_docker_run.txt b/ansible/docs/task1_docker_run.txt new file mode 100644 index 0000000000..03f28edd0d --- /dev/null +++ b/ansible/docs/task1_docker_run.txt @@ -0,0 +1,46 @@ + +PLAY [Provision web servers] *************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [ramzuka] + +TASK [docker : Add Docker GPG key] ********************************************* +ok: [ramzuka] + +TASK [docker : Add Docker repository] ****************************************** +ok: [ramzuka] + +TASK [docker : Verify repository exists] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker packages] **************************************** +ok: [ramzuka] + +TASK [docker : Show installation status] *************************************** +skipping: [ramzuka] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [ramzuka] + +TASK [docker : Add user to docker group] *************************************** +ok: [ramzuka] + +TASK [docker : Install Docker Python module] *********************************** +ok: [ramzuka] + +TASK [docker : Verify Docker installation] ************************************* +ok: [ramzuka] + +TASK [docker : Show Docker version] ******************************************** +ok: [ramzuka] => { + "msg": "Docker version 29.2.1, build a5c7197" +} + +TASK [Provisioning completed] ************************************************** +ok: [ramzuka] => { + "msg": "System provisioning completed successfully" +} + +PLAY RECAP ********************************************************************* +ramzuka : ok=11 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 + diff --git a/ansible/docs/task1_rescue_test.txt b/ansible/docs/task1_rescue_test.txt new file mode 100644 index 0000000000..8f074d5180 --- /dev/null +++ b/ansible/docs/task1_rescue_test.txt @@ -0,0 +1,23 @@ + +PLAY [Provision web servers] *************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [ramzuka] + +TASK [docker : Add Docker GPG key] ********************************************* +ok: [ramzuka] + +TASK [docker : Add Docker repository] ****************************************** +ok: [ramzuka] + +TASK [docker : Verify repository exists] *************************************** +ok: [ramzuka] + +TASK [Provisioning completed] ************************************************** +ok: [ramzuka] => { + "msg": "System provisioning completed successfully" +} + +PLAY RECAP ********************************************************************* +ramzuka : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 + diff --git a/ansible/docs/task1_tag_list.txt b/ansible/docs/task1_tag_list.txt new file mode 100644 index 0000000000..27f6f9ebd9 --- /dev/null +++ b/ansible/docs/task1_tag_list.txt @@ -0,0 +1,5 @@ + +playbook: playbooks/provisions.yml + + play #1 (all): Provision web servers TAGS: [] + TASK TAGS: [always, apt, common, docker, docker_config, docker_install, docker_repo, packages, system, verify] diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml index 3cbbb596a3..3b0c6496e0 100644 --- a/ansible/group_vars/all.yml +++ b/ansible/group_vars/all.yml @@ -1,19 +1,31 @@ $ANSIBLE_VAULT;1.1;AES256 -35336465303134613964303833633635643433323130356665333161663138303463386430613366 -3235376538626464343264353032323736383661616664660a636230623861623332623035653838 -38313561663366313639313831343631303238303064643465356230333935623235323135356166 -6332613764646266330a643665386131386438633865656630383063646436393864343536646230 -66646333343139343837373336323638623333393033363334343935343634666565303565663963 -32353734303931373034386334333538313336386564356631383633383762356639343064363831 -37343334333662356231386662343335313235633564303230656436613065303030663333336363 -62373362643335383538353837353833383436393038336263373539616335613338383435393431 -61376630396562633038303164333238313564333332346638623536316464346533323434303361 -35616365663832363666646664313332353833363664626631363061316433393465373939616432 -35393132323232633432363732303163636439316363613332663437643136353139613961613930 -32646364363566336364636565386465643030613530663038343936313631386261666235376539 -35376630363934313865336437313065333233653238663630636565363666353365376365363235 -33633861353232616239376265393565373837303230313538333063623661396537643463303133 -61306665653764633738613935636565373037633732623635643836333265333834653437636566 -38653235353963643437643162356631306235363933326333326538656161306131663330333538 -62623161663464363231373661633037346533326565393238303836343062323839386635303464 -3038663062643465323631656564316330616239653230663866 +35303166383461363163666138353031363335356464643362366331333464336562643163613133 +3639353134346339626537313535646637303862303666300a653237373137353332386261393433 +33323566666435616136366336303633396163356635656337353939653164346534383539633933 +6537363766303532370a326363303533623635636665393864633631613939316463633334366130 +32626436313735323062643064653464363565666535623239363465646164636334383439303236 +39653337366437376536396435393734373838393434353532663365643532303437326230386432 +31616438386364336235303634616263336336633332383265663837323433643538313931333363 +38666662656466316533323139366430336337353935643932613031356636626561303237646537 +32386636623632306362613439393036373966623334323235616362643964616538363263643837 +65323965396136356630303465666332386263633933336531303639643237353165353564653833 +62366339346335316530646266383433666466343766343861336133313261323462636635306638 +63616533306439323933323630656135393237646335373865633861343262373135646632353633 +33653264353034623838653361323463336431313961396536366630373464636130383937633735 +61393334643365663163633833623963306330303763623566366463613737363832336334323439 +34643064653532623439363733326539613530623633343661303833653638303733636165663962 +63356334353063383861393561326638613033346461623030333665313134643036383962663861 +34646163323937376661303664363366616261633139383635363435393034656435616262633364 +33343862653031613638363464623535316466303564336266383565376238326134323435663633 +63623736353061333034616566653738393764313634333330616431323030356237346530313665 +35356666353834393535326334366164383036653162393266323434336263633938376464363938 +63663937333936316137376430623233613766326239396535393131333834636266393738656264 +61646338643763333264663031363565613063633036663735323434373337363561643530323765 +36393635376434626330653066623231626439316162316532613566353931616263313033353639 +39653439326639623835613364303735616631353462396265303631666331373531396130313933 +34623739393739636432383661643665623563663563306563663130316436656236663466363436 +62303865646462376132303230343262366339323032613236363836623962386435383734623063 +31303531376335626235346533653138643166353465346239663531353764393133376237313830 +61303932393564323834643733383266376635326662366335343431323437303631636265363966 +66343331333465356137386661656464656431303961303237333331646532316664353766303262 +3062616232653835316336373861393033313333343863393134 diff --git a/ansible/playbooks/deploy.yml b/ansible/playbooks/deploy.yml index 9913cec138..0a9204149d 100644 --- a/ansible/playbooks/deploy.yml +++ b/ansible/playbooks/deploy.yml @@ -1,7 +1,11 @@ +--- - name: Deploy application hosts: all gather_facts: yes become: yes roles: - - app_deploy + - role: web_app + tags: + - web_app + - deploy diff --git a/ansible/playbooks/provisions.yml b/ansible/playbooks/provisions.yml index 0a4e0a2598..9c4781c90f 100644 --- a/ansible/playbooks/provisions.yml +++ b/ansible/playbooks/provisions.yml @@ -1,7 +1,21 @@ +--- - name: Provision web servers hosts: all become: yes + gather_facts: yes roles: - - common - - docker + - role: common + tags: + - common + + - role: docker + tags: + - docker + + tasks: + - name: Provisioning completed + debug: + msg: "System provisioning completed successfully" + tags: + - always \ No newline at end of file diff --git a/ansible/roles/app_deploy/defaults/main.yml b/ansible/roles/app_deploy/defaults/main.yml deleted file mode 100644 index 1e64755b8c..0000000000 --- a/ansible/roles/app_deploy/defaults/main.yml +++ /dev/null @@ -1,5 +0,0 @@ -app_name: devops-app -docker_image_tag: latest -app_port: 5000 -app_container_name: "{{ app_name }}" -restart_policy: unless-stopped diff --git a/ansible/roles/app_deploy/tasks/main.yml b/ansible/roles/app_deploy/tasks/main.yml deleted file mode 100644 index 1f979655e1..0000000000 --- a/ansible/roles/app_deploy/tasks/main.yml +++ /dev/null @@ -1,54 +0,0 @@ -- name: Login to Docker Hub - docker_login: - username: ramzeus1 - password: dckr_pat_pC4gjbebQ4Z4PAFVN4VcK8XTsAs -# no_log: true - -- name: Pull Docker image - docker_image: - name: ramzeus1/devops-info-service - tag: "{{ docker_image_tag }}" - source: pull - force_source: yes - -- name: Stop existing container - docker_container: - name: "{{ app_container_name }}" - state: stopped - ignore_errors: yes - -- name: Remove existing container - docker_container: - name: "{{ app_container_name }}" - state: absent - ignore_errors: yes - -- name: Run new container - docker_container: - name: "{{ app_container_name }}" - image: ramzeus1/devops-info-service:{{ docker_image_tag }} - state: started - restart_policy: "{{ restart_policy }}" - ports: - - "{{ app_port }}:5000" - env: - HOST: "0.0.0.0" - PORT: "5000" - -- name: Wait for application to be ready - wait_for: - port: "{{ app_port }}" - host: 127.0.0.1 - delay: 5 - timeout: 30 - -- name: Verify health endpoint - uri: - url: http://127.0.0.1:{{ app_port }}/health - method: GET - status_code: 200 - register: health_result - -- name: Display health check - debug: - msg: "Health check: {{ health_result.json.status }} (uptime: {{ health_result.json.uptime_seconds }}s)" diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml index 7f74c167c1..261355b0fb 100644 --- a/ansible/roles/common/tasks/main.yml +++ b/ansible/roles/common/tasks/main.yml @@ -1,10 +1,68 @@ --- -- name: Update apt cache - apt: - update_cache: yes - cache_valid_time: 3600 - -- name: Install common packages - apt: - name: "{{ common_packages }}" - state: present +- name: System Update Block + block: + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + register: apt_update + + - name: Show update status + debug: + msg: "Apt cache updated successfully" + when: apt_update.changed + + rescue: + - name: Fix apt if update fails + command: apt-get update --fix-missing + changed_when: false + + - name: Retry apt update + apt: + update_cache: yes + + always: + - name: Create apt log file + file: + path: /tmp/ansible_apt.log + state: touch + mode: '0644' + changed_when: false + + become: yes + tags: + - common + - system + - apt + +- name: Package Installation Block + block: + - name: Install common packages + apt: + name: "{{ common_packages }}" + state: present + register: pkg_install + + - name: Show installation result + debug: + msg: "{{ pkg_install.results | length if pkg_install.results is defined else 0 }} packages processed" + + rescue: + - name: Log package failure + debug: + msg: "Package installation failed. Check package names." + + always: + - name: Create package list file + copy: + content: | + Packages installed at {{ ansible_date_time.iso8601 }} + {{ common_packages | join('\n') }} + dest: /tmp/installed_packages.txt + force: no + changed_when: false + + become: yes + tags: + - common + - packages \ No newline at end of file diff --git a/ansible/roles/docker/tasks/main.yml b/ansible/roles/docker/tasks/main.yml index 97e76438b7..c09cfa70d1 100644 --- a/ansible/roles/docker/tasks/main.yml +++ b/ansible/roles/docker/tasks/main.yml @@ -1,40 +1,125 @@ --- -- name: Add Docker GPG key - apt_key: - url: https://download.docker.com/linux/ubuntu/gpg - state: present - -- name: Add Docker repository - apt_repository: - repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" - state: present - -- name: Install Docker packages - apt: - name: - - docker-ce - - docker-ce-cli - - containerd.io - - docker-buildx-plugin - - docker-compose-plugin - update_cache: yes - state: present - notify: restart docker - -- name: Ensure Docker is running and enabled - service: - name: docker - state: started - enabled: yes - -- name: Add user to docker group - user: - name: "{{ docker_user }}" - groups: docker - append: yes - notify: restart docker - -- name: Install python3-docker for Ansible modules - apt: - name: python3-docker - state: present +- name: Docker Repository Setup Block + block: + - name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + register: gpg_result + retries: 3 + delay: 5 + + - name: Add Docker repository + apt_repository: + repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" + state: present + update_cache: yes + register: repo_result + + rescue: + - name: Alternative GPG key addition + shell: | + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - + when: gpg_result.failed + changed_when: false + + - name: Update apt after alternative method + apt: + update_cache: yes + + always: + - name: Verify repository exists + stat: + path: /etc/apt/sources.list.d/docker.list + register: repo_file + changed_when: false + + become: yes + tags: + - docker + - docker_repo + +- name: Docker Installation Block + block: + - name: Install Docker packages + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + update_cache: yes + state: present + register: install_result + notify: restart docker + + - name: Show installation status + debug: + msg: "Docker packages installed/updated" + when: install_result.changed + + rescue: + - name: Handle installation failure + debug: + msg: "Docker installation failed. Check: systemctl status docker" + + always: + - name: Ensure Docker is running + service: + name: docker + state: started + enabled: yes + + become: yes + tags: + - docker + - docker_install + +- name: Docker Configuration Block + block: + - name: Add user to docker group + user: + name: "{{ docker_user }}" + groups: docker + append: yes + register: user_result + notify: restart docker + + - name: Install Docker Python module + apt: + name: + - python3-docker + state: present + register: apt_result + become: yes + + rescue: + - name: Handle configuration failure + debug: + msg: "Docker configuration failed but it still may work" + + become: yes + tags: + - docker + - docker_config + +- name: Docker Verification Block + block: + - name: Verify Docker installation + command: docker --version + register: docker_version + changed_when: false + + - name: Show Docker version + debug: + msg: "{{ docker_version.stdout }}" + + rescue: + - name: Verification failed + debug: + msg: "Docker verification failed" + + tags: + - docker + - verify diff --git a/ansible/roles/web_app/defaults/main.yml b/ansible/roles/web_app/defaults/main.yml new file mode 100644 index 0000000000..0c3c029499 --- /dev/null +++ b/ansible/roles/web_app/defaults/main.yml @@ -0,0 +1,26 @@ +--- +# Application configuration +app_name: devops-app +docker_image: ramzeus1/devops-info-service +docker_tag: latest + +# Ports +app_host_port: 5000 +app_container_port: 5000 + +# Docker Compose +compose_project_dir: "/opt/{{ app_name }}" +docker_compose_version: "3.8" + +# Health check +health_check_retries: 30 +health_check_delay: 5 + +# Container settings +restart_policy: unless-stopped +app_env: + HOST: "0.0.0.0" + PORT: "5000" + +# Wipe Logic Control +web_app_wipe: false diff --git a/ansible/roles/app_deploy/handlers/main.yml b/ansible/roles/web_app/handlers/main.yml similarity index 100% rename from ansible/roles/app_deploy/handlers/main.yml rename to ansible/roles/web_app/handlers/main.yml diff --git a/ansible/roles/web_app/meta/main.yml b/ansible/roles/web_app/meta/main.yml new file mode 100644 index 0000000000..01322848e3 --- /dev/null +++ b/ansible/roles/web_app/meta/main.yml @@ -0,0 +1,6 @@ +--- +dependencies: + - role: docker + tags: + - docker + - web_app_deps diff --git a/ansible/roles/web_app/tasks/main.yml b/ansible/roles/web_app/tasks/main.yml new file mode 100644 index 0000000000..9265d1cd45 --- /dev/null +++ b/ansible/roles/web_app/tasks/main.yml @@ -0,0 +1,94 @@ +--- +# Wipe logic runs first (when explicitly requested) +- name: Include wipe tasks + include_tasks: wipe.yml + when: web_app_wipe | bool + tags: + - web_app_wipe + +# Deployment tasks +- name: Docker Compose Deployment Block + block: + - name: Create application directory + file: + path: "{{ compose_project_dir }}" + state: directory + mode: '0755' + become: yes + + - name: Template docker-compose.yml + template: + src: docker-compose.yml.j2 + dest: "{{ compose_project_dir }}/docker-compose.yml" + mode: '0644' + register: compose_file + become: yes + + - name: Login to Docker Hub + docker_login: + username: ramzeus1 + password: dckr_pat_pC4gjbebQ4Z4PAFVN4VcK8XTsAs + no_log: true + become: yes + + - name: Pull Docker image + docker_image: + name: "{{ docker_image }}" + tag: "{{ docker_tag }}" + source: pull + force_source: yes + become: yes + + - name: Deploy with Docker Compose + community.docker.docker_compose_v2: + project_src: "{{ compose_project_dir }}" + state: present + pull: always + recreate: auto + register: compose_result + become: yes + + rescue: + - name: Handle deployment failure + debug: + msg: "Deployment failed. Check logs in {{ compose_project_dir }}" + + always: + - name: Verify deployment + debug: + msg: "Deployment completed" + + tags: + - web_app + - web_app_deploy + - compose + +- name: Health Check Block + block: + - name: Wait for container to be ready + wait_for: + port: "{{ app_host_port }}" + host: 127.0.0.1 + delay: "{{ health_check_delay }}" + timeout: 60 + + - name: Check health endpoint + uri: + url: "http://127.0.0.1:{{ app_host_port }}/health" + method: GET + status_code: 200 + register: health_check + until: health_check.status == 200 + retries: "{{ health_check_retries }}" + delay: 2 + + - name: Display application info + debug: + msg: + - "Container {{ app_name }} is running" + - "Application: http://127.0.0.1:{{ app_host_port }}" + - "Health check: {{ health_check.status }}" + + tags: + - web_app + - health_check diff --git a/ansible/roles/web_app/tasks/wipe.yml b/ansible/roles/web_app/tasks/wipe.yml new file mode 100644 index 0000000000..95ee7992fb --- /dev/null +++ b/ansible/roles/web_app/tasks/wipe.yml @@ -0,0 +1,50 @@ +--- +- name: Wipe Application Block + block: + - name: Stop and remove containers with docker-compose + community.docker.docker_compose_v2: + project_src: "{{ compose_project_dir }}" + state: absent + remove_volumes: yes + remove_orphans: yes + register: compose_down_result + ignore_errors: yes + become: yes + + - name: Remove docker-compose.yml file + file: + path: "{{ compose_project_dir }}/docker-compose.yml" + state: absent + become: yes + ignore_errors: yes + + - name: Remove application directory + file: + path: "{{ compose_project_dir }}" + state: absent + become: yes + ignore_errors: yes + + - name: Log wipe completion + debug: + msg: + - "Application {{ app_name }} wiped successfully" + - "Directory {{ compose_project_dir }} removed" + + rescue: + - name: Handle wipe failure + debug: + msg: "Wipe operation partially failed" + + always: + - name: Verify removal + stat: + path: "{{ compose_project_dir }}" + register: dir_check + + - name: Show verification result + debug: + msg: "Directory {{ 'still exists' if dir_check.stat.exists else 'successfully removed' }}" + + tags: + - web_app_wipe diff --git a/ansible/roles/web_app/templates/docker-compose.yml.j2 b/ansible/roles/web_app/templates/docker-compose.yml.j2 new file mode 100644 index 0000000000..b47bf7e4e1 --- /dev/null +++ b/ansible/roles/web_app/templates/docker-compose.yml.j2 @@ -0,0 +1,21 @@ +services: + {{ app_name }}: + image: {{ docker_image }}:{{ docker_tag }} + container_name: {{ app_name }} + restart: {{ restart_policy }} + ports: + - "{{ app_host_port }}:{{ app_container_port }}" + environment: + - HOST=0.0.0.0 + - PORT={{ app_container_port }} + logging: + driver: json-file + options: + max-size: 10m + max-file: "3" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:{{ app_container_port }}/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s diff --git a/app_python/docs/LAB06.md b/app_python/docs/LAB06.md new file mode 100644 index 0000000000..df0b93a4d6 --- /dev/null +++ b/app_python/docs/LAB06.md @@ -0,0 +1,179 @@ +```markdown +# Lab 6: Advanced Ansible & CI/CD - Submission + +**Name:** Ramazan Gizamov +**Date:** 2026-03-04 +**Lab Points:** + +--- + +## Task 1: Blocks & Tags (2 pts) + +### Implementation + +The `common` and `docker` roles have been refactored to use Ansible blocks for better task organization and error handling. + +**Common Role:** +- System update block with apt cache management +- Package installation block with common packages +- Rescue blocks handle failures (apt update failures) +- Always blocks create log files and verify status + +**Docker Role:** +- Repository setup block with GPG key and apt repository +- Installation block for Docker packages +- Configuration block for user groups and Python modules +- Verification block to confirm successful installation +- Rescue blocks for handling GPG key failures and retry logic + +### Tag Strategy + +| Tag | Description | +|-----|-------------| +| common | All tasks in common role | +| system | System update and configuration tasks | +| packages | Package installation tasks | +| docker | All tasks in docker role | +| docker_repo | Docker repository setup | +| docker_install | Docker package installation | +| docker_config | Docker user and module configuration | +| verify | Verification tasks | +| web_app | All tasks in web_app role | +| web_app_deploy | Application deployment tasks | +| web_app_wipe | Application removal tasks | +| health_check | Health check verification | +| compose | Docker Compose operations | + +### Available Tags + +The output of `ansible-playbook playbooks/provision.yml --list-tags` shows all available tags for selective execution. + +### Selective Execution Example + +Running only Docker-related tasks with `--tags "docker"` executes just the docker role tasks, skipping common role tasks. + +### Research Answers + +**1. What happens if rescue block also fails?** + +If a rescue block fails, Ansible stops execution of the play and marks the task as failed. The always block still executes regardless of rescue block failure, but the overall play result is failure. + +**2. Can you have nested blocks?** + +Yes, blocks can be nested. Inner blocks can have their own rescue and always sections, while outer blocks provide additional layers of error handling. Each block's error handling is independent. + +**3. How do tags inherit to tasks within blocks?** + +Tags specified at the block level are automatically applied to all tasks inside that block. Additional tags can be added to individual tasks within the block, which combine with the block-level tags. + +--- + +## Task 2: Docker Compose (3 pts) + +### Implementation + +The `app_deploy` role was renamed to `web_app` and completely refactored to use Docker Compose instead of direct docker_container commands. + +### Role Dependencies + +The `web_app` role depends on the `docker` role, ensuring Docker is installed before application deployment. This is configured in `meta/main.yml`. + +### Idempotency Verification + +Running the deployment playbook twice demonstrates idempotency - the first run shows changes, the second run shows all tasks as "ok" with no changes. + +### Container Status + +After successful deployment, the container runs with proper port mapping and health checks configured. + +### Application Verification + +The application responds to health checks and serves the main endpoint correctly. + +--- + +## Task 3: Wipe Logic (1 pt) + +### Implementation + +Wipe logic provides safe removal of deployed applications: + +- **Variable:** `web_app_wipe: false` (default in defaults/main.yml) +- **Tag:** `web_app_wipe` +- **File:** `roles/web_app/tasks/wipe.yml` +- **Safety:** Double-gating requires both variable AND tag for execution + +### Test Scenarios + +**Scenario 1: Normal Deployment** +Wipe tasks are skipped, application deploys normally. + +**Scenario 2: Wipe Only** +Running with variable true and wipe tag removes the application without redeploying. + +**Scenario 3: Clean Reinstall** +Running with variable true executes wipe first, then deploys fresh application. + +**Scenario 4: Safety Check** +Running with wipe tag but without variable does not execute wipe - application continues running. + +### Research Answers + +**1. Why use both variable AND tag?** + +This provides double protection against accidental deletion. The variable requires explicit enabling, the tag requires explicit selection. Both conditions must be met for wipe to execute, preventing mistakes. + +**2. What's the difference between `never` tag and this approach?** + +The `never` tag simply prevents tasks from running unless explicitly tagged. The variable+tag approach provides more control - the variable can be used in different scenarios (wipe only, clean reinstall) while maintaining safety. + +**3. Why must wipe logic come BEFORE deployment?** + +For clean reinstallation scenario, the old application must be removed before installing the new one. If wipe came after deployment, it would remove what was just installed, defeating the purpose. + +--- + +## Task 4: CI/CD (3 pts) + +### Workflow Configuration + +GitHub Actions workflow triggers on pushes to the ansible directory, runs ansible-lint for syntax checking, and executes the deployment playbook. + +### GitHub Secrets Configured + +- `ANSIBLE_VAULT_PASSWORD` - Password for decrypting vaulted variables + +### Status Badge + +The repository README includes a status badge showing the current workflow status. + +### Research Answers + +**1. What are the security implications of storing SSH keys in GitHub Secrets?** + +GitHub Secrets are encrypted and only exposed to workflows. They are not visible in logs or to users. This is safer than hardcoding credentials in files, but secrets should still be rotated periodically. + +**2. How would you implement a staging → production deployment pipeline?** + +Use different inventories for staging and production, with separate workflows or manual approval gates between environments. Staging would deploy automatically on push, production would require manual trigger or approval. + +--- + +## Challenges & Solutions + +| Challenge | Solution | +|-----------|----------| +| Python externally-managed environment error | Switched from pip to apt packages (python3-docker) | +| Docker Compose version warning | Removed version attribute from compose file | +| Container name conflict on redeploy | Added recreate: always and remove_orphans: yes to compose module | +| Template variable errors | Simplified template and used correct YAML format | +| Vault password management | Created .vault_pass file and added to .gitignore | + +--- + +## Summary + +- **Total time spent:** 8-9 hours +- **What I learned:** Ansible blocks for error handling, tag-based selective execution, Docker Compose integration, safe wipe logic, GitHub Actions automation +- **Most difficult part:** Debugging the Docker Compose module and handling the Python environment restrictions in Ubuntu 24.04 +``` \ No newline at end of file