Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
130 changes: 130 additions & 0 deletions .github/workflows/ansible-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
name: Ansible Deployment

on:
workflow_dispatch:
push:
branches: [ main, master, lab6-solution ]
paths:
- 'ansible/**'
- '!ansible/docs/**'
- '.github/workflows/ansible-deploy.yml'

jobs:
lint:
name: Ansible Lint
runs-on: ubuntu-latest
steps:
- 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


- name: Run ansible-lint (exclude vault and roles)
run: |
echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > /tmp/vault_pass
chmod 600 /tmp/vault_pass

cd lab_solutions/lab1/ansible

export ANSIBLE_VAULT_PASSWORD_FILE=/tmp/vault_pass
ansible-lint playbooks/*.yml --exclude group_vars/ --exclude roles/ --profile=min

- name: Cleanup Lint Vault File
if: always()
run: rm -f /tmp/vault_pass



syntax-check:
name: Ansible Syntax Check
runs-on: ubuntu-latest
steps:
- 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

- name: Create vault password file
run: |
echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > /tmp/vault_pass
chmod 600 /tmp/vault_pass

- name: Run syntax check
run: |
cd lab_solutions/lab1/ansible
ansible-playbook playbooks/deploy.yml --syntax-check --vault-password-file /tmp/vault_pass
ansible-playbook playbooks/provision.yml --syntax-check

- name: Cleanup
if: always()
run: rm -f /tmp/vault_pass

deploy:
name: Deploy to VM
needs: [lint, syntax-check]
runs-on: ubuntu-latest
if: github.event_name == 'push'
environment: production

steps:
- 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
chmod 600 /tmp/vault_pass

- name: Create inventory file
run: |
cd lab_solutions/lab1/ansible
mkdir -p inventory
cat > inventory/hosts.ini <<INNER_EOF
[webservers]
lab-vm ansible_host=${{ secrets.VM_HOST }} ansible_user=ubuntu ansible_ssh_private_key_file=/tmp/ssh_key

[webservers:vars]
ansible_python_interpreter=/usr/bin/python3
INNER_EOF

- name: Setup SSH key
run: |
echo "${{ secrets.SSH_PRIVATE_KEY }}" > /tmp/ssh_key
chmod 600 /tmp/ssh_key

- name: Run Ansible deploy
run: |
cd lab_solutions/lab1/ansible
ansible-playbook playbooks/deploy.yml \
--vault-password-file /tmp/vault_pass \
--ssh-common-args="-o StrictHostKeyChecking=no"

- name: Cleanup
if: always()
run: |
rm -f /tmp/vault_pass /tmp/ssh_key

- name: Verify deployment
run: |
sleep 10
curl -f http://${{ secrets.VM_HOST }}:8000/health || echo "Health check failed"
86 changes: 86 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: python-ci

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

concurrency:
group: python-ci-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
APP_DIR: lab_solutions/lab1/app_python
IMAGE_NAME: ${{ secrets.DOCKERHUB_USERNAME }}/devops-info-service

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"
cache-dependency-path: |
lab_solutions/lab1/app_python/requirements.txt
lab_solutions/lab1/app_python/requirements-dev.txt

- name: Install dependencies
working-directory: ${{ env.APP_DIR }}
run: pip install -r requirements.txt -r requirements-dev.txt

- name: Lint
working-directory: ${{ env.APP_DIR }}
run: ruff check .

- name: Run tests with coverage
working-directory: ${{ env.APP_DIR }}
run: pytest --cov=app --cov-report=term --cov-report=xml

- name: Snyk scan
uses: snyk/actions/python@0.4.0
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --file=lab_solutions/lab1/app_python/requirements.txt --severity-threshold=high --skip-unresolved

docker:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
steps:
- name: Checkout
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: Set version (CalVer)
run: echo "VERSION=$(date +%Y.%m.%d)" >> $GITHUB_ENV

- name: Build and push image
uses: docker/build-push-action@v6
with:
context: ${{ env.APP_DIR }}
file: ${{ env.APP_DIR }}/Dockerfile
push: true
tags: |
${{ env.IMAGE_NAME }}:${{ env.VERSION }}
${{ env.IMAGE_NAME }}:latest
23 changes: 22 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
test
test
/venv
*.tfstate
*.tfstate.*
.terraform/
terraform.tfvars
*.tfvars
sa-key.json
*.pem
*.key
inno-key.json
.terraform.lock.hcl

# Ansible
*.retry
.vault_pass
ansible/inventory/*.pyc
__pycache__/
venv

.vault_pass
.retry
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![Labs](https://img.shields.io/badge/Labs-18-blue)](#labs)
[![Exam](https://img.shields.io/badge/Exam-Optional-green)](#exam-alternative)
[![Duration](https://img.shields.io/badge/Duration-18%20Weeks-lightgrey)](#course-roadmap)
[![Ansible Deployment](https://github.com/Leropsis/DevOps-Core-Course-v/actions/workflows/ansible-deploy.yml/badge.svg)](https://github.com/Leropsis/DevOps-Core-Course-v/actions/workflows/ansible-deploy.yml)

Master **production-grade DevOps practices** through hands-on labs. Build, containerize, deploy, monitor, and scale applications using industry-standard tools.

Expand Down
92 changes: 92 additions & 0 deletions lab_solutions/lab1/LAB04.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Lab 4 — Terraform Documentation

## 1. Cloud Provider & Infrastructure
- **Cloud provider chosen:** Yandex Cloud — chosen because it offers free tier, works in Russia without VPN issues, and has good Terraform/Pulumi provider support.
- **Instance type/size and why:**
- Platform: standard-v2
- Resources: 2 vCPUs, 1 GB RAM, core_fraction=20%
- Disk: 10 GB network-hdd
- Reason: This matches Yandex Cloud free tier limits, costs $0, and is sufficient for running Docker containers and testing.
- **Region/zone selected:** ru-central1-a (Moscow region) — lowest latency for users in Russia/Europe.
- **Total cost:** $0 (free tier, within limits)
- **Resources created:**
- VPC Network (lab4-vm-network)
- Subnet (lab4-vm-subnet, CIDR: 192.168.10.0/24)
- Security Group (lab4-vm-security-group) with rules for ports 22, 80, 5000
- Compute Instance (lab4-vm) running Ubuntu 22.04 LTS
- Public IP address (assigned via NAT)

## 2. Terraform Implementation
- Terraform version used - 1.9.8
- Project structure explanation - root dir terraform containing main.tf, variables.tf, outputs.tf, terraform.tfvars and inno-key.json
- Key configuration decisions - Used service account key instead of short-lived IAM token
- **Challenges encountered**
- SSH public key path issue — Terraform doesn't expand `~`, fixed by using absolute path

### terraform init
- ![terraform init](../artifacts/terraform-init.png)

### terraform plan
- ![terraform plan start](../artifacts/terraform-plan-1.png)
- ![terraform plan end](../artifacts/terraform-plan-9.png)

### terraform apply
- ![terraform apply start](../artifacts/terraform-apply-1.png)
- ![terraform apply end](../artifacts/terraform-apply-10.png)

### SSH connection to VM
- ![SSH connection](../artifacts/ssh.png)

## 3. Pulumi Implementation
- **Pulumi version and language used:**
- Pulumi v3.142.0
- Language: Python 3.12
- Provider: pulumi-yandex 0.13.0

- **How code differs from Terraform:**
- Terraform uses declarative HCL and Pulumi uses imperative Python
- Pulumi requires explicit virtual environment management
- Resource arguments often need plural forms (`ingresses` vs `ingress`, `network_interfaces` vs `network_interface`)
- Pulumi uses `pulumi.export()` for outputs instead of Terraform's `output` blocks
- Configuration in Pulumi uses `pulumi.Config()` object; Terraform uses `variable` blocks
- Pulumi stores state in Pulumi Cloud (optional) while Terraform uses local `.tfstate` files

- **Advantages you discovered:**
- Full Python language features (loops, variables, etc.)
- Better IDE support — autocomplete, type checking, and linting
- Secrets encrypted by default in state files (no manual `.gitignore` needed for credentials)
- Cleaner syntax for dynamic resource creation (e.g., generating multiple resources with `for` loops and generators)

- **Challenges encountered:**
- `pkg_resources` missing in Python 3.12 — fixed by installing `setuptools` in venv
- Configuration namespace issues — had to use `pulumi.Config()` without arguments to match stack config
- Network quota limits — had to destroy Terraform VM first to free up VPC limit

### pulumi preview
- ![pulumi preview](../../pulumi/artifacts/pulumi-preview.png)

### pulumi up
- ![pulumi up](../../pulumi/artifacts/pulumi-up-1.png)

### SSH connection to VM
- ![pulumi up](../../pulumi/artifacts/pulumi-ssh.png)

## 4. Terraform vs Pulumi Comparison

- **Ease of Learning:** Terraform definetely is easier. HCL is purpose-built for infrastructure and has fewer edge cases. Pulumi requires Python knowledge

- **Code Readability:** Terraform is more readable for infrastructure-only tasks. HCL is declarative and self-documenting — you can look at a `.tf` file and immediately understand what resources will be created. Pulumi's Python code is clean but requires understanding imperative logic flow

- **Debugging:** Terraform has clearer error messages. When something goes wrong, Terraform points directly to the issue (e.g., "file not found at path X"). Pulumi's error messages can be cryptic — for example, "Attribute must be a list" doesn't tell you which attribute or what value caused the problem, requiring trial and error to fix.

- **Documentation:** Terraform's documentation is more mature. The Terraform Registry has extensive examples for every resource, and community resources are abundant. Pulumi's documentation exists but sometimes shows outdated argument names (e.g., `family` instead of `source_family` in examples), causing frustration.

- **Use Case:**
- **Choose Terraform when:** You need pure infrastructure provisioning, your team doesn't have extensive programming background, or you want simplicity and battle-tested stability.
- **Choose Pulumi when:** You need complex infrastructure logic (loops, conditionals, custom functions)

**Conclusion:** For this lab and typical DevOps infrastructure tasks, Terraform is the easier and more practical choice. Its declarative model, clearer error messages, and mature documentation make it more accessible

## 5. Lab 5 Preparation & Cleanup
- Are you keeping your VM for Lab 5? - Yes
- If yes, which VM? - Terraform
13 changes: 13 additions & 0 deletions lab_solutions/lab1/ansible/ansible.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[defaults]
inventory = inventory/hosts.ini
roles_path = roles
host_key_checking = False
remote_user = ubuntu
retry_files_enabled = False
stdout_callback = default
callback_whitelist = profile_tasks

[privilege_escalation]
become = True
become_method = sudo
become_user = root
Loading