diff --git a/lab3/.gitignore b/lab3/.gitignore new file mode 100644 index 0000000..e9c8978 --- /dev/null +++ b/lab3/.gitignore @@ -0,0 +1,8 @@ +*.tfvars +!*.tfvars.example +.terraform/ +*.tfstate +*.tfstate.backup +.terraform.lock.hcl +ansible/inventory.ini +.DS_Store diff --git a/lab3/ansible/ansible.cfg b/lab3/ansible/ansible.cfg new file mode 100644 index 0000000..de8aa00 --- /dev/null +++ b/lab3/ansible/ansible.cfg @@ -0,0 +1,5 @@ +[defaults] +roles_path = ../ansible +connect_timeout = 30 +host_key_checking = False +inventory = ./inventory.ini diff --git a/lab3/ansible/docker-install/tasks/main.yml b/lab3/ansible/docker-install/tasks/main.yml new file mode 100644 index 0000000..6a56d5f --- /dev/null +++ b/lab3/ansible/docker-install/tasks/main.yml @@ -0,0 +1,50 @@ +- name: update apt list + apt: + update_cache: yes + +- name: Install basic packages + ansible.builtin.apt: + name: + - apt-transport-https + - ca-certificates + - curl + - python3 + - python3-pip + state: present + +- name: Add Docker GPG key + ansible.builtin.apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + +- name: Add Docker repository + ansible.builtin.apt_repository: + repo: 'deb [arch=amd64] https://download.docker.com/linux/ubuntu jammy stable' + state: present + update_cache: yes + +- name: Install Docker packages + ansible.builtin.apt: + name: + - libc6 + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + state: present + update_cache: true + +- name: Install docker python packages + ansible.builtin.pip: + name: + - docker==6.1.3 + - docker-compose==1.29.2 + - requests==2.28.1 + - urllib3==1.26.18 + +- name: Enable and start Docker service + ansible.builtin.systemd: + name: docker + enabled: yes + state: started diff --git a/lab3/ansible/docker-service/defaults/main.yml b/lab3/ansible/docker-service/defaults/main.yml new file mode 100644 index 0000000..32186ee --- /dev/null +++ b/lab3/ansible/docker-service/defaults/main.yml @@ -0,0 +1,5 @@ +docker_service__compose: + version: '3' + services: + app: + image: hello-world diff --git a/lab3/ansible/docker-service/tasks/main.yml b/lab3/ansible/docker-service/tasks/main.yml new file mode 100644 index 0000000..7fe4979 --- /dev/null +++ b/lab3/ansible/docker-service/tasks/main.yml @@ -0,0 +1,24 @@ +--- +- name: Create docker-compose dirs + file: + state: directory + path: "{{ item }}" + mode: 0755 + owner: root + group: root + loop: + - /opt/service + - /opt/service/compose + - /opt/service/compose/data/ + +- name: Configure service compose file + template: + src: docker-compose.yml.j2 + dest: /opt/service/compose/docker-compose.yml + mode: 0644 + owner: root + group: root + +- name: Start containers + community.docker.docker_compose_v2: + project_src: /opt/service/compose diff --git a/lab3/ansible/docker-service/templates/docker-compose.yml.j2 b/lab3/ansible/docker-service/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..a6d7cd7 --- /dev/null +++ b/lab3/ansible/docker-service/templates/docker-compose.yml.j2 @@ -0,0 +1 @@ +{{ docker_service__compose | to_nice_yaml }} diff --git a/lab3/ansible/docker-service/vars/database.yml b/lab3/ansible/docker-service/vars/database.yml new file mode 100644 index 0000000..7e1ed54 --- /dev/null +++ b/lab3/ansible/docker-service/vars/database.yml @@ -0,0 +1,15 @@ +--- +docker_service__compose: + version: "3" + services: + mysql: + image: mysql:5.7 + ports: + - "3306:3306" + volumes: + - todo-mysql-data:/var/lib/mysql + environment: + MYSQL_ROOT_PASSWORD: secret + MYSQL_DATABASE: todos + volumes: + todo-mysql-data: diff --git a/lab3/ansible/docker-service/vars/webapp.yml b/lab3/ansible/docker-service/vars/webapp.yml new file mode 100644 index 0000000..da3bc4e --- /dev/null +++ b/lab3/ansible/docker-service/vars/webapp.yml @@ -0,0 +1,14 @@ +--- +docker_service__compose: + version: "3" + services: + app: + image: antonaleks/101-todo-app + restart: on-failure + ports: + - "80:80" + environment: + MYSQL_HOST: "{{ private_database_ip }}" + MYSQL_USER: root + MYSQL_PASSWORD: secret + MYSQL_DB: todos diff --git a/lab3/ansible/group_vars/all.yml b/lab3/ansible/group_vars/all.yml new file mode 100644 index 0000000..d3b4f97 --- /dev/null +++ b/lab3/ansible/group_vars/all.yml @@ -0,0 +1,2 @@ +ansible_user: root +ansible_ssh_private_key_file: ~/.ssh/id_rsa diff --git a/lab3/ansible/playbook.yml b/lab3/ansible/playbook.yml new file mode 100644 index 0000000..5a62a4e --- /dev/null +++ b/lab3/ansible/playbook.yml @@ -0,0 +1,19 @@ +--- +- name: "Installing Docker and docker compose" + hosts: all + roles: + - docker-install + +- name: "Run Docker compose service: database" + hosts: database + vars_files: + - "./docker-service/vars/database.yml" + roles: + - docker-service + +- name: "Run Docker compose service: web app" + hosts: webapp + vars_files: + - "./docker-service/vars/webapp.yml" + roles: + - docker-service diff --git a/lab3/report.md b/lab3/report.md new file mode 100644 index 0000000..5a6ab31 --- /dev/null +++ b/lab3/report.md @@ -0,0 +1,133 @@ +# Лабораторная работа 3 - Cloud Platforms + +**Студент:** Голиков Иван +**Предмет:** ПО Облачных платформ + +## Задача + +Развернуть инфраструктуру в облаке Selectel с помощью Terraform и настроить приложение с помощью Ansible. + +Выбранная конфигурация: 2 прерываемые ВМ - `webapp` (todo app) и `database` (MySQL). + +## Архитектура + +``` +Internet + | + | (floating IP) (floating IP) + | | +[webapp VM] <-- private net --> [database VM] +antonaleks/101-todo-app mysql:5.7 +port 80 port 3306 +``` + +ВМ находятся в одной приватной подсети `10.0.1.0/24`. Todo app подключается к MySQL по приватному IP базы данных. Обе ВМ имеют публичный Floating IP для доступа извне (webapp - для браузера, database - для SSH-отладки). + +## Terraform + +### Ресурсы + +| Ресурс | Описание | +|--------|----------| +| `openstack_compute_keypair_v2` | SSH-ключ для доступа к ВМ | +| `openstack_networking_network_v2` | Приватная сеть `lab3-network` | +| `openstack_networking_subnet_v2` | Подсеть `10.0.1.0/24` | +| `openstack_networking_router_v2` | Роутер с выходом в интернет | +| `openstack_compute_flavor_v2` | Flavor: 1 vCPU, 2 GB RAM | +| `openstack_blockstorage_volume_v3` x2 | Загружаемые диски 10 GB HDD (basic.ru-7a) | +| `openstack_compute_instance_v2` x2 | Прерываемые ВМ (тег `preemptible`) | +| `openstack_networking_floatingip_v2` x2 | Публичные IP-адреса | +| `local_file` | Генерация `ansible/inventory.ini` | + +### Параметры ВМ + +- Образ: Ubuntu 22.04 LTS 64-bit +- Зона: `ru-7a` (единственная зона для прерываемых ВМ) +- Диск: 10 GB, тип `basic.ru-7a` +- Flavor: 1 CPU, 2 GB RAM + +### Безопасность + +Чувствительные данные (логин, пароль, tenant_id, domain_name) хранятся только в `terraform.tfvars`, который исключен из репозитория через `.gitignore`. В репозитории присутствует только шаблон `terraform.tfvars.example`. + +### Запуск + +```bash +cd terraform + +# Скопировать шаблон и заполнить реальными данными +cp terraform.tfvars.example terraform.tfvars +# Отредактировать terraform.tfvars + +terraform init +terraform plan +terraform apply +``` + +После `terraform apply` автоматически генерируется файл `ansible/inventory.ini`. + +## Ansible + +### Структура + +``` +ansible/ + ansible.cfg - конфиг (host_key_checking=False) + playbook.yml - основной плейбук + inventory.ini - генерируется Terraform (gitignored) + group_vars/ + all.yml - общие переменные (ansible_user, ssh key) + docker-install/ - роль: установка Docker + docker-service/ - роль: запуск docker-compose сервиса + vars/ + database.yml - конфиг MySQL контейнера + webapp.yml - конфиг todo app контейнера + templates/ + docker-compose.yml.j2 +``` + +### Плейбук + +Плейбук состоит из трех play: +1. Установка Docker на все хосты (`docker-install`) +2. Запуск MySQL на хосте `database` (`docker-service` + `database.yml`) +3. Запуск todo app на хосте `webapp` (`docker-service` + `webapp.yml`) + +Приватный IP базы данных передается из inventory как host-переменная `private_database_ip` и подставляется в конфиг todo app через Jinja2-шаблон. + +### Запуск + +```bash +cd ansible +ansible-playbook playbook.yml +``` + +## Скриншоты + +### 1. Вывод terraform apply + +![terraform apply](screenshots/01_terraform_apply.png) + +Вывод команды `terraform apply` - создание 2 ВМ, сети, роутера, floating IP и генерация inventory.ini. + +### 2. ВМ в консоли Selectel + +![Selectel VMs](screenshots/02_selectel_vms.png) + +Список виртуальных машин в консоли Selectel с публичными IP-адресами. + +### 3. Вывод ansible-playbook + +![ansible-playbook](screenshots/03_ansible_playbook.png) + +Вывод команды `ansible-playbook playbook.yml` - установка Docker и запуск контейнеров. + +### 4. Todo приложение в браузере + +![Todo app](screenshots/04_todo_app.png) + +Todo app доступен по публичному IP webapp ВМ на порту 80. + +## Вывод + +С помощью Terraform была автоматически развернута инфраструктура из 2 прерываемых ВМ в облаке Selectel (OpenStack). Ansible обеспечил установку Docker и запуск todo app + MySQL через docker-compose. Все чувствительные данные исключены из репозитория. diff --git a/lab3/screenshots/01_terraform_apply.png b/lab3/screenshots/01_terraform_apply.png new file mode 100644 index 0000000..da664c8 Binary files /dev/null and b/lab3/screenshots/01_terraform_apply.png differ diff --git a/lab3/screenshots/02_selectel_vms.png b/lab3/screenshots/02_selectel_vms.png new file mode 100644 index 0000000..87a89f1 Binary files /dev/null and b/lab3/screenshots/02_selectel_vms.png differ diff --git a/lab3/screenshots/03_ansible_playbook.png b/lab3/screenshots/03_ansible_playbook.png new file mode 100644 index 0000000..62025b1 Binary files /dev/null and b/lab3/screenshots/03_ansible_playbook.png differ diff --git a/lab3/screenshots/04_todo_app.png b/lab3/screenshots/04_todo_app.png new file mode 100644 index 0000000..43d5f69 Binary files /dev/null and b/lab3/screenshots/04_todo_app.png differ diff --git a/lab3/terraform/main.tf b/lab3/terraform/main.tf new file mode 100644 index 0000000..423cc62 --- /dev/null +++ b/lab3/terraform/main.tf @@ -0,0 +1,177 @@ +resource "random_string" "suffix" { + length = 5 + special = false + upper = false +} + +# SSH keypair +resource "openstack_compute_keypair_v2" "lab3_key" { + name = var.keypair_name + public_key = file(pathexpand(var.keypair_public_key_path)) +} + +# Image +data "openstack_images_image_v2" "ubuntu_22" { + name = var.image_name + most_recent = true +} + +# External network +data "openstack_networking_network_v2" "external_net" { + name = "external-network" + external = true +} + +# Private network +resource "openstack_networking_network_v2" "lab3_net" { + name = "lab3-network" +} + +# Subnet +resource "openstack_networking_subnet_v2" "lab3_subnet" { + name = "lab3-subnet" + network_id = openstack_networking_network_v2.lab3_net.id + cidr = "10.0.1.0/24" + dns_nameservers = ["188.93.16.19", "188.93.17.19"] +} + +# Router +resource "openstack_networking_router_v2" "lab3_router" { + name = "lab3-router" + external_network_id = data.openstack_networking_network_v2.external_net.id +} + +# Router interface +resource "openstack_networking_router_interface_v2" "lab3_router_iface" { + router_id = openstack_networking_router_v2.lab3_router.id + subnet_id = openstack_networking_subnet_v2.lab3_subnet.id +} + +# Shared flavor (1 CPU, 2 GB RAM, HDD) +resource "openstack_compute_flavor_v2" "lab3_flavor" { + name = "lab3-flavor-${random_string.suffix.result}" + vcpus = var.server_vcpus + ram = var.server_ram_mb + disk = 0 + is_public = false +} + +# --- webapp --- + +resource "openstack_networking_port_v2" "webapp_port" { + name = "webapp-port" + network_id = openstack_networking_network_v2.lab3_net.id + fixed_ip { + subnet_id = openstack_networking_subnet_v2.lab3_subnet.id + } +} + +resource "openstack_blockstorage_volume_v3" "webapp_vol" { + name = "webapp-volume" + size = var.server_disk_gb + image_id = data.openstack_images_image_v2.ubuntu_22.id + volume_type = var.volume_type + availability_zone = var.zone + + lifecycle { + ignore_changes = [image_id] + } +} + +resource "openstack_compute_instance_v2" "webapp" { + name = "webapp" + flavor_id = openstack_compute_flavor_v2.lab3_flavor.id + key_pair = openstack_compute_keypair_v2.lab3_key.name + availability_zone = var.zone + + network { + port = openstack_networking_port_v2.webapp_port.id + } + + block_device { + uuid = openstack_blockstorage_volume_v3.webapp_vol.id + source_type = "volume" + destination_type = "volume" + boot_index = 0 + } + + tags = ["preemptible"] + + vendor_options { + ignore_resize_confirmation = true + } +} + +resource "openstack_networking_floatingip_v2" "webapp_fip" { + pool = "external-network" +} + +resource "openstack_networking_floatingip_associate_v2" "webapp_fip_assoc" { + port_id = openstack_networking_port_v2.webapp_port.id + floating_ip = openstack_networking_floatingip_v2.webapp_fip.address +} + +# --- database --- + +resource "openstack_networking_port_v2" "db_port" { + name = "db-port" + network_id = openstack_networking_network_v2.lab3_net.id + fixed_ip { + subnet_id = openstack_networking_subnet_v2.lab3_subnet.id + } +} + +resource "openstack_blockstorage_volume_v3" "db_vol" { + name = "db-volume" + size = var.server_disk_gb + image_id = data.openstack_images_image_v2.ubuntu_22.id + volume_type = var.volume_type + availability_zone = var.zone + + lifecycle { + ignore_changes = [image_id] + } +} + +resource "openstack_compute_instance_v2" "database" { + name = "database" + flavor_id = openstack_compute_flavor_v2.lab3_flavor.id + key_pair = openstack_compute_keypair_v2.lab3_key.name + availability_zone = var.zone + + network { + port = openstack_networking_port_v2.db_port.id + } + + block_device { + uuid = openstack_blockstorage_volume_v3.db_vol.id + source_type = "volume" + destination_type = "volume" + boot_index = 0 + } + + tags = ["preemptible"] + + vendor_options { + ignore_resize_confirmation = true + } +} + +resource "openstack_networking_floatingip_v2" "db_fip" { + pool = "external-network" +} + +resource "openstack_networking_floatingip_associate_v2" "db_fip_assoc" { + port_id = openstack_networking_port_v2.db_port.id + floating_ip = openstack_networking_floatingip_v2.db_fip.address +} + +# Ansible inventory +resource "local_file" "ansible_inventory" { + content = templatefile("${path.module}/resources/inventory.tmpl", { + webapp_public_ip = openstack_networking_floatingip_v2.webapp_fip.address + db_public_ip = openstack_networking_floatingip_v2.db_fip.address + db_private_ip = openstack_networking_port_v2.db_port.all_fixed_ips[0] + }) + filename = "${path.module}/../ansible/inventory.ini" +} diff --git a/lab3/terraform/outputs.tf b/lab3/terraform/outputs.tf new file mode 100644 index 0000000..436ca67 --- /dev/null +++ b/lab3/terraform/outputs.tf @@ -0,0 +1,11 @@ +output "webapp_ssh" { + value = "ssh root@${openstack_networking_floatingip_v2.webapp_fip.address}" +} + +output "database_ssh" { + value = "ssh root@${openstack_networking_floatingip_v2.db_fip.address}" +} + +output "webapp_url" { + value = "http://${openstack_networking_floatingip_v2.webapp_fip.address}" +} diff --git a/lab3/terraform/provider.tf b/lab3/terraform/provider.tf new file mode 100644 index 0000000..a99ae67 --- /dev/null +++ b/lab3/terraform/provider.tf @@ -0,0 +1,8 @@ +provider "openstack" { + auth_url = var.os_auth_url + domain_name = var.domain_name + tenant_id = var.tenant_id + user_name = var.user_name + password = var.password + region = var.region +} diff --git a/lab3/terraform/resources/inventory.tmpl b/lab3/terraform/resources/inventory.tmpl new file mode 100644 index 0000000..c99dae5 --- /dev/null +++ b/lab3/terraform/resources/inventory.tmpl @@ -0,0 +1,5 @@ +[webapp] +${webapp_public_ip} private_database_ip=${db_private_ip} + +[database] +${db_public_ip} diff --git a/lab3/terraform/terraform.tfvars.example b/lab3/terraform/terraform.tfvars.example new file mode 100644 index 0000000..a8053c2 --- /dev/null +++ b/lab3/terraform/terraform.tfvars.example @@ -0,0 +1,5 @@ +domain_name = "your_domain_name" +tenant_id = "your_project_id" +user_name = "your_username" +password = "your_password" +region = "ru-7" diff --git a/lab3/terraform/vars.tf b/lab3/terraform/vars.tf new file mode 100644 index 0000000..f432345 --- /dev/null +++ b/lab3/terraform/vars.tf @@ -0,0 +1,60 @@ +variable "os_auth_url" { + default = "https://api.selvpc.ru/identity/v3" +} + +variable "domain_name" { + sensitive = true +} + +variable "tenant_id" { + sensitive = true +} + +variable "user_name" { + sensitive = true +} + +variable "password" { + sensitive = true +} + +variable "region" { + default = "ru-7" +} + +variable "zone" { + default = "ru-7a" + + validation { + condition = can(regex("ru-7a", var.zone)) + error_message = "Preemptible servers are available in ru-7a zone only." + } +} + +variable "keypair_name" { + default = "lab3-keypair" +} + +variable "keypair_public_key_path" { + default = "~/.ssh/id_rsa.pub" +} + +variable "image_name" { + default = "Ubuntu 22.04 LTS 64-bit" +} + +variable "volume_type" { + default = "basic.ru-7a" +} + +variable "server_vcpus" { + default = 1 +} + +variable "server_ram_mb" { + default = 2048 +} + +variable "server_disk_gb" { + default = 10 +} diff --git a/lab3/terraform/versions.tf b/lab3/terraform/versions.tf new file mode 100644 index 0000000..4cc5dff --- /dev/null +++ b/lab3/terraform/versions.tf @@ -0,0 +1,17 @@ +terraform { + required_version = ">= 0.14.0" + required_providers { + openstack = { + source = "terraform-provider-openstack/openstack" + version = "~> 1.49.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.5" + } + local = { + source = "hashicorp/local" + version = "~> 2.4" + } + } +}