z2 - Kubernetes

This commit is contained in:
Ferko 2026-04-20 11:55:43 +02:00
parent 12643a8a97
commit 56e08dbe7b
22 changed files with 400 additions and 0 deletions

45
z2/README.md Normal file
View File

@ -0,0 +1,45 @@
# ZKT Zadanie 2 - Kubernetes
## Opis aplikacie
Aplikacia predstavuje TODO list, kde pouzivatel moze pridavat, upravovat a mazat ulohy. Data su ukladane do databazy
MongoDB.
Aplikacia pozostava z backendu (Node.js) a databazy (MongoDB), ktore su nasadene v Kubernetes klastri.
## Pouzite kontajnery
- Node.js (backend + frontend)
- MongoDB (databaza)
## Kubernetes objekty
- Namespace izolacia aplikacie
- Deployment backend aplikacia
- StatefulSet MongoDB databaza
- Service komunikacia medzi komponentami
- PersistentVolume trvale ulozisko
- PersistentVolumeClaim poziadavka na ulozisko
## Virtualne siete a zvazky
- Service "web-service" spristupnuje aplikaciu
- Service "mongo" umoznuje komunikaciu s databazou
- MongoDB pouziva volume /data/db
## Konfiguracia kontajnerov
- backend bezi na porte 3000
- MongoDB bezi na porte 27017
- komunikacia prebieha cez nazvy sluzieb (mongo)
## Spustenie
./prepare-app.sh
./start-app.sh
## Zastavenie
./stop-app.sh
## Pristup k aplikacii
http://localhost:30007
## Poznamka
Aplikacia bola upravena zo zadania c.1 pre nasadenie do Kubernetes.

11
z2/app/Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM node:18
WORKDIR /app
COPY . .
RUN npm init -y && npm install express mongoose cors
EXPOSE 3000
CMD ["node", "server.js"]

83
z2/app/index.html Normal file
View File

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html>
<head>
<title>TODO LIST</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>TODO List - Kubernetes</h1>
<input id="task" placeholder="napis ulohu">
<input id="due" type="datetime-local">
<button onclick="addTask()">Pridaj</button>
<div id="list"></div>
</div>
<script>
async function loadTasks() {
const res = await fetch('http://localhost:30007/tasks');
const data = await res.json();
const list = document.getElementById('list');
list.innerHTML = "";
data.forEach(t => {
const li = document.createElement('div');
li.className = "task";
li.innerHTML = `
<span>
${t.text} |
${t.due ? t.due.split('T')[0] : ""} |
${t.due ? t.due.split('T')[1] : ""}
</span>
<div class="buttons">
<button class="delete" onclick="deleteTask('${t._id}')">X</button>
<button class="edit" onclick="editTask('${t._id}', '${t.text}', '${t.due}')">Edit</button>
</div>
`;
list.appendChild(li);
});
}
async function addTask() {
const text = document.getElementById('task').value;
const due = document.getElementById('due').value;
await fetch('http://localhost:30007/tasks', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({text, due})
});
loadTasks();
}
async function deleteTask(id) {
await fetch(`http://localhost:30007/tasks/${id}`, {
method: 'DELETE'
});
loadTasks();
}
async function editTask(id, oldText, oldDue) {
const text = prompt("novy text:", oldText);
const due = prompt("novy datum:", oldDue);
await fetch(`http://localhost:30007/tasks/${id}`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({text, due})
});
loadTasks();
}
loadTasks();
</script>
</body>
</html>

59
z2/app/server.js Normal file
View File

@ -0,0 +1,59 @@
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const path = require('path');
const app = express();
app.use(express.json());
app.use(cors());
app.use(express.static(path.join(__dirname)));
mongoose.connect('mongodb://mongo:27017/todo');
const Task = mongoose.model('Task', {
text: String,
due: String
});
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.get('/tasks', async (req, res) => {
const tasks = await Task.find();
res.json(tasks);
});
app.post('/tasks', async (req, res) => {
const task = new Task({
text: req.body.text,
due: req.body.due
});
await task.save();
res.send("ok");
});
app.delete('/tasks/:id', async (req, res) => {
await Task.findByIdAndDelete(req.params.id);
res.send("deleted");
});
app.put('/tasks/:id', async (req, res) => {
await Task.findByIdAndUpdate(req.params.id, {
text: req.body.text,
due: req.body.due
});
res.send("updated");
});
app.listen(3000, () => {
console.log("backend bezi na porte 3000");
});

65
z2/app/style.css Normal file
View File

@ -0,0 +1,65 @@
body {
font-family: Arial;
background: #f4f6f8;
margin: 0;
padding: 0;
}
.container {
background: white;
padding: 20px;
border-radius: 10px;
width: 400px;
margin: 50px auto; /* vycentrovanie */
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center; /* nadpis na stred */
}
input {
padding: 8px;
margin: 5px 0;
width: 100%;
box-sizing: border-box;
}
button {
padding: 8px;
margin-top: 5px;
cursor: pointer;
width: 100%;
}
#list {
margin-top: 15px;
}
.task {
display: flex;
justify-content: space-between;
align-items: center;
background: #eee;
padding: 8px;
margin-top: 5px;
border-radius: 5px;
}
.task span {
text-align: left; /* zarovnanie dolava */
}
.buttons button {
margin-left: 5px;
}
.delete {
background: red;
color: white;
}
.edit {
background: orange;
color: white;
}

21
z2/k8s/deployment.yaml Normal file
View File

@ -0,0 +1,21 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
namespace: my-app-ns
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: my-web-app
imagePullPolicy: Never
ports:
- containerPort: 3000

4
z2/k8s/namespace.yaml Normal file
View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: my-app-ns

13
z2/k8s/service.yaml Normal file
View File

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: web-service
namespace: my-app-ns
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 3000
nodePort: 30007

72
z2/k8s/statefulset.yaml Normal file
View File

@ -0,0 +1,72 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongo-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data/mongo
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongo-pvc
namespace: my-app-ns
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
# 🔥 TOTO JE TEN DÔLEŽITÝ SERVICE
apiVersion: v1
kind: Service
metadata:
name: mongo
namespace: my-app-ns
spec:
selector:
app: mongo
ports:
- port: 27017
targetPort: 27017
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongo
namespace: my-app-ns
spec:
serviceName: "mongo"
replicas: 1
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo
ports:
- containerPort: 27017
volumeMounts:
- name: mongo-storage
mountPath: /data/db
volumeClaimTemplates:
- metadata:
name: mongo-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi

7
z2/prepare-app.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
echo "building docker image..."
docker build -t my-web-app ./app
echo "done"

10
z2/start-app.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
echo "starting app..."
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/statefulset.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
echo "app started"

10
z2/stop-app.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
echo "stopping app..."
kubectl delete -f k8s/service.yaml
kubectl delete -f k8s/deployment.yaml
kubectl delete -f k8s/statefulset.yaml
kubectl delete -f k8s/namespace.yaml
echo "app stopped"