Skip to content
Open

Lab05 #2744

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
119 changes: 119 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
name: Python CI Pipeline

on:
push:
paths:
- "app_python/**"
- ".github/workflows/python-ci.yml"
pull_request:
paths:
- "app_python/**"
- ".github/workflows/python-ci.yml"

# GitHub cancels older runs when a newer one starts in the same group
concurrency:
group: python-ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Lint & Test
runs-on: ubuntu-latest

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

- uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
cache-dependency-path: |
app_python/requirements.txt

- name: Install dependencies
working-directory: app_python
run: |
pip install --upgrade pip
pip install -r requirements.txt
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
pip install flake8

- name: Cache flake8
uses: actions/cache@v3
with:
path: ~/.cache/flake8
key: ${{ runner.os }}-flake8

- name: Run linter (flake8)
working-directory: app_python
run: flake8 .

- name: Run unit tests
working-directory: app_python
run: pytest -v

security:
name: Snyk Security Scan
runs-on: ubuntu-latest
needs: test

permissions:
security-events: write

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
cache-dependency-path: |
app_python/requirements.txt

- name: Install dependencies
working-directory: app_python
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Run Snyk scan
uses: snyk/actions/python@master
continue-on-error: true # To make sure that SARIF upload gets called
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --sarif-file-output=snyk.sarif --skip-unresolved app_python

- name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: snyk.sarif

docker:
name: Build & Push Docker Image
runs-on: ubuntu-latest
needs: [test, security]
if: github.ref == 'refs/heads/master'

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

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

- name: Generate CalVer version
run: echo "VERSION=$(date +'%Y.%m')" >> $GITHUB_ENV

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: ./app_python
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/app_python:${{ env.VERSION }}
${{ secrets.DOCKERHUB_USERNAME }}/app_python:latest
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
test
test
**/__pycache__/
1 change: 1 addition & 0 deletions ansible/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
inventory/group_vars/all.yml
11 changes: 11 additions & 0 deletions ansible/ansible.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[defaults]
inventory = inventory/hosts.ini
roles_path = roles
host_key_checking = False
remote_user = ubuntu
retry_files_enabled = False

[privilege_escalation]
become = True
become_method = sudo
become_user = root
198 changes: 198 additions & 0 deletions ansible/docs/LAB05.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Lab 5 — Ansible Fundamentals Documentation

## 1. Architecture Overview

- **Ansible version:** 2.20.0
- **Target VM OS:** Ubuntu 24.04 LTS
- **Role-based structure:**
The project uses a modular role-based approach with three main roles: `common`, `docker`, and `app_deploy`. Each role contains tasks, handlers, and default variables to ensure reusability and maintainability.

**Role structure diagram:**

```
ansible/
├── roles/
│ ├── common/
│ ├── docker/
│ └── app_deploy/
├── playbooks/
│ ├── site.yml
│ ├── provision.yml
│ └── deploy.yml
├── inventory/
│ └── hosts.ini
├── group_vars/
│ └── all.yml
└── ansible.cfg
```


**Why roles instead of monolithic playbooks?**
Roles allow modular, reusable, and maintainable automation. Changes can be made in one role without affecting others, and roles can be reused across multiple playbooks or projects.

---

## 2. Roles Documentation

### 2.1 `common` Role

- **Purpose:** System provisioning, including updating apt cache, installing essential packages, and configuring basic system settings.
- **Variables (defaults/main.yml):**
```yaml
common_packages:
- python3-pip
- curl
- git
- vim
- htop
```

- Tasks:
- Update apt cache
- Install common packages
- Handlers: None
- Dependencies: None

2.2 `docker` **Role**
- Purpose: Install Docker engine, manage Docker service, and configure user access.
- Variables (defaults/main.yml):
```yaml
docker_user: ubuntu
docker_packages:
- docker-ce
- docker-ce-cli
- containerd.io
```

- Tasks:
- Add Docker GPG key and repository
- Install Docker packages
- Add user to docker group

- Handlers:
- `restart docker` — triggered if Docker service needs to restart

- Dependencies: None

2.3 `app_deploy` Role
- Purpose: Deploy containerized Python application.
- Variables (vaulted in group_vars/all.yml):
```yaml
dockerhub_username: <vaulted>
dockerhub_password: <vaulted>
app_name: devops-app
docker_image: "{{ dockerhub_username }}/{{ app_name }}"
docker_image_tag: latest
app_port: 5000
app_container_name: "{{ app_name }}"
```

- Tasks:
- Docker login with vaulted credentials
- Pull Docker image
- Stop and remove existing container
- Run new container with port mapping and restart policy
- Wait for port to become available
- Health check via /health endpoint

- Handlers:
- `restart app container` — triggered if container needs to restart

- Dependencies: Docker must be installed (docker role)

## 3. Idempotency Demonstration

### First Run (provision.yml)
```text
TASK [common : Update apt cache] ... changed
TASK [common : Install common packages] ... changed
TASK [docker : Install Docker packages] ... changed
TASK [docker : Add user to docker group] ... changed
```

### Second Run (provision.yml)
```text
TASK [common : Update apt cache] ... ok
TASK [common : Install common packages] ... ok
TASK [docker : Install Docker packages] ... ok
TASK [docker : Add user to docker group] ... ok
```

### Analysis:

- First run shows changed because packages and users were added.
- Second run shows ok because the desired state is already achieved.
- This confirms idempotency of roles and tasks.

---

## 4. Ansible Vault Usage

- Purpose: Securely store sensitive credentials (Docker Hub username and password).
- Vault file: inventory/group_vars/all.yml
- Vault commands used:

```bash
ansible-vault create inventory/group_vars/all.yml
ansible-playbook playbooks/deploy.yml --ask-vault-pass
```

- Vault password management: Prompted interactively during playbook runs. Password not stored in repo.

- Importance: Prevents sensitive data from being exposed in version control or logs.

---

## 5. Deployment Verification

- Playbook run:

```text
TASK [app_deploy : Login to Docker Hub] ... ok
TASK [app_deploy : Pull Docker image] ... ok
TASK [app_deploy : Run app container] ... ok
```

- Container status:

```
yandex-cluod | CHANGED | rc=0 >>
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d55ac5f0abd1 danielambda/devops-app:latest "python app.py" 47 minutes ago Up 47 minutes 0.0.0.0:5000->5000/tcp, 8000/tcp devops-app
```

- Health check:

```bash
󰘧 curl http://89.169.148.189:5000/health
{"status":"healthy","timestamp":"2026-02-25T17:54:54.118796+00:00","uptime_seconds":2987}
```

- Handler execution:
The `restart app container` handler triggers only if the container needs a restart.

---

## 6. Key Decisions

- Why use roles instead of plain playbooks?
Roles improve modularity, reusability, and maintainability; tasks are organized logically.

- How do roles improve reusability?
Each role can be used in multiple playbooks or projects without rewriting tasks.

- What makes a task idempotent?
Tasks use stateful modules (`apt`, `service`, `docker_container`) to ensure repeated runs produce the same outcome.

- How do handlers improve efficiency?
Handlers run only when triggered, reducing unnecessary service restarts and optimizing playbook execution.

- Why is Ansible Vault necessary?
Vault encrypts sensitive credentials (like Docker Hub passwords), allowing secure storage in version control.

---

## 7. Challenges (Optional)
- Initial confusion with group_vars location; fixed by placing it under inventory/.
- Docker image 404 error because image was not pushed to Docker Hub; resolved by building and pushing the image.
- Ensuring idempotency in package installation and Docker tasks.
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]
yandex-cluod ansible_host=89.169.148.189 ansible_user=ubuntu
6 changes: 6 additions & 0 deletions ansible/playbooks/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- name: Deploy application
hosts: webservers
become: yes

roles:
- app_deploy
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: yes

roles:
- common
- docker
Empty file added ansible/playbooks/site.yml
Empty file.
1 change: 1 addition & 0 deletions ansible/roles/app_deploy/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
restart_policy: unless-stopped
4 changes: 4 additions & 0 deletions ansible/roles/app_deploy/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- name: restart app container
community.docker.docker_container:
name: "{{ app_container_name }}"
state: restarted
Loading