reuploading assignments to git

This commit is contained in:
Andriy Frolov 2025-04-15 21:41:17 +02:00
parent 5380e26a33
commit f7988f1f2d
21 changed files with 644 additions and 0 deletions

64
assignment1/README.md Normal file
View File

@ -0,0 +1,64 @@
# Docker Web Application Deployment
The application consists of two services:
1. **Web Service (Nginx):**
- Uses the official Nginx image.
- Listens on port 80 inside the container and is mapped to host port 8080.
2. **Database Service (MySQL 5.7):**
- Uses the official MySQL 5.7 image.
- Configured with a root password and a default database.
- Persists its data using the named volume `mysql-data`.
## Files Overview
- **prepare-app.sh:**
- Creates the required Docker network (`myapp-net`) and persistent volume (`mysql-data`).
- **docker-compose.yaml:**
- Defines the two services along with their ports, environment variables, volumes, and restart policies.
- **start-app.sh:**
- Starts the services using Docker Compose in detached mode.
- Displays the URL to access the web service.
- **stop-app.sh:**
- Stops the running containers without deleting the persistent volume.
- **remove-app.sh:**
- Removes all the created resources (containers, network, and volume) from the deployment.
## Deployment Instructions
1. **Preparation:**
- Ensure Docker and Docker Compose are installed.
- Make the scripts executable:
```bash
chmod +x prepare-app.sh start-app.sh stop-app.sh remove-app.sh
```
- Run the preparation script:
```bash
./prepare-app.sh
```
2. **Starting the Application:**
- Launch the services by running:
```bash
./start-app.sh
```
- Open your web browser and navigate to [http://localhost:8080](http://localhost:8080) to see the Nginx welcome page.
3. **Stopping the Application:**
- Stop the services without losing data:
```bash
./stop-app.sh
```
4. **Removing the Application:**
- To completely remove all deployed resources, run:
```bash
./remove-app.sh
```
## Notes
- The application uses an external network (`myapp-net`) and a persistent volume (`mysql-data`) that are created in `prepare-app.sh`.
- The Nginx container depends on the MySQL container to demonstrate inter-service communication within the `myapp-net` network.
- Containers are configured to restart on failure.

View File

@ -0,0 +1,32 @@
version: '3.8'
services:
web:
image: nginx:latest
ports:
- "8080:80"
networks:
- myapp-net
restart: on-failure
depends_on:
- db
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: example
MYSQL_DATABASE: appdb
volumes:
- mysql-data:/var/lib/mysql
networks:
- myapp-net
restart: on-failure
networks:
myapp-net:
external: true
volumes:
mysql-data:
external: true

20
assignment1/prepare-app.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
# prepare-app.sh
if ! docker network ls | grep -w myapp-net >/dev/null; then
docker network create myapp-net
echo "Created Docker network: myapp-net"
else
echo "Docker network myapp-net already exists."
fi
if ! docker volume ls | grep -w mysql-data >/dev/null; then
docker volume create mysql-data
echo "Created Docker volume: mysql-data"
else
echo "Docker volume mysql-data already exists."
fi
echo "Preparation complete."

10
assignment1/remove-app.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
# remove-app.sh
docker-compose down
docker volume rm mysql-data
docker network rm myapp-net
echo "All application resources removed."

9
assignment1/start-app.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
# start-app.sh
docker-compose up -d
sleep 5
echo "Application started. Access the web service at http://localhost:8080"

6
assignment1/stop-app.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
# stop-app.sh
docker-compose stop
echo "Application services stopped. Persistent data remains intact."

Binary file not shown.

2
assignment2/Dockerfile Normal file
View File

@ -0,0 +1,2 @@
FROM nginx:alpine
COPY weather.html /usr/share/nginx/html/index.html

View File

@ -0,0 +1,6 @@
FROM node:18-alpine
WORKDIR /app
COPY server.js .
RUN npm init -y && npm install express pg body-parser
EXPOSE 3000
CMD ["node", "server.js"]

136
assignment2/README.md Normal file
View File

@ -0,0 +1,136 @@
# Weather Web App - Kubernetes Deployment
This project deploys a full-stack weather web application to Kubernetes. It includes:
- A frontend website to search weather by city
- A backend PostgreSQL database for logging searches
- A Node.js API to connect the frontend to the database
---
## Application Description
- **Frontend (weather.html):** A responsive, modern web UI that shows current weather data using the OpenWeatherMap API.
- **Backend (PostgreSQL):** Stores weather search history via a `weather_log` table.
- **API Server (Node.js):** Receives weather data from the frontend and logs it to the PostgreSQL database.
---
## Containers Used
| Container | Image | Description |
|------------------|--------------------|----------------------------------------------|
| `weather-frontend` | Custom Nginx-based | Serves the `weather.html` UI |
| `postgres` | `postgres:15` | Provides relational database backend |
| `weather-api` | Custom Node.js | API that logs weather data to PostgreSQL |
---
## ☸️ Kubernetes Objects
| Object Type | File | Description |
|------------------------|----------------------|----------------------------------------------------------------------|
| `Namespace` | (inside script) | Isolates resources under `webapp-ns` |
| `Deployment` | `deployment.yaml` | Manages frontend app |
| `Deployment` | `deployment-api.yaml` | Manages the Node.js API |
| `StatefulSet` | `statefulset.yaml` | Manages PostgreSQL instance with persistent volume |
| `PersistentVolume` | `statefulset.yaml` | Host-mounted volume for PostgreSQL data |
| `PersistentVolumeClaim`| `statefulset.yaml` | Requests storage for StatefulSet |
| `Service` | `service.yaml` | Exposes frontend via NodePort |
| `Service` | `deployment-api.yaml` | Exposes API via NodePort |
| `ConfigMap` | Created by script | Stores `init-db.sql` used to create the `weather_log` table |
---
## Networking & Volumes
### Virtual Networks:
- Kubernetes handles inter-service communication via internal DNS and `ClusterIP` services.
- Frontend and backend are externally reachable using `NodePort` services.
### Volumes:
- `PersistentVolume` and `PersistentVolumeClaim` ensure PostgreSQL data is retained.
- `ConfigMap` mounts the SQL init script for PostgreSQL setup.
---
## ⚙️ Container Configurations
### Frontend
- Dockerfile uses `nginx:alpine`
- Serves `weather.html` (renamed as `index.html` inside container)
- Exposed on port `80`
### Node.js API
- Built with Node.js 18
- Listens on port `3000`
- Connects to PostgreSQL using service DNS
- Accepts POST requests at `/log` with weather data
### PostgreSQL
- `postgres:15` with `weatheruser`, `weatherpass`, and `weatherdb`
- Init script creates a `weather_log` table with a sample row
---
## How to Use the Application
### 1. Build Docker Images
```bash
./prepare-app.sh
```
### 2. Start the App (create namespace and apply all configs)
```bash
./start-app.sh
```
### 3. View the App
```bash
minikube service weather-service -n webapp-ns
```
Or get the NodePort manually:
```bash
kubectl get svc -n webapp-ns
minikube ip
```
Then open:
```
http://<minikube-ip>:<frontend-nodeport>
```
### 4. Use the App
- Enter a city
- Weather will be shown
- Data is logged in PostgreSQL automatically
### 5. Stop and Clean Up
```bash
./stop-app.sh
```
---
## API Key Setup
- Register at [OpenWeatherMap](https://openweathermap.org/api)
- Get your free API key
- Replace the placeholder in `weather.html`:
```js
const apiKey = "YOUR_API_KEY";
```
---
## Conclusion
- Frontend calls OpenWeatherMap and logs data to your backend
- PostgreSQL is prepped with a table to store queries
- You can expand the API to serve saved data or stats

View File

@ -0,0 +1,36 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: weather-api
namespace: webapp-ns
spec:
replicas: 1
selector:
matchLabels:
app: weather-api
template:
metadata:
labels:
app: weather-api
spec:
containers:
- name: weather-api
image: weather-api:latest
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: weather-api-service
namespace: webapp-ns
spec:
selector:
app: weather-api
ports:
- protocol: TCP
port: 3000
targetPort: 3000
nodePort: 31000
type: NodePort

View File

@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: weather-frontend
namespace: webapp-ns
spec:
replicas: 1
selector:
matchLabels:
app: weather-frontend
template:
metadata:
labels:
app: weather-frontend
spec:
containers:
- name: weather-frontend
image: weather-frontend:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80

11
assignment2/init-db.sql Normal file
View File

@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS weather_log (
id SERIAL PRIMARY KEY,
city VARCHAR(100),
temperature DECIMAL(5,2),
description TEXT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO weather_log (city, temperature, description)
VALUES ('London', 15.5, 'partly cloudy');

Binary file not shown.

11
assignment2/prepare-app.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
IMAGE_NAME=weather-frontend
IMAGE_TAG=latest
echo "🔧 Building Docker image: $IMAGE_NAME:$IMAGE_TAG"
docker build -t $IMAGE_NAME:$IMAGE_TAG .
docker build -t weather-api:latest -f Dockerfile.api .
echo "✅ Build complete."

35
assignment2/server.js Normal file
View File

@ -0,0 +1,35 @@
const express = require('express');
const bodyParser = require('body-parser');
const { Pool } = require('pg');
const app = express();
const port = 3000;
app.use(bodyParser.json());
const pool = new Pool({
host: 'postgres-0.postgres.webapp-ns.svc.cluster.local',
user: 'weatheruser',
password: 'weatherpass',
database: 'weatherdb',
port: 5432
});
app.post('/log', async (req, res) => {
const { city, temperature, description } = req.body;
try {
await pool.query(
'INSERT INTO weather_log (city, temperature, description) VALUES ($1, $2, $3)',
[city, temperature, description]
);
res.status(200).send('Logged!');
} catch (err) {
console.error(err);
res.status(500).send('Error saving data');
}
});
app.listen(port, () => {
console.log(`Weather API listening on port ${port}`);
});

14
assignment2/service.yaml Normal file
View File

@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: weather-service
namespace: webapp-ns
spec:
selector:
app: weather-frontend
ports:
- protocol: TCP
port: 80
targetPort: 80
type: NodePort

17
assignment2/start-app.sh Executable file
View File

@ -0,0 +1,17 @@
#!/bin/bash
echo "🚀 Creating namespace 'webapp-ns'..."
kubectl create namespace webapp-ns
echo "📦 Deploying frontend app..."
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
echo "🧾 Creating ConfigMap with init-db.sql..."
kubectl create configmap db-init-script --from-file=init-db.sql=init-db.sql -n webapp-ns
echo "🗄️ Deploying database backend..."
kubectl apply -f statefulset.yaml
kubectl apply -f deployment-api.yaml
echo "✅ All resources created."

View File

@ -0,0 +1,84 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgres-pv
namespace: webapp-ns
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data/postgres"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: webapp-ns
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: webapp-ns
spec:
serviceName: "postgres"
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
initContainers:
- name: init-db
image: postgres:15
env:
- name: POSTGRES_USER
value: "weatheruser"
- name: POSTGRES_PASSWORD
value: "weatherpass"
- name: POSTGRES_DB
value: "weatherdb"
volumeMounts:
- name: init-script
mountPath: /docker-entrypoint-initdb.d
- name: postgres-storage
mountPath: /var/lib/postgresql/data
containers:
- name: postgres
image: postgres:15
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
value: "weatheruser"
- name: POSTGRES_PASSWORD
value: "weatherpass"
- name: POSTGRES_DB
value: "weatherdb"
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: init-script
configMap:
name: db-init-script
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi

7
assignment2/stop-app.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
echo "🧼 Deleting all resources in namespace 'webapp-ns'..."
kubectl delete namespace webapp-ns
echo "✅ Cleanup complete."

122
assignment2/weather.html Normal file
View File

@ -0,0 +1,122 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Weather Info</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
<style>
* {
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(to right, #e0f7fa, #80deea);
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.container {
background: white;
padding: 2rem;
border-radius: 1rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
text-align: center;
}
h1 {
margin-bottom: 1.5rem;
font-size: 1.75rem;
font-weight: 600;
color: #00796b;
}
input {
padding: 0.5rem;
width: 65%;
border: 1px solid #ccc;
border-radius: 5px;
margin-right: 0.5rem;
font-size: 1rem;
}
button {
padding: 0.55rem 1rem;
background-color: #00796b;
color: white;
border: none;
border-radius: 5px;
font-size: 1rem;
cursor: pointer;
}
button:hover {
background-color: #00695c;
}
#weather {
margin-top: 2rem;
font-size: 1.1rem;
}
#weather p {
margin: 0.25rem 0;
}
</style>
</head>
<body>
<div class="container">
<h1>🌦️ Check the Weather</h1>
<input type="text" id="city" placeholder="Enter city" />
<button onclick="getWeather()">Get Weather</button>
<div id="weather"></div>
</div>
<script>
async function getWeather() {
const city = document.getElementById("city").value;
const apiKey = "0ef7f5ac4207c6da232b7843eb1a663e";
try {
const res = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`
);
const data = await res.json();
if (data.cod === 200) {
document.getElementById("weather").innerHTML = `
<h2>${data.name}, ${data.sys.country}</h2>
<p><strong>${data.weather[0].description}</strong></p>
<p>🌡️ <strong>${data.main.temp}°C</strong></p>
`;
// Log to backend
await fetch("http://192.168.49.2:31000/log", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
city: data.name,
temperature: data.main.temp,
description: data.weather[0].description
})
});
} else {
document.getElementById("weather").innerHTML = `<p style="color:red;">City not found!</p>`;
}
} catch (err) {
console.error("Fetch failed:", err);
document.getElementById("weather").innerHTML = `<p style="color:red;">Error getting weather data.</p>`;
}
}
</script>
</body>
</html>