diff --git a/z2/Dockerfile b/z2/Dockerfile new file mode 100644 index 0000000..a62458e --- /dev/null +++ b/z2/Dockerfile @@ -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"] \ No newline at end of file diff --git a/z2/README.md b/z2/README.md new file mode 100644 index 0000000..a53d8ac --- /dev/null +++ b/z2/README.md @@ -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 +``` \ No newline at end of file diff --git a/z2/app.py b/z2/app.py new file mode 100644 index 0000000..df8a3e3 --- /dev/null +++ b/z2/app.py @@ -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""" + + + My Blog + + + +
+
+

My Blog

+
+ + + +
+
+
+

Articles

+ {''.join(f'

{a[1]}

{a[2]}

' for a in articles)} +
+
+ + + """ + except Exception as e: + return f"An error occurred: {str(e)}", 500 + +@app.route("/delete/", 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 "" + 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) \ No newline at end of file diff --git a/z2/deployment.yaml b/z2/deployment.yaml new file mode 100644 index 0000000..c6dd034 --- /dev/null +++ b/z2/deployment.yaml @@ -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" \ No newline at end of file diff --git a/z2/namespace.yaml b/z2/namespace.yaml new file mode 100644 index 0000000..e14643a --- /dev/null +++ b/z2/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myapp-namespace \ No newline at end of file diff --git a/z2/prepare-app.sh b/z2/prepare-app.sh new file mode 100644 index 0000000..5bedeeb --- /dev/null +++ b/z2/prepare-app.sh @@ -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" \ No newline at end of file diff --git a/z2/requirements.txt b/z2/requirements.txt new file mode 100644 index 0000000..8860e6a --- /dev/null +++ b/z2/requirements.txt @@ -0,0 +1,3 @@ +Flask +psycopg2-binary +requests \ No newline at end of file diff --git a/z2/service.yaml b/z2/service.yaml new file mode 100644 index 0000000..6b18866 --- /dev/null +++ b/z2/service.yaml @@ -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 \ No newline at end of file diff --git a/z2/start-app.sh b/z2/start-app.sh new file mode 100644 index 0000000..875945d --- /dev/null +++ b/z2/start-app.sh @@ -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 \ No newline at end of file diff --git a/z2/statefulset.yaml b/z2/statefulset.yaml new file mode 100644 index 0000000..e55aa29 --- /dev/null +++ b/z2/statefulset.yaml @@ -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 \ No newline at end of file diff --git a/z2/stop-app.sh b/z2/stop-app.sh new file mode 100644 index 0000000..16cd0f3 --- /dev/null +++ b/z2/stop-app.sh @@ -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." \ No newline at end of file