diff --git a/Militsyn_work_3.docx b/Militsyn_work_3.docx new file mode 100644 index 0000000..b42a64a Binary files /dev/null and b/Militsyn_work_3.docx differ diff --git a/ansible/ansible/ansible.cfg b/ansible/ansible/ansible.cfg new file mode 100644 index 0000000..883c28f --- /dev/null +++ b/ansible/ansible/ansible.cfg @@ -0,0 +1,5 @@ +[defaults] +roles_path = ./roles +connect_timeout = 30 +host_key_checking = False +inventory = ./inventory.ini diff --git a/ansible/ansible/playbook.yml b/ansible/ansible/playbook.yml new file mode 100644 index 0000000..3bd7139 --- /dev/null +++ b/ansible/ansible/playbook.yml @@ -0,0 +1,14 @@ +--- +- name: Deploy MySQL database + hosts: db_servers + become: true + roles: + - db + +- name: Deploy Flask Todo application + hosts: app_servers + become: true + vars: + db_host: "{{ hostvars[groups['db_servers'][0]]['ansible_host'] }}" + roles: + - app diff --git a/ansible/ansible/roles/app/tasks/main.yml b/ansible/ansible/roles/app/tasks/main.yml new file mode 100644 index 0000000..4885b76 --- /dev/null +++ b/ansible/ansible/roles/app/tasks/main.yml @@ -0,0 +1,25 @@ +--- +- name: Install python3 and pip + apt: + name: + - python3 + - python3-pip + state: present + update_cache: yes + +- name: Build and run Flask app container on host + shell: | + cd /mnt/c/Users/user/Desktop/homework3/app + docker build -t todoapp . + docker rm -f todoapp 2>/dev/null || true + docker run -d --name todoapp \ + -e DB_HOST=localhost \ + -e DB_USER=todouser \ + -e DB_PASSWORD=todopassword \ + -e DB_NAME=todoapp \ + --network host \ + -p 5000:5000 \ + --restart always \ + todoapp + delegate_to: localhost + become: false diff --git a/ansible/ansible/roles/db/tasks/main.yml b/ansible/ansible/roles/db/tasks/main.yml new file mode 100644 index 0000000..74698bc --- /dev/null +++ b/ansible/ansible/roles/db/tasks/main.yml @@ -0,0 +1,21 @@ +--- +- name: Install python3 and pip + apt: + name: + - python3 + - python3-pip + state: present + update_cache: yes + +- name: Pull and run MySQL container on host + shell: | + docker run -d --name mysql \ + -e MYSQL_ROOT_PASSWORD=rootpassword \ + -e MYSQL_DATABASE=todoapp \ + -e MYSQL_USER=todouser \ + -e MYSQL_PASSWORD=todopassword \ + -p 3306:3306 \ + --restart always \ + mysql:8.0 || docker start mysql + delegate_to: localhost + become: false diff --git a/ansible/ansible/roles/docker/tasks/main.yml b/ansible/ansible/roles/docker/tasks/main.yml new file mode 100644 index 0000000..85fbc1c --- /dev/null +++ b/ansible/ansible/roles/docker/tasks/main.yml @@ -0,0 +1,38 @@ +--- +- name: Install dependencies + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - gnupg + - lsb-release + - python3-pip + state: present + update_cache: yes + +- name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + +- name: Add Docker repository + apt_repository: + repo: "deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable" + state: present + +- name: Install Docker + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-compose-plugin + state: present + update_cache: yes + +- name: Start Docker service + service: + name: docker + state: started + enabled: yes diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..0883157 --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.12-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY . . +CMD ["python", "app.py"] diff --git a/app/app.py b/app/app.py new file mode 100644 index 0000000..c7c630e --- /dev/null +++ b/app/app.py @@ -0,0 +1,73 @@ +from flask import Flask, render_template, request, redirect +import mysql.connector +import os +import time + +app = Flask(__name__) + +def get_db(): + for _ in range(10): + try: + return mysql.connector.connect( + host=os.environ.get("DB_HOST", "localhost"), + user=os.environ.get("DB_USER", "todouser"), + password=os.environ.get("DB_PASSWORD", "todopassword"), + database=os.environ.get("DB_NAME", "todoapp") + ) + except: + time.sleep(3) + +def init_db(): + db = get_db() + cursor = db.cursor() + cursor.execute(""" + CREATE TABLE IF NOT EXISTS tasks ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + done BOOLEAN DEFAULT FALSE + ) + """) + db.commit() + db.close() + +@app.route("/") +def index(): + db = get_db() + cursor = db.cursor() + cursor.execute("SELECT * FROM tasks") + tasks = cursor.fetchall() + db.close() + return render_template("index.html", tasks=tasks) + +@app.route("/add", methods=["POST"]) +def add(): + title = request.form.get("title") + if title: + db = get_db() + cursor = db.cursor() + cursor.execute("INSERT INTO tasks (title) VALUES (%s)", (title,)) + db.commit() + db.close() + return redirect("/") + +@app.route("/toggle/") +def toggle(task_id): + db = get_db() + cursor = db.cursor() + cursor.execute("UPDATE tasks SET done = NOT done WHERE id = %s", (task_id,)) + db.commit() + db.close() + return redirect("/") + +@app.route("/delete/") +def delete(task_id): + db = get_db() + cursor = db.cursor() + cursor.execute("DELETE FROM tasks WHERE id = %s", (task_id,)) + db.commit() + db.close() + return redirect("/") + +if __name__ == "__main__": + init_db() + app.run(host="0.0.0.0", port=5000) diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..2241087 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,2 @@ +flask==3.0.3 +mysql-connector-python==8.3.0 diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..83d8fb9 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,35 @@ + + + + + Todo App + + + +

