Téléverser les fichiers vers "z2"

This commit is contained in:
Antonin Filippi 2025-04-21 18:59:47 +00:00
parent 16aa94ff55
commit b91eb63711
14 changed files with 676 additions and 0 deletions

12
z2/Dockerfile Normal file
View File

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

107
z2/README.md Normal file
View File

@ -0,0 +1,107 @@
# Shopping List Web Application on Kubernetes
## Overview
This project deploys a Python shopping list web application using Flask and PostgreSQL on a Kubernetes cluster. The application allows users to create, track, and manage their shopping items in a persistent way. The deployment includes a Namespace, Deployments for both the web application and PostgreSQL database, a StatefulSet (with PersistentVolume and PersistentVolumeClaim), and Service.
## Application Description
- Simple shopping list application that allows users to add, mark as purchased, and delete items
- Each item can have a quantity associated with it
- All data is stored in PostgreSQL for persistence between application restarts
- The application uses a Flask web framework with a minimalist interface
## Containers
- **simple-web-app**: Runs the Python Flask application on port 5000
- **postgres**: Runs PostgreSQL database to store shopping list items
## Kubernetes Objects
- **Namespace**: Isolates all the resources under `my-app`
- **Deployment (Web App)**: Manages the stateless web application pods with 2 replicas for high availability
- **Deployment (PostgreSQL)**: Manages the PostgreSQL database with persistent storage
- **StatefulSet**: Manages stateful application pods that require persistent storage
- **PersistentVolume (PV)**: Provides persistent storage from the host (1GB)
- **PersistentVolumeClaim (PVC)**: Claims the PV for storage
- **Service (Web App)**: Exposes the web application externally via LoadBalancer
- **Service (PostgreSQL)**: Headless service for internal database access
## Networking and Storage
- Internal service discovery allows the web application to connect to PostgreSQL
- PostgreSQL uses persistent storage to maintain shopping list data even if pods are restarted
- The web application is exposed externally using a LoadBalancer service
## Container Configuration
- The web app container is based on Python and includes Flask and psycopg2
- PostgreSQL container uses the official PostgreSQL image
- Resource limits and readiness probes are configured for better stability
## How to Prepare, Run, Pause, and Delete the Application
1. **Prepare the application:**
```bash
./prepare-app.sh
```
This script builds the Docker image and creates the directory for persistent volume.
2. **Start the application:**
```bash
./start-app.sh
```
This script creates all necessary Kubernetes objects in the correct order, including PostgreSQL.
3. **Pause or delete the application:**
```bash
./stop-app.sh
```
This script removes all Kubernetes objects created by `start-app.sh`.
## Accessing the Application
To access the application:
1. Find the IP address of your Kubernetes node:
```bash
kubectl get nodes -o wide
```
2. Access the application in your browser at:
```
http://<NODE_IP>:80
```
Where `<NODE_IP>` is the IP address of any of your Kubernetes nodes.
## Application Features
- Add items with quantity
- Mark items as purchased/unpurchased
- Delete items
- Items list is separated into "to buy" and "purchased" sections
- Data persists between sessions and application restarts
## Database Schema
The application uses a simple PostgreSQL schema:
- Table: `shopping_items`
- Fields:
- `id`: Serial primary key
- `item`: Text (item name)
- `quantity`: Integer (defaults to 1)
- `purchased`: Boolean flag (defaults to false)
## Troubleshooting
If you encounter issues:
1. Check pod status:
```bash
kubectl get pods -n my-app
```
2. View pod logs:
```bash
kubectl logs <pod-name> -n my-app
```
3. Check service status:
```bash
kubectl get svc -n my-app
```
4. Check database connectivity:
```bash
kubectl exec -it <web-app-pod-name> -n my-app -- python -c "import psycopg2; conn = psycopg2.connect(host='postgres', dbname='postgres', user='postgres', password='postgres'); print('Connection successful')"
```

173
z2/app.py Normal file
View File

