Compare commits

...

No commits in common. "master" and "main" have entirely different histories.
master ... main

32 changed files with 688 additions and 167 deletions

View File

@ -1,58 +0,0 @@
# Docker Web Application
## Prerequisites
- Docker installed on the system.
## Application Description
This is a simple Flask web application running inside a Docker container. The app accepts user input via a form and displays the submitted data.
## Docker Setup
1. **Preparation**
Run the following command to set up the environment:
```bash
./prepare-app.sh
```
2. **Start the Application**
Start the application with:
```bash
./start-app.sh
```
The app will be available at: [http://localhost:5000](http://localhost:5000)
3. **Stop the Application**
To stop the application:
```bash
./stop-app.sh
```
4. **Remove the Application**
To remove all Docker containers, networks, and volumes:
```bash
./remove-app.sh
```
## Working Example
1. Prepare the application:
```bash
./prepare-app.sh
```
2. Start the app:
```bash
./start-app.sh
```
3. Visit [http://localhost:5000](http://localhost:5000) in a browser.
4. Stop the app:
```bash
./stop-app.sh
```
5. Remove the app:
```bash
./remove-app.sh
```
## Conclusion
This application demonstrates Docker containerization of a Flask app with basic form handling.

View File

@ -1,18 +0,0 @@
version: '3.8'
services:
flask-app:
build: .
ports:
- "5000:5000"
networks:
- flask-network
restart: unless-stopped
# You can add other services here, like a database if required
networks:
flask-network:
driver: bridge
volumes:
flask-data:
driver: local

View File

@ -1,21 +0,0 @@
# Use an official Python runtime as a parent image
FROM python:3.8-slim
# Set the working directory inside the container
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY . /app
# Install Flask
RUN pip install --no-cache-dir flask
# Set the environment variable for Flask
ENV FLASK_APP=testapp.py
ENV FLASK_RUN_HOST=0.0.0.0
# Expose port 5000 for the Flask app
EXPOSE 5000
# Run the Flask application
CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"]

View File

@ -1,11 +0,0 @@
#!/bin/bash
# Create a Docker network
echo "Creating Docker network..."
docker network create flask-network
# Create a named volume for persistent storage (for example, for database)
echo "Creating Docker volume..."
docker volume create flask-data
echo "Preparation complete!"

View File

@ -1,15 +0,0 @@
#!/bin/bash
# Remove the Flask app container
echo "Removing Flask app container..."
docker rm flask-app
# Remove Docker network
echo "Removing Docker network..."
docker network rm flask-network
# Remove Docker volume
echo "Removing Docker volume..."
docker volume rm flask-data
echo "App removed successfully!"

View File

@ -1,9 +0,0 @@
#!/bin/bash
# Start the Flask app container
echo "Starting Flask app container..."
docker run -d --restart unless-stopped --name flask-app --network flask-network -p 5000:5000 flask-app
# Optionally, start any additional services (like a database) here if needed
echo "App is running at http://localhost:5000"

View File

@ -1,9 +0,0 @@
#!/bin/bash
# Stop the Flask app container
echo "Stopping Flask app container..."
docker stop flask-app
# Optionally, stop other services like the database here
echo "App stopped successfully!"

View File

@ -1,26 +0,0 @@
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
if request.method == 'POST':
college_name = request.form['college_name']
student_name = request.form['student_name']
course_name = request.form['course_name']
year_of_study = request.form['year_of_study']
return f"College: {college_name}, Student: {student_name}, Course: {course_name}, Year: {year_of_study}"
return '''
<form method="POST">
College Name: <input type="text" name="college_name"><br>
Student Name: <input type="text" name="student_name"><br>
Course Name: <input type="text" name="course_name"><br>
Year of Study: <input type="text" name="year_of_study"><br>
<input type="submit" value="Submit">
</form>
'''
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5000) # This allows the app to run inside the container

9
z1/Dockerfile Normal file
View File

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

32
z1/README.md Normal file
View File

@ -0,0 +1,32 @@
# Flask + PostgreSQL Kubernetes Deployment
This project deploys a Flask web application with a PostgreSQL backend using Kubernetes.
## 🛠 Structure
- `app/`: Flask source code (`main.py`, `requirements.txt`)
- `k8s/`: Kubernetes manifests for deployments, services, namespace, etc.
- `Dockerfile`: Builds the Flask app image
- `prepare-app.sh`: Builds Docker image and prepares volumes
- `start-app.sh`: Applies all Kubernetes objects
- `stop-app.sh`: Deletes all Kubernetes resources
- `statefulset.yaml`: Defines StatefulSet, PV, and PVC
## 🚀 Steps to Deploy
1. **Prepare the application:**
```bash
./prepare-app.sh
hafzal03@LAPTOP-ELUS3HGM:~/mypro/z2$ kubectl get pods -n webapp-namespace
NAME READY STATUS RESTARTS AGE
flask-app-6b844bf6-cq9t6 1/1 Running 0 8m37s
postgres-644fc4c86d-l9h4f 1/1 Running 0 14m
hafzal03@LAPTOP-ELUS3HGM:~/mypro/z2$ minikube service flask-service -n webapp-namespace
minikube service flask-service -n webapp-namespace
kubectl get pods -n webapp-namespace
kubectl rollout restart deployment flask-app -n webapp-namespace

81
z1/app/main.py Normal file
View File

@ -0,0 +1,81 @@
from flask import Flask, request, jsonify, render_template_string
import psycopg2
import os
app = Flask(__name__)
def get_db_connection():
return psycopg2.connect(
host=os.environ.get("POSTGRES_HOST"),
database=os.environ.get("POSTGRES_DB"),
user=os.environ.get("POSTGRES_USER"),
password=os.environ.get("POSTGRES_PASSWORD"),
port=os.environ.get("POSTGRES_PORT", 5432),
)
def init_db():
conn = get_db_connection()
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
room_number TEXT NOT NULL,
faculty TEXT NOT NULL
);
""")
conn.commit()
cur.close()
conn.close()
@app.route("/")
def index():
return "<h1>Welcome!</h1><p>Go to /add to add a student, /students to view all.</p>"
# HTML Form + POST Submission
@app.route("/add", methods=["GET", "POST"])
def add_student():
if request.method == "POST":
name = request.form.get("name")
room_number = request.form.get("room_number")
faculty = request.form.get("faculty")
if not name or not room_number or not faculty:
return "All fields are required!", 400
conn = get_db_connection()
cur = conn.cursor()
cur.execute("INSERT INTO students (name, room_number, faculty) VALUES (%s, %s, %s)", (name, room_number, faculty))
conn.commit()
cur.close()
conn.close()
return "<p>Student added successfully!</p><a href='/add'>Add another</a>"
# HTML form
return render_template_string("""
<h2>Add Student</h2>
<form method="post">
Name: <input name="name"><br>
Room Number: <input name="room_number"><br>
Faculty: <input name="faculty"><br>
<input type="submit" value="Add Student">
</form>
""")
# View all students
@app.route("/students", methods=["GET"])
def get_students():
conn = get_db_connection()
cur = conn.cursor()
cur.execute("SELECT id, name, room_number, faculty FROM students")
rows = cur.fetchall()
cur.close()
conn.close()
return jsonify([
{"id": row[0], "name": row[1], "room_number": row[2], "faculty": row[3]} for row in rows
])
if __name__ == "__main__":
init_db()
app.run(host="0.0.0.0", port=8000)

5
z1/app/requirements.txt Normal file
View File

@ -0,0 +1,5 @@
Flask==2.0.1
Werkzeug==2.0.3
psycopg2-binary==2.9.1
python-dotenv==0.19.0
Jinja2==3.0.3

32
z1/k8s/deployment.yaml Normal file
View File

@ -0,0 +1,32 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-app
namespace: webapp-namespace
spec:
replicas: 1
selector:
matchLabels:
app: flask
template:
metadata:
labels:
app: flask
spec:
containers:
- name: flask
image: flask-app:latest
imagePullPolicy: Never
ports:
- containerPort: 8000
env:
- name: POSTGRES_HOST
value: postgres
- name: POSTGRES_DB
value: mydatabase
- name: POSTGRES_USER
value: myuser
- name: POSTGRES_PASSWORD
value: mypassword
- name: POSTGRES_PORT
value: "5432"

12
z1/k8s/migrate-job.yaml Normal file
View File

@ -0,0 +1,12 @@
apiVersion: batch/v1
kind: Job
metadata:
name: migrate
spec:
template:
spec:
containers:
- name: migrator
image: myapp:latest
command: ["python", "manage.py", "migrate"]
restartPolicy: Never

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

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

View File

@ -0,0 +1,57 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: webapp-namespace
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: webapp-namespace
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:13
env:
- name: POSTGRES_DB
value: mydatabase
- name: POSTGRES_USER
value: myuser
- name: POSTGRES_PASSWORD
value: mypassword
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: webapp-namespace
spec:
selector:
app: postgres
ports:
- port: 5432

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

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: flask-service
namespace: webapp-namespace
spec:
selector:
app: flask
type: NodePort
ports:
- port: 8000
targetPort: 8000
nodePort: 30080 # <-- Changed this

10
z1/prepare-app.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
echo "Building Docker image for Flask app..."
docker build -t flask-app:latest .
echo "Creating Minikube image cache (if using Minikube)..."
eval $(minikube docker-env)
docker build -t flask-app:latest .
echo "Preparation complete."

18
z1/start-app.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
echo "Creating Namespace..."
kubectl apply -f k8s/namespace.yaml
echo "Creating Persistent Volumes and StatefulSet..."
kubectl apply -f k8s/statefulset.yaml -n webapp-namespace
echo "Deploying PostgreSQL..."
kubectl apply -f k8s/postgres-deployment.yaml -n webapp-namespace
echo "Deploying Flask App..."
kubectl apply -f k8s/deployment.yaml -n webapp-namespace
echo "Creating Service..."
kubectl apply -f k8s/service.yaml -n webapp-namespace
echo "All resources have been applied."

18
z1/stop-app.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
echo "Deleting Flask App Deployment..."
kubectl delete -f k8s/deployment.yaml -n webapp-namespace
echo "Deleting Flask Service..."
kubectl delete -f k8s/service.yaml -n webapp-namespace
echo "Deleting PostgreSQL Deployment and PVC..."
kubectl delete -f k8s/postgres-deployment.yaml -n webapp-namespace
echo "Deleting StatefulSet and PVs..."
kubectl delete -f k8s/statefulset.yaml -n webapp-namespace
echo "Deleting Namespace..."
kubectl delete -f k8s/namespace.yaml
echo "All resources have been deleted."

9
z2/Dockerfile Normal file
View File

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

138
z2/README.md Normal file
View File

@ -0,0 +1,138 @@
# Flask + PostgreSQL Kubernetes WebApp
A lightweight full-stack web application powered by **Flask** and **PostgreSQL**, containerized with **Docker**, and deployed on **Kubernetes (Minikube)** using custom `Namespace`, `Deployment`, `StatefulSet`, and `Services`. Designed to demonstrate a production-like cloud-native architecture.
---
## Project Structure
```bash
z2/
├── app/
│ ├── main.py # Flask application logic
│ └── requirements.txt # Python dependencies
├── k8s/
│ ├── namespace.yaml # Custom namespace
│ ├── deployment.yaml # Flask app deployment
│ ├── postgres-deployment.yaml # PostgreSQL StatefulSet + PVC + Service
│ ├── migrate-job.yaml # Migration job (optional)
│ ├── service.yaml # Flask NodePort Service
├── Dockerfile # For building Flask app image
├── prepare-app.sh # Script: Build & tag Docker image
├── start-app.sh # Script: Apply all Kubernetes configs
├── stop-app.sh # Script: Delete all resources
└── README.md # Project documentation
```
---
## How It Works
1. **Prepare the Environment**
```bash
./prepare-app.sh
```
- Builds and tags Docker image locally
- Prepares persistent volume (PVC) for PostgreSQL
2. **Deploy the Application**
```bash
./start-app.sh
```
- Creates namespace, StatefulSet, Deployments, Services
3. **Access the Web App**
```bash
minikube service flask-service -n webapp-namespace
```
- Opens the app in your browser (NodePort)
4. **Stop and Clean Everything**
```bash
./stop-app.sh
```
- Deletes all created Kubernetes resources
---
## Technologies Used
| Tool | Purpose |
|----------------|------------------------------|
| Flask | Web framework (Python) |
| PostgreSQL | Relational database |
| Docker | Containerization |
| Kubernetes | Container orchestration |
| Minikube | Local Kubernetes environment |
---
## Health Checks
To ensure resilience and uptime, Kubernetes probes can be added:
```yaml
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
```
Add a `/health` route to your `main.py`:
```python
@app.route('/health')
def health():
return "OK", 200
```
---
## Troubleshooting
| Problem | Solution |
|-------------------------------------|--------------------------------------------------------------------------|
| ❌ `ImagePullBackOff` or `ErrImagePull` | Make sure image is built locally with `docker build -t flask-app .` and you're using `imagePullPolicy: Never` |
| ❌ App not accessible | Check service type is `NodePort`, port 30080 is exposed, and run `minikube service flask-service -n webapp-namespace` |
| ❌ Database errors | Ensure PostgreSQL pod is running (`kubectl get pods -n webapp-namespace`), check PVC and env vars |
| ❌ "command not found: kubectl" | Install `kubectl` and make sure its configured with `minikube` context |
| ❌ Connection refused errors | Check that `POSTGRES_HOST` matches the service name (`postgres`) and app waits until DB is ready |
---
## Future Enhancements
- Use Helm for templated deployments
- Add database migrations (Alembic / Flask-Migrate)
- CI/CD integration with GitLab or GitHub Actions
- Add Redis or caching layer
- Auto-scaling and HorizontalPodAutoscaler
## Author
**Hafzal Ahamed Hasan Mohamed**
TUKE, Faculty of Electrical Engineering and Informatics
Git: [git.kemt.fei.tuke.sk/hh304ug/zkt25](https://git.kemt.fei.tuke.sk/hh304ug/zkt25)
hafzal03@LAPTOP-ELUS3HGM:~/mypro/z2$ kubectl get pods -n webapp-namespace
NAME READY STATUS RESTARTS AGE
flask-app-6b844bf6-cq9t6 1/1 Running 0 8m37s
postgres-644fc4c86d-l9h4f 1/1 Running 0 14m
hafzal03@LAPTOP-ELUS3HGM:~/mypro/z2$ minikube service flask-service -n webapp-namespace
minikube service flask-service -n webapp-namespace
kubectl get pods -n webapp-namespace
kubectl rollout restart deployment flask-app -n webapp-namespace

81
z2/app/main.py Normal file
View File

@ -0,0 +1,81 @@
from flask import Flask, request, jsonify, render_template_string
import psycopg2
import os
app = Flask(__name__)
def get_db_connection():
return psycopg2.connect(
host=os.environ.get("POSTGRES_HOST"),
database=os.environ.get("POSTGRES_DB"),
user=os.environ.get("POSTGRES_USER"),
password=os.environ.get("POSTGRES_PASSWORD"),
port=os.environ.get("POSTGRES_PORT", 5432),
)
def init_db():
conn = get_db_connection()
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
room_number TEXT NOT NULL,
faculty TEXT NOT NULL
);
""")
conn.commit()
cur.close()
conn.close()
@app.route("/")
def index():
return "<h1>Welcome!</h1><p>Go to /add to add a student, /students to view all.</p>"
# HTML Form + POST Submission
@app.route("/add", methods=["GET", "POST"])
def add_student():
if request.method == "POST":
name = request.form.get("name")
room_number = request.form.get("room_number")
faculty = request.form.get("faculty")
if not name or not room_number or not faculty:
return "All fields are required!", 400
conn = get_db_connection()
cur = conn.cursor()
cur.execute("INSERT INTO students (name, room_number, faculty) VALUES (%s, %s, %s)", (name, room_number, faculty))
conn.commit()
cur.close()
conn.close()
return "<p>Student added successfully!</p><a href='/add'>Add another</a>"
# HTML form
return render_template_string("""
<h2>Add Student</h2>
<form method="post">
Name: <input name="name"><br>
Room Number: <input name="room_number"><br>
Faculty: <input name="faculty"><br>
<input type="submit" value="Add Student">
</form>
""")
# View all students
@app.route("/students", methods=["GET"])
def get_students():
conn = get_db_connection()
cur = conn.cursor()
cur.execute("SELECT id, name, room_number, faculty FROM students")
rows = cur.fetchall()
cur.close()
conn.close()
return jsonify([
{"id": row[0], "name": row[1], "room_number": row[2], "faculty": row[3]} for row in rows
])
if __name__ == "__main__":
init_db()
app.run(host="0.0.0.0", port=8000)

5
z2/app/requirements.txt Normal file
View File

@ -0,0 +1,5 @@
Flask==2.0.1
Werkzeug==2.0.3
psycopg2-binary==2.9.1
python-dotenv==0.19.0
Jinja2==3.0.3

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

@ -0,0 +1,32 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: flask-app
namespace: webapp-namespace
spec:
replicas: 1
selector:
matchLabels:
app: flask
template:
metadata:
labels:
app: flask
spec:
containers:
- name: flask
image: flask-app:latest
imagePullPolicy: Never
ports:
- containerPort: 8000
env:
- name: POSTGRES_HOST
value: postgres
- name: POSTGRES_DB
value: mydatabase
- name: POSTGRES_USER
value: myuser
- name: POSTGRES_PASSWORD
value: mypassword
- name: POSTGRES_PORT
value: "5432"

12
z2/k8s/migrate-job.yaml Normal file
View File

@ -0,0 +1,12 @@
apiVersion: batch/v1
kind: Job
metadata:
name: migrate
spec:
template:
spec:
containers:
- name: migrator
image: myapp:latest
command: ["python", "manage.py", "migrate"]
restartPolicy: Never

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

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

View File

@ -0,0 +1,57 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: webapp-namespace
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: webapp-namespace
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:13
env:
- name: POSTGRES_DB
value: mydatabase
- name: POSTGRES_USER
value: myuser
- name: POSTGRES_PASSWORD
value: mypassword
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: webapp-namespace
spec:
selector:
app: postgres
ports:
- port: 5432

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

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: flask-service
namespace: webapp-namespace
spec:
selector:
app: flask
type: NodePort
ports:
- port: 8000
targetPort: 8000
nodePort: 30080 # <-- Changed this

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

@ -0,0 +1,10 @@
#!/bin/bash
echo "Building Docker image for Flask app..."
docker build -t flask-app:latest .
echo "Creating Minikube image cache (if using Minikube)..."
eval $(minikube docker-env)
docker build -t flask-app:latest .
echo "Preparation complete."

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

@ -0,0 +1,18 @@
#!/bin/bash
echo "Creating Namespace..."
kubectl apply -f k8s/namespace.yaml
echo "Creating Persistent Volumes and StatefulSet..."
kubectl apply -f k8s/statefulset.yaml -n webapp-namespace
echo "Deploying PostgreSQL..."
kubectl apply -f k8s/postgres-deployment.yaml -n webapp-namespace
echo "Deploying Flask App..."
kubectl apply -f k8s/deployment.yaml -n webapp-namespace
echo "Creating Service..."
kubectl apply -f k8s/service.yaml -n webapp-namespace
echo "All resources have been applied."

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

@ -0,0 +1,18 @@
#!/bin/bash
echo "Deleting Flask App Deployment..."
kubectl delete -f k8s/deployment.yaml -n webapp-namespace
echo "Deleting Flask Service..."
kubectl delete -f k8s/service.yaml -n webapp-namespace
echo "Deleting PostgreSQL Deployment and PVC..."
kubectl delete -f k8s/postgres-deployment.yaml -n webapp-namespace
echo "Deleting StatefulSet and PVs..."
kubectl delete -f k8s/statefulset.yaml -n webapp-namespace
echo "Deleting Namespace..."
kubectl delete -f k8s/namespace.yaml
echo "All resources have been deleted."