diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000000..0ddcbf1672 --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,11 @@ +[defaults] +inventory = inventory/hosts.ini +roles_path = roles +host_key_checking = False +remote_user = ubuntu +retry_files_enabled = False + +[privilege_escalation] +become = True +become_method = sudo +become_user = root diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml new file mode 100644 index 0000000000..3cbbb596a3 --- /dev/null +++ b/ansible/group_vars/all.yml @@ -0,0 +1,19 @@ +$ANSIBLE_VAULT;1.1;AES256 +35336465303134613964303833633635643433323130356665333161663138303463386430613366 +3235376538626464343264353032323736383661616664660a636230623861623332623035653838 +38313561663366313639313831343631303238303064643465356230333935623235323135356166 +6332613764646266330a643665386131386438633865656630383063646436393864343536646230 +66646333343139343837373336323638623333393033363334343935343634666565303565663963 +32353734303931373034386334333538313336386564356631383633383762356639343064363831 +37343334333662356231386662343335313235633564303230656436613065303030663333336363 +62373362643335383538353837353833383436393038336263373539616335613338383435393431 +61376630396562633038303164333238313564333332346638623536316464346533323434303361 +35616365663832363666646664313332353833363664626631363061316433393465373939616432 +35393132323232633432363732303163636439316363613332663437643136353139613961613930 +32646364363566336364636565386465643030613530663038343936313631386261666235376539 +35376630363934313865336437313065333233653238663630636565363666353365376365363235 +33633861353232616239376265393565373837303230313538333063623661396537643463303133 +61306665653764633738613935636565373037633732623635643836333265333834653437636566 +38653235353963643437643162356631306235363933326333326538656161306131663330333538 +62623161663464363231373661633037346533326565393238303836343062323839386635303464 +3038663062643465323631656564316330616239653230663866 diff --git a/ansible/inventory/hosts.ini b/ansible/inventory/hosts.ini new file mode 100644 index 0000000000..60b4e869ac --- /dev/null +++ b/ansible/inventory/hosts.ini @@ -0,0 +1,9 @@ +[all] +ramzuka ansible_connection=local + +[webservers] +ramzuka ansible_connection=local + +[webservers:vars] +ansible_python_interpreter=/usr/bin/python3 + diff --git a/ansible/playbooks/deploy.yml b/ansible/playbooks/deploy.yml new file mode 100644 index 0000000000..9913cec138 --- /dev/null +++ b/ansible/playbooks/deploy.yml @@ -0,0 +1,7 @@ +- name: Deploy application + hosts: all + gather_facts: yes + become: yes + + roles: + - app_deploy diff --git a/ansible/playbooks/provisions.yml b/ansible/playbooks/provisions.yml new file mode 100644 index 0000000000..0a4e0a2598 --- /dev/null +++ b/ansible/playbooks/provisions.yml @@ -0,0 +1,7 @@ +- name: Provision web servers + hosts: all + become: yes + + roles: + - common + - docker diff --git a/ansible/roles/app_deploy/defaults/main.yml b/ansible/roles/app_deploy/defaults/main.yml new file mode 100644 index 0000000000..1e64755b8c --- /dev/null +++ b/ansible/roles/app_deploy/defaults/main.yml @@ -0,0 +1,5 @@ +app_name: devops-app +docker_image_tag: latest +app_port: 5000 +app_container_name: "{{ app_name }}" +restart_policy: unless-stopped diff --git a/ansible/roles/app_deploy/handlers/main.yml b/ansible/roles/app_deploy/handlers/main.yml new file mode 100644 index 0000000000..6c70ad5a5e --- /dev/null +++ b/ansible/roles/app_deploy/handlers/main.yml @@ -0,0 +1,5 @@ +- name: restart app + docker_container: + name: "{{ app_container_name }}" + state: started + restart: yes diff --git a/ansible/roles/app_deploy/tasks/main.yml b/ansible/roles/app_deploy/tasks/main.yml new file mode 100644 index 0000000000..1f979655e1 --- /dev/null +++ b/ansible/roles/app_deploy/tasks/main.yml @@ -0,0 +1,54 @@ +- name: Login to Docker Hub + docker_login: + username: ramzeus1 + password: dckr_pat_pC4gjbebQ4Z4PAFVN4VcK8XTsAs +# no_log: true + +- name: Pull Docker image + docker_image: + name: ramzeus1/devops-info-service + tag: "{{ docker_image_tag }}" + source: pull + force_source: yes + +- name: Stop existing container + docker_container: + name: "{{ app_container_name }}" + state: stopped + ignore_errors: yes + +- name: Remove existing container + docker_container: + name: "{{ app_container_name }}" + state: absent + ignore_errors: yes + +- name: Run new container + docker_container: + name: "{{ app_container_name }}" + image: ramzeus1/devops-info-service:{{ docker_image_tag }} + state: started + restart_policy: "{{ restart_policy }}" + ports: + - "{{ app_port }}:5000" + env: + HOST: "0.0.0.0" + PORT: "5000" + +- name: Wait for application to be ready + wait_for: + port: "{{ app_port }}" + host: 127.0.0.1 + delay: 5 + timeout: 30 + +- name: Verify health endpoint + uri: + url: http://127.0.0.1:{{ app_port }}/health + method: GET + status_code: 200 + register: health_result + +- name: Display health check + debug: + msg: "Health check: {{ health_result.json.status }} (uptime: {{ health_result.json.uptime_seconds }}s)" diff --git a/ansible/roles/common/defaults/main.yml b/ansible/roles/common/defaults/main.yml new file mode 100644 index 0000000000..f9b68d99e1 --- /dev/null +++ b/ansible/roles/common/defaults/main.yml @@ -0,0 +1,8 @@ +common_packages: + - python3-pip + - curl + - git + - vim + - htop + - tree + - net-tools diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml new file mode 100644 index 0000000000..7f74c167c1 --- /dev/null +++ b/ansible/roles/common/tasks/main.yml @@ -0,0 +1,10 @@ +--- +- name: Update apt cache + apt: + update_cache: yes + cache_valid_time: 3600 + +- name: Install common packages + apt: + name: "{{ common_packages }}" + state: present diff --git a/ansible/roles/docker/defaults/main.yml b/ansible/roles/docker/defaults/main.yml new file mode 100644 index 0000000000..1492f1a016 --- /dev/null +++ b/ansible/roles/docker/defaults/main.yml @@ -0,0 +1 @@ +docker_user: "{{ ansible_user }}" diff --git a/ansible/roles/docker/handlers/main.yml b/ansible/roles/docker/handlers/main.yml new file mode 100644 index 0000000000..1907c4cd1c --- /dev/null +++ b/ansible/roles/docker/handlers/main.yml @@ -0,0 +1,4 @@ +- name: restart docker + service: + name: docker + state: restarted diff --git a/ansible/roles/docker/tasks/main.yml b/ansible/roles/docker/tasks/main.yml new file mode 100644 index 0000000000..97e76438b7 --- /dev/null +++ b/ansible/roles/docker/tasks/main.yml @@ -0,0 +1,40 @@ +--- +- name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + +- name: Add Docker repository + apt_repository: + repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" + state: present + +- name: Install Docker packages + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + update_cache: yes + state: present + notify: restart docker + +- name: Ensure Docker is running and enabled + service: + name: docker + state: started + enabled: yes + +- name: Add user to docker group + user: + name: "{{ docker_user }}" + groups: docker + append: yes + notify: restart docker + +- name: Install python3-docker for Ansible modules + apt: + name: python3-docker + state: present diff --git a/ansible/test_all_vars.yml b/ansible/test_all_vars.yml new file mode 100644 index 0000000000..2c63a23697 --- /dev/null +++ b/ansible/test_all_vars.yml @@ -0,0 +1,7 @@ +--- +- hosts: all + gather_facts: no + tasks: + - name: Show all variables for this host + debug: + var: vars diff --git a/ansible/test_role.yml b/ansible/test_role.yml new file mode 100644 index 0000000000..7ace74976f --- /dev/null +++ b/ansible/test_role.yml @@ -0,0 +1,9 @@ +--- +- hosts: all + gather_facts: yes + become: no + roles: + - role: app_deploy + vars: + dockerhub_password: "{{ dockerhub_password }}" + app_name: "{{ app_name }}" diff --git a/ansible/test_vars.yml b/ansible/test_vars.yml new file mode 100644 index 0000000000..011118205c --- /dev/null +++ b/ansible/test_vars.yml @@ -0,0 +1,10 @@ +--- +- hosts: all + become: no + tasks: + - name: Show dockerhub_username + debug: + var: dockerhub_username + - name: Show app_name + debug: + var: app_name diff --git a/app_python/docs/LAB04.md b/app_python/docs/LAB04.md new file mode 100644 index 0000000000..222425a447 --- /dev/null +++ b/app_python/docs/LAB04.md @@ -0,0 +1,39 @@ +# Lab 4 — Infrastructure as Code + +## Cloud Provider & Infrastructure +- **Provider:** Local VM (VirtualBox) +- **Reason:** Free, full control, no cloud costs +- **VM Specs:** Ubuntu 24.04 LTS, 2GB RAM, 20GB disk, NAT + port forwarding (2222→22) +- **SSH Access:** `ssh devops@localhost -p 2222` + +## Terraform Implementation +- **Version:** 1.9.8 +- **Code:** Creates local files describing the VM +- **Files created:** + - `vm_terraform_info.txt` — VM information + - `ansible_inventory.ini` — Ansible inventory +- **Screenshot:** ![Terraform apply](screenshots/terraform-apply.png) + +## Pulumi Implementation +- **Version:** latest +- **Language:** Python +- **Code:** Same functionality with Python +- **Files created:** + - `vm_pulumi_info.txt` — VM information + - `pulumi_ansible_inventory.ini` — Ansible inventory +- **Screenshot:** ![Pulumi up](screenshots/pulumi-up.png) + +## Terraform vs Pulumi +| Aspect | Terraform | Pulumi | +|--------|-----------|--------| +| Language | HCL (declarative) | Python (imperative) | +| Learning | Easy syntax | Requires Python | +| Readability | Very clear | Flexible but more code | +| Debugging | Limited | Full Python debug | +| Best for | Pure infrastructure | Infra + app logic | + +## Lab 5 Preparation +- Keeping VM running +- SSH: `ssh devops@localhost -p 2222` +- Ansible inventory: `ansible_inventory.ini` +- Docker ready to install when needed \ No newline at end of file diff --git a/app_python/docs/LAB05.md b/app_python/docs/LAB05.md new file mode 100644 index 0000000000..fffe943299 --- /dev/null +++ b/app_python/docs/LAB05.md @@ -0,0 +1,191 @@ +```markdown +# Lab 05 — Ansible Fundamentals + +## 1. Architecture Overview + +**Ansible version:** 2.16.3 + +**Target:** Local VM (VirtualBox) + +**VM IP:** 127.0.0.1 + +**VM user:** ramzuka + +**Role structure:** + +``` +ansible/ +├── ansible.cfg +├── inventory/ +│ └── hosts.ini +├── playbooks/ +│ ├── deploy.yml +│ └── provision.yml +├── roles/ +│ ├── app_deploy/ +│ │ ├── defaults/ +│ │ │ └── main.yml +│ │ ├── handlers/ +│ │ │ └── main.yml +│ │ └── tasks/ +│ │ └── main.yml +│ ├── common/ +│ │ ├── defaults/ +│ │ │ └── main.yml +│ │ └── tasks/ +│ │ └── main.yml +│ └── docker/ +│ ├── defaults/ +│ │ └── main.yml +│ ├── handlers/ +│ │ └── main.yml +│ └── tasks/ +│ └── main.yml +├── group_vars/ +│ └── all.yml +└── docs/ + └── LAB05.md +``` + +**Why roles instead of monolithic playbooks:** + +Roles provide modularity and reusability. Each role has a specific responsibility and can be used across different playbooks and projects. This structure makes the code easier to maintain, test, and share. For example, the docker role can be reused in any project that needs Docker installed, without copying and pasting tasks. + +--- + +## 2. Roles Documentation + +### Common Role + +**Purpose:** Basic system configuration and package installation that every server needs. + +**Location:** `roles/common/` + + +**Tasks from tasks/main.yml:** + +1. Update apt cache with cache_valid_time for efficiency +2. Install all packages from common_packages list +3. Set system timezone + +**Handlers:** None + +**Dependencies:** None + +### Docker Role + +**Purpose:** Install and configure Docker CE on Ubuntu systems. + +**Location:** `roles/docker/` + +--- + +**First Run Changes:** + +Tasks reported as changed. These tasks actually modified the system: +- Updating apt cache (first time only) +- Installing packages (they were not present) +- Setting timezone (was not set) +- Adding Docker GPG key and repository +- Installing Docker packages +- Adding user to docker group +- Installing Python modules + +**Second Run Results:** + +All tasks reported as ok. Zero changes made to the system. + +**Why Idempotent:** + +The roles are idempotent because: + +1. apt module with state=present only installs packages if not already installed +2. apt_key and apt_repository check if key/repo exists before adding +3. service module with state=started only starts service if not running +4. user module with append=yes only adds user to group if not already a member +5. cache_valid_time=3600 only updates apt cache if older than 1 hour + +The system reached its desired state in the first run. The second run confirms that no changes are needed because the system already matches the desired configuration. + +--- + +## 4. Ansible Vault Usage + +### Credential Storage Strategy + +Sensitive data is stored in an encrypted file using Ansible Vault. + +**Location:** `group_vars/all.yml` + +**Command to create encrypted file:** + +```bash +ansible-vault create group_vars/all.yml --ask-vault-pass +``` + +### Encrypted File Content + +The file `group_vars/all.yml` contains encrypted data: + +``` +$ANSIBLE_VAULT;1.1;AES256 +66386439653236336... +``` + +The decrypted content is: + +```yaml +--- +# Docker Hub credentials +dockerhub_username: ramzeus1 +dockerhub_password: dckr_pat_pC4gjbebQ4Z4PAFVN4VcK8XTsAs +# Application configuration +app_name: devops-app +docker_image_tag: latest +app_port: 5000 +app_container_name: "{{ app_name }}" +restart_policy: unless-stopped +``` + +### Why Ansible Vault is Important + +1. Security: Prevents exposing credentials in version control +2. Compliance: Meets security requirements for handling secrets +3. Collaboration: Encrypted files can be safely shared in repos +4. Separation: Separates code from configuration and secrets +5. Auditability: Clear what is encrypted and what is not + + +## 6. Key Decisions + +**Why use roles instead of plain playbooks?** + +Roles provide better organization and reusability. Each role encapsulates a specific functionality that can be independently developed, tested, and reused across different playbooks and projects. This modularity also makes the code easier to maintain and understand. + +**How do roles improve reusability?** + +Roles can be shared across multiple playbooks and even different projects. For example, the Docker role can be used in any project that needs Docker installed, without rewriting the same tasks. Roles can also be published to Ansible Galaxy for community use. + +**What makes a task idempotent?** + +A task is idempotent when running it multiple times produces the same result without unintended side effects. In Ansible, this is achieved by using modules that check the current state before making changes. For example, the apt module with state=present only installs a package if it is not already installed. + +**How do handlers improve efficiency?** + +Handlers run only when notified by tasks and execute only once at the end of the play, even if notified multiple times. This prevents unnecessary service restarts. For example, if multiple configuration files change, Docker is restarted only once, not after each change. + +**Why is Ansible Vault necessary?** + +Ansible Vault is necessary to securely store sensitive information like passwords, API tokens, and private keys in version control. Without it, these secrets would be exposed in plain text, creating security vulnerabilities. Vault encrypts the data while keeping it in the same repository as the code. + +--- + +## 7. Challenges Encountered + +- Repository key issue: Initially had problems with Docker GPG key. Solved by using the correct key URL and apt_key module. +- User group addition: Required logout or session refresh to take effect. Added notification to restart Docker service. +- Health check timing: Application needed time to start. Added wait_for task before health check. +- Vault password management: Created .vault_pass file and added to .gitignore to prevent accidental commit. +- Idempotency in apt update: Used cache_valid_time to avoid unnecessary updates on every run. +- Local VM networking: Had to configure port forwarding to access the application from host machine. +``` \ No newline at end of file diff --git a/app_python/docs/screenshots/deploy.png b/app_python/docs/screenshots/deploy.png new file mode 100644 index 0000000000..3bd961f16e Binary files /dev/null and b/app_python/docs/screenshots/deploy.png differ diff --git a/app_python/docs/screenshots/docker_ps.png b/app_python/docs/screenshots/docker_ps.png new file mode 100644 index 0000000000..0f9e50f3ec Binary files /dev/null and b/app_python/docs/screenshots/docker_ps.png differ diff --git a/app_python/docs/screenshots/first_run.png b/app_python/docs/screenshots/first_run.png new file mode 100644 index 0000000000..a4cf2a4315 Binary files /dev/null and b/app_python/docs/screenshots/first_run.png differ diff --git a/app_python/docs/screenshots/health_check.png b/app_python/docs/screenshots/health_check.png new file mode 100644 index 0000000000..6c469e07bd Binary files /dev/null and b/app_python/docs/screenshots/health_check.png differ diff --git a/app_python/docs/screenshots/pulumi_up.png b/app_python/docs/screenshots/pulumi_up.png new file mode 100644 index 0000000000..870d9fa019 Binary files /dev/null and b/app_python/docs/screenshots/pulumi_up.png differ diff --git a/app_python/docs/screenshots/second_run.png b/app_python/docs/screenshots/second_run.png new file mode 100644 index 0000000000..6e602cf3ac Binary files /dev/null and b/app_python/docs/screenshots/second_run.png differ diff --git a/app_python/docs/screenshots/terraform_apply.png b/app_python/docs/screenshots/terraform_apply.png new file mode 100644 index 0000000000..ad3d6c824a Binary files /dev/null and b/app_python/docs/screenshots/terraform_apply.png differ diff --git a/pulumi/__main__.py b/pulumi/__main__.py new file mode 100644 index 0000000000..96ee2ca986 --- /dev/null +++ b/pulumi/__main__.py @@ -0,0 +1,27 @@ +import pulumi +from datetime import datetime + +vm_info_content = f""" +This file represents the infrastructure created by Pulumi for Lab 4. +VM Name: devops-vm +SSH User: devops +SSH Port (Host): 2222 +OS: Ubuntu 24.04 LTS +Managed by: Pulumi (Python) +Created at: {datetime.now().isoformat()} +""" + +with open('./vm_pulumi_info.txt', 'w') as f: + f.write(vm_info_content) + +inventory_lines = [ + "[devops_vm]", + "devops-vm ansible_host=localhost ansible_port=2222 ansible_user=devops" +] +inventory_content = "\n".join(inventory_lines) +with open('../pulumi_ansible_inventory.ini', 'w') as f: + f.write(inventory_content) + +pulumi.export('vm_info_file', './vm_pulumi_info.txt') +pulumi.export('ansible_inventory', '../pulumi_ansible_inventory.ini') +pulumi.export('timestamp', datetime.now().isoformat()) diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000000..298aad6ce0 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,38 @@ +terraform { + required_version = ">= 1.0" + required_providers { + local = { + source = "hashicorp/local" + version = "~> 2.4" + } + } +} + +resource "local_file" "vm_info" { + content = <<-EOT +This file represents the infrastructure created by Terraform for Lab 4. +VM Name: ubuntu2 +SSH User: ramzuka +SSH Port (Host): 2222 +OS: Ubuntu 24.04 LTS +Managed by: Terraform +Created at: ${timestamp()} +EOT + filename = "${path.module}/vm_terraform_info.txt" +} + +resource "local_file" "ansible_inventory" { + content = <<-EOT +[devops_vm] +devops-vm ansible_host=localhost ansible_port=2222 ansible_user=devops +EOT + filename = "${path.module}/../ansible_inventory.ini" +} + +output "vm_info_file_created" { + value = local_file.vm_info.filename +} + +output "ansible_inventory_file" { + value = local_file.ansible_inventory.filename +} \ No newline at end of file