Assignment 2 Done

This commit is contained in:
Sarukesh Boominathan 2026-04-22 08:17:47 +02:00
parent c07ce6fcb6
commit d8e97aaaf7
14 changed files with 617 additions and 0 deletions

123
Z2/z2/README.md Normal file
View File

@ -0,0 +1,123 @@
# Notes App — Kubernetes Deployment
A simple web application where you can write, view, and delete notes. Built with Flask (Python) and PostgreSQL.
---
## What the app does
- Add a note using the text box on the main page
- View all saved notes
- Delete any note with a button click
- Notes are stored in a PostgreSQL database and survive pod restarts
---
## Containers used
| Container | Image | Description |
|-----------|-------|-------------|
| `notes-web` | `notes-web:latest` (built locally) | Flask web app — serves the UI and handles requests |
| `postgres` | `postgres:15-alpine` | PostgreSQL database — stores notes |
---
## Kubernetes objects
| Object | Name | Description |
|--------|------|-------------|
| Namespace | `notes-app` | Isolates all app objects from other workloads |
| Deployment | `notes-web` | Runs 2 replicas of the Flask web app |
| StatefulSet | `postgres` | Runs 1 PostgreSQL pod with stable identity |
| PersistentVolume | `postgres-pv` | 1Gi volume backed by host path `/mnt/notes-postgres-data` |
| PersistentVolumeClaim | `postgres-pvc` | Claims the PV for use by the StatefulSet |
| Service (NodePort) | `notes-web-service` | Exposes the web app on port `30080` |
| Service (ClusterIP) | `postgres-service` | Internal access to PostgreSQL for the web app |
| Service (Headless) | `postgres-headless` | Required by the StatefulSet for stable DNS |
---
## Networking and volumes
**Services:**
- `notes-web-service` — NodePort, exposes port `30080` externally → routes to pod port `5000`
- `postgres-service` — ClusterIP, internal only, port `5432` → the web app connects to this
- `postgres-headless` — Headless service (no ClusterIP) used by the StatefulSet
**Volume:**
- `postgres-pv` uses a `hostPath` at `/mnt/notes-postgres-data` on the node
- Data persists even if the PostgreSQL pod is restarted or recreated
---
## Container configuration
**notes-web:**
- Environment variables set the database connection: `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASS`
- `imagePullPolicy: Never` — uses the locally built image (no registry needed)
- Runs on port `5000`
**postgres:**
- `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD` configure the database on first start
- Data directory `/var/lib/postgresql/data` is mounted from the PVC
---
## How to use
### 1. Prepare (build image, create host directory)
```bash
chmod +x prepare-app.sh start-app.sh stop-app.sh show-url.sh
./prepare-app.sh
```
### 2. Start the application
```bash
./start-app.sh
```
### 3. Open in browser
```bash
./show-url.sh
```
Or open manually:
- **Minikube:** `minikube service notes-web-service -n notes-app`
- **Other:** `http://<node-ip>:30080`
### 4. Stop (keep objects, scale down)
```bash
kubectl scale deployment notes-web -n notes-app --replicas=0
kubectl scale statefulset postgres -n notes-app --replicas=0
```
### 5. Delete everything
```bash
./stop-app.sh
```
---
## File overview
```
z2/
├── app/
│ ├── app.py # Flask application
│ ├── requirements.txt # Python dependencies
│ └── Dockerfile # Image definition for notes-web
├── namespace.yaml # Namespace: notes-app
├── deployment.yaml # Deployment: notes-web (Flask)
├── statefulset.yaml # StatefulSet + PV + PVC (PostgreSQL)
├── service.yaml # Services: notes-web-service, postgres-service
├── prepare-app.sh # Build image, create host directory
├── start-app.sh # Apply all Kubernetes objects
├── stop-app.sh # Delete all Kubernetes objects
├── show-url.sh # Print the URL to open the app
└── README.md # This file
```

12
Z2/z2/app/Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]

111
Z2/z2/app/app.py Normal file
View File

