diff --git a/.github/workflows/ansible-deploy.yml b/.github/workflows/ansible-deploy.yml new file mode 100644 index 0000000000..7b000f2aa3 --- /dev/null +++ b/.github/workflows/ansible-deploy.yml @@ -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 < /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" \ No newline at end of file diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml new file mode 100644 index 0000000000..3269f5d97d --- /dev/null +++ b/.github/workflows/python-ci.yml @@ -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 diff --git a/.gitignore b/.gitignore index 30d74d2584..6f33d00cf8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,22 @@ -test \ No newline at end of file +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 diff --git a/README.md b/README.md index a66ee3dc20..198c06e62e 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/lab_solutions/lab1/LAB04.md b/lab_solutions/lab1/LAB04.md new file mode 100644 index 0000000000..5366250b1e --- /dev/null +++ b/lab_solutions/lab1/LAB04.md @@ -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 diff --git a/lab_solutions/lab1/ansible/ansible.cfg b/lab_solutions/lab1/ansible/ansible.cfg new file mode 100644 index 0000000000..24a611b72c --- /dev/null +++ b/lab_solutions/lab1/ansible/ansible.cfg @@ -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 diff --git a/lab_solutions/lab1/ansible/docs/LAB05.md b/lab_solutions/lab1/ansible/docs/LAB05.md new file mode 100644 index 0000000000..dfc8f23898 --- /dev/null +++ b/lab_solutions/lab1/ansible/docs/LAB05.md @@ -0,0 +1,147 @@ +# Lab 5 — Ansible Fundamentals + +## 1. Architecture Overview + +- **Ansible Version:** 2.16.0 +- **Target VM OS:** Ubuntu 22.04 LTS +- **Control Node:** Ubuntu +- **Role structure explanation:** Each role contains `tasks/main.yml` (what to do), `handlers/main.yml` (conditional service restarts), and `defaults/main.yml` (default variables). `common` installs base packages, `docker` installs Docker CE, `app_deploy` pulls and runs the container. +- **Why roles instead of monolithic playbooks?** Roles provide reusability (can be used across different projects), they maintain separation of concerns (each role is responsible for one single action). Also they can be composed flexibly in different playbooks. + - **Control Node:** Ubuntu + - **Role structure:** + ```text + ansible/ + ├── inventory/ + │ └── hosts.ini + ├── roles/ + │ ├── common/ + │ │ ├── tasks/main.yml + │ │ └── defaults/main.yml + │ ├── docker/ + │ │ ├── tasks/main.yml + │ │ ├── handlers/main.yml + │ │ └── defaults/main.yml + │ └── app_deploy/ + │ ├── tasks/main.yml + │ ├── handlers/main.yml + │ └── defaults/main.yml + ├── playbooks/ + │ ├── provision.yml + │ └── deploy.yml + ├── ansible.cfg + └── group_vars/all.yml (vaulted) + ``` + + - **Why roles instead of monolithic playbooks?** + Roles promote reuse, clear separation of concerns, and easier testing; they make large projects maintainable by organizing tasks, handlers, and defaults per functional unit. + +## 2. Roles Documentation + +### `roles/common` +- Purpose: Provide base system provisioning: update APT cache, install essential packages, ensure Python 3 is present for Ansible remote operations, and set basic system configuration (timezone, users, cleanup). +- Variables: + - `common_packages` (list): packages installed by default (example: `['python3-pip','curl','git','vim','htop']`). + - `timezone` (string): system timezone to set (e.g. `UTC`). +- Handlers: + - `restart cron` (optional) — triggered if cron config changed. + - `reload rsyslog` (optional) — when logging config changed. +- Dependencies: None. + +### `roles/docker` +- Purpose: Install and configure Docker Engine from the official repository, ensure the Docker service is enabled and running, and add the deployment user to the `docker` group. +- Variables: + - `docker_packages` (list): `['docker-ce','docker-ce-cli','containerd.io']` (may vary by distro). + - `docker_user` (string): user to add to `docker` group (e.g. `ubuntu`). + - `docker_version` (optional): pin Docker version if required. +- Handlers: + - `restart docker` — restarts the Docker service when configuration or package changes require it. +- Dependencies: `roles/common` should run first to ensure required packages and Python are present. + +### `roles/app_deploy` +- Purpose: Authenticate to Docker Hub using vaulted credentials, pull the application image, stop and remove the previous container if present, and start the new container with correct port mappings and restart policy. +- Variables: + - `dockerhub_username` (string) — provided via `group_vars/all.yml` (vaulted). + - `dockerhub_password` (string) — vaulted. + - `app_name` (string) — container name (default: `devops-app`). + - `docker_image` (string) — image name (e.g. `{{ dockerhub_username }}/devops-app`). + - `docker_image_tag` (string) — image tag (default: `latest`). + - `app_port` (int) — container port (default: `5000`). +- Handlers: + - `restart app container` — restarts the application container when configuration changes. +- Dependencies: `roles/docker` must be applied before this role so Docker is available on the host. + +## 3. Idempotency Demonstration + +### First run (provision) +Initial run of the provisioning playbook (shows changed tasks): + +![ansible playbook first run](ansible-playbook.png) + +### Second run (idempotency) +Second run should show `ok` for previously changed tasks: + +![ansible playbook idempotent run](ansible-playbook-idemp.png) + +### Provision fixes proof +First provisioning run missed Docker tasks/handlers; after fixes the provision playbook ran successfully: + +![provision before fix](ansible-provision.png) +![provision after fix](ansible-provision-1.png) + +## 3.5 Idempotency analysis (summary) +- First run observations (changed): + - `apt` update and package installs ran and reported `changed` because packages were absent. + - Docker repository and GPG key were added (changed). + - Docker packages installed and service started (changed). + - Docker group membership and user creation (changed). + - Image pull and container creation (changed). +- Second run observations (idempotent): + - `apt` tasks returned `ok` (cache valid), package tasks returned `ok` (packages already present). + - Repository, keys, service state, group membership, image presence and container state all returned `ok` (no changes needed). +- Why this demonstrates idempotency: tasks use stateful modules (`apt`, `service`, `docker_image`, `docker_container`) which check existing state and only apply changes when needed. Handlers are triggered only on actual changes. + +## 4. Ansible Vault Usage +- **How you store credentials securely:** I use `ansible-vault` to encrypt `group_vars/all.yml` which contains Docker Hub credentials and any secret configuration. The vault file is edited with `ansible-vault edit group_vars/all.yml` and the vault password is kept out of the repo (either prompted at runtime with `--ask-vault-pass` or provided by a CI secret/password file listed in `.gitignore`). + +![encrypted variables file](encrypted.png) + +Why Ansible Vault is important: It allows safe version control of secrets by encrypting them at rest. Combined with `no_log: true` on tasks that use secrets, it prevents accidental leaks in logs and ensures credentials are not stored plaintext in the repository or CI logs. +## 5. Deployment Verification + +### Deploy playbook run +![deploy step 1](deploy-1.png) +![deploy step 2](deploy-2.png) + +### Health check and main endpoint +![health check from local](health-check-from-local.png) +![main endpoint from local](main-endpoint-from-local.png) + +### Container status on target +![docker ps on target](docker-ps.png) + +## 6. Key Decisions + +- Why use roles instead of plain playbooks? + Roles provide structure, enable reuse across projects, and separate concerns so teams can work on independent pieces (e.g., provisioning vs deployment) without conflicts. + +- How do roles improve reusability? + By encapsulating tasks, defaults, handlers, and templates into a named role, the same role can be included in multiple playbooks or projects with minimal changes. + +- What makes a task idempotent? + Using modules that declare desired state (e.g., `apt: state=present`, `service: state=started`, `docker_container: state=started`) makes Ansible check current state and only apply changes when necessary. + +- How do handlers improve efficiency? + Handlers run only when notified by a changed task, preventing repeated or unnecessary service restarts; they coalesce multiple changes into a single action. + +- Why is Ansible Vault necessary? + Vault protects secrets while keeping them version-controlled and auditable; it prevents accidental leakage of credentials into the repository or logs. +## 7. Challenges + +I encountered an issue with the `requests` package raising "Not supported URL scheme http+docker". It was resolved by downgrading `requests` from `2.34` to `2.31` on both the control node and the VM. + +Problem evidence: +![deploy problem](deploy-problem.png) + +Solution evidence (first and last of the solve series): +![deploy problem solved - step 1](deploy-problem-solve-1.png) +![deploy problem solved - final step](deploy-problem-solve-4.png) diff --git a/lab_solutions/lab1/ansible/docs/LAB06.md b/lab_solutions/lab1/ansible/docs/LAB06.md new file mode 100644 index 0000000000..c005d197bd --- /dev/null +++ b/lab_solutions/lab1/ansible/docs/LAB06.md @@ -0,0 +1,111 @@ +# Lab 6: Advanced Ansible & CI/CD - Submission + +**Name:** FILL ME +**Date:** FILL ME +**Lab Points:** 10 + bonus + +--- + +## Overview + +This lab enhanced the Ansible automation from Lab 5 with production-ready features. Technologies used: Ansible 2.16+, Docker Compose v2, GitHub Actions, Jinja2 templating. + +**What was accomplished:** +- Refactored roles with blocks for error handling (`block`/`rescue`/`always`) +- Implemented comprehensive tag strategy for selective execution +- Migrated from `docker_container` to Docker Compose with Jinja2 templates +- Added role dependencies for automatic Docker installation +- Implemented double-gated wipe logic (variable + tag) +- Configured GitHub Actions CI/CD with ansible-lint, syntax check, and automated deployment +- Achieved idempotency for all system-level tasks + +## Task 1: Blocks & Tags (2 pts) + +### Block usage + +| Role | Blocks | Rescue | Always | Purpose | +|------|--------|--------|--------|---------| +| `common` | System provisioning, User management, Timezone | Apt cache fix on failure | Log completion | Ensure base system setup with error recovery | +| `docker` | Docker installation, Docker configuration | GPG key retry on timeout | Enable Docker service | Handle network issues during Docker repo setup | +| `web_app` | Deployment with Compose | Log deployment failure | - | Atomic deployment with cleanup on failure | + +### Tag Strategy + +| Tag | Scope | Used For | +|-----|-------|----------| +| `common` | Entire common role | Skip common setup | +| `packages` | Apt operations | Quick package updates | +| `users` | User management | User-only changes | +| `docker` | Entire docker role | Docker-only runs | +| `docker_install` | Docker installation only | Fresh Docker setup | +| `docker_config` | Docker configuration only | User/group changes | +| `app_deploy` | Full deployment | Normal deployments | +| `web_app_wipe` | Wipe only | Selective cleanup | + +### Rescue Block Demonstration + +The Docker GPG key task includes a rescue block that waits 10 seconds and retries on failure, simulating transient network issues common with external repository keys. + +- Selective execution with Ansible tags (all tags) + ![Selective execution with Ansible tags](/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-all.png) + +- Selective execution with Ansible tags (docker tags) + ![Selective execution with Ansible tags](/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-docker-1.png) + +- Selective execution with Ansible tags (skip tags) + ![Selective execution with Ansible tags](/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-skip.png) + +- Selective execution with Ansible tags (packages tags) + ![Selective execution with Ansible tags](/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-packages-1.png) + +- Selective execution with Ansible tags (check tags) + ![Selective execution with Ansible tags](/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-check.png) + +- Selective execution with Ansible tags (docker_install tags) + ![Selective execution with Ansible tags](/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-docker-install.png) +- Rescue block + ![Selective execution with Ansible tags](/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-rescue.png) + +## Task 2: Docker Compose (3 pts) +FILL ME + +- Idempotency check on the second playbook run + + Deploy with Docker Compose is being showed as changed because it always looks for docker image, but ansible doesn't create new container if one already exist + + ![Idempotency check on the second playbook run](/lab_solutions/lab1/ansible/docs/lab6-evidence/idemp-1.png) + ![Idempotency check on the second playbook run](/lab_solutions/lab1/ansible/docs/lab6-evidence/idemp-2.png) + +- Docker Compose deployment success + ![Docker Compose deployment success](/lab_solutions/lab1/ansible/docs/lab6-evidence/deploy-ev-1.png) + ![Docker Compose deployment success](/lab_solutions/lab1/ansible/docs/lab6-evidence/deploy-ev-2.png) + +- Contents of templated docker-compose.yml + ![Provision playbook initial run](/lab_solutions/lab1/ansible/docs/lab6-evidence/templated-docker-yml.png) + +## Task 3: Wipe Logic (1 pt) +FILL ME + + +- Output of Scenario 1 + ![Output of Scenario 1](/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-1-1.png) + ![Output of Scenario 1](/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-1-2.png) + +- Output of Scenario 2 + ![Output of Scenario 2](/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-2.png) + +- Output of Scenarion 3 + ![Output of Scenario 3](/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-1.png) + ![Output of Scenario 3](/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-2.png) + ![Output of Scenario 3 + app health check](/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-3.png) + +## Task 4: CI/CD (3 pts) +FILL ME + +- Workflow evidence + ![ansible lint](/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-1.png) + ![ansible syntax](/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-2.png) + ![ansible deploy](/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-3.png) + +## Summary +Was quite interesting configuring ansible deployment in CI/CD. Tags really provides a mojor assist while developing, you can quickly change ansimble instructions. diff --git a/lab_solutions/lab1/ansible/docs/ansible-ping.png b/lab_solutions/lab1/ansible/docs/ansible-ping.png new file mode 100644 index 0000000000..b0904b2072 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/ansible-ping.png differ diff --git a/lab_solutions/lab1/ansible/docs/ansible-playbook-idemp.png b/lab_solutions/lab1/ansible/docs/ansible-playbook-idemp.png new file mode 100644 index 0000000000..8d61150936 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/ansible-playbook-idemp.png differ diff --git a/lab_solutions/lab1/ansible/docs/ansible-playbook.png b/lab_solutions/lab1/ansible/docs/ansible-playbook.png new file mode 100644 index 0000000000..b31582a0a1 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/ansible-playbook.png differ diff --git a/lab_solutions/lab1/ansible/docs/ansible-provision-1.png b/lab_solutions/lab1/ansible/docs/ansible-provision-1.png new file mode 100644 index 0000000000..8674fc7cf8 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/ansible-provision-1.png differ diff --git a/lab_solutions/lab1/ansible/docs/ansible-provision.png b/lab_solutions/lab1/ansible/docs/ansible-provision.png new file mode 100644 index 0000000000..a5b30e8bab Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/ansible-provision.png differ diff --git a/lab_solutions/lab1/ansible/docs/deploy-1.png b/lab_solutions/lab1/ansible/docs/deploy-1.png new file mode 100644 index 0000000000..fa1383f6fb Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/deploy-1.png differ diff --git a/lab_solutions/lab1/ansible/docs/deploy-2.png b/lab_solutions/lab1/ansible/docs/deploy-2.png new file mode 100644 index 0000000000..78d0be10a8 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/deploy-2.png differ diff --git a/lab_solutions/lab1/ansible/docs/deploy-problem-solve-1.png b/lab_solutions/lab1/ansible/docs/deploy-problem-solve-1.png new file mode 100644 index 0000000000..d40e82a25c Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/deploy-problem-solve-1.png differ diff --git a/lab_solutions/lab1/ansible/docs/deploy-problem-solve-2.png b/lab_solutions/lab1/ansible/docs/deploy-problem-solve-2.png new file mode 100644 index 0000000000..859e976a99 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/deploy-problem-solve-2.png differ diff --git a/lab_solutions/lab1/ansible/docs/deploy-problem-solve-3.png b/lab_solutions/lab1/ansible/docs/deploy-problem-solve-3.png new file mode 100644 index 0000000000..59f281e8a3 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/deploy-problem-solve-3.png differ diff --git a/lab_solutions/lab1/ansible/docs/deploy-problem-solve-4.png b/lab_solutions/lab1/ansible/docs/deploy-problem-solve-4.png new file mode 100644 index 0000000000..b01650b063 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/deploy-problem-solve-4.png differ diff --git a/lab_solutions/lab1/ansible/docs/deploy-problem.png b/lab_solutions/lab1/ansible/docs/deploy-problem.png new file mode 100644 index 0000000000..367f7ca071 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/deploy-problem.png differ diff --git a/lab_solutions/lab1/ansible/docs/docker-ps.png b/lab_solutions/lab1/ansible/docs/docker-ps.png new file mode 100644 index 0000000000..3a8e36bb0b Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/docker-ps.png differ diff --git a/lab_solutions/lab1/ansible/docs/encrypted.png b/lab_solutions/lab1/ansible/docs/encrypted.png new file mode 100644 index 0000000000..163f300894 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/encrypted.png differ diff --git a/lab_solutions/lab1/ansible/docs/health-check-from-local.png b/lab_solutions/lab1/ansible/docs/health-check-from-local.png new file mode 100644 index 0000000000..8b0cebfbc8 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/health-check-from-local.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-1.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-1.png new file mode 100644 index 0000000000..d60c2948a2 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-1.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-2.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-2.png new file mode 100644 index 0000000000..258b917e4b Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-2.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-3.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-3.png new file mode 100644 index 0000000000..f7ca9e169c Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/action-ev-3.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/deploy-ev-1.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/deploy-ev-1.png new file mode 100644 index 0000000000..ac089fde8f Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/deploy-ev-1.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/deploy-ev-2.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/deploy-ev-2.png new file mode 100644 index 0000000000..bbbdc70758 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/deploy-ev-2.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/docker-compose-ev.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/docker-compose-ev.png new file mode 100644 index 0000000000..a4cba2b4f1 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/docker-compose-ev.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/idemp-1.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/idemp-1.png new file mode 100644 index 0000000000..f314bb62f2 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/idemp-1.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/idemp-2.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/idemp-2.png new file mode 100644 index 0000000000..2460d8965c Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/idemp-2.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-all.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-all.png new file mode 100644 index 0000000000..b7279e1e7d Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-all.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-check.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-check.png new file mode 100644 index 0000000000..06ce1dbed0 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-check.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-common.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-common.png new file mode 100644 index 0000000000..691c299c58 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-common.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-docker-1.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-docker-1.png new file mode 100644 index 0000000000..4c41c6a16f Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-docker-1.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-docker-install.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-docker-install.png new file mode 100644 index 0000000000..6314d41ff5 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-docker-install.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-docker.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-docker.png new file mode 100644 index 0000000000..37d03912fd Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-docker.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-packages-1.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-packages-1.png new file mode 100644 index 0000000000..fd2aaa74b7 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-packages-1.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-packages.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-packages.png new file mode 100644 index 0000000000..3a538c9363 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-packages.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-rescue.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-rescue.png new file mode 100644 index 0000000000..bdbe71b19d Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-rescue.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-skip.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-skip.png new file mode 100644 index 0000000000..62ae1cf9cc Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/tags-skip.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/templated-docker-yml.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/templated-docker-yml.png new file mode 100644 index 0000000000..f7cae4079f Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/templated-docker-yml.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-1-1.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-1-1.png new file mode 100644 index 0000000000..1093245178 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-1-1.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-1-2.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-1-2.png new file mode 100644 index 0000000000..60e876becc Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-1-2.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-2.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-2.png new file mode 100644 index 0000000000..cbd8215b5e Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-2.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-1.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-1.png new file mode 100644 index 0000000000..9b8073aa34 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-1.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-2.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-2.png new file mode 100644 index 0000000000..c1ad1f8d9c Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-2.png differ diff --git a/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-3.png b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-3.png new file mode 100644 index 0000000000..4ef227ccb8 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/lab6-evidence/wipe-3-3.png differ diff --git a/lab_solutions/lab1/ansible/docs/main-endpoint-from-local.png b/lab_solutions/lab1/ansible/docs/main-endpoint-from-local.png new file mode 100644 index 0000000000..bbd8c137f9 Binary files /dev/null and b/lab_solutions/lab1/ansible/docs/main-endpoint-from-local.png differ diff --git a/lab_solutions/lab1/ansible/group_vars/all.yml b/lab_solutions/lab1/ansible/group_vars/all.yml new file mode 100644 index 0000000000..88607185f3 --- /dev/null +++ b/lab_solutions/lab1/ansible/group_vars/all.yml @@ -0,0 +1,18 @@ +$ANSIBLE_VAULT;1.1;AES256 +66636134326335386633313763396436623265613138306131333762353236313563666564303066 +3236616437333032363666656130616265313637323132640a623430323762353430653666656236 +32383533383832616165626336643539333636313738373666386564323162626131303633363362 +3062646636373661330a616637383662613635336336313934646162653630333363613965323666 +37313864343532663062363334656231326261663062383333333465653335656335333832373865 +36323032356661356531316533303837383038646262343963393032393235663137636433613863 +33633234363038393661623233303735663661316161663863633737396335326330376133363134 +32653461376635623861653933646430343338316266353963656463323234386634353030333664 +35316337353431343962316431383336383361386432356436323763636534666261323463663938 +35316339373138333866633033646465333133666266323438363565376634623038613135333433 +61333234663334636562646434393932346663643962653438383436663434333739623962623066 +30626439316338393966633262326537306135636361366134363933643832633630323431383566 +38386139656362643566353735313062313361323833656138313661376132386134626333623436 +30636666663765343263646134393432633962313565396532306631623466643164393534326338 +39333866623138313931623735646638303563323830323337663737356134313238363062366261 +32376630623632313138613137303539346532343230363365353230313430643433626231303066 +3831 diff --git a/lab_solutions/lab1/ansible/inventory/hosts.ini b/lab_solutions/lab1/ansible/inventory/hosts.ini new file mode 100644 index 0000000000..2db008cef9 --- /dev/null +++ b/lab_solutions/lab1/ansible/inventory/hosts.ini @@ -0,0 +1,5 @@ +[webservers] +lab-vm ansible_host=51.250.9.132 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa + +[webservers:vars] +ansible_python_interpreter=/usr/bin/python3 diff --git a/lab_solutions/lab1/ansible/playbooks/deploy.yml b/lab_solutions/lab1/ansible/playbooks/deploy.yml new file mode 100644 index 0000000000..5f983da974 --- /dev/null +++ b/lab_solutions/lab1/ansible/playbooks/deploy.yml @@ -0,0 +1,19 @@ +- name: Deploy application to web server + hosts: webservers + become: yes + vars_files: + - ../group_vars/all.yml + + roles: + - web_app + + post_tasks: + - name: Show deployment summary + debug: + msg: + - "Deployment complete" + - "Container: {{ app_name }}" + - "Image: {{ docker_image }}:{{ docker_tag | default('latest') }}" + - "Port: {{ app_port }}" + - "URL: http://{{ ansible_default_ipv4.address }}:{{ app_port }}" + tags: [deploy, debug] diff --git a/lab_solutions/lab1/ansible/playbooks/provision.yml b/lab_solutions/lab1/ansible/playbooks/provision.yml new file mode 100644 index 0000000000..ab9ee4f5b2 --- /dev/null +++ b/lab_solutions/lab1/ansible/playbooks/provision.yml @@ -0,0 +1,18 @@ +- name: Provision web server with common tools and Docker + hosts: webservers + become: yes + gather_facts: yes + + roles: + - common + - docker + + post_tasks: + - name: Display system information + debug: + msg: + - "Hostname: {{ ansible_hostname }}" + - "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}" + - "Kernel: {{ ansible_kernel }}" + - "CPU Cores: {{ ansible_processor_cores }}" + - "Memory: {{ ansible_memtotal_mb }} MB" diff --git a/lab_solutions/lab1/ansible/playbooks/rescue-test.yml b/lab_solutions/lab1/ansible/playbooks/rescue-test.yml new file mode 100644 index 0000000000..df1f451669 --- /dev/null +++ b/lab_solutions/lab1/ansible/playbooks/rescue-test.yml @@ -0,0 +1,23 @@ +- name: Test rescue block with Docker installation + hosts: webservers + become: yes + + tasks: + - name: Temporarily break Docker GPG key (for testing) + block: + - name: Add Docker GPG key with wrong URL + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg-INVALID + state: present + rescue: + - name: Rescue block - fix with correct URL + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + register: rescue_result + + - name: Show rescue succeeded + debug: + msg: "Rescue block executed successfully!" + + tags: [test, rescue] diff --git a/lab_solutions/lab1/ansible/roles/common/defaults/main.yml b/lab_solutions/lab1/ansible/roles/common/defaults/main.yml new file mode 100644 index 0000000000..625d683d4e --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/common/defaults/main.yml @@ -0,0 +1,13 @@ +common_packages: + - python3-pip + - curl + - git + - vim + - htop + - net-tools + - ca-certificates + - gnupg + - lsb-release + - software-properties-common + +timezone: Europe/Moscow diff --git a/lab_solutions/lab1/ansible/roles/common/docker/defaults/main.yml b/lab_solutions/lab1/ansible/roles/common/docker/defaults/main.yml new file mode 100644 index 0000000000..2b4d486435 --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/common/docker/defaults/main.yml @@ -0,0 +1,2 @@ +docker_user: ubuntu +docker_compose_version: 2.24.0 diff --git a/lab_solutions/lab1/ansible/roles/common/docker/handlers/main.yml b/lab_solutions/lab1/ansible/roles/common/docker/handlers/main.yml new file mode 100644 index 0000000000..1907c4cd1c --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/common/docker/handlers/main.yml @@ -0,0 +1,4 @@ +- name: restart docker + service: + name: docker + state: restarted diff --git a/lab_solutions/lab1/ansible/roles/common/docker/tasks/main.yml b/lab_solutions/lab1/ansible/roles/common/docker/tasks/main.yml new file mode 100644 index 0000000000..6cf1ffd4b3 --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/common/docker/tasks/main.yml @@ -0,0 +1,70 @@ +- name: Install prerequisites for Docker + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - lsb-release + state: present + update_cache: yes + tags: [docker, packages] + +- name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + tags: [docker, repo] + +- 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 + tags: [docker, repo] + +- name: Install Docker packages + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + notify: restart docker + tags: [docker, packages] + +- name: Ensure Docker service is running and enabled + service: + name: docker + state: started + enabled: yes + tags: [docker, service] + +- name: Add user to docker group + user: + name: "{{ docker_user }}" + groups: docker + append: yes + notify: restart docker + tags: [docker, user] + +- name: Install Docker SDK for Python (for Ansible modules) + pip: + name: + - docker + - docker-compose + state: present + tags: [docker, pip] + +- name: Check Docker version + command: docker --version + register: docker_version + changed_when: false + tags: [docker, debug] + +- name: Display Docker version + debug: + msg: "{{ docker_version.stdout }}" + tags: [docker, debug] diff --git a/lab_solutions/lab1/ansible/roles/common/handlers/main.yml b/lab_solutions/lab1/ansible/roles/common/handlers/main.yml new file mode 100644 index 0000000000..1d8b8f0080 --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/common/handlers/main.yml @@ -0,0 +1,4 @@ +- name: restart cron + service: + name: cron + state: restarted diff --git a/lab_solutions/lab1/ansible/roles/common/tasks/main.yml b/lab_solutions/lab1/ansible/roles/common/tasks/main.yml new file mode 100644 index 0000000000..1f0b13059a --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/common/tasks/main.yml @@ -0,0 +1,68 @@ +- name: System provisioning with error handling + block: + - name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + tags: [packages, common] + + - name: Install common packages + apt: + name: "{{ common_packages | default(['python3-pip', 'curl', 'git', 'vim', 'htop', 'net-tools', 'ca-certificates']) }}" + state: present + tags: [packages, common] + + rescue: + - name: Fix apt cache on failure + command: apt-get update --fix-missing + register: fix_result + tags: [packages, common] + + - name: Display fix result + debug: + msg: "Apt cache fixed: {{ fix_result.stdout }}" + tags: [packages, common] + + always: + - name: Log provisioning completion + copy: + content: | + Common role completed at {{ ansible_date_time.iso8601 }} + Host: {{ ansible_hostname }} + OS: {{ ansible_distribution }} {{ ansible_distribution_version }} + dest: /tmp/common_provisioning.log + mode: '0644' + tags: [packages, common] + + become: true + tags: [common] + +- name: User management block + block: + - name: Ensure users exist + user: + name: "{{ item }}" + state: present + shell: /bin/bash + loop: "{{ extra_users | default([]) }}" + tags: [users, common] + + rescue: + - name: Log user creation failure + debug: + msg: "Failed to create users: {{ ansible_failed_result }}" + tags: [users, common] + + become: true + tags: [common, users] + +- name: Timezone configuration block + block: + - name: Set timezone + timezone: + name: "{{ timezone | default('UTC') }}" + notify: restart cron + tags: [timezone, common] + + become: true + tags: [common, timezone] diff --git a/lab_solutions/lab1/ansible/roles/docker/handlers/main.yml b/lab_solutions/lab1/ansible/roles/docker/handlers/main.yml new file mode 100644 index 0000000000..1907c4cd1c --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/docker/handlers/main.yml @@ -0,0 +1,4 @@ +- name: restart docker + service: + name: docker + state: restarted diff --git a/lab_solutions/lab1/ansible/roles/docker/tasks/main.yml b/lab_solutions/lab1/ansible/roles/docker/tasks/main.yml new file mode 100644 index 0000000000..400caa304e --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/docker/tasks/main.yml @@ -0,0 +1,98 @@ +- name: Docker installation block + block: + - name: Install prerequisites for Docker + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - lsb-release + state: present + update_cache: yes + tags: [docker_install, docker] + + - name: Add Docker GPG key with retry + block: + - name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + rescue: + - name: Wait 10 seconds and retry GPG key + wait_for: + timeout: 10 + tags: [docker_install, docker] + + - name: Retry adding Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + tags: [docker_install, docker] + + - 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 + tags: [docker_install, docker] + + - name: Install Docker packages + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + notify: restart docker + tags: [docker_install, docker] + + rescue: + - name: Log Docker installation failure + debug: + msg: "Docker installation failed: {{ ansible_failed_result }}" + tags: [docker_install, docker] + + always: + - name: Ensure Docker service is enabled + service: + name: docker + enabled: yes + tags: [docker_install, docker] + + become: true + tags: [docker] + +- name: Docker configuration block + block: + - name: Add user to docker group + user: + name: "{{ ansible_user }}" + groups: docker + append: yes + tags: [docker_config, docker] + notify: restart docker + + - name: Install Docker SDK for Python + pip: + name: + - docker + - requests==2.31.0 + state: present + tags: [docker_config, docker] + + - name: Verify Docker installation + command: docker --version + register: docker_version + changed_when: false + tags: [docker_config, docker] + + - name: Display Docker version + debug: + msg: "{{ docker_version.stdout }}" + tags: [docker_config, docker] + + become: true + tags: [docker, docker_config] diff --git a/lab_solutions/lab1/ansible/roles/web_app/defaults/main.yml b/lab_solutions/lab1/ansible/roles/web_app/defaults/main.yml new file mode 100644 index 0000000000..cdaef24199 --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/web_app/defaults/main.yml @@ -0,0 +1,12 @@ +app_name: devops-python +docker_image: "{{ dockerhub_username }}/devops-info-service" +docker_tag: latest +app_port: 8000 +app_internal_port: 5000 +app_restart_policy: unless-stopped +compose_project_dir: "/opt/{{ app_name }}" + +dockerhub_username: "" +dockerhub_password: "" + +web_app_wipe: false diff --git a/lab_solutions/lab1/ansible/roles/web_app/handlers/main.yml b/lab_solutions/lab1/ansible/roles/web_app/handlers/main.yml new file mode 100644 index 0000000000..04148ed6d7 --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/web_app/handlers/main.yml @@ -0,0 +1,6 @@ +- name: check_app_health + wait_for: + port: "{{ app_port }}" + timeout: 30 + delay: 3 + listen: "check_app_health" diff --git a/lab_solutions/lab1/ansible/roles/web_app/meta/main.yml b/lab_solutions/lab1/ansible/roles/web_app/meta/main.yml new file mode 100644 index 0000000000..6938607932 --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/web_app/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - role: docker + tags: [docker, docker_install, docker_config] diff --git a/lab_solutions/lab1/ansible/roles/web_app/tasks/main.yml b/lab_solutions/lab1/ansible/roles/web_app/tasks/main.yml new file mode 100644 index 0000000000..b362ba2c3d --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/web_app/tasks/main.yml @@ -0,0 +1,76 @@ +- name: Include wipe tasks + include_tasks: wipe.yml + when: web_app_wipe | bool + tags: + - web_app_wipe + +- name: Deploy application with Docker Compose + block: + - name: Create application directory + file: + path: "{{ compose_project_dir | default('/opt/' + app_name) }}" + state: directory + mode: '0755' + tags: [app_deploy, compose] + + - name: Template docker-compose file + template: + src: docker-compose.yml.j2 + dest: "{{ compose_project_dir | default('/opt/' + app_name) }}/docker-compose.yml" + mode: '0644' + tags: [app_deploy, compose] + + - name: Login to Docker Hub + community.docker.docker_login: + registry_url: https://index.docker.io/v1/ + username: "{{ dockerhub_username }}" + password: "{{ dockerhub_password }}" + no_log: true + tags: [app_deploy, docker_login] + + - name: Deploy with Docker Compose + community.docker.docker_compose_v2: + project_src: "{{ compose_project_dir | default('/opt/' + app_name) }}" + state: present + pull: always + recreate: always + register: compose_result + tags: [app_deploy, compose] + + - name: Wait for container to be ready + wait_for: + port: "{{ app_port }}" + timeout: 60 + delay: 5 + tags: [app_deploy, health] + + - name: Verify health endpoint + uri: + url: "http://localhost:{{ app_port }}/health" + return_content: yes + status_code: 200 + timeout: 10 + register: health_response + retries: 10 + delay: 5 + until: health_response.status == 200 + ignore_errors: yes + tags: [app_deploy, health] + + - name: Display container status + debug: + msg: + - "Container {{ app_name }} is running" + - "Image: {{ docker_image }}:{{ docker_tag | default('latest') }}" + - "Port: {{ app_port }}" + - "URL: http://{{ ansible_default_ipv4.address }}:{{ app_port }}" + tags: [app_deploy, debug] + + rescue: + - name: Log deployment failure + debug: + msg: "Deployment failed: {{ ansible_failed_result }}" + tags: [app_deploy] + + become: true + tags: [app_deploy] diff --git a/lab_solutions/lab1/ansible/roles/web_app/tasks/wipe.yml b/lab_solutions/lab1/ansible/roles/web_app/tasks/wipe.yml new file mode 100644 index 0000000000..601c6a19f5 --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/web_app/tasks/wipe.yml @@ -0,0 +1,36 @@ +- name: Wipe web application + block: + - name: Stop and remove containers via Docker Compose + community.docker.docker_compose_v2: + project_src: "{{ compose_project_dir | default('/opt/' + app_name) }}" + state: absent + ignore_errors: yes + tags: [web_app_wipe] + + - name: Remove docker-compose.yml file + file: + path: "{{ compose_project_dir | default('/opt/' + app_name) }}/docker-compose.yml" + state: absent + tags: [web_app_wipe] + + - name: Remove application directory + file: + path: "{{ compose_project_dir | default('/opt/' + app_name) }}" + state: absent + tags: [web_app_wipe] + + - name: Remove Docker image (optional) + community.docker.docker_image: + name: "{{ docker_image }}:{{ docker_tag | default('latest') }}" + state: absent + ignore_errors: yes + tags: [web_app_wipe] + + - name: Log wipe completion + debug: + msg: "Application {{ app_name }} wiped successfully" + tags: [web_app_wipe] + + when: web_app_wipe | bool + become: true + tags: [web_app_wipe] diff --git a/lab_solutions/lab1/ansible/roles/web_app/templates/docker-compose.yml.j2 b/lab_solutions/lab1/ansible/roles/web_app/templates/docker-compose.yml.j2 new file mode 100644 index 0000000000..f1fc533ed1 --- /dev/null +++ b/lab_solutions/lab1/ansible/roles/web_app/templates/docker-compose.yml.j2 @@ -0,0 +1,15 @@ +version: '3.8' + +services: + {{ app_name }}: + image: {{ docker_image }}:{{ docker_tag | default('latest') }} + container_name: {{ app_name }} + ports: + - "{{ app_port }}:{{ app_internal_port | default(app_port) }}" + restart: unless-stopped + networks: + - app_network + +networks: + app_network: + driver: bridge diff --git a/lab_solutions/lab1/app_python/.dockerignore b/lab_solutions/lab1/app_python/.dockerignore new file mode 100644 index 0000000000..37cd402203 --- /dev/null +++ b/lab_solutions/lab1/app_python/.dockerignore @@ -0,0 +1,43 @@ +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd +.Python +*.so +*.egg +*.egg-info/ +dist/ +build/ + +# Virtual environments +venv/ +.venv/ +env/ +ENV/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Git +.git/ +.gitignore + +# Logs +*.log + +# Tests +tests/ +.pytest_cache/ + +# Documentation +docs/ +README.md diff --git a/lab_solutions/lab1/app_python/.gitignore b/lab_solutions/lab1/app_python/.gitignore new file mode 100644 index 0000000000..4de420a8f7 --- /dev/null +++ b/lab_solutions/lab1/app_python/.gitignore @@ -0,0 +1,12 @@ +# Python +__pycache__/ +*.py[cod] +venv/ +*.log + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store \ No newline at end of file diff --git a/lab_solutions/lab1/app_python/Dockerfile b/lab_solutions/lab1/app_python/Dockerfile new file mode 100644 index 0000000000..c751d50e2b --- /dev/null +++ b/lab_solutions/lab1/app_python/Dockerfile @@ -0,0 +1,22 @@ +FROM python:3.13-slim + +# restricted user +RUN useradd --create-home --shell /bin/bash appuser + +WORKDIR /app + +COPY requirements.txt . + +# pip installation requires root +RUN pip install --no-cache-dir -r requirements.txt + +COPY app.py . + +# adduser now owns app +RUN chown -R appuser:appuser /app + +USER appuser + +EXPOSE 5000 + +CMD ["python", "app.py"] diff --git a/lab_solutions/lab1/app_python/README.md b/lab_solutions/lab1/app_python/README.md new file mode 100644 index 0000000000..9bbfd3b80e --- /dev/null +++ b/lab_solutions/lab1/app_python/README.md @@ -0,0 +1,66 @@ +# DevOps Info Service (FastAPI) + +[![python-ci](https://github.com///actions/workflows/python-ci.yml/badge.svg)](https://github.com///actions/workflows/python-ci.yml) + +## Overview +A lightweight web service that reports system and runtime information for the DevOps course labs. + +## Prerequisites +- Python 3.11+ +- FastAPI - perfect fit for small WEB application here. It has built-in OpenAPI docs and provides modern async interface. + +## Installation +```bash +python -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +## Running the Application +```bash +python app.py +# Or with custom config +PORT=8080 python app.py +``` + +## Testing +```bash +pip install -r requirements.txt -r requirements-dev.txt +pytest +``` + +## Linting +```bash +ruff check . +``` + +## API Endpoints +- `GET /` - Service and system information +- `GET /health` - Health check + +## Configuration +| Variable | Default | Description | +| --- | --- | --- | +| HOST | 0.0.0.0 | Bind address | +| PORT | 5000 | HTTP port | +| DEBUG | False | Enable auto-reload and debug logging | + +## Docker + +### Building the Image +```bash +docker build -t chupapupa/devops-info-service:latest . +``` + +### Running the Container +```bash +docker run -p 5000:5000 chupapupa/devops-info-service:latest +# With custom port +docker run -p 8080:5000 -e PORT=5000 chupapupa/devops-info-service:latest +``` + +### Pulling from Docker Hub +```bash +docker pull chupapupa/devops-info-service:latest +docker run -p 5000:5000 chupapupa/devops-info-service:latest +``` diff --git a/lab_solutions/lab1/app_python/app.py b/lab_solutions/lab1/app_python/app.py new file mode 100644 index 0000000000..6791c574c0 --- /dev/null +++ b/lab_solutions/lab1/app_python/app.py @@ -0,0 +1,109 @@ +import os +import logging +import platform +import socket +from datetime import datetime, timezone +from fastapi import FastAPI, Request + + +HOST = os.getenv("HOST", "0.0.0.0") +PORT = int(os.getenv("PORT", "5000")) +DEBUG = os.getenv("DEBUG", "False").lower() == "true" + + +logging.basicConfig( + level=logging.DEBUG if DEBUG else logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) + + +logger = logging.getLogger(__name__) + + +START_TIME = datetime.now(timezone.utc) + + +app = FastAPI(title="DevOps Info Service", version="1.0.0") + + +def get_uptime(): + delta = datetime.now(timezone.utc) - START_TIME + seconds = int(delta.total_seconds()) + hours = seconds // 3600 + minutes = (seconds % 3600) // 60 + return {"seconds": seconds, "human": f"{hours} hours, {minutes} minutes"} + + +def get_system_info(): + return { + "hostname": socket.gethostname(), + "platform": platform.system(), + "platform_version": platform.platform(), + "architecture": platform.machine(), + "cpu_count": os.cpu_count() or 0, + "python_version": platform.python_version(), + } + + +def get_request_info(request: Request): + client_ip = request.client.host if request.client else "unknown" + user_agent = request.headers.get("user-agent", "unknown") + return { + "client_ip": client_ip, + "user_agent": user_agent, + "method": request.method, + "path": request.url.path, + } + + +def get_runtime_info(): + now = datetime.now(timezone.utc) + return { + "uptime_seconds": get_uptime()["seconds"], + "uptime_human": get_uptime()["human"], + "current_time": now.isoformat().replace("+00:00", "Z"), + "timezone": now.tzname() or "UTC", + } + + +@app.get("/") +async def index(request: Request): + logger.info("Request: %s %s", request.method, request.url.path) + return { + "service": { + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "FastAPI", + }, + "system": get_system_info(), + "runtime": get_runtime_info(), + "request": get_request_info(request), + "endpoints": [ + {"path": "/", "method": "GET", "description": "Service information"}, + {"path": "/health", "method": "GET", "description": "Health check"}, + ], + } + + +@app.get("/health") +async def health(): + now = datetime.now(timezone.utc) + return { + "status": "healthy", + "timestamp": now.isoformat().replace("+00:00", "Z"), + "uptime_seconds": get_uptime()["seconds"], + } + + +if __name__ == "__main__": + import uvicorn + + logger.info("Starting application on %s:%s", HOST, PORT) + uvicorn.run( + "app:app", + host=HOST, + port=PORT, + reload=DEBUG, + log_level="debug" if DEBUG else "info", + ) diff --git a/lab_solutions/lab1/app_python/docs/LAB01.md b/lab_solutions/lab1/app_python/docs/LAB01.md new file mode 100644 index 0000000000..1762dfb035 --- /dev/null +++ b/lab_solutions/lab1/app_python/docs/LAB01.md @@ -0,0 +1,11 @@ +# LAB01 - FastAPI Selection + +## Framework Selection +**FastAPI** has been chosen for this lab because it provides automatic documentation, excellent performance, and modern API while staying lightweight for a simple service. + +### Quick Comparison +| Framework | Pros | Cons | +| --- | --- | --- | +| FastAPI | Fast, async-ready, built-in docs, type hints | Slightly more setup than Flask | +| Flask | Minimal, easy to learn | No built-in docs, fewer batteries | +| Django | Full-featured, ORM included | Heavy for a small API | diff --git a/lab_solutions/lab1/app_python/docs/LAB02.md b/lab_solutions/lab1/app_python/docs/LAB02.md new file mode 100644 index 0000000000..2b676a4326 --- /dev/null +++ b/lab_solutions/lab1/app_python/docs/LAB02.md @@ -0,0 +1,160 @@ +# LAB02 - Docker Containerization + +## Docker Best Practices Applied + +### 1. Non-Root User +**Implementation:** +```dockerfile +RUN useradd --create-home --shell /bin/bash appuser +USER appuser +``` + +**Non-Root User motivation:** Running containers as root is a security risk. If an attacker compromises the application, they gain root privileges inside the container, which can be escalated to host-level access in some scenarios. Non-root users limit the damage potential. + +### 2. Specific Base Image Version +**Implementation:** +```dockerfile +FROM python:3.13-slim +``` + +**Why it matters:** Using a specific version (not `latest`) ensures reproducible builds. The `slim` variant reduces image size by excluding unnecessary packages, reducing attack surface and download time. + +### 3. Layer Caching Optimization +**Implementation:** +```dockerfile +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY app.py . +``` + +**Why it matters:** Docker caches layers. By copying `requirements.txt` before application code, dependency installation is only re-run when dependencies change. Since code changes more often than dependencies, this speeds up rebuilds significantly. + +### 4. .dockerignore File +**Implementation:** Excluded unnecessary files like `venv/`, `__pycache__/`, `.git/`, `docs/`, `tests/` + +**Why it matters:** Reduces build context size sent to Docker daemon, speeding up builds. Also prevents accidentally copying sensitive files or development artifacts into the image. + +### 5. Minimal File Copying +**Implementation:** Only copied essential files (`requirements.txt` and `app.py`) + +**Why it matters:** Smaller images mean faster deployments, less storage, and reduced attack surface. Each file copied is a potential security or bloat issue. + +### 6. No Cache for pip +**Implementation:** +```dockerfile +RUN pip install --no-cache-dir -r requirements.txt +``` + +**Why it matters:** pip cache is useless in a container (build-time only) and wastes space in the final image. + +--- + +## Image Information & Decisions + +### Base Image: `python:3.13-slim` +**Justification:** +- **Slim variant:** Balances size and functionality - includes what Python apps need, excludes build tools and docs +- **Version 3.13:** Latest stable Python, matches development environment +- **Alternative considered:** `alpine` is smaller but uses `musl` instead of `glibc`, causing compatibility issues with some Python packages + +### Final Image Size +164MB + +### Layer Structure +1. Base image (`python:3.13-slim`) +2. User creation +3. Working directory setup +4. Dependency installation (cached layer) +5. Application code (changes frequently) +6. Permission changes +7. User switch + +### Optimization Choices +- Separated dependency installation from code copy for caching +- Used `--no-cache-dir` to avoid storing pip cache +- Created non-root user before copying files to ensure proper ownership + +--- + +## Build & Run Process + +### Build Output +```bash +# Build command +docker build -t chupapupa/devops-info-service:latest . + +``` + +### Run Output +```bash +# Run command +docker run -p 5000:5000 chupapupa/devops-info-service:latest + +``` + +### Testing Endpoints +```bash + +curl http://localhost:5000/ + + +curl http://localhost:5000/health +``` + +### Docker Hub Repository +URL: `https://hub.docker.com/r/chupapupa/devops-info-service` + +--- + +## Technical Analysis + +### Why Does This Dockerfile Work? + +1. **Layer Order:** Dependencies are installed before code is copied, leveraging Docker's layer caching. If only `app.py` changes, Docker reuses the cached dependency layer. + +2. **User Permissions:** Files are copied as root, then ownership is changed to `appuser`. This ensures the non-root user can read application files while maintaining security. + +3. **Working Directory:** `WORKDIR /app` creates the directory if it doesn't exist and sets it as the context for subsequent commands. + +### What Would Happen If Layer Order Changed? + +If we copied `app.py` before `requirements.txt`: +- Every code change would invalidate the dependency installation layer +- Builds would take much longer (re-installing packages every time) +- No functional difference, just slower workflow + +### Security Considerations + +1. **Non-root user:** Limits privilege escalation potential +2. **Slim base image:** Reduces attack surface (fewer packages = fewer vulnerabilities) +3. **Specific versions:** Prevents supply chain attacks via `latest` tag changes +4. **Minimal file copying:** Reduces risk of exposing sensitive files + +### .dockerignore Benefits + +1. **Build Speed:** Smaller build context = faster uploads to Docker daemon +2. **Security:** Prevents accidentally copying `.git`, `.env`, or credentials +3. **Image Size:** Excludes unnecessary files like `venv/` or `__pycache__/` + +--- + +## Challenges & Solutions + +### Challenge 1: Permission Denied When Running as Non-Root +**Problem:** Initially tried switching to non-root user before copying files, which caused permission errors. + +**Solution:** Copy files as root first, then use `chown` to change ownership, then switch user. Order matters! + +### Challenge 2: Understanding Layer Caching +**Problem:** Initial builds took a long time even with minor code changes. + +**Solution:** Researched Docker layer caching and restructured Dockerfile to copy dependencies before code. Dramatically improved rebuild times. + +--- + +## What I Learned + +1. **Security by default:** Non-root users should be standard, not optional +2. **Layer order impacts build speed:** Docker's caching can save significant time +3. **Base image choice matters:** Balance between size, compatibility, and security +4. **Documentation is understanding:** Writing this doc helped solidify my Docker knowledge diff --git a/lab_solutions/lab1/app_python/docs/LAB03.md b/lab_solutions/lab1/app_python/docs/LAB03.md new file mode 100644 index 0000000000..36005c1467 --- /dev/null +++ b/lab_solutions/lab1/app_python/docs/LAB03.md @@ -0,0 +1,37 @@ +# LAB03 - Continuous Integration (CI/CD) + +## Overview +- **Testing framework:** pytest. It has concise assertions, great fixtures, and strong FastAPI support via TestClient +- **Coverage:** Tests verify `GET /`, `GET /health`, and 404 responses. They assert required JSON fields and data types. +- **Workflow triggers:** Runs on push and pull request changes inside `lab_solutions/lab1/app_python/`. +- **Versioning strategy:** CalVer (`YYYY.MM.DD`). It is simple for a service that is continuously deployed without strict release cycles. + +## Workflow Evidence +- **Successful workflow run:** https://github.com/Leropsis/DevOps-Core-Course-v/actions/runs/ +- **Docker image:** https://hub.docker.com/r/chupapupa/devops-info-service/tags +- **Status badge:** Added in `app_python/README.md` + +### Local test run +```bash +pip install -r requirements.txt -r requirements-dev.txt +pytest +``` + +## Best Practices Implemented +- **Dependency caching:** `actions/setup-python` with pip cache to reduce install time on repeated runs. +- **Fail fast:** If lint or tests fail, the workflow stops and Docker build is skipped. +- **Job dependencies:** Docker build job depends on tests (`needs: test`). +- **Least privilege:** Workflow permissions set to `contents: read`. +- **Path filters:** CI only runs when app files or the workflow change. +- **Concurrency:** Cancels previous runs on the same branch to avoid duplicate work. +- **Security scan:** Snyk scan on dependencies with a high severity threshold. + +## Key Decisions +- **Versioning Strategy:** CalVer for date-based releases that are easy to trace and automate. +- **Docker Tags:** `${VERSION}` and `latest` for clear versioned releases plus a default tag. +- **Workflow Triggers:** Path filters prevent unnecessary runs when unrelated files change. +- **Test Coverage:** Focus on endpoint behavior and response structure. + +## Challenges +- **Snyk setup:** Requires a `SNYK_TOKEN` GitHub secret. After adding the token, the scan runs successfully. +- **Docker Hub auth:** Requires `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN` secrets for pushing images. diff --git a/lab_solutions/lab1/app_python/docs/screenshots/docker_build_1.png b/lab_solutions/lab1/app_python/docs/screenshots/docker_build_1.png new file mode 100644 index 0000000000..409c8ecc67 Binary files /dev/null and b/lab_solutions/lab1/app_python/docs/screenshots/docker_build_1.png differ diff --git a/lab_solutions/lab1/app_python/docs/screenshots/docker_build_2.png b/lab_solutions/lab1/app_python/docs/screenshots/docker_build_2.png new file mode 100644 index 0000000000..752d0d59bc Binary files /dev/null and b/lab_solutions/lab1/app_python/docs/screenshots/docker_build_2.png differ diff --git a/lab_solutions/lab1/app_python/docs/screenshots/docker_hub_1.png b/lab_solutions/lab1/app_python/docs/screenshots/docker_hub_1.png new file mode 100644 index 0000000000..13d0f14ee3 Binary files /dev/null and b/lab_solutions/lab1/app_python/docs/screenshots/docker_hub_1.png differ diff --git a/lab_solutions/lab1/app_python/docs/screenshots/docker_hub_2.png b/lab_solutions/lab1/app_python/docs/screenshots/docker_hub_2.png new file mode 100644 index 0000000000..13c9376327 Binary files /dev/null and b/lab_solutions/lab1/app_python/docs/screenshots/docker_hub_2.png differ diff --git a/lab_solutions/lab1/app_python/docs/screenshots/docker_run.png b/lab_solutions/lab1/app_python/docs/screenshots/docker_run.png new file mode 100644 index 0000000000..026838aaf9 Binary files /dev/null and b/lab_solutions/lab1/app_python/docs/screenshots/docker_run.png differ diff --git a/lab_solutions/lab1/app_python/docs/screenshots/get_call.png b/lab_solutions/lab1/app_python/docs/screenshots/get_call.png new file mode 100644 index 0000000000..5ccbeeaeed Binary files /dev/null and b/lab_solutions/lab1/app_python/docs/screenshots/get_call.png differ diff --git a/lab_solutions/lab1/app_python/docs/screenshots/health_call.png b/lab_solutions/lab1/app_python/docs/screenshots/health_call.png new file mode 100644 index 0000000000..a3dcfa8e06 Binary files /dev/null and b/lab_solutions/lab1/app_python/docs/screenshots/health_call.png differ diff --git a/lab_solutions/lab1/app_python/docs/screenshots/health_call_dockerized.png b/lab_solutions/lab1/app_python/docs/screenshots/health_call_dockerized.png new file mode 100644 index 0000000000..163380d5e9 Binary files /dev/null and b/lab_solutions/lab1/app_python/docs/screenshots/health_call_dockerized.png differ diff --git a/lab_solutions/lab1/app_python/docs/screenshots/logs.png b/lab_solutions/lab1/app_python/docs/screenshots/logs.png new file mode 100644 index 0000000000..ec31c17132 Binary files /dev/null and b/lab_solutions/lab1/app_python/docs/screenshots/logs.png differ diff --git a/lab_solutions/lab1/app_python/docs/screenshots/logs_dockerized.png b/lab_solutions/lab1/app_python/docs/screenshots/logs_dockerized.png new file mode 100644 index 0000000000..53833a936d Binary files /dev/null and b/lab_solutions/lab1/app_python/docs/screenshots/logs_dockerized.png differ diff --git a/lab_solutions/lab1/app_python/docs/screenshots/working_service.png b/lab_solutions/lab1/app_python/docs/screenshots/working_service.png new file mode 100644 index 0000000000..f171a0a71a Binary files /dev/null and b/lab_solutions/lab1/app_python/docs/screenshots/working_service.png differ diff --git a/lab_solutions/lab1/app_python/requirements-dev.txt b/lab_solutions/lab1/app_python/requirements-dev.txt new file mode 100644 index 0000000000..6a502c8be5 --- /dev/null +++ b/lab_solutions/lab1/app_python/requirements-dev.txt @@ -0,0 +1,4 @@ +pytest==8.3.4 +pytest-cov==5.0.0 +ruff==0.8.4 +httpx==0.27.2 diff --git a/lab_solutions/lab1/app_python/requirements.txt b/lab_solutions/lab1/app_python/requirements.txt new file mode 100644 index 0000000000..792449289f --- /dev/null +++ b/lab_solutions/lab1/app_python/requirements.txt @@ -0,0 +1,2 @@ +fastapi==0.115.0 +uvicorn[standard]==0.32.0 diff --git a/lab_solutions/lab1/app_python/tests/__init__.py b/lab_solutions/lab1/app_python/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lab_solutions/lab1/app_python/tests/test_app.py b/lab_solutions/lab1/app_python/tests/test_app.py new file mode 100644 index 0000000000..baad6329f2 --- /dev/null +++ b/lab_solutions/lab1/app_python/tests/test_app.py @@ -0,0 +1,45 @@ +from fastapi.testclient import TestClient + +from app import app + + +client = TestClient(app) + + +def test_root_endpoint_structure(): + response = client.get("/", headers={"User-Agent": "pytest"}) + assert response.status_code == 200 + + data = response.json() + assert data["service"]["name"] == "devops-info-service" + assert data["service"]["framework"] == "FastAPI" + + assert "hostname" in data["system"] + assert "platform" in data["system"] + assert isinstance(data["system"]["cpu_count"], int) + + assert isinstance(data["runtime"]["uptime_seconds"], int) + assert "current_time" in data["runtime"] + + assert data["request"]["method"] == "GET" + assert data["request"]["path"] == "/" + + endpoints = {item["path"] for item in data["endpoints"]} + assert "/" in endpoints + assert "/health" in endpoints + + +def test_health_endpoint(): + response = client.get("/health") + assert response.status_code == 200 + + data = response.json() + assert data["status"] == "healthy" + assert isinstance(data["uptime_seconds"], int) + assert isinstance(data["timestamp"], str) + + +def test_unknown_endpoint_returns_404(): + response = client.get("/does-not-exist") + assert response.status_code == 404 + assert response.json()["detail"] == "Not Found" diff --git a/lab_solutions/lab1/pulumi/.gitignore b/lab_solutions/lab1/pulumi/.gitignore new file mode 100644 index 0000000000..a3807e5bdb --- /dev/null +++ b/lab_solutions/lab1/pulumi/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/lab_solutions/lab1/pulumi/Pulumi.dev1.yaml b/lab_solutions/lab1/pulumi/Pulumi.dev1.yaml new file mode 100644 index 0000000000..0cd634f840 --- /dev/null +++ b/lab_solutions/lab1/pulumi/Pulumi.dev1.yaml @@ -0,0 +1 @@ +{config: {'pulumi-lab4:cloudId': b1gslkhnl4tfvf0q42jg, 'pulumi-lab4:folderId': b1ggi4t7gp9stkm4mos2, 'pulumi-lab4:zone': ru-central1-a, 'pulumi-lab4:token': {secure: AAABAAdjjdvRcihbkfCZC3WN1y10rSusVyW/6En+4TQkLIJE0dIZKgTv1bfFepF+6u3YqYPx5Gsx0NcorSs9v3sS7j61HHwQKO09NTPxY3Ks+kBfbA6iOksEjITiewZcKJDogE9fZ/szGPBYLPrZIFyUvVnpr+BKws4lRPDrXnb3wcFthtf4JR35jdfG7NNS3GaFrPiS2oy3IZYG74YeGYvr/GKi8HgTG78zXd1RAngEFtliFH8UoOXCkYxSguN/5rUHYucb6xp4TFPeIR0ij100qOn18PdRdT3EEUZ46+akW41KKUMl4U9BeQe0pDUtNdsdEInEjcMiwi/Hh3k5E3Wv5PvfNfGpppo+bpsWgGENFJQbNSNzJQ==}}} diff --git a/lab_solutions/lab1/pulumi/Pulumi.yaml b/lab_solutions/lab1/pulumi/Pulumi.yaml new file mode 100644 index 0000000000..73677727ae --- /dev/null +++ b/lab_solutions/lab1/pulumi/Pulumi.yaml @@ -0,0 +1,11 @@ +name: pulumi-lab4 +description: Lab4 Pulumi VM +runtime: + name: python + options: + toolchain: pip + virtualenv: venv +config: + pulumi:tags: + value: + pulumi:template: python diff --git a/lab_solutions/lab1/pulumi/__main__.py b/lab_solutions/lab1/pulumi/__main__.py new file mode 100644 index 0000000000..e9feb360b1 --- /dev/null +++ b/lab_solutions/lab1/pulumi/__main__.py @@ -0,0 +1,127 @@ +import os +import pulumi +from pulumi_yandex import ( + VpcNetwork, VpcSubnet, VpcSecurityGroup, + ComputeInstance, ComputeImage +) + +cloud_id = os.environ.get("YC_CLOUD_ID") +folder_id = os.environ.get("YC_FOLDER_ID") +zone = os.environ.get("YC_ZONE", "ru-central1-a") + +if not cloud_id or not folder_id: + raise ValueError("YC_CLOUD_ID and YC_FOLDER_ID must be set") + +import os +ssh_key_path = os.path.expanduser("~/.ssh/id_rsa.pub") +with open(ssh_key_path, "r") as f: + ssh_public_key = f.read().strip() + +network = VpcNetwork( + "pulumi-lab-network", + name="pulumi-lab-network", + description="Network for Pulumi Lab4 VM", + labels={ + "environment": "lab", + "managed-by": "pulumi", + "course": "devops", + "lab": "04" + } +) + +subnet = VpcSubnet( + "pulumi-lab-subnet", + name="pulumi-lab-subnet", + description="Subnet for Pulumi Lab4 VM", + zone=zone, + network_id=network.id, + v4_cidr_blocks=["192.168.20.0/24"], + labels={ + "environment": "lab", + "managed-by": "pulumi" + } +) + +security_group = VpcSecurityGroup( + "pulumi-lab-sg", + name="pulumi-lab-sg", + description="Security group for Pulumi Lab4 VM", + network_id=network.id, + ingresses=[ # Changed from 'ingress' to 'ingresses' + { + "protocol": "TCP", + "description": "SSH access", + "port": 22, + "v4_cidr_blocks": ["0.0.0.0/0"], + }, + { + "protocol": "TCP", + "description": "HTTP web traffic", + "port": 80, + "v4_cidr_blocks": ["0.0.0.0/0"], + }, + { + "protocol": "TCP", + "description": "Application port", + "port": 5000, + "v4_cidr_blocks": ["0.0.0.0/0"], + }, + ], + egresses=[ # Changed from 'egress' to 'egresses' + { + "protocol": "ANY", + "description": "Allow all outgoing", + "v4_cidr_blocks": ["0.0.0.0/0"], + "from_port": 0, + "to_port": 65535, + } + ], + labels={ + "environment": "lab", + "managed-by": "pulumi" + } +) + +image = ComputeImage("ubuntu-image", source_family="ubuntu-2204-lts") + +vm = ComputeInstance( + "pulumi-lab-vm", + name="pulumi-lab-vm", + description="Pulumi Lab4 virtual machine", + platform_id="standard-v2", + zone=zone, + resources={ + "cores": 2, + "memory": 1, + "core_fraction": 20, + }, + boot_disk={ + "auto_delete": True, + "initialize_params": { + "image_id": image.id, + "size": 10, + "type": "network-hdd", + } + }, + network_interfaces=[{ + "subnet_id": subnet.id, + "security_group_ids": [security_group.id], + "nat": True, + }], + metadata={ + "ssh-keys": f"ubuntu:{ssh_public_key}", + }, + allow_stopping_for_update=True, + labels={ + "environment": "lab", + "managed-by": "pulumi", + "course": "devops", + "lab": "04" + } +) + +pulumi.export("vm_public_ip", vm.network_interfaces[0].nat_ip_address) +pulumi.export("vm_private_ip", vm.network_interfaces[0].ip_address) +pulumi.export("vm_id", vm.id) +pulumi.export("ssh_command", pulumi.Output.concat("ssh -i ~/.ssh/lab4-key ubuntu@", vm.network_interfaces[0].nat_ip_address)) + diff --git a/lab_solutions/lab1/pulumi/artifacts/pulumi-preview.png b/lab_solutions/lab1/pulumi/artifacts/pulumi-preview.png new file mode 100644 index 0000000000..ece4df0a51 Binary files /dev/null and b/lab_solutions/lab1/pulumi/artifacts/pulumi-preview.png differ diff --git a/lab_solutions/lab1/pulumi/artifacts/pulumi-ssh.png b/lab_solutions/lab1/pulumi/artifacts/pulumi-ssh.png new file mode 100644 index 0000000000..0d51a13c88 Binary files /dev/null and b/lab_solutions/lab1/pulumi/artifacts/pulumi-ssh.png differ diff --git a/lab_solutions/lab1/pulumi/artifacts/pulumi-stack.png b/lab_solutions/lab1/pulumi/artifacts/pulumi-stack.png new file mode 100644 index 0000000000..d50e6d4996 Binary files /dev/null and b/lab_solutions/lab1/pulumi/artifacts/pulumi-stack.png differ diff --git a/lab_solutions/lab1/pulumi/artifacts/pulumi-up-1.png b/lab_solutions/lab1/pulumi/artifacts/pulumi-up-1.png new file mode 100644 index 0000000000..975cf88926 Binary files /dev/null and b/lab_solutions/lab1/pulumi/artifacts/pulumi-up-1.png differ diff --git a/lab_solutions/lab1/pulumi/requirements.txt b/lab_solutions/lab1/pulumi/requirements.txt new file mode 100644 index 0000000000..bc4e43087b --- /dev/null +++ b/lab_solutions/lab1/pulumi/requirements.txt @@ -0,0 +1 @@ +pulumi>=3.0.0,<4.0.0 diff --git a/lab_solutions/lab1/terraform/artifacts/ssh.png b/lab_solutions/lab1/terraform/artifacts/ssh.png new file mode 100644 index 0000000000..d0e868f3f7 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/ssh.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-apply-1.png b/lab_solutions/lab1/terraform/artifacts/terraform-apply-1.png new file mode 100644 index 0000000000..4f27fbbe97 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-apply-1.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-apply-10.png b/lab_solutions/lab1/terraform/artifacts/terraform-apply-10.png new file mode 100644 index 0000000000..2b11bc765d Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-apply-10.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-apply-2.png b/lab_solutions/lab1/terraform/artifacts/terraform-apply-2.png new file mode 100644 index 0000000000..f7968531eb Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-apply-2.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-apply-3.png b/lab_solutions/lab1/terraform/artifacts/terraform-apply-3.png new file mode 100644 index 0000000000..1d749f3f04 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-apply-3.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-apply-4.png b/lab_solutions/lab1/terraform/artifacts/terraform-apply-4.png new file mode 100644 index 0000000000..111e417482 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-apply-4.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-apply-5.png b/lab_solutions/lab1/terraform/artifacts/terraform-apply-5.png new file mode 100644 index 0000000000..801e4dfec4 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-apply-5.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-apply-6.png b/lab_solutions/lab1/terraform/artifacts/terraform-apply-6.png new file mode 100644 index 0000000000..f89e837925 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-apply-6.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-apply-7.png b/lab_solutions/lab1/terraform/artifacts/terraform-apply-7.png new file mode 100644 index 0000000000..75aa82d04a Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-apply-7.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-apply-8.png b/lab_solutions/lab1/terraform/artifacts/terraform-apply-8.png new file mode 100644 index 0000000000..b181485455 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-apply-8.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-apply-9.png b/lab_solutions/lab1/terraform/artifacts/terraform-apply-9.png new file mode 100644 index 0000000000..4257a108c5 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-apply-9.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-init.png b/lab_solutions/lab1/terraform/artifacts/terraform-init.png new file mode 100644 index 0000000000..088dd17867 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-init.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-list.png b/lab_solutions/lab1/terraform/artifacts/terraform-list.png new file mode 100644 index 0000000000..519e56cc69 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-list.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-output.png b/lab_solutions/lab1/terraform/artifacts/terraform-output.png new file mode 100644 index 0000000000..63a0cc7fc0 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-output.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-plan-1.png b/lab_solutions/lab1/terraform/artifacts/terraform-plan-1.png new file mode 100644 index 0000000000..b49c9576f6 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-plan-1.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-plan-3.png b/lab_solutions/lab1/terraform/artifacts/terraform-plan-3.png new file mode 100644 index 0000000000..de42223e7e Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-plan-3.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-plan-4.png b/lab_solutions/lab1/terraform/artifacts/terraform-plan-4.png new file mode 100644 index 0000000000..82d35ad0e4 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-plan-4.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-plan-5.png b/lab_solutions/lab1/terraform/artifacts/terraform-plan-5.png new file mode 100644 index 0000000000..1325141fb6 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-plan-5.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-plan-6.png b/lab_solutions/lab1/terraform/artifacts/terraform-plan-6.png new file mode 100644 index 0000000000..c4cd1b6672 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-plan-6.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-plan-7.png b/lab_solutions/lab1/terraform/artifacts/terraform-plan-7.png new file mode 100644 index 0000000000..6fb728390c Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-plan-7.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-plan-8.png b/lab_solutions/lab1/terraform/artifacts/terraform-plan-8.png new file mode 100644 index 0000000000..0a557e50b0 Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-plan-8.png differ diff --git a/lab_solutions/lab1/terraform/artifacts/terraform-plan-9.png b/lab_solutions/lab1/terraform/artifacts/terraform-plan-9.png new file mode 100644 index 0000000000..fe2ad7e93a Binary files /dev/null and b/lab_solutions/lab1/terraform/artifacts/terraform-plan-9.png differ diff --git a/lab_solutions/lab1/terraform/main.tf b/lab_solutions/lab1/terraform/main.tf new file mode 100644 index 0000000000..c2facda5b4 --- /dev/null +++ b/lab_solutions/lab1/terraform/main.tf @@ -0,0 +1,139 @@ +terraform { + required_version = ">= 0.13" + + required_providers { + yandex = { + source = "yandex-cloud/yandex" + version = "~> 0.85" + } + } +} + +provider "yandex" { + service_account_key_file = var.service_account_key_file + cloud_id = var.cloud_id + folder_id = var.folder_id + zone = var.zone +} + +data "local_file" "public_key" { + filename = var.public_key_path +} + +resource "yandex_vpc_network" "lab_network" { + name = "${var.instance_name}-network" + description = "Network for lab4 vm" + + labels = { + environment = "lab" + managed-by = "terraform" + purpose = "devops" + } +} + +resource "yandex_vpc_subnet" "lab_subnet" { + name = "${var.instance_name}-subnet" + description = "Subnet for lab4 vm in ${var.zone}" + zone = var.zone + network_id = yandex_vpc_network.lab_network.id + v4_cidr_blocks = [var.vpc_cidr] + + labels = { + environment = "lab" + managed-by = "terraform" + } +} + +resource "yandex_vpc_security_group" "lab_sg" { + name = "${var.instance_name}-security-group" + description = "Security group with firewall rules for Lab4 VM" + network_id = yandex_vpc_network.lab_network.id + + labels = { + environment = "lab" + managed-by = "terraform" + } + + ingress { + description = "SSH access for remote management" + protocol = "TCP" + port = 22 + v4_cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "HTTP web traffic" + protocol = "TCP" + port = 80 + v4_cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "Application port for Docker container" + protocol = "TCP" + port = 5000 + v4_cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "Lab6 App port 8000" + protocol = "TCP" + port = 8000 + v4_cidr_blocks = ["0.0.0.0/0"] + } + + egress { + description = "Allow all outgoing traffic" + protocol = "ANY" + v4_cidr_blocks = ["0.0.0.0/0"] + from_port = 0 + to_port = 65535 + } +} + +data "yandex_compute_image" "ubuntu" { + family = "ubuntu-2204-lts" +} + +resource "yandex_compute_instance" "lab_vm" { + name = var.instance_name + description = "Lab4 virtual machine for DevOps" + platform_id = "standard-v2" + zone = var.zone + + resources { + cores = 2 + memory = 1 + core_fraction = 20 + } + + boot_disk { + auto_delete = true + + initialize_params { + image_id = data.yandex_compute_image.ubuntu.id + size = 10 + type = "network-hdd" + } + } + + network_interface { + subnet_id = yandex_vpc_subnet.lab_subnet.id + security_group_ids = [yandex_vpc_security_group.lab_sg.id] + nat = true + } + + metadata = { + ssh-keys = "${var.vm_username}:${chomp(data.local_file.public_key.content)}" + } + + allow_stopping_for_update = true + + labels = { + environment = "lab" + managed-by = "terraform" + course = "devops" + lab = "04" + } +} + diff --git a/lab_solutions/lab1/terraform/outputs.tf b/lab_solutions/lab1/terraform/outputs.tf new file mode 100644 index 0000000000..94fc033871 --- /dev/null +++ b/lab_solutions/lab1/terraform/outputs.tf @@ -0,0 +1,15 @@ +output "vm_public_ip" { + description = "Public IP address of the VM" + value = yandex_compute_instance.lab_vm.network_interface[0].nat_ip_address +} + +output "ssh_connection_command" { + description = "SSH command to connect to the VM" + value = "ssh ${var.vm_username}@${yandex_compute_instance.lab_vm.network_interface[0].nat_ip_address}" +} + +output "vm_id" { + description = "ID of the VM instance" + value = yandex_compute_instance.lab_vm.id +} + diff --git a/lab_solutions/lab1/terraform/variables.tf b/lab_solutions/lab1/terraform/variables.tf new file mode 100644 index 0000000000..0a20ece8f5 --- /dev/null +++ b/lab_solutions/lab1/terraform/variables.tf @@ -0,0 +1,45 @@ +variable "cloud_id" { + description = "Yandex Cloud ID where resources will be created" + type = string +} + +variable "folder_id" { + description = "Yandex Cloud Folder ID where resources will be created" + type = string +} + +variable "zone" { + description = "Yandex Cloud availability zone" + type = string + default = "ru-central1-a" +} + +variable "service_account_key_file" { + description = "Path to the service account key JSON file" + type = string + default = "inno-key.json" +} + +variable "public_key_path" { + description = "Path to the public SSH key for VM access" + type = string + default = "~/.ssh/id_rsa.pub" +} + +variable "instance_name" { + description = "Name of the VM instance" + type = string + default = "lab4-vm" +} + +variable "vm_username" { + description = "Username for SSH access to the VM" + type = string + default = "ubuntu" +} + +variable "vpc_cidr" { + description = "CIDR block for the VPC network" + type = string + default = "192.168.10.0/24" +}