Upload folders
This commit is contained in:
parent
3c4251226a
commit
1efa8b83f2
16
z2/Dockerfile
Normal file
16
z2/Dockerfile
Normal file
@ -0,0 +1,16 @@
|
||||
FROM python:3.9-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY app.py .
|
||||
|
||||
ENV FLASK_APP=app.py
|
||||
ENV FLASK_RUN_HOST=0.0.0.0
|
||||
ENV FLASK_RUN_PORT=5000
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["flask", "run"]
|
60
z2/README.md
Normal file
60
z2/README.md
Normal file
@ -0,0 +1,60 @@
|
||||
# Blog Application with Kubernetes
|
||||
|
||||
This project deploys a simple blog application with Flask and PostgreSQL on Kubernetes.
|
||||
|
||||
## Application Features
|
||||
|
||||
- Create and publish blog articles with title and content
|
||||
- View all articles in a responsive interface
|
||||
- Delete articles with a single click
|
||||
- Persistent storage for database content
|
||||
|
||||
## Architecture
|
||||
|
||||
The application consists of:
|
||||
- Flask web application for creating and deleting blog articles
|
||||
- PostgreSQL database to store the articles
|
||||
- Kubernetes deployment for orchestration and scaling
|
||||
|
||||
## Technologies Used
|
||||
|
||||
- **Frontend**: HTML, CSS, Flask Templates
|
||||
- **Backend**: Python, Flask
|
||||
- **Database**: PostgreSQL
|
||||
- **Container**: Docker
|
||||
- **Orchestration**: Kubernetes, Minikube
|
||||
|
||||
## Deployment Instructions
|
||||
|
||||
### Prerequisites
|
||||
- Docker
|
||||
- Minikube
|
||||
- kubectl
|
||||
|
||||
### Step 1: Preparation
|
||||
Run the preparation script to create the namespace and build the Docker image:
|
||||
```
|
||||
chmod +x prepare-app.sh
|
||||
./prepare-app.sh
|
||||
```
|
||||
|
||||
### Step 2: Deployment
|
||||
Launch the application with:
|
||||
```
|
||||
chmod +x start-app.sh
|
||||
./start-app.sh
|
||||
```
|
||||
|
||||
### Step 3: Access the Application
|
||||
Get the application URL with:
|
||||
```
|
||||
minikube service blog-app-service -n myapp-namespace --url
|
||||
```
|
||||
Open this URL in your browser to access the blog application.
|
||||
|
||||
### Step 4: Shutdown
|
||||
When finished, clean up all resources with:
|
||||
```
|
||||
chmod +x stop-app.sh
|
||||
./stop-app.sh
|
||||
```
|
136
z2/app.py
Normal file
136
z2/app.py
Normal file
@ -0,0 +1,136 @@
|
||||
from flask import Flask, request, jsonify
|
||||
import os
|
||||
import psycopg2
|
||||
import time
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Database Configuration
|
||||
db_host = os.environ.get("DB_HOST", "blog-db-service")
|
||||
db_name = os.environ.get("POSTGRES_DB", "blogdb")
|
||||
db_user = os.environ.get("POSTGRES_USER", "admin")
|
||||
db_password = os.environ.get("POSTGRES_PASSWORD", "password")
|
||||
|
||||
def get_db_connection():
|
||||
return psycopg2.connect(host=db_host, database=db_name, user=db_user, password=db_password)
|
||||
|
||||
def create_table():
|
||||
max_retries = 10
|
||||
retry_delay = 5 # seconds
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
print(f"Attempt {attempt+1}/{max_retries} to connect to database...")
|
||||
conn = get_db_connection()
|
||||
cur = conn.cursor()
|
||||
cur.execute('''
|
||||
CREATE TABLE IF NOT EXISTS articles (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL
|
||||
)
|
||||
''')
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
print("Table 'articles' created successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Database connection attempt {attempt+1}/{max_retries} failed: {e}")
|
||||
if attempt < max_retries - 1:
|
||||
print(f"Retrying in {retry_delay} seconds...")
|
||||
time.sleep(retry_delay)
|
||||
|
||||
print("Failed to create table after multiple attempts")
|
||||
return False
|
||||
|
||||
# Initialize the variable at startup
|
||||
table_created = False
|
||||
|
||||
@app.route("/", methods=["GET", "POST"])
|
||||
def index():
|
||||
global table_created
|
||||
|
||||
# If the table is not yet created, try to create it
|
||||
if not table_created:
|
||||
table_created = create_table()
|
||||
|
||||
# Check if the table has been created before continuing
|
||||
if not table_created:
|
||||
return "Database connection failed. Please try again later.", 500
|
||||
|
||||
try:
|
||||
conn = get_db_connection()
|
||||
cur = conn.cursor()
|
||||
|
||||
if request.method == "POST":
|
||||
title = request.form.get("title")
|
||||
content = request.form.get("content")
|
||||
if title and content:
|
||||
cur.execute("INSERT INTO articles (title, content) VALUES (%s, %s)", (title, content))
|
||||
conn.commit()
|
||||
|
||||
cur.execute("SELECT * FROM articles ORDER BY id DESC")
|
||||
articles = cur.fetchall()
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
return f"""
|
||||
<html>
|
||||
<head>
|
||||
<title>My Blog</title>
|
||||
<style>
|
||||
body {{ font-family: 'Poppins', sans-serif; background-color: #f5f5f5; color: #333; margin: 0; padding: 0; }}
|
||||
.container {{ max-width: 1100px; margin: 50px auto; background: #6a0dad; padding: 30px; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); display: flex; gap: 20px; }}
|
||||
.form-container, .articles-container {{ flex: 1; background: #ffffff; padding: 20px; border-radius: 10px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); }}
|
||||
h1{{ text-align: center; color: #6a0dad; font-size: 30px; }}
|
||||
form {{ display: flex; flex-direction: column; gap: 10px; align-items: center; }}
|
||||
input, textarea {{ width: 90%; padding: 14px; border: 1px solid #ccc; border-radius: 5px; background: #fff; color: #333; font-size: 18px; }}
|
||||
button {{ background-color: #6a0dad; color: white; border: none; padding: 12px 25px; cursor: pointer; border-radius: 5px; transition: background 0.3s; font-size: 20px; }}
|
||||
button:hover {{ background-color: #5a0dad; }}
|
||||
.articles-container {{ display: flex; flex-direction: column; gap: 15px; }}
|
||||
.article {{ background: #e0e0e0; padding: 20px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); position: relative; }}
|
||||
.article h3 {{ margin: 0; color: #333; font-size: 22px; }}
|
||||
.article p {{ margin: 10px 0; color: #555; font-size: 18px; }}
|
||||
.delete-form {{ position: absolute; top: 10px; right: 10px; }}
|
||||
.delete-button {{ background-color: #ff5555; padding: 8px 12px; font-size: 14px; border-radius: 5px; }}
|
||||
.delete-button:hover {{ background-color: #cc4444; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="form-container">
|
||||
<h1>My Blog</h1>
|
||||
<form method="post">
|
||||
<input type="text" name="title" placeholder="Title" required>
|
||||
<textarea name="content" placeholder="Content" required></textarea>
|
||||
<button type="submit">Add Article</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="articles-container">
|
||||
<h1>Articles</h1>
|
||||
{''.join(f'<div class="article"><h3>{a[1]}</h3><p>{a[2]}</p><form method="post" action="/delete/{a[0]}" class="delete-form"><button type="submit" class="delete-button">Delete</button></form></div>' for a in articles)}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
except Exception as e:
|
||||
return f"An error occurred: {str(e)}", 500
|
||||
|
||||
@app.route("/delete/<int:article_id>", methods=["POST"])
|
||||
def delete_article(article_id):
|
||||
try:
|
||||
conn = get_db_connection()
|
||||
cur = conn.cursor()
|
||||
cur.execute("DELETE FROM articles WHERE id = %s", (article_id,))
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
return "<script>window.location.href='/'</script>"
|
||||
except Exception as e:
|
||||
return f"An error occurred while deleting: {str(e)}", 500
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_table()
|
||||
app.run(host="0.0.0.0", port=5000)
|
32
z2/deployment.yaml
Normal file
32
z2/deployment.yaml
Normal file
@ -0,0 +1,32 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: blog-app
|
||||
namespace: myapp-namespace
|
||||
labels:
|
||||
app: blog
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: blog
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: blog
|
||||
spec:
|
||||
containers:
|
||||
- name: blog-app
|
||||
image: blog-app:latest
|
||||
imagePullPolicy: Never
|
||||
ports:
|
||||
- containerPort: 5000
|
||||
env:
|
||||
- name: DB_HOST
|
||||
value: "blog-db-service"
|
||||
- name: POSTGRES_DB
|
||||
value: "blogdb"
|
||||
- name: POSTGRES_USER
|
||||
value: "admin"
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: "password"
|
4
z2/namespace.yaml
Normal file
4
z2/namespace.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: myapp-namespace
|
18
z2/prepare-app.sh
Normal file
18
z2/prepare-app.sh
Normal file
@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Create the namespace
|
||||
kubectl apply -f namespace.yaml
|
||||
|
||||
# Build the Docker image
|
||||
docker build -t blog-app:latest .
|
||||
|
||||
# Ensure minikube uses the local image
|
||||
eval $(minikube docker-env)
|
||||
docker build -t blog-app:latest .
|
||||
|
||||
# Start Minikube if it's not already running
|
||||
if ! minikube status | grep -q "Running"; then
|
||||
minikube start
|
||||
fi
|
||||
|
||||
echo "Preparation complete. You can now run ./start-app.sh"
|
3
z2/requirements.txt
Normal file
3
z2/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
Flask
|
||||
psycopg2-binary
|
||||
requests
|
30
z2/service.yaml
Normal file
30
z2/service.yaml
Normal file
@ -0,0 +1,30 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: blog-app-service
|
||||
namespace: myapp-namespace
|
||||
spec:
|
||||
selector:
|
||||
app: blog
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 5000
|
||||
nodePort: 30080
|
||||
type: NodePort
|
||||
|
||||
---
|
||||
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: blog-db-service
|
||||
namespace: myapp-namespace
|
||||
spec:
|
||||
selector:
|
||||
app: blog-db
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
targetPort: 5432
|
||||
type: ClusterIP
|
17
z2/start-app.sh
Normal file
17
z2/start-app.sh
Normal file
@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Create the namespace if it doesn't already exist
|
||||
kubectl apply -f namespace.yaml
|
||||
|
||||
# Create the statefulset for the database
|
||||
kubectl apply -f statefulset.yaml
|
||||
|
||||
# Create the deployment for the application
|
||||
kubectl apply -f deployment.yaml
|
||||
|
||||
# Create the services
|
||||
kubectl apply -f service.yaml
|
||||
|
||||
# Display created resources
|
||||
echo "Resources created:"
|
||||
kubectl get all -n myapp-namespace
|
40
z2/statefulset.yaml
Normal file
40
z2/statefulset.yaml
Normal file
@ -0,0 +1,40 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: blog-db
|
||||
namespace: myapp-namespace
|
||||
spec:
|
||||
serviceName: "blog-db-service"
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: blog-db
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: blog-db
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:13
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
env:
|
||||
- name: POSTGRES_DB
|
||||
value: "blogdb"
|
||||
- name: POSTGRES_USER
|
||||
value: "admin"
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: "password"
|
||||
volumeMounts:
|
||||
- name: postgres-storage
|
||||
mountPath: /var/lib/postgresql/data
|
||||
subPath: pgdata
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: postgres-storage
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
15
z2/stop-app.sh
Normal file
15
z2/stop-app.sh
Normal file
@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Delete services
|
||||
kubectl delete -f service.yaml
|
||||
|
||||
# Delete deployment
|
||||
kubectl delete -f deployment.yaml
|
||||
|
||||
# Delete statefulset
|
||||
kubectl delete -f statefulset.yaml
|
||||
|
||||
# Delete namespace
|
||||
kubectl delete -f namespace.yaml
|
||||
|
||||
echo "All resources have been deleted."
|
Loading…
Reference in New Issue
Block a user