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

+
+ + +
+ + +
+ + + """, 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 + +![Успешный запуск Ansible](assets/images/01_ansible_success.jpg) + +### 2. Контейнер приложения на Linux A + +Команда: +docker ps + +Ожидаемый контейнер: +todo-app + +![Контейнер Todo App](assets/images/02_app_container.jpg) + +### 3. Контейнер базы данных на Linux B + +Команда: +ssh borodin_2@192.168.11.1 "docker ps" + +Ожидаемый контейнер: +todo-mysql + +![Контейнер MariaDB](assets/images/03_db_container.jpg) + +### 4. Проверка доступа к приложению + +Команда: +curl -I http://127.0.0.1:8080 + +Ожидаемый результат: +HTTP/1.1 200 OK + +![HTTP 200 OK](assets/images/04_http_200.jpg) + +### 5. Проверка в браузере + +Адрес: +http://127.0.0.1:8080 + +В приложении была добавлена тестовая задача. После обновления страницы задача сохранилась, значит приложение успешно работает с базой данных. + +![Todo App](assets/images/05_todo_browser.jpg) + +## Итоговая проверка + +| Проверка | Результат | +|---|---| +| Ansible playbook | Успешно | +| Todo App контейнер | Работает | +| MariaDB контейнер | Работает | +| Подключение к БД | Работает | +| HTTP 8080 | Работает | +| Сохранение задач | Работает | + +## Вывод + +В ходе работы было выполнено автоматизированное развертывание Todo App на двух виртуальных машинах. + +Ansible настроил обе VM, установил Docker и запустил контейнеры через Docker Compose. Приложение на Linux A успешно подключается к базе данных MariaDB на Linux B и сохраняет задачи.