diff --git a/.github/workflows/ansible-deploy.yml b/.github/workflows/ansible-deploy.yml new file mode 100644 index 0000000000..6951f935f1 --- /dev/null +++ b/.github/workflows/ansible-deploy.yml @@ -0,0 +1,69 @@ +name: Ansible Deployment + +on: + push: + paths: + - 'ansible/**' # Ansible code + - '!ansible/docs/**' # Exclude docs + - '!ansible/README.md' + - '.github/workflows/ansible-deploy.yml' # Workflow changes + pull_request: + paths: + - 'ansible/**' # Ansible code + - '!ansible/docs/**' # Exclude docs + - '!ansible/README.md' + - '.github/workflows/ansible-deploy.yml' # Workflow changes + +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.14' + + - name: Install dependencies + run: | + pip install ansible ansible-lint + + - name: Run ansible-lint + run: | + cd ansible + ansible-lint playbooks/*.yml + + deploy: + name: Deploy Application + needs: lint + runs-on: self-hosted + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.14' + + - name: Install dependencies + run: | + pip install ansible + + - name: Run playbook + run: | + cd ansible + touch /tmp/vaultpass.txt + chmod 600 /tmp/vaultpass.txt + echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > /tmp/vaultpass.txt + ansible-playbook playbooks/deploy.yml -i inventory/hosts-runner.ini --vault-password-file /tmp/vaultpass.txt + rm /tmp/vaultpass.txt + + - name: Verify deployment + run: | + sleep 10 # Wait for app to start + curl -f http://${{ secrets.VM_HOST }}:5000 || exit 1 + curl -f http://${{ secrets.VM_HOST }}:5000/health || exit 1 diff --git a/ansible/.ansible-lint b/ansible/.ansible-lint new file mode 100644 index 0000000000..0727309ce5 --- /dev/null +++ b/ansible/.ansible-lint @@ -0,0 +1,2 @@ +skip_list: + - no-handler diff --git a/ansible/README.md b/ansible/README.md new file mode 100644 index 0000000000..1785e97af1 --- /dev/null +++ b/ansible/README.md @@ -0,0 +1,3 @@ +[![Ansible Deployment](https://github.com/Error10556/DevOps-Core-Course/actions/workflows/ansible-deploy.yml/badge.svg)](https://github.com/Error10556/DevOps-Core-Course/actions/workflows/ansible-deploy.yml) + +# Ansible deployment diff --git a/ansible/docs/LAB06.md b/ansible/docs/LAB06.md new file mode 100644 index 0000000000..c5813c375b --- /dev/null +++ b/ansible/docs/LAB06.md @@ -0,0 +1,678 @@ +# Task 1 + +```sh +# Test provision with only docker +ansible-playbook playbooks/provision.yml --tags "docker" --ask-vault-pass +``` + +```text +PLAY [Provision web servers] *************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [docker : Add Docker repo] ************************************************ +ok: [devops-vm] + +TASK [docker : update apt cache] *********************************************** +skipping: [devops-vm] + +TASK [docker : Install Docker] ************************************************* +ok: [devops-vm] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [devops-vm] + +TASK [docker : Add ubuntu user to docker group] ******************************** +ok: [devops-vm] + +PLAY RECAP ********************************************************************* +devops-vm : ok=5 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 +``` + +```sh +# Skip common role +ansible-playbook playbooks/provision.yml --skip-tags "common" --ask-vault-pass +``` + +```text +PLAY [Provision web servers] *************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [docker : Add Docker repo] ************************************************ +ok: [devops-vm] + +TASK [docker : update apt cache] *********************************************** +skipping: [devops-vm] + +TASK [docker : Install Docker] ************************************************* +ok: [devops-vm] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [devops-vm] + +TASK [docker : Add ubuntu user to docker group] ******************************** +ok: [devops-vm] + +PLAY RECAP ********************************************************************* +devops-vm : ok=5 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 + +``` + +```sh +# Install packages only across all roles +ansible-playbook playbooks/provision.yml --tags "packages" --ask-vault-pass +``` + +```text +PLAY [Provision web servers] *************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [common : Update apt cache] *********************************************** +ok: [devops-vm] + +TASK [common : Install common packages] **************************************** +ok: [devops-vm] + +TASK [common : Log completion] ************************************************* +changed: [devops-vm] + +PLAY RECAP ********************************************************************* +devops-vm : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 + +``` + +```sh +# Check mode to see what would run +ansible-playbook playbooks/provision.yml --tags "docker" --check --ask-vault-pass +``` + +```text +PLAY [Provision web servers] *************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [docker : Add Docker repo] ************************************************ +ok: [devops-vm] + +TASK [docker : update apt cache] *********************************************** +skipping: [devops-vm] + +TASK [docker : Install Docker] ************************************************* +ok: [devops-vm] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [devops-vm] + +TASK [docker : Add ubuntu user to docker group] ******************************** +ok: [devops-vm] + +PLAY RECAP ********************************************************************* +devops-vm : ok=5 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 +``` + +```sh +# Run only docker installation tasks +ansible-playbook playbooks/provision.yml --tags "docker_install" --ask-vault-pass +``` + +```text +PLAY [Provision web servers] *************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [docker : Add Docker repo] ************************************************ +ok: [devops-vm] + +TASK [docker : update apt cache] *********************************************** +skipping: [devops-vm] + +TASK [docker : Install Docker] ************************************************* +ok: [devops-vm] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [devops-vm] + +PLAY RECAP ********************************************************************* +devops-vm : ok=4 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 +``` + +Note that in some invokations the "Log completion" task is always marked as "changed". This is the consequence of +logging `ansible-playbook` runs. The lab requires to log successful completion, so this is intentional. + +**Tags:** + +```sh +ansible-playbook playbooks/provision.yml --list-tags +``` + +```text +playbook: playbooks/provision.yml + + play #1 (webservers): Provision web servers TAGS: [] + TASK TAGS: [common, docker, docker_config, docker_install, packages] +``` + +# Task 2 + +The "docker" dependency is needed to ensure that the `web_app` role (that requires Docker to function) runs **after** +the `docker` role (which installs Docker). + +First run: + +```text +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [docker : Add Docker repo] ************************************************ +ok: [devops-vm] + +TASK [docker : update apt cache] *********************************************** +skipping: [devops-vm] + +TASK [docker : Install Docker] ************************************************* +ok: [devops-vm] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [devops-vm] + +TASK [docker : Add ubuntu user to docker group] ******************************** +ok: [devops-vm] + +TASK [web_app : load vault] **************************************************** +ok: [devops-vm] + +TASK [web_app : DockerHub Login] *********************************************** +ok: [devops-vm] + +TASK [web_app : Create app directory] ****************************************** +ok: [devops-vm] + +TASK [web_app : Template docker-compose file] ********************************** +changed: [devops-vm] + +TASK [web_app : Deploy with docker-compose] ************************************ +[WARNING]: Docker compose: unknown None: /opt/devops-infoservice/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion +changed: [devops-vm] + +TASK [web_app : Wait for port] ************************************************* +ok: [devops-vm] + +TASK [web_app : Healthcheck] *************************************************** +ok: [devops-vm] + +PLAY RECAP ********************************************************************* +devops-vm : ok=12 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 +``` +(2 changes) + +Second run: +```text +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [docker : Add Docker repo] ************************************************ +ok: [devops-vm] + +TASK [docker : update apt cache] *********************************************** +skipping: [devops-vm] + +TASK [docker : Install Docker] ************************************************* +ok: [devops-vm] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [devops-vm] + +TASK [docker : Add ubuntu user to docker group] ******************************** +ok: [devops-vm] + +TASK [web_app : load vault] **************************************************** +ok: [devops-vm] + +TASK [web_app : DockerHub Login] *********************************************** +ok: [devops-vm] + +TASK [web_app : Create app directory] ****************************************** +ok: [devops-vm] + +TASK [web_app : Template docker-compose file] ********************************** +ok: [devops-vm] + +TASK [web_app : Deploy with docker-compose] ************************************ +[WARNING]: Docker compose: unknown None: /opt/devops-infoservice/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion +ok: [devops-vm] + +TASK [web_app : Wait for port] ************************************************* +ok: [devops-vm] + +TASK [web_app : Healthcheck] *************************************************** +ok: [devops-vm] + +PLAY RECAP ********************************************************************* +devops-vm : ok=12 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 +``` +(0 changes) + +Evidence: +```sh +[timur@timur-croc ~/proj/DevOps-Core-Course/ansible]$ ssh vboxuser@127.0.0.1 -p 10022 +``` +```text +Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.17.0-14-generic x86_64) + + * Documentation: https://help.ubuntu.com + * Management: https://landscape.canonical.com + * Support: https://ubuntu.com/pro + +Expanded Security Maintenance for Applications is not enabled. + +13 updates can be applied immediately. +To see these additional updates run: apt list --upgradable + +Enable ESM Apps to receive additional future security updates. +See https://ubuntu.com/esm or run: sudo pro status + +Last login: Tue Mar 3 11:12:13 2026 from 10.0.2.2 +``` +```sh +vboxuser@devops-vm:~$ docker ps +``` +```text +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +eaa998499d91 timurusmanov/devops-infoservice:latest "gunicorn -b 0.0.0.0…" 4 minutes ago Up 4 minutes 0.0.0.0:5000->5000/tcp devops-infoservice +``` +```sh +vboxuser@devops-vm:~$ docker compose -f /opt/devops-infoservice/docker-compose.yml ps +``` +```text +WARN[0000] /opt/devops-infoservice/docker-compose.yml: the attribute `version` is obsolete , it will be ignored, please remove it to avoid potential confusion +NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS +devops-infoservice timurusmanov/devops-infoservice:latest "gunicorn -b 0.0.0.0…" devops-infoservice 4 minutes ago Up 4 minutes 0.0.0.0:5000->5000/tcp +``` +```sh +vboxuser@devops-vm:~$ curl http://localhost:5000 +``` +(output given as it appeared in the terminal) +```text +{"endpoints":[{"description":"Service information","method":"GET","path":"/"},{"descriptio +n":"Health check","method":"GET","path":"/health"}],"request":{"client_ip":"172.18.0.1","m +ethod":"GET","path":"/","user_agent":"curl/8.5.0"},"runtime":{"current_time":"2026-03-03T1 +1:14:36.366+00:00","timezone":"UTC","uptime_human":"0 hours, 5 minutes","uptime_seconds":3 +00},"service":{"description":"DevOps course info service","framework":"Flask","name":"devo +ps-info-service","version":"1.0.0"},"system":{"architecture":"x86_64","cpu_count":2,"hostn +ame":"eaa998499d91","platform":"Linux","platform_version":"#14~24.04.1-Ubuntu SMP PREEMPT_ +DYNAMIC Thu Jan 15 15:52:10 UTC 2","python_version":"3.13.12"}} +vboxuser@devops-vm:~$ +``` + +Contents of `docker-compose.yml`: +```text +version: '3.8' + +services: + devops-infoservice: + image: timurusmanov/devops-infoservice:latest + container_name: devops-infoservice + ports: + - 0.0.0.0:5000:5000 + environment: {} + restart: unless-stopped +``` + +# Task 3 + +## Scenario 1 + +```sh +ansible-playbook playbooks/deploy.yml +``` +```text +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [docker : Add Docker repo] ************************************************ +ok: [devops-vm] + +TASK [docker : update apt cache] *********************************************** +skipping: [devops-vm] + +TASK [docker : Install Docker] ************************************************* +ok: [devops-vm] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [devops-vm] + +TASK [docker : Add ubuntu user to docker group] ******************************** +ok: [devops-vm] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/timur/proj/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml for devops-vm + +TASK [web_app : Stop and remove containers] ************************************ +skipping: [devops-vm] + +TASK [web_app : Remove docker-compose file and directory] ********************** +skipping: [devops-vm] + +TASK [web_app : Log wipe completion] ******************************************* +skipping: [devops-vm] + +TASK [web_app : load vault] **************************************************** +ok: [devops-vm] + +TASK [web_app : DockerHub Login] *********************************************** +ok: [devops-vm] + +TASK [web_app : Create app directory] ****************************************** +ok: [devops-vm] + +TASK [web_app : Template docker-compose file] ********************************** +ok: [devops-vm] + +TASK [web_app : Deploy with docker-compose] ************************************ +[WARNING]: Docker compose: unknown None: /opt/devops-infoservice/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion +ok: [devops-vm] + +TASK [web_app : Wait for port] ************************************************* +ok: [devops-vm] + +TASK [web_app : Healthcheck] *************************************************** +ok: [devops-vm] + +PLAY RECAP ********************************************************************* +devops-vm : ok=13 changed=0 unreachable=0 failed=0 skipped=4 rescued=0 ignored=0 +``` +All wiping tasks skipped, as expected. + +```sh +ssh vboxuser@127.0.0.1 -p 10022 docker ps +``` +```text +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +eaa998499d91 timurusmanov/devops-infoservice:latest "gunicorn -b 0.0.0.0…" 4 hours ago Up 4 hours 0.0.0.0:5000->5000/tcp devops-infoservice +``` + +## Scenario 2 + +```sh +ansible-playbook playbooks/deploy.yml \ + -e "web_app_wipe=true" \ + --tags web_app_wipe +``` +```text +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [web_app : Load vault] **************************************************** +ok: [devops-vm] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/timur/proj/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml for devops-vm + +TASK [web_app : Stop and remove containers] ************************************ +[WARNING]: Docker compose: unknown None: /opt/devops-infoservice/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion +changed: [devops-vm] + +TASK [web_app : Remove docker-compose file and directory] ********************** +changed: [devops-vm] + +TASK [web_app : Log wipe completion] ******************************************* +ok: [devops-vm] => { + "msg": "Application devops-infoservice wiped successfully" +} + +PLAY RECAP ********************************************************************* +devops-vm : ok=6 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 +``` + +```sh +ssh vboxuser@127.0.0.1 -p 10022 docker ps +``` +```text +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +``` +```sh +ssh vboxuser@127.0.0.1 -p 10022 ls /opt +``` +```text +containerd +VBoxGuestAdditions-7.2.6 +``` + +## Scenario 3 + +```sh +ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" +``` +```text +/usr/lib/python3.14/getpass.py:99: GetPassWarning: Can not control echo on the terminal. + passwd = fallback_getpass(prompt, stream) +Warning: Password input may be echoed. +Vault password: + +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [docker : Add Docker repo] ************************************************ +ok: [devops-vm] + +TASK [docker : update apt cache] *********************************************** +skipping: [devops-vm] + +TASK [docker : Install Docker] ************************************************* +ok: [devops-vm] + +TASK [docker : Ensure Docker is running] *************************************** +ok: [devops-vm] + +TASK [docker : Add ubuntu user to docker group] ******************************** +ok: [devops-vm] + +TASK [web_app : Load vault] **************************************************** +ok: [devops-vm] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/timur/proj/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml for devops-vm + +TASK [web_app : load vault] **************************************************** +ok: [devops-vm] + +TASK [web_app : Stop and remove containers] ************************************ +[ERROR]: Task failed: Module failed: "/opt/devops-infoservice" is not a directory +Origin: /home/timur/proj/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml:6:7 + +4 - name: Wipe web application +5 block: +6 - name: Stop and remove containers + ^ column 7 + +fatal: [devops-vm]: FAILED! => {"changed": false, "msg": "\"/opt/devops-infoservice\" is not a directory"} +...ignoring + +TASK [web_app : Remove docker-compose file and directory] ********************** +ok: [devops-vm] + +TASK [web_app : Log wipe completion] ******************************************* +ok: [devops-vm] => { + "msg": "Application devops-infoservice wiped successfully" +} + +TASK [web_app : DockerHub Login] *********************************************** +ok: [devops-vm] + +TASK [web_app : Create app directory] ****************************************** +changed: [devops-vm] + +TASK [web_app : Template docker-compose file] ********************************** +changed: [devops-vm] + +TASK [web_app : Deploy with docker-compose] ************************************ +[WARNING]: Docker compose: unknown None: /opt/devops-infoservice/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion +changed: [devops-vm] + +TASK [web_app : Wait for port] ************************************************* +ok: [devops-vm] + +TASK [web_app : Healthcheck] *************************************************** +ok: [devops-vm] + +PLAY RECAP ********************************************************************* +devops-vm : ok=17 changed=3 unreachable=0 failed=0 skipped=1 rescued=0 ignored=1 +``` +Here, the task of removing the containers failed because the project has been wiped previously, but this is fine, so the +error is ignored. + +```sh +ssh vboxuser@127.0.0.1 -p 10022 "docker ps" +``` +```text +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +53b905e53d5c timurusmanov/devops-infoservice:latest "gunicorn -b 0.0.0.0…" 2 minutes ago Up 2 minutes 0.0.0.0:5000->5000/tcp devops-infoservice +``` + +# Scenario 4 + +```sh +# 4a: Tag specified but variable false (when condition blocks it) +ansible-playbook playbooks/deploy.yml --tags web_app_wipe +# Result: wipe tasks skipped, deployment runs normally +``` +```text +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [web_app : Load vault] **************************************************** +ok: [devops-vm] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/timur/proj/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml for devops-vm + +TASK [web_app : Stop and remove containers] ************************************ +skipping: [devops-vm] + +TASK [web_app : Remove docker-compose file and directory] ********************** +skipping: [devops-vm] + +TASK [web_app : Log wipe completion] ******************************************* +skipping: [devops-vm] + +PLAY RECAP ********************************************************************* +devops-vm : ok=3 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0 +``` +Indeed, wiping was skipped. + +```sh +# 4b: Variable true, deployment skipped (only wipe runs) +ansible-playbook playbooks/deploy.yml -e "web_app_wipe=true" --tags web_app_wipe +# Result: only wipe, no deployment +``` +```text +PLAY [Deploy application] ****************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [devops-vm] + +TASK [web_app : Load vault] **************************************************** +ok: [devops-vm] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/timur/proj/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml for devops-vm + +TASK [web_app : Stop and remove containers] ************************************ +[WARNING]: Docker compose: unknown None: /opt/devops-infoservice/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion +changed: [devops-vm] + +TASK [web_app : Remove docker-compose file and directory] ********************** +changed: [devops-vm] + +TASK [web_app : Log wipe completion] ******************************************* +ok: [devops-vm] => { + "msg": "Application devops-infoservice wiped successfully" +} + +PLAY RECAP ********************************************************************* +devops-vm : ok=6 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 +``` +App wiped. + +## Screenshot after clean install + +![Browser in vm](/ansible/docs/LAB06_Screenshot_app_in_vm.png) + +# Task 4 + +## Evidence + +### Screenshot of successful workflow run +![successful_workflow_run](/ansible/docs/LAB06_Screenshot_successful_workflow_run.png) +### Output logs showing ansible-lint passing +![ansible-lint_passing](/ansible/docs/LAB06_Screenshot_ansible-lint_passing.png) +### Output logs showing ansible-playbook execution +![ansible-playbook_exec](/ansible/docs/LAB06_Screenshot_ansible-playbook_exec.png) +### Verification step output showing app responding +![deployment_verification](/ansible/docs/LAB06_Screenshot_deployment_verification.png) +### Status badge in README showing passing +![passing_badge](/ansible/docs/LAB06_Screenshot_passing_badge.png) + +## Research + +### What are the security implications of storing SSH keys in GitHub Secrets? + +If I store the private key in GitHub Secrets and my profile gets compromised, the bad actors may log into the machines +that accept my private key. Also, GitHub administration may have access to all the secrets. + +That is why I do not store the keys there, and instead use a local VM. + +### How would you implement a staging → production deployment pipeline? + +With CI/CD on Github Actions: + +1. Deploy to a staging server with ansible in the `dev` branch (or a special `staging` branch) +2. If all is well, merge to `prod` branch, which would deploy to production. + +### What would you add to make rollbacks possible? + +I would use concrete versions instead of `latest` in `group_vars/all.yml`, and then we would simply need to push a +revert commit. + +### How does self-hosted runner improve security compared to GitHub-hosted? + +By principle of least privilege: we do not need to give away our keys and infrastructure information to third parties. +Self-hosted runners do not share any information, so the private information is less likely to be compromised with them. + +# Task 5 + +### Overview + +I deployed a toy application to a toy server by using production-grade professional tooling. + +Technologies: +- ansible, ansible-lint +- Docker (+compose) +- Oracle VirtualBox +- GitHub Actions + +### Every other required section & evidence + +I assume that all documentation should have been written at the stage when I reach task 5. However, I have been +fulfilling the documentation requirements throughout the lab, and the sections in this task only seem to refer to the +previously stated documentation requirements. Therefore, please, kindly refer to the previous 600 lines of this +document. diff --git a/ansible/docs/LAB06_Screenshot_ansible-lint_passing.png b/ansible/docs/LAB06_Screenshot_ansible-lint_passing.png new file mode 100644 index 0000000000..426bdb277a Binary files /dev/null and b/ansible/docs/LAB06_Screenshot_ansible-lint_passing.png differ diff --git a/ansible/docs/LAB06_Screenshot_ansible-playbook_exec.png b/ansible/docs/LAB06_Screenshot_ansible-playbook_exec.png new file mode 100644 index 0000000000..1635380416 Binary files /dev/null and b/ansible/docs/LAB06_Screenshot_ansible-playbook_exec.png differ diff --git a/ansible/docs/LAB06_Screenshot_app_in_vm.png b/ansible/docs/LAB06_Screenshot_app_in_vm.png new file mode 100644 index 0000000000..5fa86bdc40 Binary files /dev/null and b/ansible/docs/LAB06_Screenshot_app_in_vm.png differ diff --git a/ansible/docs/LAB06_Screenshot_deployment_verification.png b/ansible/docs/LAB06_Screenshot_deployment_verification.png new file mode 100644 index 0000000000..8e27eb0c10 Binary files /dev/null and b/ansible/docs/LAB06_Screenshot_deployment_verification.png differ diff --git a/ansible/docs/LAB06_Screenshot_passing_badge.png b/ansible/docs/LAB06_Screenshot_passing_badge.png new file mode 100644 index 0000000000..61397fe3e5 Binary files /dev/null and b/ansible/docs/LAB06_Screenshot_passing_badge.png differ diff --git a/ansible/docs/LAB06_Screenshot_successful_workflow_run.png b/ansible/docs/LAB06_Screenshot_successful_workflow_run.png new file mode 100644 index 0000000000..3953819aa1 Binary files /dev/null and b/ansible/docs/LAB06_Screenshot_successful_workflow_run.png differ diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml index 6f28a0f3e1..3fec329b87 100644 --- a/ansible/group_vars/all.yml +++ b/ansible/group_vars/all.yml @@ -1,18 +1,17 @@ -$ANSIBLE_VAULT;1.1;AES256 -35666430376135623761373732656538383137656466336266366239303435363364353462323136 -3765323833663966376435333161643165363562656364390a663564353162643738303335646261 -32613533636439346534623965376632363761656532666462613536613235633965626138353866 -3636316635316430320a633562383464383065626561333064633661616364663534653531636533 -31326237313365373739663031353664613036656230323239613165663932656364326630353135 -35306339626335616238623561353030323063363531383331646464323066626230616336616436 -39386238633763616530396361336563663366383831643431663562316331323834663336613932 -61643833383139396265353330643237356335343266363261333765363735633731346565646437 -65653262333733623732623962326434613334383131393461333533356136303836356264383461 -33303533653533633038383038643330393763356334353465373464373166366665346366643432 -32646233323362653635313966633636633139393165353339653535666136613039666336646161 -34366137623666333632386231643238366265373064346564316637393935643063313831343861 -36303064383063363630386566393539333664386665386162376238313134666538303261363332 -34303537656335376338363533646630653862656164313939366263663662383136373932663665 -30393463643534663434663538663934303666316534333932346164343866636161383763666139 -38393365653832383536613535666534383634353536353233653537653762626133376361613065 -36383937376137623539626363363866336566643235393565633463326665396664 +--- +dockerhub_username: timurusmanov +dockerhub_password: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 38633834386561613630303439353562303338306334353166653965653335333263383966643930 + 3633346163343963623737646666393461306638383461360a336431356230306536626239333066 + 36633732373961653162323132666164373731666130346335306439383031336333653632343234 + 6539616536623663330a383333626434343934616361376531313435323462303964383532376566 + 38643132656339333438633264623932346661356637376666313565663266613237353033356635 + 6334356236306138643930366535663332366436653232313665 + +app_name: devops-infoservice +docker_image: "{{ dockerhub_username }}/{{ app_name }}" +docker_image_tag: latest +app_host: 0.0.0.0 +app_port: 5000 +app_container_name: "{{ app_name }}" diff --git a/ansible/inventory/hosts-runner.ini b/ansible/inventory/hosts-runner.ini new file mode 100644 index 0000000000..58e297acda --- /dev/null +++ b/ansible/inventory/hosts-runner.ini @@ -0,0 +1,2 @@ +[webservers] +devops-vm ansible_host=127.0.0.1 ansible_user=vboxuser diff --git a/ansible/playbooks/deploy.yml b/ansible/playbooks/deploy.yml index 56850a7585..95174b9e0e 100644 --- a/ansible/playbooks/deploy.yml +++ b/ansible/playbooks/deploy.yml @@ -1,7 +1,7 @@ --- - name: Deploy application hosts: webservers - become: yes + become: true roles: - - app_deploy + - web_app diff --git a/ansible/playbooks/provision.yml b/ansible/playbooks/provision.yml index 1e88a7e3ca..a2173bfa9c 100644 --- a/ansible/playbooks/provision.yml +++ b/ansible/playbooks/provision.yml @@ -1,7 +1,9 @@ - name: Provision web servers hosts: webservers - become: yes + become: true roles: - - common + - role: common + tags: + - common - docker diff --git a/ansible/playbooks/site.yml b/ansible/playbooks/site.yml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ansible/roles/app_deploy/defaults/main.yml b/ansible/roles/app_deploy/defaults/main.yml deleted file mode 100644 index 28b2e06e76..0000000000 --- a/ansible/roles/app_deploy/defaults/main.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -default_port: 5000 -default_restart_policy: unless-stopped -default_environment_variables: '{}' diff --git a/ansible/roles/app_deploy/handlers/main.yml b/ansible/roles/app_deploy/handlers/main.yml deleted file mode 100644 index 02471f6ea9..0000000000 --- a/ansible/roles/app_deploy/handlers/main.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -- name: restart container - community.docker.docker_container: - name: '{{ app_container_name }}' - image: '{{ docker_image }}:{{ docker_image_tag }}' - published_ports: - - '{{ app_port }}:{{ default_port }}' - env: "{{ default_environment_variables }}" - restart_policy: '{{ default_restart_policy }}' - state: restarted diff --git a/ansible/roles/app_deploy/tasks/main.yml b/ansible/roles/app_deploy/tasks/main.yml deleted file mode 100644 index d43fa6ee63..0000000000 --- a/ansible/roles/app_deploy/tasks/main.yml +++ /dev/null @@ -1,38 +0,0 @@ ---- -- name: load vault - ansible.builtin.include_vars: '../group_vars/all.yml' - -- name: DockerHub Login - community.docker.docker_login: - username: '{{ dockerhub_username }}' - password: '{{ dockerhub_password }}' - no_log: true - -- name: Pull image - community.docker.docker_image_pull: - name: '{{ docker_image }}' - tag: '{{ docker_image_tag }}' - -- name: Stop and remove running container - community.docker.docker_container: - name: '{{ app_container_name }}' - state: absent - -- name: Run container - community.docker.docker_container: - name: '{{ app_container_name }}' - image: '{{ docker_image }}:{{ docker_image_tag }}' - published_ports: - - '{{ app_port }}:{{ default_port }}' - env: "{{ default_environment_variables }}" - restart_policy: '{{ default_restart_policy }}' - state: started - -- name: Wait for port - ansible.builtin.wait_for: - port: '{{ app_port }}' - -- name: Healthcheck - ansible.builtin.uri: - url: 'http://localhost:{{ app_port }}/health' - status_code: 200 diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml index 493e3d0be3..f27c207303 100644 --- a/ansible/roles/common/tasks/main.yml +++ b/ansible/roles/common/tasks/main.yml @@ -1,13 +1,26 @@ ---- -- name: Update apt cache - apt: - update_cache: yes - cache_valid_time: 3600 +- name: Install required apt packages + tags: + - packages + block: + - name: Update apt cache + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 -- name: Install common packages - apt: - name: "{{ common_packages }}" - state: present + - name: Install common packages + ansible.builtin.apt: + name: "{{ common_packages }}" + state: present + rescue: + - name: Fix apt + ansible.builtin.apt: + state: fixed + always: + - name: Log completion + ansible.builtin.file: + path: /tmp/ansible-packages-complete + state: touch + mode: '644' - name: Set timezone to UTC community.general.timezone: diff --git a/ansible/roles/docker/defaults/main.yml b/ansible/roles/docker/defaults/main.yml index 51c637c764..8fb62a69dc 100644 --- a/ansible/roles/docker/defaults/main.yml +++ b/ansible/roles/docker/defaults/main.yml @@ -1,2 +1,2 @@ --- -ubuntu_user: vboxuser +docker_ubuntu_user: vboxuser diff --git a/ansible/roles/docker/handlers/main.yml b/ansible/roles/docker/handlers/main.yml index 3627303e6b..07aa0eb290 100644 --- a/ansible/roles/docker/handlers/main.yml +++ b/ansible/roles/docker/handlers/main.yml @@ -1,5 +1,5 @@ --- -- name: restart docker - service: +- name: Restart docker + ansible.builtin.service: name: docker state: restarted diff --git a/ansible/roles/docker/tasks/main.yml b/ansible/roles/docker/tasks/main.yml index e155cdb79a..103c071659 100644 --- a/ansible/roles/docker/tasks/main.yml +++ b/ansible/roles/docker/tasks/main.yml @@ -1,35 +1,53 @@ ---- -- name: Add Docker repo - ansible.builtin.deb822_repository: - check_date: true - check_valid_until: true - name: docker - uris: https://download.docker.com/linux/ubuntu - suites: '{{ ansible_facts["distribution_release"] }}' - components: stable - signed_by: https://download.docker.com/linux/ubuntu/gpg - register: repo_added +- name: Install Docker + tags: + - docker_install + - docker + block: + - name: Add Docker repo + ansible.builtin.deb822_repository: + check_date: true + check_valid_until: true + name: docker + uris: https://download.docker.com/linux/ubuntu + suites: '{{ ansible_facts["distribution_release"] }}' + components: stable + signed_by: https://download.docker.com/linux/ubuntu/gpg + register: docker_repo_added + retries: 4 + delay: 10 + until: docker_repo_added is not failed -- name: update apt cache - when: repo_added is changed - ansible.builtin.apt: - update_cache: true + - name: Update apt cache + when: docker_repo_added is changed + ansible.builtin.apt: + update_cache: true -- name: Install Docker - ansible.builtin.apt: - name: - - docker-ce - - docker-ce-cli - - containerd.io - - python3-docker - state: present - notify: - - restart docker + - name: Install Docker + ansible.builtin.apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - python3-docker + state: present + rescue: + - name: Retry updating apt cache + ansible.builtin.apt: + update_cache: true + always: + - name: Ensure Docker is running + ansible.builtin.service: + name: docker.service + enabled: true + state: started -- name: Add ubuntu user to docker group - ansible.builtin.user: - name: '{{ ubuntu_user }}' - groups: docker - append: true - notify: - - restart docker +- name: Configure Docker user + tags: + - docker_config + - docker + block: + - name: Add ubuntu user to docker group + ansible.builtin.user: + name: '{{ docker_ubuntu_user }}' + groups: docker + append: true diff --git a/ansible/roles/web_app/defaults/main.yml b/ansible/roles/web_app/defaults/main.yml new file mode 100644 index 0000000000..923a5353b8 --- /dev/null +++ b/ansible/roles/web_app/defaults/main.yml @@ -0,0 +1,7 @@ +--- +web_app_default_port: 5000 +web_app_default_restart_policy: unless-stopped +web_app_default_environment_variables: '{}' +web_app_compose_project_dir: "/opt/{{ app_name }}" +web_app_docker_compose_version: "'3.8'" +web_app_wipe: false diff --git a/ansible/roles/web_app/handlers/main.yml b/ansible/roles/web_app/handlers/main.yml new file mode 100644 index 0000000000..c9aef1b0d4 --- /dev/null +++ b/ansible/roles/web_app/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Restart container + community.docker.docker_compose_v2: + pull: always + project_src: '{{ web_app_compose_project_dir }}' + state: restarted diff --git a/ansible/roles/web_app/meta/main.yml b/ansible/roles/web_app/meta/main.yml new file mode 100644 index 0000000000..6ad37f8159 --- /dev/null +++ b/ansible/roles/web_app/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - role: docker diff --git a/ansible/roles/web_app/tasks/main.yml b/ansible/roles/web_app/tasks/main.yml new file mode 100644 index 0000000000..511b4a016b --- /dev/null +++ b/ansible/roles/web_app/tasks/main.yml @@ -0,0 +1,68 @@ +--- +- name: Load vault + ansible.builtin.include_vars: '../../group_vars/all.yml' + tags: + - web_app_wipe + +- name: Include wipe tasks + ansible.builtin.include_tasks: wipe.yml + tags: + - web_app_wipe + +- name: DockerHub Login + community.docker.docker_login: + username: '{{ dockerhub_username }}' + password: '{{ dockerhub_password }}' + no_log: true + tags: + - app_deploy + - compose + +- name: Deploy application with Docker Compose + tags: + - app_deploy + - compose + + block: + - name: Create app directory + ansible.builtin.file: + path: '{{ web_app_compose_project_dir }}' + state: directory + mode: '755' + + - name: Template docker-compose file + ansible.builtin.template: + src: docker-compose.yml.j2 + dest: '{{ web_app_compose_project_dir }}/docker-compose.yml' + mode: '644' + + - name: Deploy with docker-compose + community.docker.docker_compose_v2: + pull: always + project_src: '{{ web_app_compose_project_dir }}' + state: present + + rescue: + - name: Log failure + ansible.builtin.debug: + msg: "Docker Compose FAILED! Shutting down" + + # - name: Handle deployment failure + # community.docker.docker_compose_v2: + # project_src: '{{ compose_project_dir }}' + # state: absent + +- name: Wait for port + ansible.builtin.wait_for: + port: '{{ app_port }}' + tags: + - app_deploy + - compose + +- name: Healthcheck + ansible.builtin.uri: + url: 'http://localhost:{{ app_port }}/health' + status_code: 200 + tags: + - app_deploy + - compose diff --git a/ansible/roles/web_app/tasks/wipe.yml b/ansible/roles/web_app/tasks/wipe.yml new file mode 100644 index 0000000000..c2f635ab49 --- /dev/null +++ b/ansible/roles/web_app/tasks/wipe.yml @@ -0,0 +1,22 @@ +- name: Load vault + ansible.builtin.include_vars: '../group_vars/all.yml' + +- name: Wipe web application + when: web_app_wipe | bool + tags: + - web_app_wipe + block: + - name: Stop and remove containers + community.docker.docker_compose_v2: + project_src: '{{ web_app_compose_project_dir }}' + state: absent + failed_when: false + + - name: Remove docker-compose file and directory + ansible.builtin.file: + path: '{{ web_app_compose_project_dir }}' + state: absent + + - name: Log wipe completion + ansible.builtin.debug: + msg: "Application {{ app_name }} wiped successfully" 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..9403bf12b6 --- /dev/null +++ b/ansible/roles/web_app/templates/docker-compose.yml.j2 @@ -0,0 +1,10 @@ +version: {{ web_app_docker_compose_version }} + +services: + {{ app_name }}: + image: {{ docker_image }}:{{ docker_image_tag }} + container_name: {{ app_name }} + ports: + - {{ app_host }}:{{ app_port }}:{{ web_app_default_port }} + environment: {{ web_app_default_environment_variables }} + restart: {{ web_app_default_restart_policy }}