Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 213 additions & 0 deletions app_python/docs/LAB04.md
Original file line number Diff line number Diff line change
@@ -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
<PASTE: terraform version>
```

### 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
<PASTE: the final terraform apply output AFTER permissions fix>
```

### Outputs
```text
Public IP: <PASTE_PUBLIC_IP>
SSH command (from output):
<PASTE: terraform output ssh_command>
```

### SSH proof
```text
<PASTE: ssh session proof, e.g. `uname -a` / `whoami`>
```

Example command (Windows):
```powershell
ssh -i C:\Users\11kvv\.ssh\lab04_ed25519 ubuntu@<PUBLIC_IP>
```

---

## 3. Pulumi Implementation

### Pulumi version and language
- Language: **Python**
```text
<PASTE: pulumi version>
```

### Cleanup of Terraform resources
Before provisioning the same infrastructure with Pulumi, Terraform resources were destroyed:

```text
<PASTE: terraform destroy output>
```

### Pulumi project structure
```
pulumi/
├── __main__.py
├── requirements.txt
├── Pulumi.yaml
└── Pulumi.<stack>.yaml (gitignored if contains secrets)
```

### Planned changes (`pulumi preview`)
```text
<PASTE: pulumi preview output>
```

### Apply (`pulumi up`)
```text
<PASTE: pulumi up output>
```

### Outputs and SSH proof
```text
Pulumi public IP: <PASTE_PUBLIC_IP>
SSH proof:
<PASTE: ssh session 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): **<YES/NO>**
- If YES: Which one: **Terraform / Pulumi**
- Reason:
- <short explanation>

### Cleanup status
- Terraform resources destroyed: **<YES/NO>**
- Pulumi resources destroyed: **<YES/NO>**
- Proof (outputs/log excerpts):
```text
<PASTE: destroy outputs OR mention cloud console status>
```

---

## 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.
5 changes: 5 additions & 0 deletions pulumi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.pyc
venv/
.pulumi-state/
Pulumi.*.yaml
__pycache__/
7 changes: 7 additions & 0 deletions pulumi/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: lab04-pulumi
description: Lab04 Yandex Cloud VM
runtime: python
config:
pulumi:tags:
value:
pulumi:template: python
92 changes: 92 additions & 0 deletions pulumi/__main__.py
Original file line number Diff line number Diff line change
@@ -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}"
),
)
2 changes: 2 additions & 0 deletions pulumi/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pulumi>=3.0.0,<4.0.0
pulumi-yandex>=0.13.0
4 changes: 4 additions & 0 deletions terraform.tfvars
Original file line number Diff line number Diff line change
@@ -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"
11 changes: 11 additions & 0 deletions terraform/.gitignore
Original file line number Diff line number Diff line change
@@ -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
Loading