exam files

This commit is contained in:
Muhammed Tariq Ali Razvi 2025-04-28 23:20:13 +02:00
parent 52444ad27d
commit ee55223c6c
29 changed files with 576 additions and 0 deletions

112
sk1/README.md Normal file
View File

@ -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.

9
sk1/backend/Dockerfile Normal file
View File

@ -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"]

35
sk1/backend/app.py Normal file
View File

@ -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)

14
sk1/cluster-issuer.yaml Normal file
View File

@ -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

18
sk1/default-backend.yaml Normal file
View File

@ -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

0
sk1/frontend/= Normal file
View File

0
sk1/frontend/CACHED Normal file
View File

10
sk1/frontend/Dockerfile Normal file
View File

@ -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

0
sk1/frontend/[auth] Normal file
View File

0
sk1/frontend/[internal] Normal file
View File

17
sk1/frontend/default.conf Normal file
View File

@ -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;
}
}

0
sk1/frontend/exporting Normal file
View File

37
sk1/frontend/index.html Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Calculator</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="calculator">
<input type="text" id="display" readonly />
<div id="buttons">
<button class="num">7</button>
<button class="num">8</button>
<button class="num">9</button>
<button class="op">/</button>
<button class="num">4</button>
<button class="num">5</button>
<button class="num">6</button>
<button class="op">*</button>
<button class="num">1</button>
<button class="num">2</button>
<button class="num">3</button>
<button class="op">-</button>
<button class="num">0</button>
<button id="clear">C</button>
<button id="equals">=</button>
<button class="op">+</button>
</div>
<button id="history">Show History</button>
<ul id="history-list"></ul>
</div>
<script src="script.js"></script>
</body>
</html>

0
sk1/frontend/naming Normal file
View File

50
sk1/frontend/script.js Normal file
View File

@ -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));
}

38
sk1/frontend/style.css Normal file
View File

@ -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;
}

View File

0
sk1/frontend/writing Normal file
View File

45
sk1/ingress-svc.yaml Normal file
View File

@ -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

View File

@ -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

View File

@ -0,0 +1,10 @@
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
selector:
app: backend
ports:
- port: 5000
targetPort: 5000

View File

@ -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

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

23
sk1/kubi/ingress.yaml Normal file
View File

@ -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

View File

@ -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

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: postgres-service
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432

10
sk1/kubi/pvc.yaml Normal file
View File

@ -0,0 +1,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pgdata-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

26
sk1/prepare-app.sh Executable file
View File

@ -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!"

26
sk1/remove-app.sh Executable file
View File

@ -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!"