diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg
index 841613d..d8bfc4f 100644
--- a/ansible/ansible.cfg
+++ b/ansible/ansible.cfg
@@ -1,5 +1,4 @@
[defaults]
-roles_path = ../ansible
-connect_timeout = 30
+inventory = inventory.ini
host_key_checking = False
-inventory = ./inventory.ini
\ No newline at end of file
+retry_files_enabled = False
diff --git a/ansible/inventory.ini b/ansible/inventory.ini
new file mode 100644
index 0000000..ed376ce
--- /dev/null
+++ b/ansible/inventory.ini
@@ -0,0 +1,8 @@
+[todo_app]
+app ansible_connection=local db_host=192.168.11.1
+
+[todo_db]
+db ansible_host=192.168.11.1 ansible_user=borodin_2 ansible_ssh_common_args='-o PubkeyAuthentication=no -o PreferredAuthentications=password'
+
+[all:vars]
+ansible_python_interpreter=/usr/bin/python3
diff --git a/ansible/playbook.yml b/ansible/playbook.yml
index f9d8259..7a0a002 100644
--- a/ansible/playbook.yml
+++ b/ansible/playbook.yml
@@ -1,23 +1,84 @@
-###############################
-# WEB TO DO APPLICATION #
-###############################
-
---
-- name: "Installing Docker and docker compose"
+- name: Install Docker on all hosts
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
+ become: yes
+ tasks:
+ - name: Update apt cache
+ apt:
+ update_cache: yes
+
+ - name: Install docker and docker-compose
+ apt:
+ name:
+ - docker.io
+ - docker-compose
+ state: present
+
+ - name: Enable docker service
+ service:
+ name: docker
+ state: started
+ enabled: yes
+
+
+- name: Deploy MySQL database
+ hosts: todo_db
+ become: yes
+ tasks:
+ - name: Create database directory
+ file:
+ path: /opt/todo-db
+ state: directory
+ mode: '0755'
+
+ - name: Copy database compose file
+ template:
+ src: templates/db-compose.yml.j2
+ dest: /opt/todo-db/docker-compose.yml
+
+ - name: Start MySQL container
+ shell: docker-compose up -d
+ args:
+ chdir: /opt/todo-db
+
+ - name: Wait for database port
+ wait_for:
+ host: 127.0.0.1
+ port: 3306
+ timeout: 120
+
+ - name: Wait extra time for MariaDB initialization
+ pause:
+ seconds: 20
+
+
+- name: Deploy Todo application
+ hosts: todo_app
+ become: yes
+ tasks:
+ - name: Create app directory
+ file:
+ path: /opt/todo-app
+ state: directory
+ mode: '0755'
+
+ - name: Copy application files
+ copy:
+ src: ../app/
+ dest: /opt/todo-app/
+
+ - name: Copy app compose file
+ template:
+ src: templates/app-compose.yml.j2
+ dest: /opt/todo-app/docker-compose.yml
+
+ - name: Start Todo application
+ shell: docker-compose up -d --build --force-recreate
+ args:
+ chdir: /opt/todo-app
+
+ - name: Wait for Todo application port
+ wait_for:
+ host: 127.0.0.1
+ port: 8080
+ timeout: 120
diff --git a/ansible/templates/app-compose.yml.j2 b/ansible/templates/app-compose.yml.j2
new file mode 100644
index 0000000..db8ca22
--- /dev/null
+++ b/ansible/templates/app-compose.yml.j2
@@ -0,0 +1,15 @@
+version: '3'
+
+services:
+ todo-app:
+ build: .
+ container_name: todo-app
+ restart: always
+ ports:
+ - "8080:5000"
+ environment:
+ DB_HOST: "{{ db_host }}"
+ DB_NAME: todo
+ DB_USER: todo
+ DB_PASSWORD: todo_pass
+
diff --git a/ansible/templates/db-compose.yml.j2 b/ansible/templates/db-compose.yml.j2
new file mode 100644
index 0000000..42cd5e4
--- /dev/null
+++ b/ansible/templates/db-compose.yml.j2
@@ -0,0 +1,19 @@
+version: '3'
+
+services:
+ mysql:
+ image: mariadb:10.6
+ container_name: todo-mysql
+ restart: always
+ environment:
+ MARIADB_ROOT_PASSWORD: root_pass
+ MARIADB_DATABASE: todo
+ MARIADB_USER: todo
+ MARIADB_PASSWORD: todo_pass
+ ports:
+ - "3306:3306"
+ volumes:
+ - mysql_data:/var/lib/mysql
+
+volumes:
+ mysql_data:
diff --git a/app/Dockerfile b/app/Dockerfile
new file mode 100644
index 0000000..1fe6002
--- /dev/null
+++ b/app/Dockerfile
@@ -0,0 +1,11 @@
+FROM python:3.11-slim
+
+WORKDIR /app
+
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY app.py .
+
+CMD ["python", "app.py"]
+
diff --git a/app/app.py b/app/app.py
new file mode 100644
index 0000000..38c5c2f
--- /dev/null
+++ b/app/app.py
@@ -0,0 +1,118 @@
+import os
+import time
+import mysql.connector
+from flask import Flask, request, redirect, render_template_string
+
+app = Flask(__name__)
+
+DB_HOST = os.getenv("DB_HOST", "127.0.0.1")
+DB_NAME = os.getenv("DB_NAME", "todo")
+DB_USER = os.getenv("DB_USER", "todo")
+DB_PASSWORD = os.getenv("DB_PASSWORD", "todo_pass")
+
+
+def get_connection():
+ return mysql.connector.connect(
+ host=DB_HOST,
+ database=DB_NAME,
+ user=DB_USER,
+ password=DB_PASSWORD
+ )
+
+
+def init_db():
+ for _ in range(30):
+ try:
+ conn = get_connection()
+ cursor = conn.cursor()
+ cursor.execute("""
+ CREATE TABLE IF NOT EXISTS todos (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(255) NOT NULL
+ )
+ """)
+ conn.commit()
+ cursor.close()
+ conn.close()
+ return
+ except Exception as error:
+ print(f"Waiting for database: {error}", flush=True)
+ time.sleep(5)
+
+
+@app.route("/", methods=["GET"])
+def index():
+ conn = get_connection()
+ cursor = conn.cursor(dictionary=True)
+ cursor.execute("SELECT id, title FROM todos ORDER BY id DESC")
+ todos = cursor.fetchall()
+ cursor.close()
+ conn.close()
+
+ return render_template_string("""
+
+
+
+ Todo App
+
+
+
+
+
Todo App
+
+
+
+ {% for todo in todos %}
+ -
+ {{ todo.title }}
+
+
+ {% endfor %}
+
+
+
+
+ """, todos=todos)
+
+
+@app.route("/add", methods=["POST"])
+def add():
+ title = request.form.get("title")
+ if title:
+ conn = get_connection()
+ cursor = conn.cursor()
+ cursor.execute("INSERT INTO todos (title) VALUES (%s)", (title,))
+ conn.commit()
+ cursor.close()
+ conn.close()
+ return redirect("/")
+
+
+@app.route("/delete/", methods=["POST"])
+def delete(todo_id):
+ conn = get_connection()
+ cursor = conn.cursor()
+ cursor.execute("DELETE FROM todos WHERE id = %s", (todo_id,))
+ conn.commit()
+ cursor.close()
+ conn.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..12544ab
--- /dev/null
+++ b/app/requirements.txt
@@ -0,0 +1,3 @@
+flask
+mysql-connector-python
+
diff --git a/assets/images/01_ansible_success.jpg b/assets/images/01_ansible_success.jpg
new file mode 100644
index 0000000..00fc0d2
Binary files /dev/null and b/assets/images/01_ansible_success.jpg differ
diff --git a/assets/images/02_app_container.jpg b/assets/images/02_app_container.jpg
new file mode 100644
index 0000000..4796eaf
Binary files /dev/null and b/assets/images/02_app_container.jpg differ
diff --git a/assets/images/03_db_container.jpg b/assets/images/03_db_container.jpg
new file mode 100644
index 0000000..aa813a2
Binary files /dev/null and b/assets/images/03_db_container.jpg differ
diff --git a/assets/images/04_http_200.jpg b/assets/images/04_http_200.jpg
new file mode 100644
index 0000000..5c2037c
Binary files /dev/null and b/assets/images/04_http_200.jpg differ
diff --git a/assets/images/05_todo_browser.jpg b/assets/images/05_todo_browser.jpg
new file mode 100644
index 0000000..27e1d59
Binary files /dev/null and b/assets/images/05_todo_browser.jpg differ
diff --git a/report.md b/report.md
new file mode 100644
index 0000000..b2b438f
--- /dev/null
+++ b/report.md
@@ -0,0 +1,207 @@
+# Todo App через Ansible и Docker Compose
+
+## Цель работы
+
+Развернуть приложение на двух виртуальных машинах без Terraform, используя Ansible для автоматизации и Docker Compose для запуска сервисов.
+
+По согласованию с преподавателем работа выполнена на VM без облачного провайдера и без Terraform.
+
+## Архитектура
+
+Используются две виртуальные машины:
+
+| VM | Назначение | Компонент |
+|---|---|---|
+| Linux A | Веб-приложение + Ansible control node | Todo App |
+| Linux B | База данных | MariaDB |
+
+Схема:
+Browser -> Linux A:8080 -> Todo App -> Linux B:3306 -> MariaDB
+
+## Используемые технологии
+
+- Ubuntu Server 20.04
+- Ansible
+- Docker
+- Docker Compose
+- Flask
+- MariaDB
+- VirtualBox
+
+## Структура проекта
+
+CloudPractice/
+├── ansible/
+│ ├── ansible.cfg
+│ ├── inventory.ini
+│ ├── playbook.yml
+│ └── templates/
+│ ├── app-compose.yml.j2
+│ └── db-compose.yml.j2
+├── app/
+│ ├── app.py
+│ ├── Dockerfile
+│ └── requirements.txt
+├── assets/
+│ └── images/
+├── README.md
+└── report.md
+
+## Виртуальные машины
+
+### Linux A
+
+Пользователь:
+borodin_1
+
+Назначение:
+Todo App + запуск Ansible
+
+Проброс портов:
+2222 -> 22
+8080 -> 8080
+
+### Linux B
+
+Пользователь:
+borodin_2
+
+Назначение:
+MariaDB
+
+IP-адрес во внутренней сети:
+192.168.11.1
+
+
+Проброс портов:
+
+2223 -> 22
+
+## Ansible inventory
+
+Файл:
+ansible/inventory.ini
+
+```ini
+[todo_app]
+app ansible_connection=local db_host=192.168.11.1
+
+[todo_db]
+db ansible_host=192.168.11.1 ansible_user=borodin_2 ansible_ssh_common_args='-o PubkeyAuthentication=no -o PreferredAuthentications=password'
+
+[all:vars]
+ansible_python_interpreter=/usr/bin/python3
+```
+
+## Запуск развертывания
+
+Ansible запускается с Linux A:
+
+cd ~/CloudPractice/ansible
+ansible-playbook -i inventory.ini playbook.yml --ask-pass --ask-become-pass
+
+
+Playbook выполняет:
+
+1. Установку Docker и Docker Compose на обе VM.
+2. Запуск MariaDB на Linux B.
+3. Запуск Todo App на Linux A.
+4. Проверку доступности портов.
+
+## Контейнер базы данных
+
+На Linux B запускается контейнер:
+todo-mysql
+
+
+Образ:
+mariadb:10.6
+
+Порт:
+3306:3306
+
+MariaDB используется вместо MySQL 8.0, так как MySQL оказался слишком тяжелым для VM и вызывал зависание системы.
+
+## Контейнер приложения
+
+На Linux A запускается контейнер:
+todo-app
+
+Приложение написано на Flask.
+
+Порт:
+8080:5000
+
+Переменные окружения:
+
+DB_HOST=192.168.11.1
+DB_NAME=todo
+DB_USER=todo
+DB_PASSWORD=todo_pass
+
+## Проверка работы
+
+### 1. Успешный запуск Ansible
+
+После выполнения playbook:
+
+failed=0
+unreachable=0
+
+
+
+### 2. Контейнер приложения на Linux A
+
+Команда:
+docker ps
+
+Ожидаемый контейнер:
+todo-app
+
+
+
+### 3. Контейнер базы данных на Linux B
+
+Команда:
+ssh borodin_2@192.168.11.1 "docker ps"
+
+Ожидаемый контейнер:
+todo-mysql
+
+
+
+### 4. Проверка доступа к приложению
+
+Команда:
+curl -I http://127.0.0.1:8080
+
+Ожидаемый результат:
+HTTP/1.1 200 OK
+
+
+
+### 5. Проверка в браузере
+
+Адрес:
+http://127.0.0.1:8080
+
+В приложении была добавлена тестовая задача. После обновления страницы задача сохранилась, значит приложение успешно работает с базой данных.
+
+
+
+## Итоговая проверка
+
+| Проверка | Результат |
+|---|---|
+| Ansible playbook | Успешно |
+| Todo App контейнер | Работает |
+| MariaDB контейнер | Работает |
+| Подключение к БД | Работает |
+| HTTP 8080 | Работает |
+| Сохранение задач | Работает |
+
+## Вывод
+
+В ходе работы было выполнено автоматизированное развертывание Todo App на двух виртуальных машинах.
+
+Ansible настроил обе VM, установил Docker и запустил контейнеры через Docker Compose. Приложение на Linux A успешно подключается к базе данных MariaDB на Linux B и сохраняет задачи.