Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cloudPractice/ansible/inventory.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[app_servers]
vm1 ansible_host=<IP_VM1> ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa

[db_servers]
vm2 ansible_host=<IP_VM2> ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa

[all:vars]
ansible_python_interpreter=/usr/bin/python3
20 changes: 20 additions & 0 deletions cloudPractice/ansible/playbook.yml
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions cloudPractice/ansible/roles/app/files/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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
39 changes: 39 additions & 0 deletions cloudPractice/ansible/roles/app/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions cloudPractice/ansible/roles/db/files/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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:
8 changes: 8 additions & 0 deletions cloudPractice/ansible/roles/db/files/init.sql
Original file line number Diff line number Diff line change
@@ -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
);
26 changes: 26 additions & 0 deletions cloudPractice/ansible/roles/db/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions cloudPractice/ansible/roles/docker/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions cloudPractice/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
74 changes: 74 additions & 0 deletions cloudPractice/app/app.py
Original file line number Diff line number Diff line change
@@ -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/<int:todo_id>")
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/<int:todo_id>")
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)
2 changes: 2 additions & 0 deletions cloudPractice/app/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
flask==3.0.3
mysql-connector-python==8.4.0
49 changes: 49 additions & 0 deletions cloudPractice/app/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo — Кулаков Родион</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: Arial, sans-serif; background: #f5f5f5; display: flex; justify-content: center; padding: 40px 16px; }
.card { background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,.1); width: 100%; max-width: 520px; padding: 32px; }
h1 { font-size: 1.6rem; margin-bottom: 24px; color: #222; }
form.add-form { display: flex; gap: 8px; margin-bottom: 24px; }
form.add-form input { flex: 1; padding: 10px 14px; border: 1px solid #ddd; border-radius: 8px; font-size: 1rem; }
form.add-form button { padding: 10px 20px; background: #4f46e5; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 1rem; }
form.add-form button:hover { background: #4338ca; }
ul { list-style: none; }
li { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid #f0f0f0; }
li:last-child { border-bottom: none; }
.title { flex: 1; font-size: 1rem; color: #333; }
.title.done { text-decoration: line-through; color: #aaa; }
a.btn { padding: 5px 12px; border-radius: 6px; font-size: 0.85rem; text-decoration: none; }
a.toggle { background: #e0e7ff; color: #4f46e5; }
a.toggle:hover { background: #c7d2fe; }
a.delete { background: #fee2e2; color: #dc2626; }
a.delete:hover { background: #fecaca; }
.empty { color: #aaa; text-align: center; padding: 20px 0; }
</style>
</head>
<body>
<div class="card">
<h1>Список задач</h1>
<form class="add-form" method="POST" action="/add">
<input type="text" name="title" placeholder="Новая задача..." required>
<button type="submit">Добавить</button>
</form>
<ul>
{% for id, title, done in todos %}
<li>
<span class="title {% if done %}done{% endif %}">{{ title }}</span>
<a class="btn toggle" href="/toggle/{{ id }}">{% if done %}Вернуть{% else %}Готово{% endif %}</a>
<a class="btn delete" href="/delete/{{ id }}">Удалить</a>
</li>
{% else %}
<li><span class="empty">Задач пока нет. Добавьте первую!</span></li>
{% endfor %}
</ul>
</div>
</body>
</html>
8 changes: 8 additions & 0 deletions cloudPractice/db/init.sql
Original file line number Diff line number Diff line change
@@ -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
);
34 changes: 34 additions & 0 deletions cloudPractice/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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:
Binary file added cloudPractice/imgs/screen1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added cloudPractice/imgs/screen2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added cloudPractice/imgs/screen3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading