Загрузить файлы в «z2»

This commit is contained in:
Vladyslav Korzun 2025-04-09 23:10:47 +00:00
parent 17fe85d77c
commit ba9904a5e3
13 changed files with 430 additions and 0 deletions

7
z2/Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]

89
z2/README.md Normal file
View File

@ -0,0 +1,89 @@
# **Správa Používateľov - Kubernetes Aplikácia**
## **1. Podmienky na nasadenie a spustenie aplikácie**
Aby ste mohli aplikáciu spustiť, musíte mať nainštalované a nakonfigurované:
- **Linux (napr. WSL)** alebo iný terminálový prístup k súborom
- **Docker Desktop s povoleným Kubernetes** ([návod](https://docs.docker.com/desktop/kubernetes/))
- **kubectl** (súčasť Docker Desktop)
- **Docker CLI** na build obrazu pre Flask aplikáciu
## **2. Opis aplikácie**
Aplikácia poskytuje jednoduché webové rozhranie na správu používateľov. Používatelia sú uložení v databáze PostgreSQL a aplikácia ich dokáže pridávať alebo mazať cez jednoduché API a frontend napísaný vo Flasku.
## **3. Opis Kubernetes infraštruktúry**
- Aplikácia beží v mennom priestore `userapp`
- Flask aplikácia beží ako `Deployment`
- PostgreSQL beží ako `StatefulSet` so zachovaním dát pomocou `PersistentVolume`
- Komunikácia prebieha cez služby (`Service`)
- Aplikácia je dostupná cez prehliadač na porte **30001**
## **4. Zoznam použitých kontajnerov**
- **Flask (Python 3.9)** webová aplikácia s REST API a HTML šablónou
- **PostgreSQL (13)** relačná databáza na ukladanie používateľov
## **5. Zoznam Kubernetes objektov**
| Objekt | Typ | Popis |
|---------------------|--------------------------|--------|
| `userapp` | Namespace | Logický priestor pre všetky objekty |
| `flask-app` | Deployment | Webová aplikácia |
| `postgres` | StatefulSet | Databáza PostgreSQL |
| `postgres-pv` | PersistentVolume | Trvalý disk na hoste (hostPath) |
| `postgres-pvc` | PersistentVolumeClaim | Prepojenie PV s databázou |
| `flask-service` | Service (NodePort) | Prístup na aplikáciu z prehliadača |
| `postgres-service` | Service (ClusterIP) | Interné spojenie medzi Flask a DB |
## **6. Priebeh nasadenia aplikácie**
### **Príprava aplikácie (Namespace + databáza + tabuľka):**
```bash
./prepare-app.sh
```
### **Spustenie webovej aplikácie:**
```bash
./start-app.sh
```
### **Zastavenie aplikácie:**
```bash
./stop-app.sh
```
## **7. Ako si pozrieť aplikáciu v prehliadači**
Po spustení aplikácie otvorte v prehliadači:
```arduino
http://localhost:30001
```
## **8. Príklad práce s aplikáciou**
### **Pridanie používateľa:**
1. Otvorte aplikáciu v prehliadači
2. Zadajte meno do textového poľa
3. Kliknite na tlačidlo **"Pridať"**
4. Používateľ sa uloží do databázy a zobrazí v zozname
### **Odstránenie používateľa:**
1. Kliknite na tlačidlo **"Odstrániť"** pri konkrétnom používateľovi
2. Používateľ sa odstráni z databázy
## **9. Dôležité poznámky**
- PostgreSQL uchováva dáta v `hostPath` priečinku `/data/postgres`
- Flask obraz je buildovaný **lokálne**, preto v `deployment.yaml` je:
```yaml
imagePullPolicy: Never
```
- Aplikácia **neobsahuje pgAdmin**, ale všetko funguje bez neho
## **10. Overenie funkčnosti**
Over stav podov:
```bash
kubectl get pods -n userapp
```
Očakávaný výstup:
```
postgres-0 Running
flask-app-xxx Running
```
Po úspešnom spustení je aplikácia pripravená na používanie a odovzdanie.

58
z2/app.py Normal file
View File

@ -0,0 +1,58 @@
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="postgres-service",
database="mydatabase",
user="postgres",
password=os.getenv("POSTGRES_PASSWORD", "mysecretpassword")
)
return conn
# Главная страница с HTML
@app.route("/")
def home():
conn = get_db_connection()
cur = conn.cursor()
cur.execute("SELECT id, name 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/<int:user_id>", 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
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)

24
z2/deployment.yaml Normal file
View File

@ -0,0 +1,24 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-app
namespace: userapp
spec:
replicas: 1
selector:
matchLabels:
app: flask-app
template:
metadata:
labels:
app: flask-app
spec:
containers:
- name: flask-container
image: flask-user-app:latest
imagePullPolicy: Never
ports:
- containerPort: 5000
env:
- name: POSTGRES_PASSWORD
value: "mysecretpassword"

52
z2/index.html Normal file
View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="sk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Správa používateľov</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<h1>Zoznam používateľov</h1>
<ul id="user-list">
{% for user in users %}
<li>
{{ user[1] }}
<button class="delete-btn" onclick="deleteUser('{{ user[0] }}')">Odstrániť</button>
</li>
{% endfor %}
</ul>
<h2>Pridať používateľa</h2>
<input type="text" id="username" placeholder="Zadajte meno">
<button onclick="addUser()">Pridať</button>
</div>
<script>
function addUser() {
const name = document.getElementById("username").value;
if (!name) {
alert("Zadajte meno!");
return;
}
fetch("/add_user", {
method: "POST",
body: new URLSearchParams({ name }),
headers: { "Content-Type": "application/x-www-form-urlencoded" }
})
.then(response => response.json())
.then(() => location.reload());
}
function deleteUser(id) {
fetch(`/delete_user/${id}`, { method: "POST" })
.then(response => response.json())
.then(() => location.reload());
}
</script>
</body>
</html>

4
z2/namespace.yaml Normal file
View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: userapp

21
z2/prepare-app.sh Normal file
View File

@ -0,0 +1,21 @@
#!/bin/bash
set -e
echo "📦 Vytvárame namespace a databázu..."
kubectl apply -f namespace.yaml
kubectl apply -f statefulset.yaml
echo "⏳ Čakáme na spustenie PostgreSQL..."
sleep 5
POSTGRES_POD=postgres-0
kubectl exec -n userapp $POSTGRES_POD -- \
psql -U postgres -d mydatabase -c "
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);"
echo "✅ Databázová tabuľka 'users' bola vytvorená."

3
z2/requirements.txt Normal file
View File

@ -0,0 +1,3 @@
flask
psycopg2-binary

42
z2/service.yaml Normal file
View File

@ -0,0 +1,42 @@
apiVersion: v1
kind: Service
metadata:
name: flask-service
namespace: userapp
spec:
selector:
app: flask-app
type: NodePort
ports:
- protocol: TCP
port: 5000
targetPort: 5000
nodePort: 30001
---
apiVersion: v1
kind: Service
metadata:
name: postgres-service
namespace: userapp
spec:
selector:
app: postgres
ports:
- protocol: TCP
port: 5432
targetPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: pgadmin-service
namespace: userapp
spec:
selector:
app: pgadmin
type: NodePort
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 30002

13
z2/start-app.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/bash
set -e
echo "🚀 Nasadzujeme webovú aplikáciu..."
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
echo "⏳ Čakám 10 sekúnd na spustenie webovej aplikácie..."
sleep 10
echo "🌐 Aplikácia beží na:"
echo "http://localhost:30001"

62
z2/statefulset.yaml Normal file
View File

@ -0,0 +1,62 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgres-pv
namespace: userapp
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 1Gi
hostPath:
path: /data/postgres
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: userapp
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: userapp
spec:
serviceName: "postgres-service"
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:13
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
value: "mysecretpassword"
- name: POSTGRES_DB
value: "mydatabase"
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi

9
z2/stop-app.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/bash
set -e
kubectl delete -f service.yaml || true
kubectl delete -f deployment.yaml || true
kubectl delete -f statefulset.yaml || true
kubectl delete -f namespace.yaml || true
echo "✅ Aplikácia bola zastavená a vymazaná."

46
z2/styles.css Normal file
View File

@ -0,0 +1,46 @@
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
text-align: center;
}
.container {
width: 50%;
margin: 50px auto;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 24px;
}
ul {
list-style: none;
padding: 0;
}
li {
display: flex;
justify-content: space-between;
padding: 10px;
background: #fff;
margin: 5px 0;
border-radius: 5px;
border: 1px solid #ddd;
}
button {
background-color: #28a745;
color: white;
border: none;
padding: 5px 10px;
cursor: pointer;
border-radius: 5px;
}
button.delete-btn {
background-color: #dc3545;
}