From ee55223c6c984cfcac332eab3f8632fa0d0936d6 Mon Sep 17 00:00:00 2001 From: shane ubento Date: Mon, 28 Apr 2025 23:20:13 +0200 Subject: [PATCH] exam files --- sk1/README.md | 112 ++++++++++++++++++++++++++++++ sk1/backend/Dockerfile | 9 +++ sk1/backend/app.py | 35 ++++++++++ sk1/cluster-issuer.yaml | 14 ++++ sk1/default-backend.yaml | 18 +++++ sk1/frontend/= | 0 sk1/frontend/CACHED | 0 sk1/frontend/Dockerfile | 10 +++ sk1/frontend/[auth] | 0 sk1/frontend/[internal] | 0 sk1/frontend/default.conf | 17 +++++ sk1/frontend/exporting | 0 sk1/frontend/index.html | 37 ++++++++++ sk1/frontend/naming | 0 sk1/frontend/script.js | 50 +++++++++++++ sk1/frontend/style.css | 38 ++++++++++ sk1/frontend/transferring | 0 sk1/frontend/writing | 0 sk1/ingress-svc.yaml | 45 ++++++++++++ sk1/kubi/backend-deployment.yaml | 19 +++++ sk1/kubi/backend-service.yaml | 10 +++ sk1/kubi/frontend-deployment.yaml | 19 +++++ sk1/kubi/frontend-service.yaml | 12 ++++ sk1/kubi/ingress.yaml | 23 ++++++ sk1/kubi/postgres-deployment.yaml | 35 ++++++++++ sk1/kubi/postgres-service.yaml | 11 +++ sk1/kubi/pvc.yaml | 10 +++ sk1/prepare-app.sh | 26 +++++++ sk1/remove-app.sh | 26 +++++++ 29 files changed, 576 insertions(+) create mode 100644 sk1/README.md create mode 100644 sk1/backend/Dockerfile create mode 100644 sk1/backend/app.py create mode 100644 sk1/cluster-issuer.yaml create mode 100644 sk1/default-backend.yaml create mode 100644 sk1/frontend/= create mode 100644 sk1/frontend/CACHED create mode 100644 sk1/frontend/Dockerfile create mode 100644 sk1/frontend/[auth] create mode 100644 sk1/frontend/[internal] create mode 100644 sk1/frontend/default.conf create mode 100644 sk1/frontend/exporting create mode 100644 sk1/frontend/index.html create mode 100644 sk1/frontend/naming create mode 100644 sk1/frontend/script.js create mode 100644 sk1/frontend/style.css create mode 100644 sk1/frontend/transferring create mode 100644 sk1/frontend/writing create mode 100644 sk1/ingress-svc.yaml create mode 100644 sk1/kubi/backend-deployment.yaml create mode 100644 sk1/kubi/backend-service.yaml create mode 100644 sk1/kubi/frontend-deployment.yaml create mode 100644 sk1/kubi/frontend-service.yaml create mode 100644 sk1/kubi/ingress.yaml create mode 100644 sk1/kubi/postgres-deployment.yaml create mode 100644 sk1/kubi/postgres-service.yaml create mode 100644 sk1/kubi/pvc.yaml create mode 100755 sk1/prepare-app.sh create mode 100755 sk1/remove-app.sh diff --git a/sk1/README.md b/sk1/README.md new file mode 100644 index 0000000..e6eeede --- /dev/null +++ b/sk1/README.md @@ -0,0 +1,112 @@ +# Calculator Web Application on Azure Kubernetes Service (AKS) + +--- + + +##Application Description +This project is a simple web-based calculator that performs basic mathematical operations. +It allows users to calculate expressions and save the results. A "Show History" button displays previously saved calculations. + +Built with: + +- Frontend: HTML/CSS/JavaScript served via Nginx + +- Backend: Flask (Python) application connected to a PostgreSQL database + +- Database: PostgreSQL 15 + +--- + +##Public Cloud Environment +- Provider: Microsoft Azure + +- Service Used: Azure Kubernetes Service (AKS) + +- Certificate Issuer: cert-manager with Let's Encrypt + +- Domain: https://aliscloudwork.tech + +--- + + +##Kubernetes and Cloud Components +- Deployments: + - Frontend Deployment (Nginx server) + - Backend Deployment (Flask server) + - PostgreSQL Deployment (Database server) + +- Services: + - Frontend Service (ClusterIP) + - Backend Service (ClusterIP) + - PostgreSQL Service (ClusterIP) + - Ingress Controller Service (LoadBalancer) + +- Ingress: + - Ingress resource used to route traffic and enable HTTPS + +- Persistent Volume: + - PostgreSQL database uses a Persistent Volume Claim (PVC) for data persistence + +- Certificate Management: + - cert-manager with Let's Encrypt to generate HTTPS certificates automatically + +--- + + +##Files Included + +- prepare-app.sh: Script to deploy all Kubernetes objects and services automatically + +- remove-app.sh: Script to delete all Kubernetes objects and services + +- kubi/*.yaml: Kubernetes deployment, service, ingress, PVC configuration files + +- backend/Dockerfile: Dockerfile for the Flask backend application + +- frontend/Dockerfile: Dockerfile for the Nginx frontend application + +- frontend/default.conf: Nginx configuration file for routing + +- README.md: This documentation file + +--- + + +##How to View and Use the Application +Open a web browser and visit: https://aliscloudwork.tech + +Use the calculator to perform operations. + +Click the "Show History" button to view saved calculations. + +--- + +##Running Scripts + +- To deploy the application: + - ```bash prepare-app.sh``` +- To delete the application: + - ```bash remove-app.sh``` + +--- + + +##External Sources and Tools Used +- DockerHub for container images + +- Let's Encrypt for SSL certificates + +- cert-manager for Kubernetes certificate automation + +- Azure CLI for AKS management + +- Official documentation for Nginx, Flask, Kubernetes YAML syntax + +--- + +##Final Notes +- HTTPS is fully functional. + +- Kubernetes ensures all components are modular and recover automatically. + +- Database uses persistent storage for calculation history. diff --git a/sk1/backend/Dockerfile b/sk1/backend/Dockerfile new file mode 100644 index 0000000..504dd68 --- /dev/null +++ b/sk1/backend/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.10-slim + +WORKDIR /app + +COPY app.py . + +RUN pip install Flask flask_sqlalchemy psycopg2-binary flask-cors + +CMD ["python", "app.py"] diff --git a/sk1/backend/app.py b/sk1/backend/app.py new file mode 100644 index 0000000..81f6af9 --- /dev/null +++ b/sk1/backend/app.py @@ -0,0 +1,35 @@ +from flask import Flask, request, jsonify +from flask_sqlalchemy import SQLAlchemy +from flask_cors import CORS + +app = Flask(__name__) +CORS(app, resources={r"/*": {"origins": "*"}}) + +app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:yourpassword@postgres-service:5432/calculator' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + +db = SQLAlchemy(app) + +class Calculation(db.Model): + id = db.Column(db.Integer, primary_key=True) + expression = db.Column(db.String(100)) + result = db.Column(db.String(100)) + +with app.app_context(): + db.create_all() + +@app.route('/save', methods=['POST']) +def save_calc(): + data = request.json + calc = Calculation(expression=data['expression'], result=data['result']) + db.session.add(calc) + db.session.commit() + return jsonify({'message': 'Saved'}), 201 + +@app.route('/history', methods=['GET']) +def history(): + calcs = Calculation.query.all() + return jsonify([{'expression': c.expression, 'result': c.result} for c in calcs]) + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/sk1/cluster-issuer.yaml b/sk1/cluster-issuer.yaml new file mode 100644 index 0000000..36910fb --- /dev/null +++ b/sk1/cluster-issuer.yaml @@ -0,0 +1,14 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: muhammed.tariq.ali.razvi@student.tuke.sk + privateKeySecretRef: + name: letsencrypt-prod + solvers: + - http01: + ingress: + class: nginx diff --git a/sk1/default-backend.yaml b/sk1/default-backend.yaml new file mode 100644 index 0000000..b3092f7 --- /dev/null +++ b/sk1/default-backend.yaml @@ -0,0 +1,18 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: default-backend + namespace: default + annotations: + kubernetes.io/ingress.class: nginx +spec: + rules: + - http: + paths: + - pathType: Prefix + path: / + backend: + service: + name: frontend + port: + number: 80 diff --git a/sk1/frontend/= b/sk1/frontend/= new file mode 100644 index 0000000..e69de29 diff --git a/sk1/frontend/CACHED b/sk1/frontend/CACHED new file mode 100644 index 0000000..e69de29 diff --git a/sk1/frontend/Dockerfile b/sk1/frontend/Dockerfile new file mode 100644 index 0000000..6f9e9b3 --- /dev/null +++ b/sk1/frontend/Dockerfile @@ -0,0 +1,10 @@ +# Use official Nginx Alpine image +FROM nginx:alpine + +# Copy static files +COPY index.html /usr/share/nginx/html/ +COPY style.css /usr/share/nginx/html/ +COPY script.js /usr/share/nginx/html/ + +# Copy nginx configuration +COPY default.conf /etc/nginx/conf.d/default.conf diff --git a/sk1/frontend/[auth] b/sk1/frontend/[auth] new file mode 100644 index 0000000..e69de29 diff --git a/sk1/frontend/[internal] b/sk1/frontend/[internal] new file mode 100644 index 0000000..e69de29 diff --git a/sk1/frontend/default.conf b/sk1/frontend/default.conf new file mode 100644 index 0000000..64d98ed --- /dev/null +++ b/sk1/frontend/default.conf @@ -0,0 +1,17 @@ +server { + listen 80; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ =404; + } + + location /history { + proxy_pass http://backend:5000; + } + + location /save { + proxy_pass http://backend:5000; + } +} diff --git a/sk1/frontend/exporting b/sk1/frontend/exporting new file mode 100644 index 0000000..e69de29 diff --git a/sk1/frontend/index.html b/sk1/frontend/index.html new file mode 100644 index 0000000..306b1fe --- /dev/null +++ b/sk1/frontend/index.html @@ -0,0 +1,37 @@ + + + + + + Calculator + + + +
+ +
+ + + + + + + + + + + + + + + + +
+ + +
+ + + + + diff --git a/sk1/frontend/naming b/sk1/frontend/naming new file mode 100644 index 0000000..e69de29 diff --git a/sk1/frontend/script.js b/sk1/frontend/script.js new file mode 100644 index 0000000..6a9789f --- /dev/null +++ b/sk1/frontend/script.js @@ -0,0 +1,50 @@ +const display = document.getElementById('display'); +const buttons = document.querySelectorAll('#buttons button'); +const historyList = document.getElementById('history-list'); +const historyBtn = document.getElementById('history'); + +let expression = ''; + +buttons.forEach(button => { + button.addEventListener('click', () => { + if (button.id === 'clear') { + expression = ''; + display.value = ''; + } else if (button.id === 'equals') { + try { + const result = eval(expression); + display.value = result; + sendToBackend(expression, result); + expression = result.toString(); + } catch (e) { + display.value = 'Error'; + expression = ''; + } + } else { + expression += button.textContent; + display.value = expression; + } + }); +}); + +historyBtn.addEventListener('click', () => { + fetch('http://aliscloudwork.tech/history') // Kubernetes DNS for backend service + .then(res => res.json()) + .then(data => { + historyList.innerHTML = ''; + data.forEach(item => { + const li = document.createElement('li'); + li.textContent = `${item.expression} = ${item.result}`; + historyList.appendChild(li); + }); + }) + .catch(err => alert('Failed to load history')); +}); + +function sendToBackend(expr, res) { + fetch('http://aliscloudwork.tech/save', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({expression: expr, result: res}) + }).catch(err => console.log('Failed to save:', err)); +} diff --git a/sk1/frontend/style.css b/sk1/frontend/style.css new file mode 100644 index 0000000..2bc3d97 --- /dev/null +++ b/sk1/frontend/style.css @@ -0,0 +1,38 @@ +body { + font-family: Arial, sans-serif; + display: flex; + justify-content: center; + padding: 20px; +} + +#calculator { + border: 2px solid #333; + padding: 10px; + width: 300px; +} + +#display { + width: 100%; + height: 40px; + font-size: 1.5em; + margin-bottom: 10px; + text-align: right; + padding-right: 10px; +} + +#buttons button { + width: 22%; + height: 40px; + margin: 3px 1%; + font-size: 1.2em; +} + +#history-list { + margin-top: 10px; + list-style-type: none; + padding-left: 0; + max-height: 150px; + overflow-y: auto; + border-top: 1px solid #ccc; +} + diff --git a/sk1/frontend/transferring b/sk1/frontend/transferring new file mode 100644 index 0000000..e69de29 diff --git a/sk1/frontend/writing b/sk1/frontend/writing new file mode 100644 index 0000000..e69de29 diff --git a/sk1/ingress-svc.yaml b/sk1/ingress-svc.yaml new file mode 100644 index 0000000..8156962 --- /dev/null +++ b/sk1/ingress-svc.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app.kubernetes.io/component":"controller","app.kubernetes.io/instance":"ingress-nginx","app.kubernetes.io/name":"ingress-nginx","app.kubernetes.io/part-of":"ingress-nginx","app.kubernetes.io/version":"1.12.1"},"name":"ingress-nginx-controller","namespace":"ingress-nginx"},"spec":{"externalTrafficPolicy":"Local","ipFamilies":["IPv4"],"ipFamilyPolicy":"SingleStack","ports":[{"appProtocol":"http","name":"http","port":80,"protocol":"TCP","targetPort":"http"},{"appProtocol":"https","name":"https","port":443,"protocol":"TCP","targetPort":"https"}],"selector":{"app.kubernetes.io/component":"controller","app.kubernetes.io/instance":"ingress-nginx","app.kubernetes.io/name":"ingress-nginx"},"type":"LoadBalancer"}} + creationTimestamp: "2025-04-28T13:26:46Z" + finalizers: + - service.kubernetes.io/load-balancer-cleanup + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + app.kubernetes.io/part-of: ingress-nginx + app.kubernetes.io/version: 1.12.1 + name: ingress-nginx-controller + namespace: ingress-nginx + resourceVersion: "54668" + uid: 346026d8-8939-400c-985e-c63915980d81 +spec: + allocateLoadBalancerNodePorts: true + clusterIP: 10.0.10.162 + clusterIPs: + - 10.0.10.162 + externalTrafficPolicy: Local + healthCheckNodePort: 31025 + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 80 + - name: https + port: 443 + protocol: TCP + targetPort: 443 + selector: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx + sessionAffinity: None + type: LoadBalancer diff --git a/sk1/kubi/backend-deployment.yaml b/sk1/kubi/backend-deployment.yaml new file mode 100644 index 0000000..a37f7a8 --- /dev/null +++ b/sk1/kubi/backend-deployment.yaml @@ -0,0 +1,19 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend +spec: + replicas: 1 + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + spec: + containers: + - name: backend + image: ali0313/backend:latest + ports: + - containerPort: 5000 diff --git a/sk1/kubi/backend-service.yaml b/sk1/kubi/backend-service.yaml new file mode 100644 index 0000000..4414135 --- /dev/null +++ b/sk1/kubi/backend-service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: backend +spec: + selector: + app: backend + ports: + - port: 5000 + targetPort: 5000 diff --git a/sk1/kubi/frontend-deployment.yaml b/sk1/kubi/frontend-deployment.yaml new file mode 100644 index 0000000..7194c72 --- /dev/null +++ b/sk1/kubi/frontend-deployment.yaml @@ -0,0 +1,19 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - name: frontend + image: ali0313/frontend:latest + ports: + - containerPort: 80 diff --git a/sk1/kubi/frontend-service.yaml b/sk1/kubi/frontend-service.yaml new file mode 100644 index 0000000..0682f2b --- /dev/null +++ b/sk1/kubi/frontend-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: frontend +spec: + selector: + app: frontend + ports: + - protocol: TCP + port: 80 + targetPort: 80 + type: ClusterIP diff --git a/sk1/kubi/ingress.yaml b/sk1/kubi/ingress.yaml new file mode 100644 index 0000000..d411d50 --- /dev/null +++ b/sk1/kubi/ingress.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: app-ingress + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" +spec: + ingressClassName: nginx + tls: + - hosts: + - aliscloudwork.tech + secretName: tls-secret + rules: + - host: aliscloudwork.tech + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: frontend + port: + number: 80 diff --git a/sk1/kubi/postgres-deployment.yaml b/sk1/kubi/postgres-deployment.yaml new file mode 100644 index 0000000..3ad2b64 --- /dev/null +++ b/sk1/kubi/postgres-deployment.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres:15 + env: + - name: POSTGRES_DB + value: calculator + - name: POSTGRES_USER + value: postgres + - name: POSTGRES_PASSWORD + value: yourpassword + - name: PGDATA + value: /var/lib/postgresql/data/pgdata # ← Keep this + ports: + - containerPort: 5432 + volumeMounts: + - mountPath: /var/lib/postgresql/data # ← CHANGE HERE (one level up) + name: pgdata + volumes: + - name: pgdata + persistentVolumeClaim: + claimName: pgdata-pvc diff --git a/sk1/kubi/postgres-service.yaml b/sk1/kubi/postgres-service.yaml new file mode 100644 index 0000000..99ac9a9 --- /dev/null +++ b/sk1/kubi/postgres-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres-service +spec: + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 + diff --git a/sk1/kubi/pvc.yaml b/sk1/kubi/pvc.yaml new file mode 100644 index 0000000..7580530 --- /dev/null +++ b/sk1/kubi/pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: pgdata-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/sk1/prepare-app.sh b/sk1/prepare-app.sh new file mode 100755 index 0000000..b55933f --- /dev/null +++ b/sk1/prepare-app.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +echo "Starting application deployment..." +# Apply Persistent Volume Claim +kubectl apply -f kubi/pvc.yaml + +# Deploy PostgreSQL +kubectl apply -f kubi/postgres-deployment.yaml +kubectl apply -f kubi/postgres-service.yaml + +# Deploy Backend +kubectl apply -f kubi/backend-deployment.yaml +kubectl apply -f kubi/backend-service.yaml + +# Deploy Frontend +kubectl apply -f kubi/frontend-deployment.yaml +kubectl apply -f kubi/frontend-service.yaml + + +# Apply cert-manager ClusterIssuer +kubectl apply -f cluster-issuer.yaml + +# Apply Ingress +kubectl apply -f kubi/ingress.yaml + +echo "Deployment completed successfully!" diff --git a/sk1/remove-app.sh b/sk1/remove-app.sh new file mode 100755 index 0000000..6b08373 --- /dev/null +++ b/sk1/remove-app.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +echo "Starting application removal..." + +# Delete Ingress +kubectl delete -f kubi/ingress.yaml + +# Delete cert-manager ClusterIssuer +kubectl delete -f cluster-issuer.yaml + +# Delete Frontend +kubectl delete -f kubi/frontend-service.yaml +kubectl delete -f kubi/frontend-deployment.yaml + +# Delete Backend +kubectl delete -f kubi/backend-service.yaml +kubectl delete -f kubi/backend-deployment.yaml + +# Delete PostgreSQL +kubectl delete -f kubi/postgres-service.yaml +kubectl delete -f kubi/postgres-deployment.yaml + +# Delete Persistent Volume Claim +kubectl delete -f kubi/pvc.yaml + +echo "Application resources removed successfully!"