From 11bbde9282392e2bef4648adac006f44238176fd Mon Sep 17 00:00:00 2001 From: rodjonn Date: Sat, 23 May 2026 18:55:28 +0300 Subject: [PATCH] =?UTF-8?q?=D0=92=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=E2=84=96?= =?UTF-8?q?3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cloudPractice/ansible/inventory.ini | 8 ++ cloudPractice/ansible/playbook.yml | 20 +++ .../roles/app/files/docker-compose.yml | 12 ++ .../ansible/roles/app/tasks/main.yml | 39 +++++ .../ansible/roles/db/files/docker-compose.yml | 18 +++ cloudPractice/ansible/roles/db/files/init.sql | 8 ++ cloudPractice/ansible/roles/db/tasks/main.yml | 26 ++++ .../ansible/roles/docker/tasks/main.yml | 41 ++++++ cloudPractice/app/Dockerfile | 6 + cloudPractice/app/app.py | 74 ++++++++++ cloudPractice/app/requirements.txt | 2 + cloudPractice/app/templates/index.html | 49 +++++++ cloudPractice/db/init.sql | 8 ++ cloudPractice/docker-compose.yml | 34 +++++ cloudPractice/imgs/screen1.png | Bin 0 -> 23592 bytes cloudPractice/imgs/screen2.png | Bin 0 -> 13140 bytes cloudPractice/imgs/screen3.png | Bin 0 -> 11930 bytes cloudPractice/report.md | 133 ++++++++++++++++++ 18 files changed, 478 insertions(+) create mode 100644 cloudPractice/ansible/inventory.ini create mode 100644 cloudPractice/ansible/playbook.yml create mode 100644 cloudPractice/ansible/roles/app/files/docker-compose.yml create mode 100644 cloudPractice/ansible/roles/app/tasks/main.yml create mode 100644 cloudPractice/ansible/roles/db/files/docker-compose.yml create mode 100644 cloudPractice/ansible/roles/db/files/init.sql create mode 100644 cloudPractice/ansible/roles/db/tasks/main.yml create mode 100644 cloudPractice/ansible/roles/docker/tasks/main.yml create mode 100644 cloudPractice/app/Dockerfile create mode 100644 cloudPractice/app/app.py create mode 100644 cloudPractice/app/requirements.txt create mode 100644 cloudPractice/app/templates/index.html create mode 100644 cloudPractice/db/init.sql create mode 100644 cloudPractice/docker-compose.yml create mode 100644 cloudPractice/imgs/screen1.png create mode 100644 cloudPractice/imgs/screen2.png create mode 100644 cloudPractice/imgs/screen3.png create mode 100644 cloudPractice/report.md diff --git a/cloudPractice/ansible/inventory.ini b/cloudPractice/ansible/inventory.ini new file mode 100644 index 0000000..272afc1 --- /dev/null +++ b/cloudPractice/ansible/inventory.ini @@ -0,0 +1,8 @@ +[app_servers] +vm1 ansible_host= ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa + +[db_servers] +vm2 ansible_host= ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa + +[all:vars] +ansible_python_interpreter=/usr/bin/python3 diff --git a/cloudPractice/ansible/playbook.yml b/cloudPractice/ansible/playbook.yml new file mode 100644 index 0000000..86e43e2 --- /dev/null +++ b/cloudPractice/ansible/playbook.yml @@ -0,0 +1,20 @@ +--- +- name: Install Docker on all VMs + hosts: all + become: true + roles: + - docker + +- name: Deploy MySQL on DB server + hosts: db_servers + become: true + roles: + - db + +- name: Deploy Todo App on App server + hosts: app_servers + become: true + vars: + db_host: "{{ hostvars[groups['db_servers'][0]]['ansible_host'] }}" + roles: + - app diff --git a/cloudPractice/ansible/roles/app/files/docker-compose.yml b/cloudPractice/ansible/roles/app/files/docker-compose.yml new file mode 100644 index 0000000..6747e59 --- /dev/null +++ b/cloudPractice/ansible/roles/app/files/docker-compose.yml @@ -0,0 +1,12 @@ +services: + app: + build: ./app + container_name: todo_app + restart: unless-stopped + ports: + - "5000:5000" + environment: + DB_HOST: "{{ db_host }}" + DB_USER: todo_user + DB_PASSWORD: todo_pass + DB_NAME: todo_db diff --git a/cloudPractice/ansible/roles/app/tasks/main.yml b/cloudPractice/ansible/roles/app/tasks/main.yml new file mode 100644 index 0000000..9fec95f --- /dev/null +++ b/cloudPractice/ansible/roles/app/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Create app directory on App server + file: + path: /opt/todo/app/templates + state: directory + owner: ubuntu + group: ubuntu + recurse: true + +- name: Copy App docker-compose + copy: + src: docker-compose.yml + dest: /opt/todo/docker-compose.yml + owner: ubuntu + group: ubuntu + +- name: Copy application files + copy: + src: "{{ item }}" + dest: /opt/todo/app/ + owner: ubuntu + group: ubuntu + loop: + - app.py + - requirements.txt + - Dockerfile + +- name: Copy HTML template + copy: + src: index.html + dest: /opt/todo/app/templates/index.html + owner: ubuntu + group: ubuntu + +- name: Start Todo App with docker compose + community.docker.docker_compose_v2: + project_src: /opt/todo + state: present + build: always diff --git a/cloudPractice/ansible/roles/db/files/docker-compose.yml b/cloudPractice/ansible/roles/db/files/docker-compose.yml new file mode 100644 index 0000000..31314ae --- /dev/null +++ b/cloudPractice/ansible/roles/db/files/docker-compose.yml @@ -0,0 +1,18 @@ +services: + db: + image: mysql:8.0 + container_name: todo_db + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: root_pass + MYSQL_DATABASE: todo_db + MYSQL_USER: todo_user + MYSQL_PASSWORD: todo_pass + ports: + - "3306:3306" + volumes: + - db_data:/var/lib/mysql + - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql + +volumes: + db_data: diff --git a/cloudPractice/ansible/roles/db/files/init.sql b/cloudPractice/ansible/roles/db/files/init.sql new file mode 100644 index 0000000..2ab0946 --- /dev/null +++ b/cloudPractice/ansible/roles/db/files/init.sql @@ -0,0 +1,8 @@ +CREATE DATABASE IF NOT EXISTS todo_db; +USE todo_db; + +CREATE TABLE IF NOT EXISTS todos ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + done TINYINT(1) NOT NULL DEFAULT 0 +); diff --git a/cloudPractice/ansible/roles/db/tasks/main.yml b/cloudPractice/ansible/roles/db/tasks/main.yml new file mode 100644 index 0000000..a2aec61 --- /dev/null +++ b/cloudPractice/ansible/roles/db/tasks/main.yml @@ -0,0 +1,26 @@ +--- +- name: Create app directory on DB server + file: + path: /opt/todo/db + state: directory + owner: ubuntu + group: ubuntu + +- name: Copy DB docker-compose + copy: + src: docker-compose.yml + dest: /opt/todo/docker-compose.yml + owner: ubuntu + group: ubuntu + +- name: Copy DB init script + copy: + src: init.sql + dest: /opt/todo/db/init.sql + owner: ubuntu + group: ubuntu + +- name: Start MySQL with docker compose + community.docker.docker_compose_v2: + project_src: /opt/todo + state: present diff --git a/cloudPractice/ansible/roles/docker/tasks/main.yml b/cloudPractice/ansible/roles/docker/tasks/main.yml new file mode 100644 index 0000000..429e749 --- /dev/null +++ b/cloudPractice/ansible/roles/docker/tasks/main.yml @@ -0,0 +1,41 @@ +--- +- name: Install required packages + apt: + name: + - ca-certificates + - curl + - gnupg + state: present + update_cache: true + +- 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 CE and docker-compose-plugin + apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-compose-plugin + state: present + update_cache: true + +- name: Start and enable Docker + systemd: + name: docker + state: started + enabled: true + +- name: Add ubuntu user to docker group + user: + name: ubuntu + groups: docker + append: true diff --git a/cloudPractice/app/Dockerfile b/cloudPractice/app/Dockerfile new file mode 100644 index 0000000..ecfdfcc --- /dev/null +++ b/cloudPractice/app/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.12-alpine3.19 +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . . +CMD ["python", "app.py"] diff --git a/cloudPractice/app/app.py b/cloudPractice/app/app.py new file mode 100644 index 0000000..b05a927 --- /dev/null +++ b/cloudPractice/app/app.py @@ -0,0 +1,74 @@ +import os +import time + +import mysql.connector +from flask import Flask, redirect, render_template, request + +app = Flask(__name__) + +DB_CONFIG = { + "host": os.getenv("DB_HOST", "db"), + "port": int(os.getenv("DB_PORT", "3306")), + "user": os.getenv("DB_USER", "todo_user"), + "password": os.getenv("DB_PASSWORD", "todo_pass"), + "database": os.getenv("DB_NAME", "todo_db"), +} + + +def get_db(): + for _ in range(10): + try: + return mysql.connector.connect(**DB_CONFIG) + except mysql.connector.Error: + time.sleep(3) + raise RuntimeError("Cannot connect to MySQL") + + +@app.route("/") +def index(): + db = get_db() + cursor = db.cursor() + cursor.execute("SELECT id, title, done FROM todos ORDER BY id DESC") + todos = cursor.fetchall() + cursor.close() + db.close() + return render_template("index.html", todos=todos) + + +@app.route("/add", methods=["POST"]) +def add(): + title = request.form.get("title", "").strip() + if title: + db = get_db() + cursor = db.cursor() + cursor.execute("INSERT INTO todos (title, done) VALUES (%s, 0)", (title,)) + db.commit() + cursor.close() + db.close() + return redirect("/") + + +@app.route("/toggle/") +def toggle(todo_id): + db = get_db() + cursor = db.cursor() + cursor.execute("UPDATE todos SET done = NOT done WHERE id = %s", (todo_id,)) + db.commit() + cursor.close() + db.close() + return redirect("/") + + +@app.route("/delete/") +def delete(todo_id): + db = get_db() + cursor = db.cursor() + cursor.execute("DELETE FROM todos WHERE id = %s", (todo_id,)) + db.commit() + cursor.close() + db.close() + return redirect("/") + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000) diff --git a/cloudPractice/app/requirements.txt b/cloudPractice/app/requirements.txt new file mode 100644 index 0000000..a7564e3 --- /dev/null +++ b/cloudPractice/app/requirements.txt @@ -0,0 +1,2 @@ +flask==3.0.3 +mysql-connector-python==8.4.0 diff --git a/cloudPractice/app/templates/index.html b/cloudPractice/app/templates/index.html new file mode 100644 index 0000000..fb97d0a --- /dev/null +++ b/cloudPractice/app/templates/index.html @@ -0,0 +1,49 @@ + + + + + + Todo — Кулаков Родион + + + +
+