Todo List

+
+ + +
+ + + diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..706c464 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,66 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/local" { + version = "2.9.0" + hashes = [ + "h1:px3Hpv/tL288wzu5knHywTTBcrydLnnGEiF/NIQBaRs=", + "zh:0baa4566cf77f1ff52f4293d1c8536202dd23edc197c3196413a28343c3ac3a0", + "zh:16b5559c3c07088ddad11a9bb9e9c0799999363c2958e9a5be2bcbbf2cd9ca64", + "zh:197c79015a10d1cce904a8ea722cbc750c42aeae2da53f44a6a0751d9fd1aa90", + "zh:29d0b03e5343a80677ebfeb2e2c31cbe4b1f65e736e53417454a4277fec2544c", + "zh:4896bfa6cf1d2fd562b47ef2e87f47862ae92a04f8ad5d764380f0c6653473b8", + "zh:531f8529cbca49f681883e57761a05a8398afaef6d1ab0d205d26bf12f4428e8", + "zh:6aaf5011d83161c86d2bfb80c0923ec934e578288758da2f37acb7aec129004b", + "zh:7430275253d3d3c40aa6179e0ec0d63212874dbbc06c5a51b9d07ec590f9756c", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:be17dc611e95e26cdf6cad79dfccf1064f0e32032a2efeb939a9bbe7fb1cbfe9", + "zh:f0e3b0aa644202e1d79d2000dca91f6019425da71e9800fa23f27e51c034f195", + "zh:f62bae4519e4ead49182ddc8afe8cf61e2a4c3ba3973b0fbba967736a2696aa3", + "zh:fcafa360a5b0b96244f26f4e3a6d642b716a376557142c2442ff2fb12d11da18", + ] +} + +provider "registry.terraform.io/selectel/selectel" { + version = "3.11.0" + constraints = "~> 3.0" + hashes = [ + "h1:DIle6UFsHenPvK2ui3t7XI+z9pJV1wsaeUplQxO9oDA=", + "zh:12944bd7e536366b626a8fe93bcf1b432f189214e0a6816ec9e51669566dda56", + "zh:14c5f2d39af5a04b6dd61006fb562042fe20fa931521d2f9063233587615b0d0", + "zh:262e8d9db0c0c532fe8a4bf3e6ded43ff99a78abe58195404ac110e14ae5527f", + "zh:2c1db1ec5f2b8eec29d45c69287c9ecfa4f26a6657b80c294538e61d04c0a365", + "zh:3a496c84b8d07339ae18b7e0f5ea84a87b21c485c56964e32a54460010b561d6", + "zh:4ef19264837dbc96ae1003c1db98359e66e4c775f0830168e331ac96ddddb1e6", + "zh:7c38566b922d9cf22541f02bb23330c5654bcbc012c8d6e3ee670497c5c324cd", + "zh:894fa4e98b1d8f5bb59f033dfbd32d7e03ca36fc145b101845f45156b2885f23", + "zh:a8208edf2062e9c166e5683a13c99fe1783127ae6ae69a149ea0ba22661461ad", + "zh:aac0b6331871989bc6b232e78d88b4f7e7bb0b1b4e7e8023d07c61a3a928f3cd", + "zh:b98d7686733e2e3c662b2423fa423bb3817097721d36f752e64a206fc0ffb49f", + "zh:e306df4c8e346ad4be61c4252e7f0d48c943fc7f428ffb5c8e86c897c3a2efc0", + "zh:efe0ec82da525811f2730cac6fd96c813a6c3c97905a376403e47c0b30083936", + "zh:f2db561579cb55566e8f8783a4b236c41de1a7c4df4be5617a6dbbdb819d19ce", + ] +} + +provider "registry.terraform.io/terraform-provider-openstack/openstack" { + version = "1.49.0" + constraints = "~> 1.49.0" + hashes = [ + "h1:EJImbAXNraVwSO3lo+EIom8qoMd1cVvMoiZ3B1lL9Jw=", + "zh:18b0a5d528fe3eb30060cf478db5a5efaed9d9837f4afb35ba58f0196ba6a51c", + "zh:3cd7f28730ed216740a7bc62169a0d630f95ecdaee1162952aab67011fcf8831", + "zh:60a827813523fd77e75d0145cd066cb4c2a89453083a5bd9e0712a8423bdc14a", + "zh:70a4e4af076ce946943d36cb81c47569d8b30722f2b89768006565a4f512fdbb", + "zh:83563688ec5a5435649191f3e80fc96b93571b7333eee12e2d448149f21ba7c5", + "zh:9067cba5ef3f89e81f01b7e68989ffce2460c239a697ece2cd08c77c73afaf87", + "zh:92589eec7fd057ad5bb00a5c5968eb93d2a4b07380c5be794410349f0188787f", + "zh:951db60626bedcd4538b88d284f9b70ef41166dfbdc568ae781518e24be0b077", + "zh:9d7340122ae1a7ea5b0e5c469e89ffc43c24f4391fc870b27efe4dba461f8b84", + "zh:9f31056e278e8bd0a4b0fbfe5b02a625ee9d072177c36148cde3295adbd4a9d1", + "zh:bf7a0beb72d9214fe2a61db76401057462f1133a48f8ce0a666756660d27b2b5", + "zh:e44dcadcc0680e7b7af94a8a4dd1e421835497178976604455182dd98d6ffe96", + "zh:f5d03f5ada85d41cb94bd7a2b956ca2eb9d7d6cb6d5382bf78a5e641be3eadb0", + "zh:fa7134711a60f8518b82c0246f5a72efd24d23e074f3aec4eda90c013c0d23b5", + ] +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..35202e2 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,99 @@ +# SSH ключ +resource "openstack_compute_keypair_v2" "militsyn_key" { + name = var.ssh_key_name + region = var.region + public_key = file(pathexpand("~/.ssh/id_rsa.pub")) +} + +# Flavor (конфигурация ВМ): 1 CPU, 2GB RAM, 10GB диск +resource "openstack_compute_flavor_v2" "militsyn_flavor" { + name = "militsyn_flavor" + ram = 2048 + vcpus = 1 + disk = 10 + is_public = false +} + +# Сеть +resource "openstack_networking_network_v2" "militsyn_network" { + name = "militsyn_network" + admin_state_up = true + region = var.region +} + +# Подсеть +resource "openstack_networking_subnet_v2" "militsyn_subnet" { + name = "militsyn_subnet" + network_id = openstack_networking_network_v2.militsyn_network.id + cidr = "10.0.0.0/24" + region = var.region + dns_nameservers = ["8.8.8.8", "8.8.4.4"] +} + +# Плавающий IP для app-сервера +resource "openstack_networking_floatingip_v2" "app_fip" { + pool = "external-network" + region = var.region +} + +# Плавающий IP для db-сервера +resource "openstack_networking_floatingip_v2" "db_fip" { + pool = "external-network" + region = var.region +} + +# ВМ 1 — app-сервер (Flask) +resource "openstack_compute_instance_v2" "app_server" { + name = "militsyn_app" + image_name = var.image_name + flavor_id = openstack_compute_flavor_v2.militsyn_flavor.id + key_pair = openstack_compute_keypair_v2.militsyn_key.name + region = var.region + availability_zone = var.zone + metadata = var.server_preemptible_tag + + network { + uuid = openstack_networking_network_v2.militsyn_network.id + } +} + +# ВМ 2 — db-сервер (MySQL) +resource "openstack_compute_instance_v2" "db_server" { + name = "militsyn_db" + image_name = var.image_name + flavor_id = openstack_compute_flavor_v2.militsyn_flavor.id + key_pair = openstack_compute_keypair_v2.militsyn_key.name + region = var.region + availability_zone = var.zone + metadata = var.server_preemptible_tag + + network { + uuid = openstack_networking_network_v2.militsyn_network.id + } +} + +# Привязка плавающих IP +resource "openstack_compute_floatingip_associate_v2" "app_fip_assoc" { + floating_ip = openstack_networking_floatingip_v2.app_fip.address + instance_id = openstack_compute_instance_v2.app_server.id +} + +resource "openstack_compute_floatingip_associate_v2" "db_fip_assoc" { + floating_ip = openstack_networking_floatingip_v2.db_fip.address + instance_id = openstack_compute_instance_v2.db_server.id +} + +# Генерация inventory.ini для Ansible +resource "local_file" "inventory" { + content = <<-EOT + [app_servers] + vm1 ansible_host=${openstack_networking_floatingip_v2.app_fip.address} ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa + + [db_servers] + vm2 ansible_host=${openstack_networking_floatingip_v2.db_fip.address} ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa + + [all:vars] + ansible_python_interpreter=/usr/bin/python3 + EOT + filename = "${path.module}/../ansible/inventory.ini" +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..3a49736 --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,14 @@ +output "app_server_ip" { + value = openstack_networking_floatingip_v2.app_fip.address +} + +output "db_server_ip" { + value = openstack_networking_floatingip_v2.db_fip.address +} + +output "ssh_commands" { + value = [ + "ssh ubuntu@${openstack_networking_floatingip_v2.app_fip.address}", + "ssh ubuntu@${openstack_networking_floatingip_v2.db_fip.address}" + ] +} diff --git a/terraform/provider.tf b/terraform/provider.tf new file mode 100644 index 0000000..7fe783a --- /dev/null +++ b/terraform/provider.tf @@ -0,0 +1,8 @@ +provider "openstack" { + auth_url = "https://api.selvpc.ru/identity/v3" + domain_name = var.domain_name + tenant_id = var.tenant_id + user_name = var.user_name + password = var.password + region = var.region +} diff --git a/terraform/vars.tf b/terraform/vars.tf new file mode 100644 index 0000000..83eeb38 --- /dev/null +++ b/terraform/vars.tf @@ -0,0 +1,43 @@ +variable "domain_name" { + type = string + sensitive = true +} + +variable "tenant_id" { + type = string + sensitive = true +} + +variable "user_name" { + type = string + sensitive = true +} + +variable "password" { + type = string + sensitive = true +} + +variable "region" { + type = string + default = "ru-7" +} + +variable "zone" { + type = string + default = "ru-7a" +} + +variable "image_name" { + type = string + default = "Ubuntu 22.04 LTS 64-bit" +} + +variable "ssh_key_name" { + type = string + default = "militsyn_key" +} + +variable "server_preemptible_tag" { + default = { "env" = "preemptible" } +} diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 0000000..4145e99 --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 0.14.0" + required_providers { + openstack = { + source = "terraform-provider-openstack/openstack" + version = "~> 1.49.0" + } + selectel = { + source = "selectel/selectel" + version = "~> 3.0" + } + } +}