@ -0,0 +1,111 @@
import os
import psycopg2
from flask import Flask, request, redirect, url_for, render_template_string
app = Flask(__name__)
DB_HOST = os.environ.get("DB_HOST", "postgres-service")
DB_PORT = os.environ.get("DB_PORT", "5432")
DB_NAME = os.environ.get("DB_NAME", "notesdb")
DB_USER = os.environ.get("DB_USER", "notesuser")
DB_PASS = os.environ.get("DB_PASS", "notespass")
HTML = """
<!DOCTYPE html>
<html>
<head>
<title>Notes App</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; }
h1 { color: #333; }
form { margin-bottom: 30px; }
textarea { width: 100%%; height: 80px; padding: 8px; font-size: 14px; box-sizing: border-box; }
button { padding: 8px 20px; background: #0066cc; color: white; border: none; cursor: pointer; font-size: 14px; }
button:hover { background: #0052a3; }
.note { border: 1px solid #ddd; padding: 12px; margin-bottom: 10px; border-radius: 4px; }
.note small { color: #888; }
.delete { background: #cc0000; float: right; padding: 4px 10px; font-size: 12px; }
.delete:hover { background: #aa0000; }
.error { color: red; margin-bottom: 10px; }
</style>
</head>
<body>
<h1>📝 Notes App</h1>
{% if error %}<p class="error">{{ error }}</p>{% endif %}
<form method="POST" action="/add">
<textarea name="content" placeholder="Write a note..." required></textarea><br><br>
<button type="submit">Add Note</button>
</form>
<h2>Notes ({{ notes|length }})</h2>
{% for note in notes %}
<div class="note">
<form method="POST" action="/delete/{{ note[0] }}" style="display:inline">
<button class="delete" type="submit">Delete</button>
</form>
<p>{{ note[1] }}</p>
<small>{{ note[2] }}</small>
</div>
{% endfor %}
{% if not notes %}<p>No notes yet.</p>{% endif %}
</body>
</html>
"""
def get_conn():
return psycopg2.connect(
host=DB_HOST, port=DB_PORT,
dbname=DB_NAME, user=DB_USER, password=DB_PASS
)
def init_db():
conn = get_conn()
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS notes (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
cur.close()
conn.close()
@app.route("/")
def index():
try:
init_db()
conn = get_conn()
cur = conn.cursor()
cur.execute("SELECT id, content, created_at FROM notes ORDER BY created_at DESC")
notes = cur.fetchall()
cur.close()
conn.close()
return render_template_string(HTML, notes=notes, error=None)
except Exception as e:
return render_template_string(HTML, notes=[], error=f"DB error: {e}")
@app.route("/add", methods=["POST"])
def add():
content = request.form.get("content", "").strip()
if content:
conn = get_conn()
cur = conn.cursor()
cur.execute("INSERT INTO notes (content) VALUES (%s)", (content,))
conn.commit()
cur.close()
conn.close()
return redirect(url_for("index"))
@app.route("/delete/<int:note_id>", methods=["POST"])
def delete(note_id):
conn = get_conn()
cur = conn.cursor()
cur.execute("DELETE FROM notes WHERE id = %s", (note_id,))
conn.commit()
cur.close()
conn.close()
return redirect(url_for("index"))
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)

View File

@ -0,0 +1,2 @@
flask==3.0.0
psycopg2-binary==2.9.9

34
Z2/z2/deployment.yaml Normal file
View File

@ -0,0 +1,34 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: notes-web
namespace: notes-app
labels:
app: notes-web
spec:
replicas: 2
selector:
matchLabels:
app: notes-web
template:
metadata:
labels:
app: notes-web
spec:
containers:
- name: notes-web
image: notes-web:latest
imagePullPolicy: Never
ports:
- containerPort: 5000
env:
- name: DB_HOST
value: "postgres-service"
- name: DB_PORT
value: "5432"
- name: DB_NAME
value: "notesdb"
- name: DB_USER
value: "notesuser"
- name: DB_PASS
value: "notespass"

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

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

134
Z2/z2/port-forward.log Normal file
View File

@ -0,0 +1,134 @@
nohup: ignoring input
Unable to listen on port 8080: Listeners failed to create with the following errors: [unable to create listener: Error listen tcp4 1Handling connection for 8080
Handling connection for 8080
E0422 08:15:48.485727 143453 portforward.go:424] "Unhandled Error" err="an error occurred forwarding 8080 -> 5000: error forwarding port 5000 to pod 4ee1b087afb63a44c5fc2569518897054db6a76a1f05c60e69cb7fba21218b5f, uid : Error response from daemon: No such container: 4ee1b087afb63a44c5fc2569518897054db6a76a1f05c60e69cb7fba21218b5f"
E0422 08:15:48.485729 143453 portforward.go:424] "Unhandled Error" err="an error occurred forwarding 8080 -> 5000: error forwarding port 5000 to pod 4ee1b087afb63a44c5fc2569518897054db6a76a1f05c60e69cb7fba21218b5f, uid : Error response from daemon: No such container: 4ee1b087afb63a44c5fc2569518897054db6a76a1f05c60e69cb7fba21218b5f"
error: lost connection to pod
s "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): namespaces "notes-app" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
Error from server (NotFound): services "notes-web-service" not found
port-forward crashed, retrying in 2s...
error: unable to forward port because pod is not running. Current status=Pending
port-forward crashed, retrying in 2s...
Unable to listen on port 8080: Listeners failed to create with the following errors: [unable to create listener: Error listen tcp4 127.0.0.1:8080: bind: address already in use unable to create listener: Error listen tcp6 [::1]:8080: bind: address already in use]
error: unable to listen on any of the requested ports: [{8080 5000}]
port-forward crashed, retrying in 2s...
Unable to listen on port 8080: Listeners failed to create with the following errors: [unable to create listener: Error listen tcp4 127.0.0.1:8080: bind: address already in use unable to create listener: Error listen tcp6 [::1]:8080: bind: address already in use]
error: unable to listen on any of the requested ports: [{8080 5000}]
port-forward crashed, retrying in 2s...
Forwarding from 127.0.0.1:8080 -> 5000
Forwarding from [::1]:8080 -> 5000
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
E0422 08:16:00.690760 146224 portforward.go:404] "Unhandled Error" err="error copying from local connection to remote stream: writeto tcp6 [::1]:8080->[::1]:39644: read tcp6 [::1]:8080->[::1]:39644: read: connection reset by peer"

