diff --git a/.gitignore b/.gitignore index 30d74d2584..e8384ee8c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,18 @@ -test \ No newline at end of file +test +# Terraform +terraform/.terraform/ +terraform/.terraform.lock.hcl +terraform/terraform.tfstate +terraform/terraform.tfstate.backup +terraform/*.tfvars + +# Yandex Cloud SA key +.yc/ + +venv/ +*/venv/ +__pycache__/ +.terraform/ +.pulumi/ +*.tfstate +*.tfstate.backup diff --git a/ansible/.github/workflows/ansible-deploy.yml b/ansible/.github/workflows/ansible-deploy.yml new file mode 100644 index 0000000000..c9dd3010cf --- /dev/null +++ b/ansible/.github/workflows/ansible-deploy.yml @@ -0,0 +1,62 @@ +name: Ansible Deployment + +on: + push: + branches: [ "main", "master", "lab06" ] + paths: + - "ansible/**" + - ".github/workflows/ansible-deploy.yml" + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install ansible + lint + run: pip install ansible ansible-lint + - name: ansible-lint + run: | + cd ansible + ansible-lint playbooks/*.yml + + deploy: + needs: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install Ansible + community.docker + run: | + pip install ansible + ansible-galaxy collection install community.docker + + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan -H "${{ secrets.VM_HOST }}" >> ~/.ssh/known_hosts + + - name: Create vault pass file + run: | + echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > /tmp/vault_pass + chmod 600 /tmp/vault_pass + + - name: Deploy + env: + ANSIBLE_HOST_KEY_CHECKING: "False" + run: | + cd ansible + ansible-playbook playbooks/deploy.yml \ + -i inventory/hosts.ini \ + --vault-password-file /tmp/vault_pass + + - name: Verify + run: | + sleep 5 + curl -f "http://${{ secrets.VM_HOST }}:5000/health" diff --git a/ansible/.gitignore b/ansible/.gitignore new file mode 100644 index 0000000000..ff6e09c2c4 --- /dev/null +++ b/ansible/.gitignore @@ -0,0 +1,6 @@ + +# Ansible +*.retry +.vault_pass +__pycache__/ +ansible/.venv/ diff --git a/ansible/0 b/ansible/0 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000000..1dae3b014c --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,13 @@ +[defaults] +inventory = inventory/hosts.ini +roles_path = roles +host_key_checking = False +remote_user = vboxuser +retry_files_enabled = False +forks = 10 +timeout = 30 + +[privilege_escalation] +become = True +become_method = sudo +become_user = root diff --git a/ansible/docs/LAB05.md b/ansible/docs/LAB05.md new file mode 100644 index 0000000000..7400bf3c02 --- /dev/null +++ b/ansible/docs/LAB05.md @@ -0,0 +1,157 @@ +# LAB05 — Ansible Fundamentals + +## 1. Architecture Overview + +- **Ansible Version:** 2.16+ +- **Target OS:** Ubuntu 24.04 LTS +- **Cloud Provider:** Yandex Cloud +- **Application:** DevOps Info Service (FastAPI) +- **Container Runtime:** Docker + +This lab implements a fully automated, role-based infrastructure provisioning and container deployment system using Ansible. + +### Why Roles? + +Roles were used instead of monolithic playbooks to achieve: + +- Modularity +- Reusability +- Separation of concerns +- Clean project structure +- Easier scalability and maintenance + +--- + +## 2. Role Structure + +### common role +Purpose: Basic system preparation + +Tasks: +- Update APT cache +- Install essential packages (curl, git, vim, htop, python3-pip) +- Configure timezone + +Idempotency ensured using: +- `apt` module with `state: present` +- `timezone` module + +--- + +### docker role +Purpose: Install and configure Docker + +Tasks: +- Install Docker from Ubuntu repository +- Enable and start docker service +- Add user to docker group +- Install python3-docker for Ansible Docker modules + +Handlers: +- Restart Docker service (if needed) + +All tasks are state-based and idempotent. + +--- + +### web_app role +Purpose: Deploy containerized application securely + +Tasks: +- Pull Docker image +- Remove old container if exists +- Run container with restart policy +- Wait for application port +- Perform health check via HTTP + +Security: +- Docker Hub credentials stored in encrypted Vault file +- `no_log: true` used for sensitive tasks + +--- + +## 3. Idempotency Demonstration + +### First Run + +Initial execution resulted in multiple `changed` tasks because packages and services were installed. + +### Second Run + +Second execution showed: + + +changed=0 + + +This confirms idempotency. + +Idempotency is achieved by: +- Using declarative modules +- Avoiding raw shell commands +- Defining desired system state explicitly + +--- + +## 4. Application Deployment Verification + +After deployment: + +- Container is running (`docker ps`) +- Port 5000 is exposed publicly +- Health endpoint returns HTTP 200 +- Root endpoint returns system metadata + +Public URL: + +http://93.77.190.119:5000 + +Health endpoint: + +http://93.77.190.119:5000/health + +--- + +## 5. Ansible Vault + +Sensitive variables are stored in: + + +group_vars/all.yml + + +File is encrypted using: + + +$ANSIBLE_VAULT;1.1;AES256 + + +Vault ensures: +- Secrets are not stored in plaintext +- Safe version control +- Secure automation + +--- + +## 6. Key DevOps Principles Applied + +- Infrastructure as Code +- Idempotent configuration management +- Secure secret management +- Containerized deployment +- Automated verification +- Role-based modular architecture + +--- + +## 7. Conclusion + +The system successfully provisions infrastructure, installs Docker, and deploys a containerized application using Ansible roles. + +The solution is: + +- Idempotent +- Secure +- Modular +- Reproducible +- Production-ready diff --git a/ansible/docs/LAB06.md b/ansible/docs/LAB06.md new file mode 100644 index 0000000000..044edeac9c --- /dev/null +++ b/ansible/docs/LAB06.md @@ -0,0 +1,351 @@ +## 1. Overview + +In this lab, the infrastructure automation was extended using more advanced Ansible concepts including: + +Task tagging and selective execution + +Blocks for logical grouping of tasks + +Docker Compose deployment + +Controlled wipe logic for application reset + +CI/CD pipeline integration using GitHub Actions + +The goal of this lab was to improve maintainability, flexibility, and automation capabilities of the infrastructure management system. + +The application deployed in this lab is the DevOps Info Service, a containerized FastAPI application running on a remote Ubuntu server. + +## 2. Infrastructure Architecture +Control Node + +Local machine running: + +Ansible 2.16+ + +SSH access to target VM + +Target Node + +Remote VM running: + +Ubuntu 24.04 LTS + +Docker Engine + +Docker Compose (v2 plugin) + +Application + +Containerized FastAPI service: + +fayzullin/devops-info-service:latest + +The service exposes the following endpoints: + +/ - service information +/health - health check endpoint + +Public access: + +http://93.77.190.119:5000 +## 3. Ansible Role Architecture + +The project uses a modular role-based structure. + +ansible/ +├── inventory/ +│ └── hosts.ini +├── playbooks/ +│ ├── provision.yml +│ └── deploy.yml +├── roles/ +│ ├── common/ +│ ├── docker/ +│ └── web_app/ +│ ├── tasks/ +│ │ ├── main.yml +│ │ └── wipe.yml +│ ├── defaults/ +│ │ └── main.yml +│ ├── templates/ +│ │ └── docker-compose.yml.j2 +│ └── meta/ +│ └── main.yml +└── docs/ + └── LAB06.md +Roles + +common + +installs base system packages + +configures system settings + +docker + +installs Docker engine + +enables docker service + +installs python docker bindings + +web_app + +deploys the application using Docker Compose + +supports application wipe/reset + +performs health verification + +## 4. Task Tags + +Tags were implemented to allow selective execution of tasks. + +Example tags used in the project: + +common +packages +docker_install +docker_config +app_deploy +compose +web_app_wipe +Listing tags +ansible-playbook playbooks/provision.yml --list-tags +Example selective execution + +Install only Docker: + +ansible-playbook playbooks/provision.yml --tags docker_install + +Install only system packages: + +ansible-playbook playbooks/provision.yml --tags packages + +Tags allow faster execution during debugging or partial updates. + +## 5. Blocks Usage + +Ansible blocks were used to group logically related tasks inside the web_app role. + +Example structure: + +block: + - create compose directory + - generate docker-compose.yml + - run docker compose + - wait for service + - run health check + +Benefits of blocks: + +improved readability + +structured execution flow + +easier error handling + +ability to apply conditions or tags to multiple tasks + +## 6. Docker Compose Deployment + +The application is deployed using Docker Compose v2. + +Compose file is generated dynamically using an Ansible template. + +Template + +roles/web_app/templates/docker-compose.yml.j2 + +Example structure: + +version: "3.8" + +services: + devops-info-service: + image: fayzullin/devops-info-service:latest + container_name: devops-info-service + ports: + - "5000:5000" + restart: unless-stopped +Deployment Flow + +The web_app role performs the following steps: + +Create project directory + +/opt/devops-info-service + +Render docker-compose file from template + +Run Docker Compose + +docker compose up -d + +Wait until port becomes available + +Verify service health endpoint + +## 7. Idempotency + +Ansible ensures idempotent infrastructure management. + +This means repeated execution does not modify the system if the desired state is already achieved. + +Example: + +First run: + +changed=3 + +Second run: + +changed=0 + +This proves the system converges to the desired state without unnecessary modifications. + +## 8. Wipe Logic (Controlled Reset) + +A controlled wipe mechanism was implemented to allow safe application reset. + +The wipe mechanism requires two conditions: + +1️⃣ variable web_app_wipe=true + +2️⃣ tag web_app_wipe + +This prevents accidental destruction of running services. + +Scenario 1 — Normal deploy +ansible-playbook deploy.yml + +Result: + +wipe skipped +application running +Scenario 2 — Wipe only +ansible-playbook deploy.yml \ +-e "web_app_wipe=true" \ +--tags web_app_wipe + +Result: + +containers removed +compose directory deleted +Scenario 3 — Clean reinstall +ansible-playbook deploy.yml \ +-e "web_app_wipe=true" + +Result: + +wipe executed +application redeployed +Scenario 4 — Tag only (blocked) +ansible-playbook deploy.yml \ +--tags web_app_wipe + +Result: + +wipe skipped (safety condition) +## 9. Health Verification + +After deployment the service health endpoint is verified: + +curl http://93.77.190.119:5000/health + +Example response: + +{ + "status": "healthy", + "timestamp": "...", + "uptime_seconds": 7128 +} + +This confirms that the containerized application is running correctly. + +## 10. CI/CD Pipeline + +A CI/CD pipeline was implemented using GitHub Actions. + +Pipeline stages: + +1. Lint Stage + +Runs: + +ansible-lint + +Purpose: + +validate Ansible syntax + +detect best practice violations + +2. Deploy Stage + +Steps: + +Checkout repository + +Install Ansible + +Install community.docker collection + +Configure SSH access + +Run Ansible deployment playbook + +Deployment command: + +ansible-playbook playbooks/deploy.yml +3. Verification Stage + +After deployment, the pipeline checks application availability: + +curl http://:5000/health + +If the endpoint responds successfully, the deployment is considered successful. + +## 11. Security Considerations + +Sensitive data is stored using Ansible Vault. + +Encrypted file: + +group_vars/all.yml + +Secrets stored: + +Docker Hub credentials + +environment variables + +Vault ensures: + +secrets are encrypted in Git + +secure infrastructure configuration + +## 12. Conclusion + +This lab extended the automation infrastructure with advanced Ansible features. + +Key improvements implemented: + +modular role architecture + +selective task execution using tags + +logical task grouping using blocks + +Docker Compose deployment + +controlled wipe/reset mechanism + +CI/CD automation using GitHub Actions + +full idempotent infrastructure management + +The final system provides a reliable, maintainable, and production-ready automated deployment pipeline. + diff --git a/ansible/docs/screenshots/curl-health.png b/ansible/docs/screenshots/curl-health.png new file mode 100644 index 0000000000..6b59e3ef28 Binary files /dev/null and b/ansible/docs/screenshots/curl-health.png differ diff --git a/ansible/docs/screenshots/lab06-compose-deploy-1.txt b/ansible/docs/screenshots/lab06-compose-deploy-1.txt new file mode 100644 index 0000000000..e25a3f9dc9 --- /dev/null +++ b/ansible/docs/screenshots/lab06-compose-deploy-1.txt @@ -0,0 +1,48 @@ + +PLAY [Deploy application container] ******************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [lab04-vm] + +TASK [docker : Ensure apt cache is up to date] ********************************* +ok: [lab04-vm] + +TASK [docker : Install Docker package from Ubuntu repo] ************************ +ok: [lab04-vm] + +TASK [docker : Install python3-docker for Ansible docker modules] ************** +ok: [lab04-vm] + +TASK [docker : Ensure docker service is enabled and running] ******************* +ok: [lab04-vm] + +TASK [docker : Ensure user is added to docker group] *************************** +ok: [lab04-vm] + +TASK [docker : Ensure docker service is enabled (always)] ********************** +ok: [lab04-vm] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/vboxuser/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml for lab04-vm + +TASK [web_app : Wipe web application (compose down)] *************************** +skipping: [lab04-vm] + +TASK [web_app : Remove compose project directory] ****************************** +skipping: [lab04-vm] + +TASK [web_app : Log wipe completion] ******************************************* +skipping: [lab04-vm] + +TASK [web_app : Create compose project directory] ****************************** +changed: [lab04-vm] + +TASK [web_app : Template docker-compose.yml] *********************************** +changed: [lab04-vm] + +TASK [web_app : Run Docker Compose (v2)] *************************************** +fatal: [lab04-vm]: FAILED! => {"changed": false, "msg": "Docker CLI /usr/bin/docker does not have the compose plugin installed"} + +PLAY RECAP ********************************************************************* +lab04-vm : ok=10 changed=2 unreachable=0 failed=1 skipped=3 rescued=0 ignored=0 + diff --git a/ansible/docs/screenshots/lab06-compose-deploy-2.txt b/ansible/docs/screenshots/lab06-compose-deploy-2.txt new file mode 100644 index 0000000000..4f51ba1394 --- /dev/null +++ b/ansible/docs/screenshots/lab06-compose-deploy-2.txt @@ -0,0 +1,48 @@ + +PLAY [Deploy application container] ******************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [lab04-vm] + +TASK [docker : Ensure apt cache is up to date] ********************************* +ok: [lab04-vm] + +TASK [docker : Install Docker package from Ubuntu repo] ************************ +ok: [lab04-vm] + +TASK [docker : Install python3-docker for Ansible docker modules] ************** +ok: [lab04-vm] + +TASK [docker : Ensure docker service is enabled and running] ******************* +ok: [lab04-vm] + +TASK [docker : Ensure user is added to docker group] *************************** +ok: [lab04-vm] + +TASK [docker : Ensure docker service is enabled (always)] ********************** +ok: [lab04-vm] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/vboxuser/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml for lab04-vm + +TASK [web_app : Wipe web application (compose down)] *************************** +skipping: [lab04-vm] + +TASK [web_app : Remove compose project directory] ****************************** +skipping: [lab04-vm] + +TASK [web_app : Log wipe completion] ******************************************* +skipping: [lab04-vm] + +TASK [web_app : Create compose project directory] ****************************** +ok: [lab04-vm] + +TASK [web_app : Template docker-compose.yml] *********************************** +ok: [lab04-vm] + +TASK [web_app : Run Docker Compose (v2)] *************************************** +fatal: [lab04-vm]: FAILED! => {"changed": false, "msg": "Docker CLI /usr/bin/docker does not have the compose plugin installed"} + +PLAY RECAP ********************************************************************* +lab04-vm : ok=10 changed=0 unreachable=0 failed=1 skipped=3 rescued=0 ignored=0 + diff --git a/ansible/docs/screenshots/lab06-compose-ps.txt b/ansible/docs/screenshots/lab06-compose-ps.txt new file mode 100644 index 0000000000..3533fee5e0 --- /dev/null +++ b/ansible/docs/screenshots/lab06-compose-ps.txt @@ -0,0 +1,83 @@ +lab04-vm | FAILED | rc=125 >> +unknown shorthand flag: 'f' in -f +See 'docker --help'. + +Usage: docker [OPTIONS] COMMAND + +A self-sufficient runtime for containers + +Common Commands: + run Create and run a new container from an image + exec Execute a command in a running container + ps List containers + build Build an image from a Dockerfile + pull Download an image from a registry + push Upload an image to a registry + images List images + login Log in to a registry + logout Log out from a registry + search Search Docker Hub for images + version Show the Docker version information + info Display system-wide information + +Management Commands: + builder Manage builds + container Manage containers + context Manage contexts + image Manage images + manifest Manage Docker image manifests and manifest lists + network Manage networks + plugin Manage plugins + system Manage Docker + trust Manage trust on Docker images + volume Manage volumes + +Swarm Commands: + swarm Manage Swarm + +Commands: + attach Attach local standard input, output, and error streams to a running container + commit Create a new image from a container's changes + cp Copy files/folders between a container and the local filesystem + create Create a new container + diff Inspect changes to files or directories on a container's filesystem + events Get real time events from the server + export Export a container's filesystem as a tar archive + history Show the history of an image + import Import the contents from a tarball to create a filesystem image + inspect Return low-level information on Docker objects + kill Kill one or more running containers + load Load an image from a tar archive or STDIN + logs Fetch the logs of a container + pause Pause all processes within one or more containers + port List port mappings or a specific mapping for the container + rename Rename a container + restart Restart one or more containers + rm Remove one or more containers + rmi Remove one or more images + save Save one or more images to a tar archive (streamed to STDOUT by default) + start Start one or more stopped containers + stats Display a live stream of container(s) resource usage statistics + stop Stop one or more running containers + tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE + top Display the running processes of a container + unpause Unpause all processes within one or more containers + update Update configuration of one or more containers + wait Block until one or more containers stop, then print their exit codes + +Global Options: + --config string Location of client config files (default "/root/.docker") + -c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with "docker context use") + -D, --debug Enable debug mode + -H, --host list Daemon socket to connect to + -l, --log-level string Set the logging level ("debug", "info", "warn", "error", "fatal") (default "info") + --tls Use TLS; implied by --tlsverify + --tlscacert string Trust certs signed only by this CA (default "/root/.docker/ca.pem") + --tlscert string Path to TLS certificate file (default "/root/.docker/cert.pem") + --tlskey string Path to TLS key file (default "/root/.docker/key.pem") + --tlsverify Use TLS and verify the remote + -v, --version Print version information and quit + +Run 'docker COMMAND --help' for more information on a command. + +For more help on how to use Docker, head to https://docs.docker.com/go/guides/non-zero return code diff --git a/ansible/docs/screenshots/lab06-deploy-second-run.txt b/ansible/docs/screenshots/lab06-deploy-second-run.txt new file mode 100644 index 0000000000..aa8620f109 --- /dev/null +++ b/ansible/docs/screenshots/lab06-deploy-second-run.txt @@ -0,0 +1,42 @@ + +PLAY [Deploy application container] ******************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [lab04-vm] + +TASK [docker : Ensure apt cache is up to date] ********************************* +ok: [lab04-vm] + +TASK [docker : Install Docker package from Ubuntu repo] ************************ +ok: [lab04-vm] + +TASK [docker : Install python3-docker for Ansible docker modules] ************** +ok: [lab04-vm] + +TASK [docker : Ensure docker service is enabled and running] ******************* +ok: [lab04-vm] + +TASK [docker : Ensure user is added to docker group] *************************** +ok: [lab04-vm] + +TASK [docker : Ensure docker service is enabled (always)] ********************** +ok: [lab04-vm] + +TASK [web_app : Pull application image] **************************************** +ok: [lab04-vm] + +TASK [web_app : Ensure old container is absent] ******************************** +changed: [lab04-vm] + +TASK [web_app : Run application container] ************************************* +changed: [lab04-vm] + +TASK [web_app : Wait for application port to become open] ********************** +ok: [lab04-vm] + +TASK [web_app : Check health endpoint] ***************************************** +ok: [lab04-vm] + +PLAY RECAP ********************************************************************* +lab04-vm : ok=12 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 + diff --git a/ansible/docs/screenshots/lab06-docker-ps.txt b/ansible/docs/screenshots/lab06-docker-ps.txt new file mode 100644 index 0000000000..e7f36b697d --- /dev/null +++ b/ansible/docs/screenshots/lab06-docker-ps.txt @@ -0,0 +1,3 @@ +lab04-vm | CHANGED | rc=0 >> +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +ff00f6efae77 fayzullin/devops-info-service:latest "python app.py" 2 hours ago Up 2 hours 0.0.0.0:5000->5000/tcp devops-info-service diff --git a/ansible/docs/screenshots/lab06-health.txt b/ansible/docs/screenshots/lab06-health.txt new file mode 100644 index 0000000000..fd25dbfc26 --- /dev/null +++ b/ansible/docs/screenshots/lab06-health.txt @@ -0,0 +1 @@ +{"status":"healthy","timestamp":"2026-03-05T21:08:46.452541+00:00","uptime_seconds":7950} \ No newline at end of file diff --git a/ansible/docs/screenshots/lab06-list-tags.txt b/ansible/docs/screenshots/lab06-list-tags.txt new file mode 100644 index 0000000000..2781daa24c --- /dev/null +++ b/ansible/docs/screenshots/lab06-list-tags.txt @@ -0,0 +1,5 @@ + +playbook: playbooks/provision.yml + + play #1 (webservers): Provision web servers TAGS: [] + TASK TAGS: [common, docker, docker_config, docker_install, packages, users] diff --git a/ansible/docs/screenshots/lab06-root.txt b/ansible/docs/screenshots/lab06-root.txt new file mode 100644 index 0000000000..73a5d61ae4 --- /dev/null +++ b/ansible/docs/screenshots/lab06-root.txt @@ -0,0 +1 @@ +{"service":{"name":"devops-info-service","version":"1.0.0","description":"DevOps course info service","framework":"FastAPI"},"system":{"hostname":"ff00f6efae77","platform":"Linux","platform_version":"5.4.0-216-generic","architecture":"x86_64","cpu_count":2,"python_version":"3.12.12"},"runtime":{"uptime_seconds":7950,"uptime_human":"2 hours, 12 minutes","current_time":"2026-03-05T21:08:46.808476+00:00","timezone":"UTC"},"request":{"client_ip":"109.248.33.37","user_agent":"curl/8.5.0","method":"GET","path":"/"},"endpoints":[{"path":"/","method":"GET","description":"Service information"},{"path":"/health","method":"GET","description":"Health check"}]} \ No newline at end of file diff --git a/ansible/docs/screenshots/lab06-tags-docker_install.txt b/ansible/docs/screenshots/lab06-tags-docker_install.txt new file mode 100644 index 0000000000..e430f38617 --- /dev/null +++ b/ansible/docs/screenshots/lab06-tags-docker_install.txt @@ -0,0 +1,18 @@ + +PLAY [Provision web servers] *************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [lab04-vm] + +TASK [docker : Ensure apt cache is up to date] ********************************* +changed: [lab04-vm] + +TASK [docker : Install Docker package from Ubuntu repo] ************************ +ok: [lab04-vm] + +TASK [docker : Install python3-docker for Ansible docker modules] ************** +ok: [lab04-vm] + +PLAY RECAP ********************************************************************* +lab04-vm : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 + diff --git a/ansible/docs/screenshots/lab06-tags-packages.txt b/ansible/docs/screenshots/lab06-tags-packages.txt new file mode 100644 index 0000000000..d0af66c6af --- /dev/null +++ b/ansible/docs/screenshots/lab06-tags-packages.txt @@ -0,0 +1,18 @@ + +PLAY [Provision web servers] *************************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [lab04-vm] + +TASK [common : Ensure apt cache is up to date] ********************************* +ok: [lab04-vm] + +TASK [common : Install common packages] **************************************** +ok: [lab04-vm] + +TASK [common : Log common completion] ****************************************** +ok: [lab04-vm] + +PLAY RECAP ********************************************************************* +lab04-vm : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 + diff --git a/ansible/docs/screenshots/lab06-wipe-s1-normal.txt b/ansible/docs/screenshots/lab06-wipe-s1-normal.txt new file mode 100644 index 0000000000..4f51ba1394 --- /dev/null +++ b/ansible/docs/screenshots/lab06-wipe-s1-normal.txt @@ -0,0 +1,48 @@ + +PLAY [Deploy application container] ******************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [lab04-vm] + +TASK [docker : Ensure apt cache is up to date] ********************************* +ok: [lab04-vm] + +TASK [docker : Install Docker package from Ubuntu repo] ************************ +ok: [lab04-vm] + +TASK [docker : Install python3-docker for Ansible docker modules] ************** +ok: [lab04-vm] + +TASK [docker : Ensure docker service is enabled and running] ******************* +ok: [lab04-vm] + +TASK [docker : Ensure user is added to docker group] *************************** +ok: [lab04-vm] + +TASK [docker : Ensure docker service is enabled (always)] ********************** +ok: [lab04-vm] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/vboxuser/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml for lab04-vm + +TASK [web_app : Wipe web application (compose down)] *************************** +skipping: [lab04-vm] + +TASK [web_app : Remove compose project directory] ****************************** +skipping: [lab04-vm] + +TASK [web_app : Log wipe completion] ******************************************* +skipping: [lab04-vm] + +TASK [web_app : Create compose project directory] ****************************** +ok: [lab04-vm] + +TASK [web_app : Template docker-compose.yml] *********************************** +ok: [lab04-vm] + +TASK [web_app : Run Docker Compose (v2)] *************************************** +fatal: [lab04-vm]: FAILED! => {"changed": false, "msg": "Docker CLI /usr/bin/docker does not have the compose plugin installed"} + +PLAY RECAP ********************************************************************* +lab04-vm : ok=10 changed=0 unreachable=0 failed=1 skipped=3 rescued=0 ignored=0 + diff --git a/ansible/docs/screenshots/lab06-wipe-s2-wipe-only.txt b/ansible/docs/screenshots/lab06-wipe-s2-wipe-only.txt new file mode 100644 index 0000000000..ad2cf0d4aa --- /dev/null +++ b/ansible/docs/screenshots/lab06-wipe-s2-wipe-only.txt @@ -0,0 +1,24 @@ + +PLAY [Deploy application container] ******************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [lab04-vm] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/vboxuser/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml for lab04-vm + +TASK [web_app : Wipe web application (compose down)] *************************** +fatal: [lab04-vm]: FAILED! => {"changed": false, "msg": "Docker CLI /usr/bin/docker does not have the compose plugin installed"} +...ignoring + +TASK [web_app : Remove compose project directory] ****************************** +changed: [lab04-vm] + +TASK [web_app : Log wipe completion] ******************************************* +ok: [lab04-vm] => { + "msg": "Application devops-info-service wiped successfully" +} + +PLAY RECAP ********************************************************************* +lab04-vm : ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1 + diff --git a/ansible/docs/screenshots/lab06-wipe-s3-clean-reinstall.txt b/ansible/docs/screenshots/lab06-wipe-s3-clean-reinstall.txt new file mode 100644 index 0000000000..5ef7098e80 --- /dev/null +++ b/ansible/docs/screenshots/lab06-wipe-s3-clean-reinstall.txt @@ -0,0 +1,51 @@ + +PLAY [Deploy application container] ******************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [lab04-vm] + +TASK [docker : Ensure apt cache is up to date] ********************************* +ok: [lab04-vm] + +TASK [docker : Install Docker package from Ubuntu repo] ************************ +ok: [lab04-vm] + +TASK [docker : Install python3-docker for Ansible docker modules] ************** +ok: [lab04-vm] + +TASK [docker : Ensure docker service is enabled and running] ******************* +ok: [lab04-vm] + +TASK [docker : Ensure user is added to docker group] *************************** +ok: [lab04-vm] + +TASK [docker : Ensure docker service is enabled (always)] ********************** +ok: [lab04-vm] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/vboxuser/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml for lab04-vm + +TASK [web_app : Wipe web application (compose down)] *************************** +fatal: [lab04-vm]: FAILED! => {"changed": false, "msg": "Docker CLI /usr/bin/docker does not have the compose plugin installed"} +...ignoring + +TASK [web_app : Remove compose project directory] ****************************** +ok: [lab04-vm] + +TASK [web_app : Log wipe completion] ******************************************* +ok: [lab04-vm] => { + "msg": "Application devops-info-service wiped successfully" +} + +TASK [web_app : Create compose project directory] ****************************** +changed: [lab04-vm] + +TASK [web_app : Template docker-compose.yml] *********************************** +changed: [lab04-vm] + +TASK [web_app : Run Docker Compose (v2)] *************************************** +fatal: [lab04-vm]: FAILED! => {"changed": false, "msg": "Docker CLI /usr/bin/docker does not have the compose plugin installed"} + +PLAY RECAP ********************************************************************* +lab04-vm : ok=13 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=1 + diff --git a/ansible/docs/screenshots/lab06-wipe-s4a-tag-only-blocked.txt b/ansible/docs/screenshots/lab06-wipe-s4a-tag-only-blocked.txt new file mode 100644 index 0000000000..b202aa972a --- /dev/null +++ b/ansible/docs/screenshots/lab06-wipe-s4a-tag-only-blocked.txt @@ -0,0 +1,21 @@ + +PLAY [Deploy application container] ******************************************** + +TASK [Gathering Facts] ********************************************************* +ok: [lab04-vm] + +TASK [web_app : Include wipe tasks] ******************************************** +included: /home/vboxuser/DevOps-Core-Course/ansible/roles/web_app/tasks/wipe.yml for lab04-vm + +TASK [web_app : Wipe web application (compose down)] *************************** +skipping: [lab04-vm] + +TASK [web_app : Remove compose project directory] ****************************** +skipping: [lab04-vm] + +TASK [web_app : Log wipe completion] ******************************************* +skipping: [lab04-vm] + +PLAY RECAP ********************************************************************* +lab04-vm : ok=2 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0 + diff --git a/ansible/docs/screenshots/provision-first.png b/ansible/docs/screenshots/provision-first.png new file mode 100644 index 0000000000..e0365e7b13 Binary files /dev/null and b/ansible/docs/screenshots/provision-first.png differ diff --git a/ansible/docs/screenshots/provision-second.png b/ansible/docs/screenshots/provision-second.png new file mode 100644 index 0000000000..a21735c389 Binary files /dev/null and b/ansible/docs/screenshots/provision-second.png differ diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml new file mode 100644 index 0000000000..649ba87324 --- /dev/null +++ b/ansible/group_vars/all.yml @@ -0,0 +1,19 @@ +$ANSIBLE_VAULT;1.1;AES256 +32356462313737333333373263633564396636643732653235326563636663386333656433356634 +3231343764306434303939316564373334623765323934610a396632613631303061363339643061 +30613132343937343461373936633532343937643635393130333730643633353235336562613733 +3539373561303261650a653739656565623434316664346162623833393566306538663837316263 +33393134643665393033313339653562363938653064313739393735393131613361366161396236 +37383065353531336334646135383530636463303135316436646637646330353365363665366436 +36666538646530396161636166373130313334383332613866386535333734323462613337323265 +62303634626336356134316461656666373165666631376231326439393862333337666662616131 +62356136633630373965356463366362373365393832626362373637356533336635383337656561 +38306466663430626431623735653463373337396364666236366433313332376466356234663535 +32656261383164613437303332646532336537343833343932323337636239383534326664356665 +30616166616534396235656437343465346163376234366232643232663765386531623238653735 +39646430333830343664373939333766326431376638336161613630373332646138306639653439 +66366333363332356532343065646237653562643937633163346165643966623638633235393030 +38353735653636366264303164326230666339623039643933373036306233333637656339643733 +38333435303966646536663865653666303166346339346330316338343935633361663634303839 +61396264316132353762323931346130353239613366656531343735653464396536306664623130 +3030333933376564363832626232623763653961313135386465 diff --git a/ansible/inventory/hosts.ini b/ansible/inventory/hosts.ini new file mode 100644 index 0000000000..2cfc94f730 --- /dev/null +++ b/ansible/inventory/hosts.ini @@ -0,0 +1,5 @@ +[webservers] +lab04-vm ansible_host=93.77.190.119 ansible_user=ubuntu ansible_ssh_private_key_file=/home/vboxuser/.ssh/id_ed25519 + +[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..5371e0570b --- /dev/null +++ b/ansible/playbooks/deploy.yml @@ -0,0 +1,10 @@ +--- +- name: Deploy application container + hosts: webservers + become: yes + + collections: + - community.docker + + roles: + - web_app diff --git a/ansible/playbooks/provision.yml b/ansible/playbooks/provision.yml new file mode 100644 index 0000000000..f53efb0248 --- /dev/null +++ b/ansible/playbooks/provision.yml @@ -0,0 +1,8 @@ +--- +- name: Provision web servers + hosts: webservers + become: yes + + roles: + - common + - docker diff --git a/ansible/roles/common/defaults/main.yml b/ansible/roles/common/defaults/main.yml new file mode 100644 index 0000000000..49824977e8 --- /dev/null +++ b/ansible/roles/common/defaults/main.yml @@ -0,0 +1,13 @@ +--- +common_timezone: "Etc/UTC" + +common_packages: + - python3-pip + - curl + - git + - vim + - htop + - ca-certificates + - apt-transport-https + - software-properties-common + - gnupg diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml new file mode 100644 index 0000000000..2fbc715722 --- /dev/null +++ b/ansible/roles/common/tasks/main.yml @@ -0,0 +1,49 @@ +--- +- name: Common | Packages block + block: + - name: Ensure apt cache is up to date + ansible.builtin.apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install common packages + ansible.builtin.apt: + name: "{{ common_packages }}" + state: present + rescue: + - name: Fix apt cache on failure + ansible.builtin.command: apt-get update --fix-missing + changed_when: false + always: + - name: Log common completion + ansible.builtin.lineinfile: + path: /tmp/ansible-common-done.txt + create: true + line: "common role finished" + state: present + become: true + tags: + - common + - packages + + +- name: Common | Users block + block: + - name: Ensure admin user exists (optional) + ansible.builtin.user: + name: "{{ common_admin_user }}" + groups: sudo + append: true + state: present + when: common_admin_user is defined and common_admin_user | length > 0 + always: + - name: Log users completion + ansible.builtin.lineinfile: + path: /tmp/ansible-users-done.txt + create: true + line: "users block finished" + state: present + become: true + tags: + - common + - users diff --git a/ansible/roles/docker/defaults/main.yml b/ansible/roles/docker/defaults/main.yml new file mode 100644 index 0000000000..d1761730cb --- /dev/null +++ b/ansible/roles/docker/defaults/main.yml @@ -0,0 +1,5 @@ +--- +docker_packages: + - docker.io + +docker_user: "ubuntu" diff --git a/ansible/roles/docker/handlers/main.yml b/ansible/roles/docker/handlers/main.yml new file mode 100644 index 0000000000..1a5058da5e --- /dev/null +++ b/ansible/roles/docker/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart docker + ansible.builtin.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..257939f4ec --- /dev/null +++ b/ansible/roles/docker/tasks/main.yml @@ -0,0 +1,56 @@ +--- +- name: Docker | Install block + block: + - name: Ensure apt cache is up to date + ansible.builtin.apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install Docker package from Ubuntu repo + ansible.builtin.apt: + name: + - docker.io + state: present + + - name: Install python3-docker for Ansible docker modules + ansible.builtin.apt: + name: python3-docker + state: present + + rescue: + - name: Wait a bit and retry apt update (network hiccup) + ansible.builtin.pause: + seconds: 10 + + - name: Retry apt update + ansible.builtin.apt: + update_cache: yes + + tags: + - docker + - docker_install + become: true + + +- name: Docker | Config block + block: + - name: Ensure docker service is enabled and running + ansible.builtin.service: + name: docker + state: started + enabled: yes + + - name: Ensure user is added to docker group + ansible.builtin.user: + name: "{{ docker_user }}" + groups: docker + append: true + always: + - name: Ensure docker service is enabled (always) + ansible.builtin.service: + name: docker + enabled: yes + tags: + - docker + - docker_config + become: true diff --git a/ansible/roles/web_app/defaults/main.yml b/ansible/roles/web_app/defaults/main.yml new file mode 100644 index 0000000000..11c518d733 --- /dev/null +++ b/ansible/roles/web_app/defaults/main.yml @@ -0,0 +1,18 @@ +--- +# Web app defaults + +app_name: devops-info-service + +docker_image: fayzullin/devops-info-service +docker_tag: latest + +app_port: 5000 +app_internal_port: 5000 + +docker_restart_policy: unless-stopped +app_env: {} + +# wipe logic (double gate) +web_app_wipe: false +compose_project_dir: "/opt/{{ app_name }}" +docker_compose_version: "3.8" diff --git a/ansible/roles/web_app/handlers/main.yml b/ansible/roles/web_app/handlers/main.yml new file mode 100644 index 0000000000..e7e8259b12 --- /dev/null +++ b/ansible/roles/web_app/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: restart app container + community.docker.docker_container: + name: "{{ app_container_name }}" + state: restarted diff --git a/ansible/roles/web_app/meta/main.yml b/ansible/roles/web_app/meta/main.yml new file mode 100644 index 0000000000..cb7d8e0460 --- /dev/null +++ b/ansible/roles/web_app/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - role: docker diff --git a/ansible/roles/web_app/tasks/main.yml b/ansible/roles/web_app/tasks/main.yml new file mode 100644 index 0000000000..83ad8223e9 --- /dev/null +++ b/ansible/roles/web_app/tasks/main.yml @@ -0,0 +1,41 @@ +--- +- name: Include wipe tasks + ansible.builtin.include_tasks: wipe.yml + tags: + - web_app_wipe + +- name: Deploy application with Docker Compose + block: + - name: Create compose project directory + ansible.builtin.file: + path: "{{ compose_project_dir }}" + state: directory + mode: "0755" + + - name: Template docker-compose.yml + ansible.builtin.template: + src: docker-compose.yml.j2 + dest: "{{ compose_project_dir }}/docker-compose.yml" + mode: "0644" + + - name: Run Docker Compose (v2) + community.docker.docker_compose_v2: + project_src: "{{ compose_project_dir }}" + pull: always + state: present + + - name: Wait for application port to become open + ansible.builtin.wait_for: + host: "{{ ansible_host | default(inventory_hostname) }}" + port: "{{ app_port }}" + delay: 2 + timeout: 60 + + - name: Check health endpoint + ansible.builtin.uri: + url: "http://{{ ansible_host | default(inventory_hostname) }}:{{ app_port }}/health" + method: GET + status_code: 200 + tags: + - app_deploy + - compose diff --git a/ansible/roles/web_app/tasks/wipe.yml b/ansible/roles/web_app/tasks/wipe.yml new file mode 100644 index 0000000000..7c2829347f --- /dev/null +++ b/ansible/roles/web_app/tasks/wipe.yml @@ -0,0 +1,24 @@ +--- +- name: Wipe web application (compose down) + community.docker.docker_compose_v2: + project_src: "{{ compose_project_dir }}" + state: absent + ignore_errors: true + when: web_app_wipe | bool + tags: + - web_app_wipe + +- name: Remove compose project directory + ansible.builtin.file: + path: "{{ compose_project_dir }}" + state: absent + when: web_app_wipe | bool + tags: + - web_app_wipe + +- name: Log wipe completion + ansible.builtin.debug: + msg: "Application {{ app_name }} wiped successfully" + when: web_app_wipe | bool + tags: + - web_app_wipe diff --git a/ansible/roles/web_app/templates/docker-compose.yml.j2 b/ansible/roles/web_app/templates/docker-compose.yml.j2 new file mode 100644 index 0000000000..e47842566b --- /dev/null +++ b/ansible/roles/web_app/templates/docker-compose.yml.j2 @@ -0,0 +1,13 @@ +version: "3.8" + +services: + {{ app_name }}: + image: {{ docker_image }}:{{ docker_tag }} + container_name: {{ app_name }} + ports: + - "{{ app_port }}:{{ app_internal_port }}" + environment: +{% for k, v in (app_env | default({})).items() %} + {{ k }}: "{{ v }}" +{% endfor %} + restart: {{ docker_restart_policy }} diff --git a/ansible/{censored: b/ansible/{censored: new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ansible/{changed: b/ansible/{changed: new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ansible/{reason: b/ansible/{reason: new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pulumi/lab04-yc/.gitignore b/pulumi/lab04-yc/.gitignore new file mode 100644 index 0000000000..a3807e5bdb --- /dev/null +++ b/pulumi/lab04-yc/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/pulumi/lab04-yc/Pulumi.yaml b/pulumi/lab04-yc/Pulumi.yaml new file mode 100644 index 0000000000..82f52af683 --- /dev/null +++ b/pulumi/lab04-yc/Pulumi.yaml @@ -0,0 +1,7 @@ +name: lab04-yc +description: A minimal Python Pulumi program +runtime: python +config: + pulumi:tags: + value: + pulumi:template: python diff --git a/pulumi/lab04-yc/__main__.py b/pulumi/lab04-yc/__main__.py new file mode 100644 index 0000000000..5703481991 --- /dev/null +++ b/pulumi/lab04-yc/__main__.py @@ -0,0 +1,128 @@ +import os +import pathlib + +import pulumi +import pulumi_yandex as yandex + + + +ZONE = "ru-central1-a" + + +FOLDER_ID = "b1g1cmmbss046n25oln3" + +SSH_PUBLIC_KEY_PATH = os.path.expanduser("~/.ssh/id_ed25519.pub") + +SSH_USERNAME = "ubuntu" + + + +def read_ssh_public_key(path: str) -> str: + p = pathlib.Path(path) + if not p.exists(): + raise FileNotFoundError(f"SSH public key not found: {p}") + return p.read_text().strip() + + +ssh_pub = read_ssh_public_key(SSH_PUBLIC_KEY_PATH) + + + +net = yandex.VpcNetwork( + "lab-network", + folder_id=FOLDER_ID, +) + +subnet = yandex.VpcSubnet( + "lab-subnet", + folder_id=FOLDER_ID, + network_id=net.id, + zone=ZONE, + v4_cidr_blocks=["10.0.0.0/24"], +) + + + +sg = yandex.VpcSecurityGroup( + "lab-sg", + folder_id=FOLDER_ID, + network_id=net.id, + description="Security group for lab04 VM (SSH, HTTP, app port)", + egresses=[ + yandex.VpcSecurityGroupEgressArgs( + protocol="ANY", + description="Allow all outbound", + v4_cidr_blocks=["0.0.0.0/0"], + from_port=0, + to_port=65535, + ) + ], + ingresses=[ + yandex.VpcSecurityGroupIngressArgs( + protocol="TCP", + description="SSH", + v4_cidr_blocks=["0.0.0.0/0"], + port=22, + ), + yandex.VpcSecurityGroupIngressArgs( + protocol="TCP", + description="HTTP", + v4_cidr_blocks=["0.0.0.0/0"], + port=80, + ), + yandex.VpcSecurityGroupIngressArgs( + protocol="TCP", + description="App port 5000", + v4_cidr_blocks=["0.0.0.0/0"], + port=5000, + ), + ], +) + + + +image = yandex.get_compute_image( + family="ubuntu-2004-lts", + folder_id="standard-images", +) + + + +vm = yandex.ComputeInstance( + "lab-vm", + folder_id=FOLDER_ID, + zone=ZONE, + platform_id="standard-v2", + resources=yandex.ComputeInstanceResourcesArgs( + cores=2, + memory=1, + core_fraction=20, + ), + boot_disk=yandex.ComputeInstanceBootDiskArgs( + initialize_params=yandex.ComputeInstanceBootDiskInitializeParamsArgs( + image_id=image.id, + size=10, + type="network-hdd", + ) + ), + network_interfaces=[ + yandex.ComputeInstanceNetworkInterfaceArgs( + subnet_id=subnet.id, + nat=True, + security_group_ids=[sg.id], + ) + ], + metadata={ + "ssh-keys": f"{SSH_USERNAME}:{ssh_pub}", + }, + labels={ + "lab": "lab04", + "tool": "pulumi", + }, +) + + +pulumi.export("external_ip", vm.network_interfaces[0].nat_ip_address) +pulumi.export("zone", vm.zone) +pulumi.export("subnet_id", subnet.id) +pulumi.export("security_group_id", sg.id) diff --git a/pulumi/lab04-yc/requirements.txt b/pulumi/lab04-yc/requirements.txt new file mode 100644 index 0000000000..bc4e43087b --- /dev/null +++ b/pulumi/lab04-yc/requirements.txt @@ -0,0 +1 @@ +pulumi>=3.0.0,<4.0.0 diff --git a/terraform/docs/LAB04.md b/terraform/docs/LAB04.md new file mode 100644 index 0000000000..72ff4cee9d --- /dev/null +++ b/terraform/docs/LAB04.md @@ -0,0 +1,212 @@ +# LAB04 — Infrastructure as Code (Terraform & Pulumi) + +## 1. Cloud Provider & Infrastructure + +**Cloud Provider:** Yandex Cloud +**Folder ID:** b1g1cmmbss046n25oln3 +**Region / Zone:** ru-central1-a +**Instance Type:** standard-v2 (2 vCPU with 20% core fraction, 1 GB RAM) +**Disk:** 10 GB HDD +**Operating System:** Ubuntu 24.04 LTS + +The smallest available instance type compatible with Yandex Cloud free tier was selected to minimize cost. + +### Security Configuration + +The following ports are opened in the security group: + +- TCP 22 — SSH (restricted access for remote management) +- TCP 80 — HTTP (future deployment) +- TCP 5000 — Application port (DevOps Info Service from previous labs) + +### Created Resources + +- VPC Network (`lab-network`) +- Subnet (`lab-subnet`) +- Security Group (`lab-sg`) +- Virtual Machine (`lab-vm`) +- Public IP Address + +Estimated cost: **0 RUB** (free tier usage). +Terraform Version: 1.9.8 +Pulumi Version: 3.222.0 + +--- + +## 2. Terraform Implementation + +### Terraform Version +Terraform CLI 1.9.x (Ubuntu Linux) + +### Project Structure + +terraform/ +├── main.tf +├── variables.tf +├── outputs.tf +└── docs/LAB04.md + + +### Authentication + +Authentication was configured using a Yandex Cloud service account JSON key: +~/.yc/terraform-key.json + + +Provider configuration: + +```hcl +provider "yandex" { + service_account_key_file = pathexpand("~/.yc/terraform-key.json") + folder_id = var.folder_id + zone = var.zone +} +``` + +### Workflow + +``` +terraform init +terraform fmt +terraform validate +terraform plan +terraform apply +``` + + +Example output from terraform plan: +Plan: 3 to add, 0 to change, 0 to destroy. + +Example output from terraform apply: +Apply complete! Resources: 3 added, 0 changed, 0 destroyed. + +Outputs: + +external_ip = "X.X.X.X" + + +SSH Verification +ssh ubuntu@ + + +SSH connection was successful. + +Cleanup + +After verifying functionality, Terraform resources were destroyed: + +terraform destroy + + +All resources created by Terraform were removed successfully to avoid duplication and unnecessary usage. + +## 3. Pulumi Implementation +Pulumi Version + +Pulumi CLI v3.222.0 + +Language + +Python + +Project Structure +pulumi/lab04-yc/ + ├── Pulumi.yaml + ├── Pulumi.dev.yaml + ├── requirements.txt + ├── __main__.py + └── venv/ + +Authentication + +Pulumi uses the same Yandex Cloud service account key: + +export YC_SERVICE_ACCOUNT_KEY_FILE=/home/vboxuser/.yc/terraform-key.json + +Resources Created + +The same infrastructure was recreated using Pulumi: + +VpcNetwork + +VpcSubnet + +VpcSecurityGroup + +ComputeInstance + +Public IP + +Pulumi Commands +pulumi preview +pulumi up + + +Preview example: + ++ yandex:index:VpcNetwork ++ yandex:index:VpcSubnet ++ yandex:index:VpcSecurityGroup ++ yandex:index:ComputeInstance + + +Apply output: + +Outputs: + external_ip : "93.77.190.119" + zone : "ru-central1-a" + +SSH Verification +ssh ubuntu@93.77.190.119 + + +SSH access was successful. + +## 4. Terraform vs Pulumi Comparison +Ease of Learning + +Terraform was easier to start with due to extensive documentation and straightforward declarative syntax. Pulumi required more setup (virtual environments, Python dependencies). + +Code Readability + +Terraform configurations are compact and declarative, making them easy to read for simple infrastructure. Pulumi provides more flexibility but adds programming complexity. + +Debugging + +Terraform errors are generally clear during plan and apply. Pulumi provides Python stack traces, which can be more detailed but sometimes harder to interpret. + +Documentation + +Terraform has broader documentation and community examples. Pulumi documentation is solid but less extensive for Yandex Cloud specifically. + +Use Case Preference + +Terraform is preferable for straightforward infrastructure definitions. +Pulumi is more powerful when complex logic, loops, or programming constructs are required. + +## 5. Lab 5 Preparation & Cleanup + +For Lab 5 (Ansible), the VM created using Pulumi will be kept active. + +Active VM: + +IP Address: 93.77.190.119 +Zone: ru-central1-a +User: ubuntu + +Terraform resources were destroyed. +Pulumi-managed VM remains running for future configuration management tasks. + +No secrets or state files were committed to Git. + +Infrastructure can be recreated at any time using: + +terraform apply + + +or + +pulumi up + +Terraform state was stored locally. The file terraform.tfstate was added to .gitignore and not committed to the repository. + diff --git a/terraform/docs/screenshots/pulumi-up.png b/terraform/docs/screenshots/pulumi-up.png new file mode 100644 index 0000000000..a6e37bd63d Binary files /dev/null and b/terraform/docs/screenshots/pulumi-up.png differ diff --git a/terraform/docs/screenshots/terraform-apply.png b/terraform/docs/screenshots/terraform-apply.png new file mode 100644 index 0000000000..e9c3bc3afd Binary files /dev/null and b/terraform/docs/screenshots/terraform-apply.png differ diff --git a/terraform/docs/screenshots/yc-vm-running.png b/terraform/docs/screenshots/yc-vm-running.png new file mode 100644 index 0000000000..7a87ed9991 Binary files /dev/null and b/terraform/docs/screenshots/yc-vm-running.png differ diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000000..62ed0354df --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,56 @@ +terraform { + required_providers { + yandex = { + source = "yandex-cloud/yandex" + } + } +} + +provider "yandex" { + service_account_key_file = "/home/vboxuser/.yc/terraform-key.json" + cloud_id = "b1g0qsmtu1cheeq79i0d" + folder_id = "b1g1cmmbss046n25oln3" + zone = "ru-central1-a" +} +resource "yandex_vpc_network" "lab_network" { + name = "lab-network" +} + +resource "yandex_vpc_subnet" "lab_subnet" { + name = "lab-subnet" + zone = "ru-central1-a" + network_id = yandex_vpc_network.lab_network.id + v4_cidr_blocks = ["10.10.0.0/24"] +} + +resource "yandex_compute_instance" "lab_vm" { + name = "lab-vm" + zone = "ru-central1-a" + platform_id = "standard-v2" + + resources { + cores = 2 + memory = 1 + core_fraction = 20 + } + + boot_disk { + initialize_params { + image_id = "fd80bm0rh4rkepi5ksdi" # Ubuntu 24.04 LTS + size = 10 + } + } + + network_interface { + subnet_id = yandex_vpc_subnet.lab_subnet.id + nat = true + } + + metadata = { + ssh-keys = "ubuntu:${file("/home/vboxuser/.ssh/id_ed25519.pub")}" + } +} + +output "external_ip" { + value = yandex_compute_instance.lab_vm.network_interface.0.nat_ip_address +}