Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/workflows/ansible-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Ansible Deployment

on:
push:
paths:
- 'ansible/**'
- '.github/workflows/ansible-deploy.yml'

jobs:

lint:
name: Ansible Lint
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install Ansible
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: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install Ansible
run: pip install ansible

- name: Setup SSH
run: |
mkdir -p $HOME/.ssh
chmod 700 $HOME/.ssh

echo "${{ secrets.SSH_PRIVATE_KEY }}" | tr -d '\r' > $HOME/.ssh/id_rsa
chmod 600 $HOME/.ssh/id_rsa

ssh-keyscan -H ${{ secrets.VM_HOST }} >> $HOME/.ssh/known_hosts
chmod 644 $HOME/.ssh/known_hosts

ls -la $HOME/.ssh

- name: Deploy with Ansible
run: |
cd ansible
echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > /tmp/vault_pass
ansible-playbook playbooks/deploy.yml \
-i inventory/hosts.ini \
--vault-password-file /tmp/vault_pass
rm /tmp/vault_pass

- name: Verify deployment
run: |
sleep 10
curl -f http://${{ secrets.VM_HOST }}:5000
83 changes: 83 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Python CI

on:
[push, pull_request]

permissions:
contents: read

jobs:

test:
name: Lint & Tests
runs-on: ubuntu-latest
timeout-minutes: 10

strategy:
fail-fast: true

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r app_python/requirements.txt
pip install pytest ruff

- name: Lint
run: ruff check .

- name: Run tests
run: pytest

- name: Setup Snyk
uses: snyk/actions/setup@master

- name: Run Snyk
run: snyk test --file=app_python/requirements.txt
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}


docker:
name: Build & Push Docker
needs: test
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set version (CalVer)
id: version
run: |
echo "VERSION=$(date +'%Y.%m')" >> $GITHUB_OUTPUT

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./app_python
file: ./app_python/Dockerfile
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/app_python:2026.02
${{ secrets.DOCKERHUB_USERNAME }}/app_python:latest
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
test
test