20
Z2/z2/prepare-app.sh Normal file
View File

@ -0,0 +1,20 @@
#!/bin/bash
set -e
echo "==> Building Docker image for notes-web..."
docker build -t notes-web:latest ./app
echo "==> Loading image into Minikube (if using Minikube)..."
if command -v minikube &>/dev/null; then
minikube image load notes-web:latest
echo " Image loaded into Minikube."
else
echo " Minikube not found, skipping image load."
fi
echo "==> Creating host directory for PersistentVolume..."
sudo mkdir -p /mnt/notes-postgres-data
sudo chmod 777 /mnt/notes-postgres-data
echo ""
echo "Preparation complete. Run ./start-app.sh to start the application."

27
Z2/z2/service.yaml Normal file
View File

@ -0,0 +1,27 @@
apiVersion: v1
kind: Service
metadata:
name: notes-web-service
namespace: notes-app
spec:
type: NodePort
selector:
app: notes-web
ports:
- port: 80
targetPort: 5000
nodePort: 30080
---
apiVersion: v1
kind: Service
metadata:
name: postgres-service
namespace: notes-app
spec:
type: ClusterIP
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432

9
Z2/z2/show-url.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/bash
if command -v minikube &>/dev/null; then
echo "==> Minikube detected. Opening service URL..."
minikube service notes-web-service -n notes-app --url
else
NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')
echo "==> App available at: http://${NODE_IP}:30080"
fi

24
Z2/z2/start-app.sh Normal file
View File

@ -0,0 +1,24 @@
#!/bin/bash
set -e
echo "==> Creating Namespace..."
kubectl apply -f namespace.yaml
echo "==> Creating PersistentVolume, PersistentVolumeClaim and StatefulSet (PostgreSQL)..."
kubectl apply -f statefulset.yaml
echo "==> Waiting for PostgreSQL to be ready..."
kubectl rollout status statefulset/postgres -n notes-app --timeout=120s
echo "==> Creating Deployment (Notes web app)..."
kubectl apply -f deployment.yaml
echo "==> Creating Services..."
kubectl apply -f service.yaml
echo "==> Waiting for web app to be ready..."
kubectl rollout status deployment/notes-web -n notes-app --timeout=120s
echo ""
echo "Application is running."
echo "Run ./show-url.sh or see README.md for how to open the app in your browser."

79
Z2/z2/statefulset.yaml Normal file
View File

@ -0,0 +1,79 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgres-pv
namespace: notes-app
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /mnt/notes-postgres-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: notes-app
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: manual
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: notes-app
spec:
serviceName: postgres-headless
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_DB
value: "notesdb"
- name: POSTGRES_USER
value: "notesuser"
- name: POSTGRES_PASSWORD
value: "notespass"
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
namespace: notes-app
spec:
clusterIP: None
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432

26
Z2/z2/status.sh Normal file
View File

@ -0,0 +1,26 @@
#!/bin/bash
echo -e "\n\033[1;34m=== CLUSTER STATUS REPORT ===\033[0m"
date
echo ""
echo "=== NAMESPACE ==="
kubectl get namespace notes-app
echo "=== DEPLOYMENT ==="
kubectl get deployment -n notes-app
echo "=== STATEFULSET ==="
kubectl get statefulset -n notes-app
echo "=== PV ==="
kubectl get pv postgres-pv
echo "=== PVC ==="
kubectl get pvc -n notes-app
echo "=== SERVICES ==="
kubectl get svc -n notes-app
echo "=== PODS ==="
kubectl get pods -n notes-app -o wide

12
Z2/z2/stop-app.sh Normal file
View File

@ -0,0 +1,12 @@
#!/bin/bash
echo "==> Deleting all objects in namespace notes-app..."
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 ""
echo "All Kubernetes objects removed."
echo "Note: The host directory /mnt/notes-postgres-data was NOT deleted."
echo "To also remove it, run: sudo rm -rf /mnt/notes-postgres-data"