@ -0,0 +1,173 @@
from flask import Flask, request, redirect, url_for, render_template_string
import os
import psycopg2
from psycopg2 import pool
app = Flask(__name__)
DB_HOST = os.environ.get('DB_HOST', 'localhost')
DB_PORT = os.environ.get('DB_PORT', '5432')
DB_NAME = os.environ.get('DB_NAME', 'postgres')
DB_USER = os.environ.get('DB_USER', 'postgres')
DB_PASS = os.environ.get('DB_PASS', 'postgres')
connection_pool = psycopg2.pool.SimpleConnectionPool(
1, 10,
host=DB_HOST,
port=DB_PORT,
database=DB_NAME,
user=DB_USER,
password=DB_PASS
)
def init_db():
conn = connection_pool.getconn()
try:
with conn.cursor() as cur:
cur.execute('''
CREATE TABLE IF NOT EXISTS shopping_items (
id SERIAL PRIMARY KEY,
item TEXT NOT NULL,
quantity INTEGER DEFAULT 1,
purchased BOOLEAN DEFAULT FALSE
);
''')
conn.commit()
except Exception as e:
print(f"Erreur lors de l'initialisation de la base de données: {e}")
conn.rollback()
finally:
connection_pool.putconn(conn)
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>Liste de Courses</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1>Ma Liste de Courses</h1>
<form action="/add" method="post">
<input type="text" name="item" placeholder="Article" required>
<input type="number" name="quantity" value="1" min="1">
<button type="submit">Ajouter</button>
</form>
<h2>Articles à acheter</h2>
<ul>
{% for item in items %}
{% if not item[3] %}
<li>
{{ item[1] }} ({{ item[2] }})
<form action="/toggle/{{ item[0] }}" method="post" style="display:inline">
<button type="submit">Acheté</button>
</form>
<form action="/delete/{{ item[0] }}" method="post" style="display:inline">
<button type="submit">Supprimer</button>
</form>
</li>
{% endif %}
{% endfor %}
</ul>
<h2>Articles achetés</h2>
<ul>
{% for item in items %}
{% if item[3] %}
<li>
{{ item[1] }} ({{ item[2] }})
<form action="/toggle/{{ item[0] }}" method="post" style="display:inline">
<button type="submit">Non acheté</button>
</form>
<form action="/delete/{{ item[0] }}" method="post" style="display:inline">
<button type="submit">Supprimer</button>
</form>
</li>
{% endif %}
{% endfor %}
</ul>
</body>
</html>
"""
@app.route('/')
def index():
conn = connection_pool.getconn()
try:
with conn.cursor() as cur:
cur.execute("SELECT id, item, quantity, purchased FROM shopping_items ORDER BY purchased, id")
items = cur.fetchall()
except Exception as e:
print(f"Erreur lors de la récupération des articles: {e}")
items = []
finally:
connection_pool.putconn(conn)
return render_template_string(HTML_TEMPLATE, items=items)
@app.route('/add', methods=['POST'])
def add_item():
item = request.form.get('item')
quantity = request.form.get('quantity', 1, type=int)
conn = connection_pool.getconn()
try:
with conn.cursor() as cur:
cur.execute(
"INSERT INTO shopping_items (item, quantity) VALUES (%s, %s)",
(item, quantity)
)
conn.commit()
except Exception as e:
print(f"Erreur lors de l'ajout d'un article: {e}")
conn.rollback()
finally:
connection_pool.putconn(conn)
return redirect(url_for('index'))
@app.route('/toggle/<int:item_id>', methods=['POST'])
def toggle_item(item_id):
conn = connection_pool.getconn()
try:
with conn.cursor() as cur:
cur.execute(
"UPDATE shopping_items SET purchased = NOT purchased WHERE id = %s",
(item_id,)
)
conn.commit()
except Exception as e:
print(f"Erreur lors du basculement du statut: {e}")
conn.rollback()
finally:
connection_pool.putconn(conn)
return redirect(url_for('index'))
@app.route('/delete/<int:item_id>', methods=['POST'])
def delete_item(item_id):
conn = connection_pool.getconn()
try:
with conn.cursor() as cur:
cur.execute("DELETE FROM shopping_items WHERE id = %s", (item_id,))
conn.commit()
except Exception as e:
print(f"Erreur lors de la suppression d'un article: {e}")
conn.rollback()
finally:
connection_pool.putconn(conn)
return redirect(url_for('index'))
with app.app_context():
try:
init_db()
except Exception as e:
print(f"Impossible d'initialiser la base de données: {e}")
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(host='0.0.0.0', port=port, debug=True)

51
z2/deployment.yaml Normal file
View File

@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app-deployment
namespace: my-app
spec:
replicas: 2
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app-container
image: antonin193/simple-web-app:latest
imagePullPolicy: Always
ports:
- containerPort: 5000
env:
- name: DB_HOST
value: postgres
- name: DB_PORT
value: "5432"
- name: DB_NAME
value: postgres
- name: DB_USER
value: postgres
- name: DB_PASS
value: postgres
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
readinessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 15
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 20
periodSeconds: 20

7
z2/namespace.yaml Normal file
View File

@ -0,0 +1,7 @@
apiVersion: v1
kind: Namespace
metadata:
name: my-app
labels:
name: my-app
environment: development

View File

@ -0,0 +1,29 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: data-pv
namespace: my-app
labels:
type: local
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /data/stateful
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
namespace: my-app
spec:
accessModes:
- ReadWriteOnce
storageClassName: manual
resources:
requests:
storage: 1Gi

View File

@ -0,0 +1,44 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: my-app
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:14
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5432
env:
- name: POSTGRES_PASSWORD
value: postgres
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_DB
value: postgres
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: data-pvc

15
z2/postgres-service.yaml Normal file
View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: my-app
labels:
app: postgres
spec:
selector:
app: postgres
ports:
- protocol: TCP
port: 5432
targetPort: 5432
clusterIP: None

22
z2/prepare-app.sh Normal file
View File

@ -0,0 +1,22 @@
#!/bin/bash
# Script to prepare the application environment
export REGION="local"
echo "Preparing deployment for region: $REGION"
# Make sure scripts are executable
chmod +x start-app.sh stop-app.sh
# Build and tag Docker image
docker build -t simple-web-app:latest .
docker tag simple-web-app:latest antonin193/simple-web-app:latest
# Push to Docker Hub if needed
echo "Pushing image to Docker Hub..."
docker push antonin193/simple-web-app:latest
# Create directory for persistent volume (Docker Desktop supports hostPath)
sudo mkdir -p /data/stateful
sudo chmod 777 /data/stateful
echo "Preparation complete: Docker image built, tagged, and volume directory created."

4
z2/requirements.txt Normal file
View File

@ -0,0 +1,4 @@
Flask>=2.0.0
Werkzeug>=2.0.0
gunicorn>=20.1.0
psycopg2-binary>=2.9.3

16
z2/service.yaml Normal file
View File

@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: web-app-service
namespace: my-app
labels:
app: web-app
spec:
type: LoadBalancer
selector:
app: web-app
ports:
- protocol: TCP
port: 80
targetPort: 5000
sessionAffinity: None

82
z2/start-app.sh Normal file
View File

@ -0,0 +1,82 @@
#!/bin/bash
# Script to deploy the app locally using Docker Desktop + Kubernetes
# Set default region (optional but kept for consistency)
if [ -z "$REGION" ]; then
export REGION="local"
echo "No region specified. Using default region: $REGION"
else
echo "Deploying to region: $REGION"
fi
# Build Docker image
echo "Building Docker image..."
docker build -t antonin193/simple-web-app:latest .
# Push to Docker Hub (optional for local, but kept if needed across machines)
echo "Pushing image to Docker Hub..."
docker push antonin193/simple-web-app:latest
# Create Kubernetes Namespace
echo "Creating namespace..."
kubectl apply -f namespace.yaml
# Create PersistentVolume and PersistentVolumeClaim
echo "Creating persistent storage..."
kubectl apply -f persistent-storage.yaml
# Wait briefly for resources to be established
sleep 2
# Deploy PostgreSQL first
echo "Creating PostgreSQL Deployment and Service..."
kubectl apply -f postgres-deployment.yaml
kubectl apply -f postgres-service.yaml
# Wait for PostgreSQL to be ready
echo "Waiting for PostgreSQL to be ready..."
kubectl wait --for=condition=ready pod -l app=postgres -n my-app --timeout=120s
# Deploy application
echo "Creating Deployment..."
kubectl apply -f deployment.yaml
echo "Creating StatefulSet..."
kubectl apply -f statefulset.yaml
echo "Creating Service..."
kubectl apply -f service.yaml
# Wait for LoadBalancer IP (Docker Desktop uses host network so it's usually localhost)
echo "Waiting for LoadBalancer to obtain an external IP (or localhost for Docker Desktop)..."
external_ip=""
attempt=0
max_attempts=4
while [ -z "$external_ip" ] && [ $attempt -lt $max_attempts ]; do
sleep 10
attempt=$((attempt+1))
external_ip=$(kubectl get svc web-app-service -n my-app --template="{{range .status.loadBalancer.ingress}}{{.ip}}{{end}}" 2>/dev/null)
if [ -z "$external_ip" ]; then
echo "Waiting for external IP... Attempt $attempt of $max_attempts"
fi
done
# Fallback to localhost if no external IP is found (common in Docker Desktop)
if [ -z "$external_ip" ]; then
external_ip="localhost"
echo ""
echo "=========================================================="
echo "Could not get external IP from LoadBalancer. Defaulting to localhost."
echo "You can try accessing your app at: http://localhost:80"
echo "Or check service status manually with:"
echo "kubectl get svc web-app-service -n my-app"
echo "=========================================================="
else
echo ""
echo "=========================================================="
echo "Application deployed successfully!"
echo "You can try accessing your app at: http://localhost:80"
echo "=========================================================="
fi

59
z2/statefulset.yaml Normal file
View File

@ -0,0 +1,59 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: stateful-app
namespace: my-app
spec:
serviceName: "stateful-app"
replicas: 1
selector:
matchLabels:
app: stateful-app
template:
metadata:
labels:
app: stateful-app
spec:
containers:
- name: stateful-app-container
image: antonin193/simple-web-app:latest
imagePullPolicy: Always
ports:
- containerPort: 5000
env:
- name: DB_HOST
value: postgres
- name: DB_PORT
value: "5432"
- name: DB_NAME
value: postgres
- name: DB_USER
value: postgres
- name: DB_PASS
value: postgres
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
volumeMounts:
- name: app-storage
mountPath: /data
readinessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 15
periodSeconds: 20
volumeClaimTemplates:
- metadata:
name: app-storage
spec:
accessModes:
- ReadWriteOnce
storageClassName: manual
resources:
requests:
storage: 1Gi

55
z2/stop-app.sh Normal file
View File

@ -0,0 +1,55 @@
#!/bin/bash
# Delete Kubernetes objects in reverse order
# Check if REGION environment variable is set
if [ -z "$REGION" ]; then
# Default region if not set
export REGION="westeurope"
echo "No region specified. Using default region: $REGION"
else
echo "Stopping application in region: $REGION"
fi
# For AKS deployments, make sure we're connected to the right cluster
if command -v az &> /dev/null; then
echo "Checking AKS connection for region $REGION..."
# You might need to adjust these parameters based on your specific Azure setup
RESOURCE_GROUP="flask-rg-$REGION"
CLUSTER_NAME="flask-aks-$REGION"
# Try to connect to the Azure cluster
if ! az aks get-credentials --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME --overwrite-existing 2>/dev/null; then
echo "Warning: Could not connect to AKS cluster in $REGION. Continuing with current kubectl context."
else
echo "Successfully connected to AKS cluster in $REGION"
fi
fi
echo "Stopping application..."
# Delete Service first to stop incoming traffic
kubectl delete -f service.yaml 2>/dev/null || echo "Service could not be deleted or does not exist."
# Delete StatefulSet and wait for pods to terminate
kubectl delete -f statefulset.yaml 2>/dev/null || echo "StatefulSet could not be deleted or does not exist."
# Delete Deployment
kubectl delete -f deployment.yaml 2>/dev/null || echo "Deployment could not be deleted or does not exist."
# Delete PostgreSQL objects
kubectl delete -f postgres-service.yaml 2>/dev/null || echo "PostgreSQL service could not be deleted or does not exist."
kubectl delete -f postgres-deployment.yaml 2>/dev/null || echo "PostgreSQL deployment could not be deleted or does not exist."
# Wait for pods to terminate
echo "Waiting for pods to terminate..."
kubectl wait --for=delete pod --selector=app=web-app -n my-app --timeout=60s 2>/dev/null || true
kubectl wait --for=delete pod --selector=app=stateful-app -n my-app --timeout=60s 2>/dev/null || true
kubectl wait --for=delete pod --selector=app=postgres -n my-app --timeout=60s 2>/dev/null || true
# Delete PersistentVolume and PersistentVolumeClaim
kubectl delete -f persistent-storage.yaml 2>/dev/null || echo "PersistentVolume and PersistentVolumeClaim could not be deleted or do not exist."
# Delete Namespace (this will delete all resources in the namespace)
kubectl delete -f namespace.yaml 2>/dev/null || echo "Namespace could not be deleted or does not exist."
echo "Application stopped in region $REGION: All Kubernetes objects have been deleted."