exam files
This commit is contained in:
parent
52444ad27d
commit
ee55223c6c
112
sk1/README.md
Normal file
112
sk1/README.md
Normal 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
9
sk1/backend/Dockerfile
Normal 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
35
sk1/backend/app.py
Normal 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
14
sk1/cluster-issuer.yaml
Normal 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
18
sk1/default-backend.yaml
Normal 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
0
sk1/frontend/=
Normal file
0
sk1/frontend/CACHED
Normal file
0
sk1/frontend/CACHED
Normal file
10
sk1/frontend/Dockerfile
Normal file
10
sk1/frontend/Dockerfile
Normal 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
0
sk1/frontend/[auth]
Normal file
0
sk1/frontend/[internal]
Normal file
0
sk1/frontend/[internal]
Normal file
17
sk1/frontend/default.conf
Normal file
17
sk1/frontend/default.conf
Normal 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
0
sk1/frontend/exporting
Normal file
37
sk1/frontend/index.html
Normal file
37
sk1/frontend/index.html
Normal 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
0
sk1/frontend/naming
Normal file
50
sk1/frontend/script.js
Normal file
50
sk1/frontend/script.js
Normal 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
38
sk1/frontend/style.css
Normal 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;
|
||||
}
|
||||
|
0
sk1/frontend/transferring
Normal file
0
sk1/frontend/transferring
Normal file
0
sk1/frontend/writing
Normal file
0
sk1/frontend/writing
Normal file
45
sk1/ingress-svc.yaml
Normal file
45
sk1/ingress-svc.yaml
Normal 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
|
19
sk1/kubi/backend-deployment.yaml
Normal file
19
sk1/kubi/backend-deployment.yaml
Normal 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
|
10
sk1/kubi/backend-service.yaml
Normal file
10
sk1/kubi/backend-service.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: backend
|
||||
spec:
|
||||
selector:
|
||||
app: backend
|
||||
ports:
|
||||
- port: 5000
|
||||
targetPort: 5000
|
19
sk1/kubi/frontend-deployment.yaml
Normal file
19
sk1/kubi/frontend-deployment.yaml
Normal 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
|
12
sk1/kubi/frontend-service.yaml
Normal file
12
sk1/kubi/frontend-service.yaml
Normal 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
23
sk1/kubi/ingress.yaml
Normal 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
|
35
sk1/kubi/postgres-deployment.yaml
Normal file
35
sk1/kubi/postgres-deployment.yaml
Normal 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
|
11
sk1/kubi/postgres-service.yaml
Normal file
11
sk1/kubi/postgres-service.yaml
Normal 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
10
sk1/kubi/pvc.yaml
Normal 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
26
sk1/prepare-app.sh
Executable 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
26
sk1/remove-app.sh
Executable 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!"
|
Loading…
Reference in New Issue
Block a user