From 5b381d2391e6a865b14827ae6902af515dba6b10 Mon Sep 17 00:00:00 2001 From: Vladyslav Korzun Date: Sun, 13 Apr 2025 21:00:58 +0000 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?sk1=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sk1/Dockerfile | 16 ++++ sk1/README.md | 72 ++++++++++++++++ sk1/app.py | 73 ++++++++++++++++ sk1/docker-compose.yml | 22 +++++ sk1/prepare-app.sh | 188 +++++++++++++++++++++++++++++++++++++++++ sk1/remove-app.sh | 60 +++++++++++++ sk1/requirements.txt | 3 + sk1/start-app.sh | 4 + sk1/stop-app.sh | 4 + 9 files changed, 442 insertions(+) create mode 100644 sk1/Dockerfile create mode 100644 sk1/README.md create mode 100644 sk1/app.py create mode 100644 sk1/docker-compose.yml create mode 100644 sk1/prepare-app.sh create mode 100644 sk1/remove-app.sh create mode 100644 sk1/requirements.txt create mode 100644 sk1/start-app.sh create mode 100644 sk1/stop-app.sh diff --git a/sk1/Dockerfile b/sk1/Dockerfile new file mode 100644 index 0000000..4140311 --- /dev/null +++ b/sk1/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.9-slim + +WORKDIR /app + +# Установка зависимостей +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt gunicorn + +# Копируем все файлы приложения +COPY . . + +# Открываем порт для приложения +EXPOSE 5000 + +# Запуск приложения через Gunicorn +CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:5000", "app:app"] diff --git a/sk1/README.md b/sk1/README.md new file mode 100644 index 0000000..4e6a973 --- /dev/null +++ b/sk1/README.md @@ -0,0 +1,72 @@ +# **Správa Používateľov - Docker Aplikácia** + +## **1. Podmienky na nasadenie a spustenie aplikácie** +Aby ste mohli aplikáciu spustiť, musíte mať nainštalované: +- **Linux** (napr. Ubuntu vo WSL, alebo iná distribúcia) +- **Docker** (https://docs.docker.com/get-docker/) +- **Docker Compose** (https://docs.docker.com/compose/install/) + +## **2. Opis aplikácie** +Aplikácia poskytuje jednoduché webové rozhranie na správu používateľov. Používateľlia sú uložení v databáze PostgreSQL a aplikácia ich dokáže pridávať alebo mazať cez jednoduché API a webové rozhranie. + +## **3. Opis virtuálnych sietí a pomenovaných zväzkov** +- **Virtuálna sieť `app_network`** - Umožňuje komunikáciu medzi kontajnermi (web, db, pgAdmin). +- **Pomenovaný zväzok `pgdata`** - Používa sa na trvalé uloženie databázových údajov PostgreSQL, aby sa nestratili pri reštarte kontajnerov. + +## **4. Opis konfigurácie kontajnerov** +Aplikácia používa **Docker Compose**, kde sú definované tri kontajnery: +- **Web** (Flask aplikácia) + - Počúva na porte **5000** + - Komunikuje s databázou `db` +- **DB** (PostgreSQL) + - Používa volume `pgdata` na trvalé uloženie údajov + - Počúva na porte **5432** +- **pgAdmin** (Grafické rozhranie na správu PostgreSQL) + - Počúva na porte **8080** + +## **5. Zoznam použitých kontajnerov** +- **Web (Flask)** - Používa Python 3.9 a Flask na poskytovanie webovej aplikácie. +- **PostgreSQL** - Používa obraz `postgres:13`, databázu `mydatabase`. +- **pgAdmin** - Používa obraz `dpage/pgadmin4`, port **8080**. + +## **6. Ako pripraviť, spustiť, pozastaviť a vymazať aplikáciu** + +### **Príprava aplikácie:** +```bash +./prepare-app.sh +``` +Týmto príkazom sa vytvorí sieť, volume a pripraví databáza. + +### **Spustenie aplikácie:** +```bash +./start-app.sh +``` +Aplikácia sa spustí a bude dostupná na **http://localhost:5000**. + +### **Pozastavenie aplikácie:** +```bash +./stop-app.sh +``` +Pozastavenie aplikácie neodstráni údaje. + +### **Vymazanie aplikácie:** +```bash +./remove-app.sh +``` +Tento príkaz **vymaže všetky kontajnery, sieť a volume**, čo znamená, že **databáza bude kompletne vymazaná**. + +## **7. Ako si pozrieť aplikáciu v prehliadači** +- **Hlavná aplikácia:** [http://localhost:5000](http://localhost:5000) +- **pgAdmin (správa databázy):** [http://localhost:8080](http://localhost:8080) + +## **8. Príklad práce s aplikáciou** + +### **Pridanie používateľa:** +1. Otvorte aplikáciu na **http://localhost:5000** +2. Zadajte meno do textového poľa. +3. Kliknite na **"Pridať"**. +4. Používateľ sa uloží do databázy a zobrazí na stránke. + +### **Odstránenie používateľa:** +1. Kliknite na tlačidlo **"Odstrániť"** pri konkrétnom používateľovi. +2. Používateľ bude vymazaný z databázy. \ No newline at end of file diff --git a/sk1/app.py b/sk1/app.py new file mode 100644 index 0000000..9e85d5e --- /dev/null +++ b/sk1/app.py @@ -0,0 +1,73 @@ +from flask import Flask, render_template, request, jsonify +import psycopg2 +import os + +app = Flask(__name__, template_folder="templates", static_folder="static") + +# Подключение к БД +def get_db_connection(): + conn = psycopg2.connect( + host=os.getenv("DB_HOST"), + database=os.getenv("DB_NAME"), + user=os.getenv("DB_USER"), + password=os.getenv("DB_PASS"), + sslmode="require" + ) + return conn + + +# Главная страница с HTML +@app.route("/") +def home(): + conn = get_db_connection() + cur = conn.cursor() + cur.execute("SELECT id, name, created_at FROM users;") + users = cur.fetchall() + cur.close() + conn.close() + return render_template("index.html", users=users) + +# API для добавления пользователя +@app.route("/add_user", methods=["POST"]) +def add_user(): + name = request.form.get("name") + if name: + conn = get_db_connection() + cur = conn.cursor() + cur.execute("INSERT INTO users (name) VALUES (%s) RETURNING id;", (name,)) + user_id = cur.fetchone()[0] + conn.commit() + cur.close() + conn.close() + return jsonify({"id": user_id, "name": name}), 201 + return jsonify({"error": "Имя не может быть пустым"}), 400 + +# API для удаления пользователя +@app.route("/delete_user/", methods=["POST"]) +def delete_user(user_id): + conn = get_db_connection() + cur = conn.cursor() + cur.execute("DELETE FROM users WHERE id = %s RETURNING id;", (user_id,)) + deleted = cur.fetchone() + conn.commit() + cur.close() + conn.close() + if deleted: + return jsonify({"message": "Пользователь удален"}) + return jsonify({"error": "Пользователь не найден"}), 404 + +# API для случайного пользователя +@app.route("/random_user") +def random_user(): + conn = get_db_connection() + cur = conn.cursor() + cur.execute("SELECT id, name, created_at FROM users ORDER BY RANDOM() LIMIT 1;") + user = cur.fetchone() + cur.close() + conn.close() + if user: + return jsonify({"id": user[0], "name": user[1], "created_at": user[2].strftime('%Y-%m-%d %H:%M:%S')}) + return jsonify({"error": "Нет пользователей"}), 404 + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=True) diff --git a/sk1/docker-compose.yml b/sk1/docker-compose.yml new file mode 100644 index 0000000..fa915c4 --- /dev/null +++ b/sk1/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3' + +services: + web: + build: . + ports: + - "5000:5000" + depends_on: + - db + restart: always # Добавлено для автоматического рестарта при сбоях + + db: + image: postgres:13 + volumes: + - pgdata:/var/lib/postgresql/data + environment: + - POSTGRES_PASSWORD=mysecretpassword + - POSTGRES_DB=mydatabase + restart: always # Также добавлено здесь + +volumes: + pgdata: diff --git a/sk1/prepare-app.sh b/sk1/prepare-app.sh new file mode 100644 index 0000000..9ef56b3 --- /dev/null +++ b/sk1/prepare-app.sh @@ -0,0 +1,188 @@ +#!/bin/bash +set -e + +# Параметры +APP_NAME="userapp" +RESOURCE_GROUP="userapp-rg" +ENV_NAME="userapp-env" +ACR_NAME="userappvladregistry" +ACR_IMAGE="$ACR_NAME.azurecr.io/z1-web" +POSTGRES_NAME="userapp-pg-vlad4" +POSTGRES_HOST="$POSTGRES_NAME.postgres.database.azure.com" +POSTGRES_DB="flexibleserverdb" +POSTGRES_USER="postgres" +POSTGRES_PASS="MySuperSecretPass123" +LOCATION="northeurope" + +# 🏗️ Создание resource group (если не существует) +echo "📁 Проверка: существует ли Resource Group $RESOURCE_GROUP..." +RG_EXISTS=$(az group exists --name $RESOURCE_GROUP) + +if [[ "$RG_EXISTS" == "false" ]]; then + echo "🌟 Создаю Resource Group $RESOURCE_GROUP..." + az group create --name $RESOURCE_GROUP --location $LOCATION +else + echo "✔️ Resource Group уже существует" +fi + +# 🔧 Проверка и создание ACR +ACR_EXISTS=$(az acr show --name $ACR_NAME --resource-group $RESOURCE_GROUP --query "name" -o tsv 2>/dev/null || echo "") +if [[ -z "$ACR_EXISTS" ]]; then + echo "🌟 Создаю Azure Container Registry..." + az acr create --resource-group $RESOURCE_GROUP --name $ACR_NAME --sku Basic +else + echo "✔️ ACR уже существует" +fi + +# Включаем admin user +az acr update --name $ACR_NAME --admin-enabled true + +# Ждем, пока ACR станет доступным +echo "⏳ Ждем пока ACR станет доступным..." +for i in {1..10}; do + if az acr repository list --name $ACR_NAME &>/dev/null; then + echo "✅ ACR готов" + break + else + echo "⏳ Ожидание... ($i/10)" + sleep 5 + fi + +done + +# Получаем учетные данные +ACR_USERNAME=$(az acr credential show --name $ACR_NAME --query "username" -o tsv) +ACR_PASSWORD=$(az acr credential show --name $ACR_NAME --query "passwords[0].value" -o tsv) + +echo "🧠 Проверка: существует ли PostgreSQL сервер..." +PG_EXISTS=$(az postgres flexible-server show \ + --name $POSTGRES_NAME \ + --resource-group $RESOURCE_GROUP \ + --query "name" -o tsv 2>/dev/null || echo "") + +if [[ -n "$PG_EXISTS" ]]; then + echo "🔐 Пробуем подключиться к PostgreSQL..." + if ! psql "host=$POSTGRES_HOST port=5432 dbname=postgres user=$POSTGRES_USER password=$POSTGRES_PASS sslmode=require" -c '\q' 2>/dev/null; then + echo "❌ Пароль не работает — удаляю и создаю заново..." + az postgres flexible-server delete --name $POSTGRES_NAME --resource-group $RESOURCE_GROUP --yes + PG_EXISTS="" + sleep 5 + else + echo "✅ Успешное подключение" + fi +fi + +if [[ -z "$PG_EXISTS" ]]; then + echo "🌟 Создаю PostgreSQL..." + az postgres flexible-server create \ + --name $POSTGRES_NAME \ + --resource-group $RESOURCE_GROUP \ + --location $LOCATION \ + --admin-user postgres \ + --admin-password $POSTGRES_PASS \ + --sku-name Standard_B1ms \ + --tier Burstable \ + --storage-size 32 \ + --yes + + echo "⏳ Ожидание готовности PostgreSQL..." + for i in {1..10}; do + STATE=$(az postgres flexible-server show --name $POSTGRES_NAME --resource-group $RESOURCE_GROUP --query "state" -o tsv 2>/dev/null || echo "") + echo "🔄 Состояние: $STATE" + if [[ "$STATE" == "Ready" ]]; then + break + fi + sleep 10 + done + + echo "🌐 Разрешаю IP..." + az postgres flexible-server firewall-rule create \ + --name $POSTGRES_NAME \ + --resource-group $RESOURCE_GROUP \ + --rule-name AllowAllIps \ + --start-ip-address 0.0.0.0 \ + --end-ip-address 255.255.255.255 +fi + +echo "🔑 Сбрасываю пароль PostgreSQL..." +az postgres flexible-server update --name $POSTGRES_NAME --resource-group $RESOURCE_GROUP --admin-password $POSTGRES_PASS + +echo "⏳ Ждем 10 сек..." +sleep 10 + +echo "📆 Логин в ACR..." +echo $ACR_PASSWORD | docker login "$ACR_NAME.azurecr.io" -u $ACR_USERNAME --password-stdin + +echo "▶️ Сборка Docker образа..." +docker build -t z1-web . + +echo "📦 Тегирование и пуш..." +docker tag z1-web $ACR_IMAGE +docker push $ACR_IMAGE + +echo "☁️ Проверка Container App Environment..." +ENV_EXISTS=$(az containerapp env show --name $ENV_NAME --resource-group $RESOURCE_GROUP --query "name" -o tsv 2>/dev/null || echo "") + +if [[ -z "$ENV_EXISTS" ]]; then + echo "🌟 Создаю Container Environment..." + az containerapp env create --name $ENV_NAME --resource-group $RESOURCE_GROUP --location $LOCATION +else + echo "✔️ Среда уже есть" +fi + +echo "☁️ Проверка Container App..." +APP_EXISTS=$(az containerapp show --name $APP_NAME --resource-group $RESOURCE_GROUP --query "name" -o tsv 2>/dev/null || echo "") + +if [[ -z "$APP_EXISTS" ]]; then + echo "🌟 Создаю Container App..." + az containerapp create \ + --name $APP_NAME \ + --resource-group $RESOURCE_GROUP \ + --environment $ENV_NAME \ + --image $ACR_IMAGE \ + --target-port 5000 \ + --ingress external \ + --registry-server "$ACR_NAME.azurecr.io" \ + --registry-username $ACR_USERNAME \ + --registry-password $ACR_PASSWORD \ + --cpu 0.5 --memory 1.0Gi \ + --env-vars \ + DB_HOST=$POSTGRES_HOST \ + DB_NAME=$POSTGRES_DB \ + DB_USER=$POSTGRES_USER \ + DB_PASS=$POSTGRES_PASS +else + echo "🔁 Обновляю Container App..." + az containerapp registry set \ + --name $APP_NAME \ + --resource-group $RESOURCE_GROUP \ + --server "$ACR_NAME.azurecr.io" \ + --username $ACR_USERNAME \ + --password $ACR_PASSWORD + + az containerapp update \ + --name $APP_NAME \ + --resource-group $RESOURCE_GROUP \ + --image $ACR_IMAGE \ + --set-env-vars \ + DB_HOST=$POSTGRES_HOST \ + DB_NAME=$POSTGRES_DB \ + DB_USER=$POSTGRES_USER \ + DB_PASS=$POSTGRES_PASS +fi + +echo "🗄️ Создание таблицы users..." +command -v psql >/dev/null 2>&1 || { + echo "❌ Установи psql: sudo apt install postgresql-client" + exit 1 +} + +psql "host=$POSTGRES_HOST port=5432 dbname=$POSTGRES_DB user=$POSTGRES_USER password=$POSTGRES_PASS sslmode=require" </dev/null; then + az containerapp delete --name $APP_NAME --resource-group $RESOURCE_GROUP --yes +else + echo "❌ Container App $APP_NAME уже удален или не существует" +fi + +# 🚮 Container Environment +echo "🚮 Удаляю Container Environment..." +if az containerapp env show --name $ENV_NAME --resource-group $RESOURCE_GROUP &>/dev/null; then + az containerapp env delete --name $ENV_NAME --resource-group $RESOURCE_GROUP --yes +else + echo "❌ Container Env $ENV_NAME уже удален или не существует" +fi + +# 🚮 PostgreSQL +echo "🚮 Удаляю PostgreSQL Server..." +if az postgres flexible-server show --name $POSTGRES_NAME --resource-group $RESOURCE_GROUP &>/dev/null; then + az postgres flexible-server delete --name $POSTGRES_NAME --resource-group $RESOURCE_GROUP --yes +else + echo "❌ PostgreSQL сервер $POSTGRES_NAME уже удален или не существует" +fi + +# 🚮 Docker образ +echo "🚮 Удаляю образ из ACR..." +if az acr repository show --name $ACR_NAME --repository $ACR_IMAGE &>/dev/null; then + az acr repository delete --name $ACR_NAME --repository $ACR_IMAGE --yes +else + echo "❌ Образ $ACR_IMAGE уже удален или не найден" +fi + +# 🚮 Удаление реестра контейнеров (ACR) +echo "🚮 Удаляю ACR $ACR_NAME..." +if az acr show --name $ACR_NAME --resource-group $RESOURCE_GROUP &>/dev/null; then + az acr delete --name $ACR_NAME --resource-group $RESOURCE_GROUP --yes +else + echo "❌ ACR $ACR_NAME уже удален или не существует" +fi + +# 🚮 Удаление группы ресурсов +echo "🚮 Удаляю группу ресурсов $RESOURCE_GROUP..." +if az group show --name $RESOURCE_GROUP &>/dev/null; then + az group delete --name $RESOURCE_GROUP --yes --no-wait +else + echo "❌ Группа ресурсов $RESOURCE_GROUP уже удалена или не существует" +fi + +echo "✅ Все успешно удалено." diff --git a/sk1/requirements.txt b/sk1/requirements.txt new file mode 100644 index 0000000..9c588c8 --- /dev/null +++ b/sk1/requirements.txt @@ -0,0 +1,3 @@ +flask +psycopg2-binary +gunicorn diff --git a/sk1/start-app.sh b/sk1/start-app.sh new file mode 100644 index 0000000..d17d4f9 --- /dev/null +++ b/sk1/start-app.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e +docker-compose up -d +echo "http://localhost:5000 (Flask)" diff --git a/sk1/stop-app.sh b/sk1/stop-app.sh new file mode 100644 index 0000000..f9bf17e --- /dev/null +++ b/sk1/stop-app.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e +echo "Zastavujeme kontajnery..." +docker-compose down