# Ansible
*.retry
.vault_pass
ansible/inventory/*.pyc
__pycache__/
10 changes: 10 additions & 0 deletions ansible/ansible.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[defaults]
inventory = inventory/hosts.ini
roles_path = roles
host_key_checking = False
retry_files_enabled = False

[privilege_escalation]
become = True
become_method = sudo
become_user = root
100 changes: 100 additions & 0 deletions ansible/docs/LAB05.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# 1. Architecture Overview

### Ansible version: 2.12.5
### Target VM OS: Ubuntu 24.04 LTS
### Role structure:
```text
ansible/
├── inventory/
│ └── hosts.ini
├── roles/
│ ├── common/
│ ├── docker/
│ └── app_deploy/
├── playbooks/
│ ├── site.yml
│ ├── provision.yml
│ └── deploy.yml
├── group_vars/
│ └── all.yml
├── ansible.cfg
└── docs/
└── LAB05.md
```

### Why roles:
- They allow you to separate tasks by functionality.
- They improve code reuse.
- They are easy to maintain and test independently.

# 2. Roles Documentation
### 2.1 common
- Purpose: Basic system setup (apt update, package installation, time settings).
- Variables: list of packages defaults/main.yml.
- Handlers: no.
- Dependencies: no.

### 2.2 docker
- Purpose: Install and run Docker, add a user to the docker group.
- Variables: Docker version, username.
- Handlers: restart docker service.
- Dependencies: common.
### 2.3 app_deploy
- Purpose: deploying a Python container.
- Variables: Docker Hub username, password, application name, port, container name, image tag.
- Handlers: restart the container if necessary.
- Dependencies: docker.

# 3. Idempotency Demonstration

### First run of playbook deploy.yml:
![First run](ansible/docs/screenshots/img.png)

### Second run of playbook deploy.yml:
![Second run](ansible/docs/screenshots/img_1.png)

### Analysis:

- `ok` — tasks where no changes were made (e.g., a port is already open).
- `changed` — tasks that updated the state (pull, run, restart the container).
The roles are idempotent by design, but starting a container always calls "changed" because we're deleting the old one and creating a new one.

# 4. Ansible Vault Usage

### Vault file: `group_vars/all.yml`
Content:
```yml
dockerhub_username: th1ef
dockerhub_password: my_password
app_name: devops-info-service
docker_image: "{{ dockerhub_username }}/{{ app_name }}"
docker_image_tag: latest
app_port: 5000
app_container_name: "{{ app_name }}"
```

# 5. Deployment Verification
### Container condition:
![Container condition](ansible/docs/screenshots/img_2.png)

### Health check:
![Health check](ansible/docs/screenshots/img_3.png)


### 6. Key Decisions

- **Why roles instead of monolithic playbooks?** \
They allow you to structure tasks and are easier to maintain and test.
- **How do roles improve reusability?** \
You can use a single role across different VMs and projects without duplicating code.
- **What makes a task idempotent?** \
The module checks the current state and makes changes only when necessary (state: present, state: started).
- **How do handlers improve efficiency?** \
They are executed only when the state changes, preventing unnecessary service restarts.
- **Why Ansible Vault?** \
To securely store sensitive data (passwords, tokens) in the repository.

### 7. Challenges
- Errors when logging into Docker Hub without a collection.
- Incorrect SSH key permissions in the container.
- Pull errors if the image wasn't on Docker Hub — resolved by uploading the image using your own account.
Binary file added ansible/docs/screenshots/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ansible/docs/screenshots/img_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ansible/docs/screenshots/img_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ansible/docs/screenshots/img_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions ansible/group_vars/all.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
$ANSIBLE_VAULT;1.1;AES256
33303266313739353338396463653730643034633662316330616232646135633331336434396235
6366396537663866393138626166613733386230636234650a303061336566353166303233653338
62323565393239613333646162313930313363323761373331326364303438336437346235653337
3034396131386262660a653637646338623762303763326238663339326565323364313137306561
63666438396263373864353032333632623366396666643835313662653966616532643566343331
38613662616235653162656530666661663934613730363364636434643731643966333738646532
32626361353164653939623439373631353566356532366132633166383137326335613063366566
65333630623338373362366363663836623730613062366465356262396132363365663661363732
31313561666339646262383162393739393531633832616132653661323164633361376137613930
39616430346330646231636339653063633233373836313738666535393232373366646665303763
38333533383236383235366465666336363038386332333336393532333664623432336266356564
35333164656161376538393136373033373634613263313236336536656633643339303963396666
35656132353061333061333661376566353461363335343936326437383230623830616239626230
38356632656364306130376338333737386239323361646337316661373161313435666264326263
34306638633239646363363537303862333764623832333466383039343632316533666232663330
38363134363234363262666336386331376662383335396334386566653036633436353833333237
65313161393535393035656332326362363535363430663961316366613030643131353662356331
61623238613462326166363330383337363030653635633236666437386233363737383137316263
62313636386532633931343963643761306434376331616438326435313930316533653332646537
30353831363266383937623862306230356661323935343766643039363131343437356130663662
33643439386138626431633736396637326333643463353863316538633933633033626530326333
3531633630643632363131653131623336336331343362396231
2 changes: 2 additions & 0 deletions ansible/inventory/hosts.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[webservers]
vm ansible_host=93.77.177.133 ansible_user=ubuntu
8 changes: 8 additions & 0 deletions ansible/playbooks/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
- name: Deploy Python Application
hosts: webservers
become: true
vars_files:
- group_vars/all.yml
roles:
- role: web_app
7 changes: 7 additions & 0 deletions ansible/playbooks/provision.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
- name: Provision Web Servers
hosts: webservers
become: true
roles:
- role: common
- role: docker
7 changes: 7 additions & 0 deletions ansible/roles/common/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
common_packages:
- python3-pip
- curl
- git
- vim
- htop
55 changes: 55 additions & 0 deletions ansible/roles/common/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
- name: Install base packages
become: true
tags:
- packages
- common
block:

- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600

- name: Install required packages
ansible.builtin.apt:
name:
- curl
- git
- vim
state: present

rescue:
- name: Fix missing packages and retry update
ansible.builtin.apt:
update_cache: true
force_apt_get: true
become: true

always:
- name: Log package block completion
ansible.builtin.copy:
content: "Package installation block completed\n"
dest: /tmp/common_packages.log
mode: "0644"

- name: Manage system users
become: true
tags:
- users
- common
block:

- name: Ensure deploy user exists
ansible.builtin.user:
name: deploy
groups: sudo
append: true
state: present

always:
- name: Log user block completion
ansible.builtin.copy:
content: "User management block completed\n"
dest: /tmp/common_users.log
mode: "0644"
2 changes: 2 additions & 0 deletions ansible/roles/docker/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
docker_user: ubuntu
Loading
Loading