diff --git a/z2/Dockerfile b/z2/Dockerfile new file mode 100644 index 0000000..5c147d4 --- /dev/null +++ b/z2/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.9-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY app.py . + +CMD ["python", "app.py"] \ No newline at end of file diff --git a/z2/README.md b/z2/README.md new file mode 100644 index 0000000..aa55c72 --- /dev/null +++ b/z2/README.md @@ -0,0 +1,82 @@ +# PI Visualization Web Application on Kubernetes + +## Overview +This project deploys a Python web application using Flask on a Kubernetes cluster. The application displays the first 300 digits of PI with an interactive visualization. The deployment includes a Namespace, Deployment, StatefulSet (with PersistentVolume and PersistentVolumeClaim), and Service. + +## Application Description +- The web application visualizes PI digits at the root URL (`/`). +- Each digit appears one at a time (every 0.5 seconds by default) with a unique color. +- Users can control the animation (start, pause, reset) and adjust the speed. +- The application uses one container image built from the Dockerfile provided. + +## Containers +- **simple-web-app**: Runs the Python Flask application on port 5000. + +## Kubernetes Objects +- **Namespace**: Isolates all the resources under `my-app`. +- **Deployment**: Manages the stateless web application pods with 2 replicas for high availability. +- **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**: Exposes the web application externally via NodePort 30007. + +## Networking and Storage +- The application uses Kubernetes networking to enable communication between pods. +- The StatefulSet uses a volume claim template that binds to a PersistentVolume mounted at `/data`. + +## Container Configuration +- The container is based on Python and includes Flask. +- It exposes port 5000 to serve the web application. +- 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. + +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://:30007 + ``` + Where `` is the IP address of any of your Kubernetes nodes. + +## Troubleshooting +If you encounter issues: + +1. Check pod status: + ```bash + kubectl get pods -n my-app + ``` + +2. View pod logs: + ```bash + kubectl logs -n my-app + ``` + +3. Check service status: + ```bash + kubectl get svc -n my-app + ``` \ No newline at end of file diff --git a/z2/app.py b/z2/app.py new file mode 100644 index 0000000..97eed71 --- /dev/null +++ b/z2/app.py @@ -0,0 +1,314 @@ +from flask import Flask, render_template_string +import math + +app = Flask(__name__) + +@app.route('/') +def index(): + # Calculate PI to 300 decimal places + # Since math.pi doesn't provide that many digits, we're using a string representation + # that includes the first 300 decimal places of PI + pi_digits = "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989" + + # HTML template with embedded CSS and JavaScript + html_template = """ + + + + + + PI Digits Visualization + + + +
+

PI Digits Visualization

+ +
+ +
Digits displayed: 0 / 1002
+ +
+ + + + + +
+ +
+
Current speed: 0.5 seconds per digit
+
+ +
+

PI digits are displayed one by one with a unique color for each digit.

+
+
+ + + + + """ + + return render_template_string(html_template, pi_digits=pi_digits) + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/z2/deployment.yaml b/z2/deployment.yaml new file mode 100644 index 0000000..37e92df --- /dev/null +++ b/z2/deployment.yaml @@ -0,0 +1,40 @@ +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 + resources: + requests: + memory: "64Mi" + cpu: "100m" + limits: + memory: "128Mi" + cpu: "200m" + readinessProbe: + httpGet: + path: / + port: 5000 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: / + port: 5000 + initialDelaySeconds: 10 + periodSeconds: 20 diff --git a/z2/namespace.yaml b/z2/namespace.yaml new file mode 100644 index 0000000..ac20ed2 --- /dev/null +++ b/z2/namespace.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: my-app + labels: + name: my-app + environment: development \ No newline at end of file diff --git a/z2/persistent-storage.yaml b/z2/persistent-storage.yaml new file mode 100644 index 0000000..5413374 --- /dev/null +++ b/z2/persistent-storage.yaml @@ -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 diff --git a/z2/prepare-app.sh b/z2/prepare-app.sh new file mode 100644 index 0000000..1383fac --- /dev/null +++ b/z2/prepare-app.sh @@ -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." diff --git a/z2/requirements.txt b/z2/requirements.txt new file mode 100644 index 0000000..4c87048 --- /dev/null +++ b/z2/requirements.txt @@ -0,0 +1,3 @@ +Flask>=2.0.0 +Werkzeug>=2.0.0 +gunicorn>=20.1.0 \ No newline at end of file diff --git a/z2/service.yaml b/z2/service.yaml new file mode 100644 index 0000000..49ee316 --- /dev/null +++ b/z2/service.yaml @@ -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 \ No newline at end of file diff --git a/z2/start-app.sh b/z2/start-app.sh new file mode 100644 index 0000000..1d555f6 --- /dev/null +++ b/z2/start-app.sh @@ -0,0 +1,73 @@ +#!/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 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 diff --git a/z2/statefulset.yaml b/z2/statefulset.yaml new file mode 100644 index 0000000..646e362 --- /dev/null +++ b/z2/statefulset.yaml @@ -0,0 +1,48 @@ +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 + resources: + requests: + memory: "64Mi" + cpu: "100m" + limits: + memory: "128Mi" + cpu: "200m" + volumeMounts: + - name: app-storage + mountPath: /data + readinessProbe: + httpGet: + path: / + port: 5000 + initialDelaySeconds: 10 + periodSeconds: 20 + volumeClaimTemplates: + - metadata: + name: app-storage + spec: + accessModes: + - ReadWriteOnce + storageClassName: manual + resources: + requests: + storage: 1Gi diff --git a/z2/stop-app.sh b/z2/stop-app.sh new file mode 100644 index 0000000..0b329b1 --- /dev/null +++ b/z2/stop-app.sh @@ -0,0 +1,50 @@ +#!/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." + +# 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 + +# 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." \ No newline at end of file