message
This commit is contained in:
parent
437a05ee15
commit
2718263509
100
z1/README.md
Normal file
100
z1/README.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Zadanie 1
|
||||||
|
|
||||||
|
## Opis aplikacie
|
||||||
|
|
||||||
|
Tato aplikacia je jednoducha webova aplikacia nasadena pomocou Docker Compose. Umoznuje zapisat meno cez webove rozhranie do databazy PostgreSQL a nasledne zobrazit zoznam ulozenych zaznamov. Aplikacia obsahuje frontend, backend, databazu a webove rozhranie Adminer na pracu s databazou.
|
||||||
|
|
||||||
|
## Potrebny software
|
||||||
|
|
||||||
|
- Linux
|
||||||
|
- Docker
|
||||||
|
- Docker Compose plugin (`docker compose`)
|
||||||
|
|
||||||
|
## Pouzite kontajnery
|
||||||
|
|
||||||
|
- `nginx:latest` - webovy server pre staticke subory frontendu
|
||||||
|
- `node:18` - backend aplikacie postaveny zo suboru `backend/Dockerfile`
|
||||||
|
- `postgres:15` - relacna databaza PostgreSQL
|
||||||
|
- `adminer` - webove rozhranie na pracu s databazou
|
||||||
|
|
||||||
|
## Siete a zvazky
|
||||||
|
|
||||||
|
Docker Compose vytvori predvolenu virtualnu siet, v ktorej spolu komunikujú sluzby:
|
||||||
|
|
||||||
|
- `web`
|
||||||
|
- `backend`
|
||||||
|
- `db`
|
||||||
|
- `adminer`
|
||||||
|
|
||||||
|
Pouzity pomenovany trvaly zvazok:
|
||||||
|
|
||||||
|
- `db_data` - uklada databazove data PostgreSQL, aby zostali zachovane aj po zastaveni aplikacie
|
||||||
|
|
||||||
|
## Konfiguracia kontajnerov
|
||||||
|
|
||||||
|
- `web` bezi v kontajneri s Nginx a spristupnuje frontend na porte `8080`
|
||||||
|
- `backend` bezi v Node.js kontajneri a je dostupny na porte `5000`
|
||||||
|
- `db` bezi ako PostgreSQL databaza s premennymi `POSTGRES_USER`, `POSTGRES_PASSWORD` a `POSTGRES_DB`
|
||||||
|
- `adminer` je dostupny na porte `8081`
|
||||||
|
- vsetky sluzby maju nastavene `restart: always`
|
||||||
|
- backend zavisi od databazy a Adminer zavisi od databazy
|
||||||
|
|
||||||
|
## Navod na pouzitie
|
||||||
|
|
||||||
|
Priecinok projektu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd z1
|
||||||
|
```
|
||||||
|
|
||||||
|
Spustenie aplikacie:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./start-app.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Zastavenie aplikacie:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./stop-app.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternativne je mozne aplikaciu spustit aj priamo cez Docker Compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pristup cez webovy prehliadac
|
||||||
|
|
||||||
|
- Hlavna aplikacia: `http://localhost:8080`
|
||||||
|
- Backend API: `http://localhost:5000`
|
||||||
|
- Adminer: `http://localhost:8081`
|
||||||
|
|
||||||
|
Prihlasovacie udaje do PostgreSQL:
|
||||||
|
|
||||||
|
- system: `PostgreSQL`
|
||||||
|
- server: `db`
|
||||||
|
- username: `user`
|
||||||
|
- password: `password`
|
||||||
|
- database: `mydb`
|
||||||
|
|
||||||
|
## Priklad prace s aplikaciou
|
||||||
|
|
||||||
|
1. Otvorte `http://localhost:8080`
|
||||||
|
2. Zadajte meno do formulara
|
||||||
|
3. Kliknite na tlacidlo `Save & Show`
|
||||||
|
4. Udaj sa ulozi do databazy a zobrazi v zozname
|
||||||
|
|
||||||
|
## Zdroje
|
||||||
|
|
||||||
|
- Docker dokumentacia
|
||||||
|
- Docker Compose dokumentacia
|
||||||
|
- Nginx oficialny image na Docker Hub
|
||||||
|
- Node.js oficialny image na Docker Hub
|
||||||
|
- PostgreSQL oficialny image na Docker Hub
|
||||||
|
- Adminer oficialny image na Docker Hub
|
||||||
|
|
||||||
|
## Pouzitie umelej inteligencie
|
||||||
|
|
||||||
|
Pri priprave dokumentacie a pomocnych skriptov bola pouzita umele inteligencia vo forme AI agenta Codex.
|
||||||
6
z1/prepare-app.sh
Normal file
6
z1/prepare-app.sh
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
docker compose build
|
||||||
|
docker compose create
|
||||||
5
z1/remove-app.sh
Normal file
5
z1/remove-app.sh
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
docker compose down -v --remove-orphans
|
||||||
9
z1/start-app.sh
Normal file
9
z1/start-app.sh
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Running app ..."
|
||||||
|
docker compose up -d
|
||||||
|
echo "The app is available at http://localhost:8080"
|
||||||
|
echo "Adminer is available at http://localhost:8081"
|
||||||
|
echo "Backend API is available at http://localhost:5000"
|
||||||
5
z1/stop-app.sh
Normal file
5
z1/stop-app.sh
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
docker compose stop
|
||||||
112
z2/README.md
Normal file
112
z2/README.md
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
# Zadanie 2 - Kubernetes web application
|
||||||
|
|
||||||
|
This project deploys a simple web application into Kubernetes. The user enters a name in the browser, the frontend sends it to the backend API, and the backend stores the value in PostgreSQL. The saved names are then displayed back in the browser.
|
||||||
|
|
||||||
|
## Used containers
|
||||||
|
|
||||||
|
- `z2-frontend:latest` - custom Nginx image serving the static frontend and proxying API requests to the backend service.
|
||||||
|
- `z2-backend:latest` - custom Node.js and Express API image that stores and reads user data from PostgreSQL.
|
||||||
|
- `postgres:15-alpine` - database container running inside a StatefulSet with persistent storage.
|
||||||
|
|
||||||
|
## Kubernetes objects
|
||||||
|
|
||||||
|
- `Namespace zkt26-z2` - isolates all application resources into one namespace.
|
||||||
|
- `Deployment backend-deployment` - runs the backend API container.
|
||||||
|
- `Deployment frontend-deployment` - runs the frontend Nginx container.
|
||||||
|
- `Service backend-service` - exposes the backend inside the cluster.
|
||||||
|
- `Service frontend-service` - exposes the frontend on NodePort `30080`.
|
||||||
|
- `Service postgres-service` - stable network endpoint for PostgreSQL.
|
||||||
|
- `PersistentVolume postgres-pv` - host storage for PostgreSQL data.
|
||||||
|
- `PersistentVolumeClaim postgres-pvc` - binds storage for the database pod.
|
||||||
|
- `StatefulSet postgres-statefulset` - runs PostgreSQL with persistent data.
|
||||||
|
|
||||||
|
## Networks and volumes
|
||||||
|
|
||||||
|
The application uses the default Kubernetes cluster networking. Pods communicate with each other through Kubernetes services:
|
||||||
|
|
||||||
|
- `frontend-service` for browser access
|
||||||
|
- `backend-service` for frontend to backend communication
|
||||||
|
- `postgres-service` for backend to database communication
|
||||||
|
|
||||||
|
Persistent data is stored using:
|
||||||
|
|
||||||
|
- `PersistentVolume postgres-pv`
|
||||||
|
- `PersistentVolumeClaim postgres-pvc`
|
||||||
|
|
||||||
|
The volume uses `hostPath` at `/tmp/zkt26-postgres-data`, so the database data remains available even after the pod restarts.
|
||||||
|
|
||||||
|
## Container configuration
|
||||||
|
|
||||||
|
The frontend container is based on Nginx and includes a custom `nginx.conf` that proxies `/api/*` requests to the backend service.
|
||||||
|
|
||||||
|
The backend container uses environment variables for the database connection:
|
||||||
|
|
||||||
|
- `DB_HOST=postgres-service`
|
||||||
|
- `DB_PORT=5432`
|
||||||
|
- `DB_USER=user`
|
||||||
|
- `DB_PASSWORD=password`
|
||||||
|
- `DB_NAME=mydb`
|
||||||
|
|
||||||
|
The PostgreSQL container is configured with:
|
||||||
|
|
||||||
|
- `POSTGRES_USER=user`
|
||||||
|
- `POSTGRES_PASSWORD=password`
|
||||||
|
- `POSTGRES_DB=mydb`
|
||||||
|
|
||||||
|
## How to prepare, start, stop and delete the application
|
||||||
|
|
||||||
|
Run the commands from the `z2` directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x prepare-app.sh start-app.sh stop-app.sh
|
||||||
|
./prepare-app.sh
|
||||||
|
./start-app.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
To stop and delete the application:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./stop-app.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to pause the application
|
||||||
|
|
||||||
|
The application can be paused by scaling deployments and the stateful set to zero:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl scale deployment frontend-deployment --replicas=0 -n zkt26-z2
|
||||||
|
kubectl scale deployment backend-deployment --replicas=0 -n zkt26-z2
|
||||||
|
kubectl scale statefulset postgres-statefulset --replicas=0 -n zkt26-z2
|
||||||
|
```
|
||||||
|
|
||||||
|
To start it again:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl scale deployment frontend-deployment --replicas=1 -n zkt26-z2
|
||||||
|
kubectl scale deployment backend-deployment --replicas=1 -n zkt26-z2
|
||||||
|
kubectl scale statefulset postgres-statefulset --replicas=1 -n zkt26-z2
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to open the web application
|
||||||
|
|
||||||
|
After starting the application in a standard Kubernetes environment, open the browser at:
|
||||||
|
|
||||||
|
- `http://localhost:30080`
|
||||||
|
|
||||||
|
If you are using Minikube in WSL with the Docker driver, get the real browser URL with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
minikube service frontend-service -n zkt26-z2 --url
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep that terminal open and open the printed URL in the browser.
|
||||||
|
|
||||||
|
If your Kubernetes environment does not expose NodePort on localhost directly, or if you want a fixed local port, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl port-forward service/frontend-service 8080:80 -n zkt26-z2
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open:
|
||||||
|
|
||||||
|
- `http://localhost:8080`
|
||||||
11
z2/backend/Dockerfile
Normal file
11
z2/backend/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY server.js .
|
||||||
|
|
||||||
|
RUN npm init -y && npm install express pg cors
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
91
z2/backend/server.js
Normal file
91
z2/backend/server.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const cors = require("cors");
|
||||||
|
const { Pool } = require("pg");
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = Number(process.env.PORT || 3000);
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
host: process.env.DB_HOST || "postgres-service",
|
||||||
|
user: process.env.DB_USER || "user",
|
||||||
|
password: process.env.DB_PASSWORD || "password",
|
||||||
|
database: process.env.DB_NAME || "mydb",
|
||||||
|
port: Number(process.env.DB_PORT || 5432)
|
||||||
|
});
|
||||||
|
|
||||||
|
async function prepareDatabase() {
|
||||||
|
await pool.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForDatabase(maxAttempts = 20, delayMs = 3000) {
|
||||||
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||||
|
try {
|
||||||
|
await prepareDatabase();
|
||||||
|
console.log(`Database is ready after attempt ${attempt}.`);
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Database is not ready yet (attempt ${attempt}/${maxAttempts}):`, error.message);
|
||||||
|
|
||||||
|
if (attempt === maxAttempts) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get("/health", async (_req, res) => {
|
||||||
|
try {
|
||||||
|
await pool.query("SELECT 1");
|
||||||
|
res.json({ status: "ok" });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Healthcheck failed:", error);
|
||||||
|
res.status(500).json({ status: "error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/save", async (req, res) => {
|
||||||
|
const name = (req.body.name || "").trim();
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return res.status(400).send("Name is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await pool.query("INSERT INTO users(name) VALUES($1)", [name]);
|
||||||
|
return res.send(`Saved to DB: ${name}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Insert failed:", error);
|
||||||
|
return res.status(500).send("Error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/users", async (_req, res) => {
|
||||||
|
try {
|
||||||
|
const result = await pool.query("SELECT * FROM users ORDER BY id ASC");
|
||||||
|
return res.json(result.rows);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Select failed:", error);
|
||||||
|
return res.status(500).send("Error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
waitForDatabase()
|
||||||
|
.then(() => {
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Backend running on port ${port}`);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Database initialization failed:", error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
60
z2/deployment.yaml
Normal file
60
z2/deployment.yaml
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: backend-deployment
|
||||||
|
namespace: zkt26-z2
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: backend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: backend
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: backend
|
||||||
|
image: z2-backend:v2
|
||||||
|
imagePullPolicy: Never
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
env:
|
||||||
|
- name: DB_HOST
|
||||||
|
value: postgres-service
|
||||||
|
- name: DB_PORT
|
||||||
|
value: "5432"
|
||||||
|
- name: DB_USER
|
||||||
|
value: user
|
||||||
|
- name: DB_PASSWORD
|
||||||
|
value: password
|
||||||
|
- name: DB_NAME
|
||||||
|
value: mydb
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 3000
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: frontend-deployment
|
||||||
|
namespace: zkt26-z2
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: frontend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: frontend
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
image: z2-frontend:latest
|
||||||
|
imagePullPolicy: Never
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
6
z2/frontend/Dockerfile
Normal file
6
z2/frontend/Dockerfile
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
FROM nginx:1.27-alpine
|
||||||
|
|
||||||
|
COPY index.html /usr/share/nginx/html/index.html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
173
z2/frontend/index.html
Normal file
173
z2/frontend/index.html
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Kubernetes Demo App</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: light;
|
||||||
|
--bg: #f4efe6;
|
||||||
|
--panel: #fffaf2;
|
||||||
|
--text: #1f2933;
|
||||||
|
--accent: #bf6d2c;
|
||||||
|
--accent-dark: #8e4b17;
|
||||||
|
--border: #e6d7c3;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
font-family: "Trebuchet MS", "Segoe UI", sans-serif;
|
||||||
|
color: var(--text);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top, rgba(191, 109, 44, 0.18), transparent 35%),
|
||||||
|
linear-gradient(180deg, #f8f2e9 0%, var(--bg) 100%);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: min(640px, 100%);
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 28px;
|
||||||
|
box-shadow: 0 20px 50px rgba(72, 49, 24, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: clamp(2rem, 5vw, 2.8rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #52606d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 1 1 260px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.15s ease, background 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: var(--accent-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
min-height: 24px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: var(--accent-dark);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li + li {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="card">
|
||||||
|
<h1>Kubernetes demo</h1>
|
||||||
|
<p>This frontend sends names to the backend API and loads them back from PostgreSQL running in a StatefulSet.</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<input id="name" placeholder="Enter name">
|
||||||
|
<button onclick="sendAndLoad()">Save and show</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="status" class="status"></div>
|
||||||
|
<ul id="list"></ul>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function loadUsers() {
|
||||||
|
const status = document.getElementById("status");
|
||||||
|
|
||||||
|
try {
|
||||||
|
status.textContent = "Loading users...";
|
||||||
|
const res = await fetch("/api/users");
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
const list = document.getElementById("list");
|
||||||
|
list.innerHTML = "";
|
||||||
|
|
||||||
|
data.forEach((user) => {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
li.innerText = user.name;
|
||||||
|
list.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
status.textContent = "Users loaded successfully.";
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
status.textContent = "Cannot connect to backend.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendAndLoad() {
|
||||||
|
const status = document.getElementById("status");
|
||||||
|
const nameInput = document.getElementById("name");
|
||||||
|
const name = nameInput.value.trim();
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
status.textContent = "Please enter a name first.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
status.textContent = "Saving user...";
|
||||||
|
await fetch("/api/save", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ name })
|
||||||
|
});
|
||||||
|
|
||||||
|
nameInput.value = "";
|
||||||
|
await loadUsers();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
status.textContent = "Saving failed.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = loadUsers;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
z2/frontend/nginx.conf
Normal file
20
z2/frontend/nginx.conf
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://backend-service:3000/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
4
z2/namespace.yaml
Normal file
4
z2/namespace.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: zkt26-z2
|
||||||
18
z2/prepare-app.sh
Normal file
18
z2/prepare-app.sh
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Building backend image..."
|
||||||
|
docker build -t z2-backend:v2 ./backend
|
||||||
|
|
||||||
|
echo "Building frontend image..."
|
||||||
|
docker build -t z2-frontend:latest ./frontend
|
||||||
|
|
||||||
|
if command -v minikube >/dev/null 2>&1; then
|
||||||
|
echo "Loading images into minikube..."
|
||||||
|
minikube image load z2-backend:v2
|
||||||
|
minikube image load z2-frontend:latest
|
||||||
|
else
|
||||||
|
echo "minikube not found, skipping image load step."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Preparation finished."
|
||||||
37
z2/service.yaml
Normal file
37
z2/service.yaml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: backend-service
|
||||||
|
namespace: zkt26-z2
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: backend
|
||||||
|
ports:
|
||||||
|
- port: 3000
|
||||||
|
targetPort: 3000
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: frontend-service
|
||||||
|
namespace: zkt26-z2
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
selector:
|
||||||
|
app: frontend
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
nodePort: 30080
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: postgres-service
|
||||||
|
namespace: zkt26-z2
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: postgres
|
||||||
|
ports:
|
||||||
|
- port: 5432
|
||||||
|
targetPort: 5432
|
||||||
11
z2/start-app.sh
Normal file
11
z2/start-app.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
kubectl apply -f namespace.yaml
|
||||||
|
kubectl apply -f statefulset.yaml
|
||||||
|
kubectl apply -f deployment.yaml
|
||||||
|
kubectl apply -f service.yaml
|
||||||
|
|
||||||
|
echo "Application started."
|
||||||
|
echo "Frontend NodePort is 30080."
|
||||||
|
echo "For Minikube on WSL use: minikube service frontend-service -n zkt26-z2 --url"
|
||||||
63
z2/statefulset.yaml
Normal file
63
z2/statefulset.yaml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: postgres-pv
|
||||||
|
spec:
|
||||||
|
storageClassName: ""
|
||||||
|
capacity:
|
||||||
|
storage: 1Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
hostPath:
|
||||||
|
path: /tmp/zkt26-postgres-data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: postgres-pvc
|
||||||
|
namespace: zkt26-z2
|
||||||
|
spec:
|
||||||
|
storageClassName: ""
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
volumeName: postgres-pv
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: postgres-statefulset
|
||||||
|
namespace: zkt26-z2
|
||||||
|
spec:
|
||||||
|
serviceName: postgres-service
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: postgres
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: postgres:15-alpine
|
||||||
|
ports:
|
||||||
|
- containerPort: 5432
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: user
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
value: password
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: mydb
|
||||||
|
volumeMounts:
|
||||||
|
- name: postgres-storage
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
volumes:
|
||||||
|
- name: postgres-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: postgres-pvc
|
||||||
9
z2/stop-app.sh
Normal file
9
z2/stop-app.sh
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
kubectl delete -f service.yaml --ignore-not-found
|
||||||
|
kubectl delete -f deployment.yaml --ignore-not-found
|
||||||
|
kubectl delete -f statefulset.yaml --ignore-not-found
|
||||||
|
kubectl delete -f namespace.yaml --ignore-not-found
|
||||||
|
|
||||||
|
echo "Application stopped."
|
||||||
Loading…
Reference in New Issue
Block a user