From ace7ee68c066b245853b3d9023c8275dfac8d2b6 Mon Sep 17 00:00:00 2001 From: Cdeth567 <11kvvkvv11@mail.ru> Date: Thu, 19 Feb 2026 23:54:07 +0300 Subject: [PATCH] LAB 04 completed --- app_python/docs/LAB04.md | 213 +++++++++++++++++++++++++++++++++++++++ pulumi/.gitignore | 5 + pulumi/Pulumi.yaml | 7 ++ pulumi/__main__.py | 92 +++++++++++++++++ pulumi/requirements.txt | 2 + terraform.tfvars | 4 + terraform/.gitignore | 11 ++ terraform/main.tf | 103 +++++++++++++++++++ terraform/outputs.tf | 14 +++ terraform/variables.tf | 68 +++++++++++++ terraform/versions.tf | 10 ++ 11 files changed, 529 insertions(+) create mode 100644 app_python/docs/LAB04.md create mode 100644 pulumi/.gitignore create mode 100644 pulumi/Pulumi.yaml create mode 100644 pulumi/__main__.py create mode 100644 pulumi/requirements.txt create mode 100644 terraform.tfvars create mode 100644 terraform/.gitignore create mode 100644 terraform/main.tf create mode 100644 terraform/outputs.tf create mode 100644 terraform/variables.tf create mode 100644 terraform/versions.tf diff --git a/app_python/docs/LAB04.md b/app_python/docs/LAB04.md new file mode 100644 index 0000000000..31c1430dc7 --- /dev/null +++ b/app_python/docs/LAB04.md @@ -0,0 +1,213 @@ +# LAB04 — Infrastructure as Code (Terraform & Pulumi) + +> Course: DevOps Core Course — Lab 4 +> Topic: Infrastructure as Code (Terraform + Pulumi) +> Cloud provider: **Yandex Cloud** +> Date: 2026-02-19 + +--- + +## 1. Cloud Provider & Infrastructure + +### Cloud provider chosen and rationale +I used **Yandex Cloud** because it is accessible from Russia and provides a free-tier friendly VM configuration that matches the lab requirements (small VM + simple networking and firewall rules). + +### Region / Zone +- Zone: `ru-central1-a` + +### Instance size (free-tier friendly) +- Platform: `standard-v2` +- vCPU: `2` with `core_fraction = 20` +- RAM: `1 GB` +- Boot disk: `10 GB` (`network-hdd`) + +### Estimated cost +- Expected cost: **$0** (free-tier / minimal resources) + +### Resources created +Using IaC, the following resources are provisioned: +- VPC network +- Subnet +- Security group with rules: + - SSH 22 — only from my IP (`95.111.204.70/32`) + - HTTP 80 — open to `0.0.0.0/0` + - App port 5000 — open to `0.0.0.0/0` +- Compute instance (VM) with NAT public IP + +--- + +## 2. Terraform Implementation + +### Terraform version +```text + +``` + +### Project structure +``` +terraform/ +├── main.tf +├── variables.tf +├── outputs.tf +├── versions.tf +├── terraform.tfvars (gitignored) +└── .gitignore +``` + +### Authentication (Yandex Cloud) +Authentication is done via **Service Account authorized key (JSON)**. + +- Service account key file (local path, not committed): + - `C:/Users/11kvv/.yc/lab04-sa-key.json` + +> Important: credential files (`*.json`) and state are excluded from Git. + +### Key configuration decisions +- **SSH access restricted** to my public IP only: `95.111.204.70/32` +- Public ports required by lab (80 and 5000) are open to the internet. +- Outputs exported: + - VM public IP + - SSH command string + +### Challenges encountered & fixes +1) **Provider authentication missing** +- Error: `one of 'token' or 'service_account_key_file' should be specified` +- Fix: generated service account key and configured `service_account_key_file`. + +2) **PermissionDenied when creating security group ingress** +- Error: `Permission denied to add ingress rule to security group` +- Fix: updated IAM roles for the service account (VPC permissions) and re-ran `terraform apply`. + +### Terminal output (sanitized) + +#### `terraform init` +```text +Initializing the backend... +Initializing provider plugins... +- Using previously-installed yandex-cloud/yandex v0.187.0 + +Terraform has been successfully initialized! +``` + +#### `terraform plan` (excerpt) +```text +Plan: 2 to add, 0 to change, 0 to destroy. + +Changes to Outputs: + + public_ip = (known after apply) + + ssh_command = (known after apply) +``` + +> Note: I also saw a warning: “Cannot connect to YC tool initialization service...”. This warning did not block plan generation. + +#### `terraform apply` (result) +```text + +``` + +### Outputs +```text +Public IP: +SSH command (from output): + +``` + +### SSH proof +```text + +``` + +Example command (Windows): +```powershell +ssh -i C:\Users\11kvv\.ssh\lab04_ed25519 ubuntu@ +``` + +--- + +## 3. Pulumi Implementation + +### Pulumi version and language +- Language: **Python** +```text + +``` + +### Cleanup of Terraform resources +Before provisioning the same infrastructure with Pulumi, Terraform resources were destroyed: + +```text + +``` + +### Pulumi project structure +``` +pulumi/ +├── __main__.py +├── requirements.txt +├── Pulumi.yaml +└── Pulumi..yaml (gitignored if contains secrets) +``` + +### Planned changes (`pulumi preview`) +```text + +``` + +### Apply (`pulumi up`) +```text + +``` + +### Outputs and SSH proof +```text +Pulumi public IP: +SSH proof: + +``` + +--- + +## 4. Terraform vs Pulumi Comparison + +### Ease of Learning +Terraform was easier to start with because HCL is concise and the workflow is very straightforward (`init → plan → apply`). Pulumi required a bit more setup (runtime, deps) and code structure, but felt natural once configured. + +### Code Readability +Terraform is very readable for simple infra because it is declarative and compact. Pulumi is more verbose but benefits from real language features (variables, functions, reuse) which can help as the project grows. + +### Debugging +Terraform errors are often direct and tied to a specific resource block. In Pulumi, errors can appear deeper in the program flow, but the ability to print/debug in code can help. + +### Documentation +Terraform has a large ecosystem and many examples. Pulumi documentation is also strong, especially when you already know the language SDK, but examples for some providers may be fewer. + +### Use Case +- Terraform: best for standard, repeatable infra with a simple declarative model, especially in teams. +- Pulumi: best when infrastructure needs non-trivial logic/reuse and you want to leverage full programming languages and testing. + +--- + +## 5. Lab 5 Preparation & Cleanup + +### VM for Lab 5 +- Keeping a VM for Lab 5 (Ansible): **** +- If YES: Which one: **Terraform / Pulumi** +- Reason: + - + +### Cleanup status +- Terraform resources destroyed: **** +- Pulumi resources destroyed: **** +- Proof (outputs/log excerpts): +```text + +``` + +--- + +## Appendix — Security & Git hygiene + +- `terraform.tfstate` and `terraform.tfvars` are not committed. +- Service account key `*.json` is not committed. +- SSH private key is not committed. +- `.gitignore` contains patterns for state and secrets. diff --git a/pulumi/.gitignore b/pulumi/.gitignore new file mode 100644 index 0000000000..3a8f831143 --- /dev/null +++ b/pulumi/.gitignore @@ -0,0 +1,5 @@ +*.pyc +venv/ +.pulumi-state/ +Pulumi.*.yaml +__pycache__/ \ No newline at end of file diff --git a/pulumi/Pulumi.yaml b/pulumi/Pulumi.yaml new file mode 100644 index 0000000000..66bf83a3bf --- /dev/null +++ b/pulumi/Pulumi.yaml @@ -0,0 +1,7 @@ +name: lab04-pulumi +description: Lab04 Yandex Cloud VM +runtime: python +config: + pulumi:tags: + value: + pulumi:template: python \ No newline at end of file diff --git a/pulumi/__main__.py b/pulumi/__main__.py new file mode 100644 index 0000000000..77aef574b2 --- /dev/null +++ b/pulumi/__main__.py @@ -0,0 +1,92 @@ +import pulumi +import pulumi_yandex as yandex + +config = pulumi.Config() +zone = config.get("zone") or "ru-central1-a" +ssh_username = config.get("sshUsername") or "yc-user" +ssh_pub_key_path = config.get("sshPubKeyPath") or "~/.ssh/id_ed25519.pub" + +import os + +ssh_pub_key = open(os.path.expanduser(ssh_pub_key_path)).read().strip() + +labels = {"project": "devops-lab04", "tool": "pulumi"} + +image = yandex.get_compute_image(family="ubuntu-2204-lts") + +network = yandex.VpcNetwork("lab04-network", name="lab04-network", labels=labels) + +subnet = yandex.VpcSubnet( + "lab04-subnet", + name="lab04-subnet", + zone=zone, + network_id=network.id, + v4_cidr_blocks=["10.0.1.0/24"], + labels=labels, +) + +sg = yandex.VpcSecurityGroup( + "lab04-sg", + name="lab04-sg", + network_id=network.id, + labels=labels, + ingresses=[ + yandex.VpcSecurityGroupIngressArgs( + description="SSH", protocol="TCP", port=22, v4_cidr_blocks=["0.0.0.0/0"] + ), + yandex.VpcSecurityGroupIngressArgs( + description="HTTP", protocol="TCP", port=80, v4_cidr_blocks=["0.0.0.0/0"] + ), + yandex.VpcSecurityGroupIngressArgs( + description="App", protocol="TCP", port=5000, v4_cidr_blocks=["0.0.0.0/0"] + ), + ], + egresses=[ + yandex.VpcSecurityGroupEgressArgs( + description="Allow all outbound", + protocol="ANY", + v4_cidr_blocks=["0.0.0.0/0"], + ), + ], +) + +user_data = f"""#cloud-config +users: + - name: {ssh_username} + sudo: ALL=(ALL) NOPASSWD:ALL + shell: /bin/bash + ssh_authorized_keys: + - {ssh_pub_key} +""" + +vm = yandex.ComputeInstance( + "lab04-vm", + name="lab04-vm", + hostname="lab04-vm", + platform_id="standard-v2", + zone=zone, + labels=labels, + resources=yandex.ComputeInstanceResourcesArgs( + cores=2, core_fraction=20, memory=1 + ), + 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={"user-data": user_data}, +) + +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( + "ssh_command", + vm.network_interfaces[0].nat_ip_address.apply( + lambda ip: f"ssh -i ~/.ssh/id_ed25519 {ssh_username}@{ip}" + ), +) \ No newline at end of file diff --git a/pulumi/requirements.txt b/pulumi/requirements.txt new file mode 100644 index 0000000000..2356228903 --- /dev/null +++ b/pulumi/requirements.txt @@ -0,0 +1,2 @@ +pulumi>=3.0.0,<4.0.0 +pulumi-yandex>=0.13.0 \ No newline at end of file diff --git a/terraform.tfvars b/terraform.tfvars new file mode 100644 index 0000000000..b8b044845f --- /dev/null +++ b/terraform.tfvars @@ -0,0 +1,4 @@ +cloud_id = "b1g07cju7m9et0m08nk6" +folder_id = "b1gmc0hedluug99gn0d8" +zone = "ru-central1-a" +ssh_public_key_path = "C:/Users/11kvv/.ssh/lab04_ed25519.pub" diff --git a/terraform/.gitignore b/terraform/.gitignore new file mode 100644 index 0000000000..daaf14ddef --- /dev/null +++ b/terraform/.gitignore @@ -0,0 +1,11 @@ +*.tfstate +*.tfstate.* +.terraform/ +.terraform.lock.hcl +terraform.tfvars +*.tfvars +crash.log +override.tf +override.tf.json +*_override.tf +*_override.tf.json \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000000..5d16a07a29 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,103 @@ +terraform { + required_providers { + yandex = { + source = "yandex-cloud/yandex" + version = "~> 0.135" + } + } + required_version = ">= 1.5" +} + +provider "yandex" { + zone = var.zone +} + +data "yandex_compute_image" "ubuntu" { + family = var.image_family +} + +resource "yandex_vpc_network" "lab" { + name = "lab04-network" + labels = var.labels +} + +resource "yandex_vpc_subnet" "lab" { + name = "lab04-subnet" + zone = var.zone + network_id = yandex_vpc_network.lab.id + v4_cidr_blocks = ["10.0.1.0/24"] + labels = var.labels +} + +resource "yandex_vpc_security_group" "lab" { + name = "lab04-sg" + network_id = yandex_vpc_network.lab.id + labels = var.labels + + ingress { + description = "SSH" + protocol = "TCP" + port = 22 + v4_cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "HTTP" + protocol = "TCP" + port = 80 + v4_cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "App" + protocol = "TCP" + port = 5000 + v4_cidr_blocks = ["0.0.0.0/0"] + } + + egress { + description = "Allow all outbound" + protocol = "ANY" + v4_cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "yandex_compute_instance" "lab" { + name = "lab04-vm" + hostname = "lab04-vm" + platform_id = var.platform_id + zone = var.zone + labels = var.labels + + resources { + cores = var.cores + core_fraction = var.core_fraction + memory = var.memory + } + + boot_disk { + initialize_params { + image_id = data.yandex_compute_image.ubuntu.id + size = var.disk_size + type = var.disk_type + } + } + + network_interface { + subnet_id = yandex_vpc_subnet.lab.id + nat = true + security_group_ids = [yandex_vpc_security_group.lab.id] + } + + metadata = { + user-data = <<-EOF + #cloud-config + users: + - name: ${var.ssh_username} + sudo: ALL=(ALL) NOPASSWD:ALL + shell: /bin/bash + ssh_authorized_keys: + - ${file(var.ssh_public_key_path)} + EOF + } +} \ No newline at end of file diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000000..f168518f56 --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,14 @@ +output "vm_public_ip" { + description = "Public IP address of the VM" + value = yandex_compute_instance.lab.network_interface[0].nat_ip_address +} + +output "vm_private_ip" { + description = "Private IP address of the VM" + value = yandex_compute_instance.lab.network_interface[0].ip_address +} + +output "ssh_command" { + description = "SSH command to connect to the VM" + value = "ssh -i ~/.ssh/id_ed25519 ${var.ssh_username}@${yandex_compute_instance.lab.network_interface[0].nat_ip_address}" +} \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000000..3e9bc6acb0 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,68 @@ +variable "zone" { + description = "Yandex Cloud availability zone" + type = string + default = "ru-central1-a" +} + +variable "platform_id" { + description = "VM platform identifier" + type = string + default = "standard-v2" +} + +variable "cores" { + description = "Number of CPU cores" + type = number + default = 2 +} + +variable "core_fraction" { + description = "Guaranteed vCPU share (%)" + type = number + default = 20 +} + +variable "memory" { + description = "RAM in GB" + type = number + default = 1 +} + +variable "disk_size" { + description = "Boot disk size in GB" + type = number + default = 10 +} + +variable "disk_type" { + description = "Boot disk type" + type = string + default = "network-hdd" +} + +variable "image_family" { + description = "OS image family" + type = string + default = "ubuntu-2204-lts" +} + +variable "ssh_username" { + description = "SSH user created on the VM" + type = string + default = "yc-user" +} + +variable "ssh_public_key_path" { + description = "Path to SSH public key" + type = string + default = "~/.ssh/id_ed25519.pub" +} + +variable "labels" { + description = "Resource labels" + type = map(string) + default = { + project = "devops-lab04" + tool = "terraform" + } +} \ No newline at end of file diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 0000000000..f1c7bd63d3 --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.9.0" + + required_providers { + yandex = { + source = "yandex-cloud/yandex" + version = ">= 0.120.0" + } + } +}