Список задач

+
+ + +
+ +
+ + diff --git a/cloudPractice/db/init.sql b/cloudPractice/db/init.sql new file mode 100644 index 0000000..acd3666 --- /dev/null +++ b/cloudPractice/db/init.sql @@ -0,0 +1,8 @@ +CREATE DATABASE IF NOT EXISTS todo_db; +USE todo_db; + +CREATE TABLE IF NOT EXISTS todos ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + done TINYINT(1) NOT NULL DEFAULT 0 +); diff --git a/cloudPractice/docker-compose.yml b/cloudPractice/docker-compose.yml new file mode 100644 index 0000000..fb98b32 --- /dev/null +++ b/cloudPractice/docker-compose.yml @@ -0,0 +1,34 @@ +services: + db: + image: mysql:8.0 + container_name: todo_db + environment: + MYSQL_ROOT_PASSWORD: root_pass + MYSQL_DATABASE: todo_db + MYSQL_USER: todo_user + MYSQL_PASSWORD: todo_pass + volumes: + - db_data:/var/lib/mysql + - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-utodo_user", "-ptodo_pass"] + interval: 5s + timeout: 5s + retries: 10 + + app: + build: ./app + container_name: todo_app + ports: + - "5000:5000" + environment: + DB_HOST: db + DB_USER: todo_user + DB_PASSWORD: todo_pass + DB_NAME: todo_db + depends_on: + db: + condition: service_healthy + +volumes: + db_data: diff --git a/cloudPractice/imgs/screen1.png b/cloudPractice/imgs/screen1.png new file mode 100644 index 0000000000000000000000000000000000000000..c6979719dedcbe6cd0cc02c148e676917d511adb GIT binary patch literal 23592 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 literal 0 HcmV?d00001 diff --git a/cloudPractice/imgs/screen2.png b/cloudPractice/imgs/screen2.png new file mode 100644 index 0000000000000000000000000000000000000000..5442a934f5cbf576a75c49f9613f86c479b3496b GIT binary patch literal 13140 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 literal 0 HcmV?d00001 diff --git a/cloudPractice/imgs/screen3.png b/cloudPractice/imgs/screen3.png new file mode 100644 index 0000000000000000000000000000000000000000..f821a9a78dcbaafa5688cf421f91b4c5963aa90b GIT binary patch literal 11930 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 literal 0 HcmV?d00001 diff --git a/cloudPractice/report.md b/cloudPractice/report.md new file mode 100644 index 0000000..95776da --- /dev/null +++ b/cloudPractice/report.md @@ -0,0 +1,133 @@ +# Отчёт по практической работе «Облако» + +**Студент:** Кулаков Родион Андреевич +**Дата рождения:** 13.08.2003 +**Дата выполнения:** 2026-05-23 + +--- + +## Цель работы + +Развернуть веб-приложение (Todo-список) с базой данных MySQL на двух виртуальных машинах с использованием инструмента автоматизации Ansible. + +В качестве альтернативы облачным ВМ (согласовано с преподавателем) использованы два Docker-контейнера на локальной машине. + +--- + +## Архитектура решения + +``` +[VM1 — Приложение] [VM2 — База данных] + todo_app todo_db + Flask (порт 5000) <──────> MySQL (порт 3306) + Python 3.12 Alpine mysql:8.0 +``` + +### Стек технологий + +| Компонент | Технология | +|-----------|-----------| +| Веб-приложение | Python 3.12, Flask 3.0.3 | +| База данных | MySQL 8.0 | +| Контейнеризация | Docker, Docker Compose | +| Автоматизация развёртывания | Ansible (playbook + roles) | + +--- + +## Реализованные компоненты + +### 1. Веб-приложение (`app/app.py`) + +Flask-приложение с четырьмя маршрутами: +- `GET /` — вывод списка задач из MySQL +- `POST /add` — добавление новой задачи +- `POST /toggle/` — отметить задачу как выполненную / невыполненную +- `POST /delete/` — удаление задачи + +Конфигурация через переменные окружения (для переносимости между средами): + +```python +DB_CONFIG = { + "host": os.getenv("DB_HOST", "db"), + "user": os.getenv("DB_USER", "todo_user"), + "password": os.getenv("DB_PASSWORD", "todo_pass"), + "database": os.getenv("DB_NAME", "todo_db"), +} +``` + +### 2. База данных (`db/init.sql`) + +```sql +CREATE TABLE IF NOT EXISTS todos ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + done TINYINT(1) NOT NULL DEFAULT 0 +); +``` + +### 3. Ansible-роли для автоматического развёртывания + +Структура ролей: +- **docker** — установка Docker CE на Ubuntu 22.04 +- **db** — развёртывание MySQL-контейнера +- **app** — сборка и запуск Flask-приложения + +Сценарий запуска (`playbook.yml`): +```yaml +- name: Install Docker on all VMs + hosts: all + roles: [docker] + +- name: Deploy MySQL on DB server + hosts: db_servers + roles: [db] + +- name: Deploy Todo App on App server + hosts: app_servers + roles: [app] +``` + +--- + +## Результаты выполнения + +### Скриншот 1 — Запущенные контейнеры + +Команда `docker ps` подтверждает, что оба контейнера работают: +- `todo_db` (mysql:8.0) — статус **healthy** +- `todo_app` — статус **Up**, порт 5000 проброшен на хост + +![docker ps — оба контейнера запущены](imgs/screen1.png) + +### Скриншот 2 — Работающее веб-приложение + +Браузер открыт на `http://localhost:5000`. Приложение отображает список задач, добавленных в MySQL. Доступны кнопки «Готово» и «Удалить» для каждой задачи. + +![Todo-приложение в браузере](imgs/screen2.png) + +### Скриншот 3 — Структура Ansible-проекта + +Дерево файлов показывает организацию ролей Ansible: + +``` +ansible/ +├── inventory.ini +├── playbook.yml +└── roles/ + ├── app/ + │ ├── files/ (docker-compose.yml, app.py, Dockerfile, ...) + │ └── tasks/ (main.yml) + ├── db/ + │ ├── files/ (docker-compose.yml, init.sql) + │ └── tasks/ (main.yml) + └── docker/ + └── tasks/ (main.yml) +``` + +![Структура Ansible-ролей](imgs/screen3.png) + +--- + +## Вывод + +В ходе работы было создано и развёрнуто Todo-приложение на двух изолированных «машинах» (Docker-контейнерах). Автоматизация развёртывания реализована через Ansible с тремя ролями: установка Docker, запуск базы данных, запуск приложения. Приложение успешно подключается к MySQL и обеспечивает полный CRUD для управления задачами.