Second assignment

This commit is contained in:
Sayed Abubaker Hashimi 2025-04-15 15:49:52 +02:00
parent a6bd21c03d
commit 18f4edbf1b
38 changed files with 630 additions and 7 deletions

36
z1/deployment.yaml Normal file
View File

@ -0,0 +1,36 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-python-image-deployment
namespace: sayedhashimi
labels:
app: my-python-image
spec:
replicas: 1
selector:
matchLabels:
app: my-python-image
template:
metadata:
labels:
app: my-python-image
spec:
containers:
- name: my-python-image
image: my-python-image:latest
imagePullPolicy: IfNotPresent
command: ['python']
args: ['manage.py', 'runserver', '0.0.0.0:8002']
ports:
- containerPort: 8002
env:
- name: POSTGRES_DB
value: postgres
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
value: testpassword
- name: POSTGRES_HOST
value: postgres-db-service

View File

@ -5,21 +5,39 @@ services:
dockerfile: Dockerfile
context: .
environment:
# DATABASE DETAILS
# DATABASE DETAILS
- DBNAME=test_db
- DBUSER=postgres
- DBPASS=postgres
- DBPORT=5450
- DBHOST=0.0.0.0
- DBPORT=5432 # Corrected port for PostgreSQL
- DBHOST=postgres # Corrected host name
ports:
- "8050:1602"
- "8050:1602" # Exposes Flask app on localhost:8050
networks:
- test-net
depends_on:
- postgres # Ensures Flask starts after PostgreSQL is up
postgres:
container_name: db_postgres
image: postgres
hostname: postgres
ports:
- "5432:5432" # Exposes PostgreSQL on localhost:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db # Ensures the database is created
volumes:
- postgres-volume:/var/lib/postgresql/data
restart: unless-stopped
networks:
- test-net
volumes:
postgres-volume:
networks:
test-net:
external: true
driver: bridge

5
z1/namespace.yaml Normal file
View File

@ -0,0 +1,5 @@
apiVersion: v1
kind: Namespace
metadata:
name: sayedhashimi

15
z1/service.yaml Normal file
View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: sayedhashimi
spec:
selector:
app: my-python-image
ports:
- protocol: TCP
port: 8001
targetPort: 9376

View File

@ -0,0 +1,16 @@
# PostgreSQL StatefulSet Service
apiVersion: v1
kind: Service
metadata:
name: postgres-db-service
namespace: sayedhashimi
spec:
selector:
app: postgresql-db
type: ClusterIP
clusterIP: None
ports:
- port: 5432
targetPort: postgresql
protocol: TCP

60
z1/statefulset.yaml Normal file
View File

@ -0,0 +1,60 @@
# PostgreSQL StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgresql-db
namespace: sayedhashimi
spec:
serviceName: postgresql-db-service
selector:
matchLabels:
app: postgresql-db
template:
metadata:
labels:
app: postgresql-db
spec:
containers:
- name: postgresql-db
image: postgres:latest
imagePullPolicy: 'IfNotPresent'
volumeMounts:
- name: pv-claim
mountPath: /var/lib/postgresql/data
env:
- name: POSTGRES_DB
value: postgres
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
value: testpassword
ports:
- name: postgresql
containerPort: 5432
protocol: TCP
volumeClaimTemplates:
- metadata:
name: pv-claim
namespace: sayedhashimi
spec:
storageClassName: ''
accessModes: ['ReadWriteOnce']
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-volume
labels:
type: local
spec:
storageClassName: ''
capacity:
storage: 2Gi
accessModes: ['ReadWriteOnce']
hostPath:
path: '/mnt/pg'

20
z2/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
# Use official Node.js LTS image
FROM node:18
# Set working directory
WORKDIR /usr/src/app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm install --production
# Copy the rest of the application code
COPY . .
# Expose the app port
EXPOSE 3000
# Start the app
CMD ["node", "src/server.js"]

View File

81
z2/README.md Normal file
View File

@ -0,0 +1,81 @@
# Contact List Web Application on Kubernetes
## Description
A simple web-based contact list app with name and number fields, using Node.js/Express and MongoDB. Data is persisted in MongoDB.
## Containers
- **contact-app**: Node.js/Express web server serving the frontend and API.
- **mongo**: Official MongoDB container for data storage.
## Kubernetes Objects
- **Namespace**: `contact-app` — all resources are grouped here.
- **Deployment**: Runs the Node.js web app.
- **StatefulSet**: Runs MongoDB with persistent storage.
- **PersistentVolume/PersistentVolumeClaim**: Stores MongoDB data.
- **Service**:
- `contact-app`: Exposes the web app on a NodePort.
- `mongo`: Headless service for MongoDB.
## Virtual Networks and Volumes
- **Headless Service**: For MongoDB pod DNS discovery.
- **Named Volume**: `/data/mongo` on the host, mounted to MongoDB for persistence.
## Container Configuration
- The Node.js app uses the `MONGODB_URI` environment variable to connect to MongoDB.
- MongoDB uses a persistent volume for `/data/db`.
## Instructions
### Prepare
```bash
chmod +x prepare-app.sh start-app.sh stop-app.sh
./prepare-app.sh
```
### Run
```bash
./start-app.sh
```
### Access the App
1. Get the NodePort:
```bash
kubectl -n contact-app get service contact-app
```
2. Open in browser:
`http://<minikube_ip>:<node_port>`
- Get Minikube IP: `minikube ip`
### Pause/Stop
```bash
./stop-app.sh
```
### Delete Volumes (optional)
```bash
rm -rf /data/mongo
```
---
## View the Application
- Open the NodePort URL in your browser.
- Add and view contacts via the web UI.
---
## Notes
- Make sure Docker is running and Minikube is started.
- All kubectl commands assume your context is set to Minikube.

View File

24
z2/deployment.yaml Normal file
View File

@ -0,0 +1,24 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: contact-app
namespace: contact-app
spec:
replicas: 1
selector:
matchLabels:
app: contact-app
template:
metadata:
labels:
app: contact-app
spec:
containers:
- name: contact-app
image: contact-app:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3000
env:
- name: MONGODB_URI
value: "mongodb://mongo:27017/contacts"

View File

4
z2/namespace.yaml Normal file
View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: contact-app

View File

15
z2/package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "contact-list-app",
"version": "1.0.0",
"description": "A simple web-based contact list application.",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js"
},
"dependencies": {
"express": "^4.17.1",
"mongoose": "^8.13.2"
},
"author": "",
"license": "ISC"
}

View File

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

@ -0,0 +1,11 @@
#!/bin/bash
# filepath: prepare-app.sh
# Use Minikube's Docker daemon
eval $(minikube docker-env)
# Build Docker image for the Node.js app
docker build -t contact-app:latest .
# Create local directory for MongoDB data if not exists
sudo mkdir -p /data/mongo

View File

13
z2/service.yaml Normal file
View File

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: contact-app
namespace: contact-app
spec:
selector:
app: contact-app
ports:
- protocol: TCP
port: 3000
targetPort: 3000
type: NodePort

View File

8
z2/src/models/contact.js Normal file
View File

@ -0,0 +1,8 @@
const mongoose = require('mongoose');
const contactSchema = new mongoose.Schema({
name: { type: String, required: true },
number: { type: String, required: true }
});
module.exports = mongoose.model('Contact', contactSchema);

View File

44
z2/src/public/app.js Normal file
View File

@ -0,0 +1,44 @@
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form');
const nameInput = document.getElementById('name');
const numberInput = document.getElementById('number');
const contactList = document.getElementById('contact-list');
// Fetch and display contacts on page load
fetch('/api/contacts')
.then(res => res.json())
.then(contacts => {
contacts.forEach(addContactToList);
});
form.addEventListener('submit', async (event) => {
event.preventDefault();
const name = nameInput.value;
const number = numberInput.value;
if (name && number) {
const response = await fetch('/api/contacts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, number }),
});
if (response.ok) {
const newContact = await response.json();
addContactToList(newContact);
form.reset();
} else {
console.error('Error saving contact');
}
}
});
function addContactToList(contact) {
const li = document.createElement('li');
li.textContent = `${contact.name}: ${contact.number}`;
contactList.appendChild(li);
}
});

View File

21
z2/src/public/index.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact List</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Contact List</h1>
<form id="contact-form">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
<label for="number">Number:</label>
<input type="text" id="number" name="number" required>
<button type="submit">Save Contact</button>
</form>
<div id="contact-list"></div>
<script src="app.js"></script>
</body>
</html>

View File

50
z2/src/public/style.css Normal file
View File

@ -0,0 +1,50 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
}
h1 {
color: #333;
}
form {
background: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
input[type="text"] {
width: 100%;
padding: 10px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
background-color: #5cb85c;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #4cae4c;
}
.contact-list {
margin-top: 20px;
}
.contact-item {
background: #fff;
padding: 10px;
margin: 5px 0;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

View File

64
z2/src/routes/contacts.js Normal file
View File

@ -0,0 +1,64 @@
const express = require('express');
const router = express.Router();
const Contact = require('../models/contact');
// Create a new contact
router.post('/', async (req, res) => {
const { name, number } = req.body;
try {
const newContact = new Contact({ name, number });
await newContact.save();
res.status(201).json(newContact);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
// Get all contacts
router.get('/', async (req, res) => {
try {
const contacts = await Contact.find();
res.status(200).json(contacts);
} catch (error) {
res.status(500).json({ message: error.message });
}
});
// Update a contact
router.put('/:id', async (req, res) => {
const { id } = req.params;
const { name, number } = req.body;
try {
const updatedContact = await Contact.findByIdAndUpdate(id, { name, number }, { new: true });
if (!updatedContact) return res.status(404).json({ message: 'Contact not found' });
res.status(200).json(updatedContact);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
// Delete a contact
router.delete('/:id', async (req, res) => {
const { id } = req.params;
try {
const deletedContact = await Contact.findByIdAndDelete(id);
if (!deletedContact) return res.status(404).json({ message: 'Contact not found' });
res.status(204).send();
} catch (error) {
res.status(500).json({ message: error.message });
}
});
module.exports = router;
router.post('/', async (req, res) => { /* ... */ });
// Get all contacts
router.get('/', async (req, res) => { /* ... */ });
// Update a contact
router.put('/:id', async (req, res) => { /* ... */ });
// Delete a contact
router.delete('/:id', async (req, res) => { /* ... */ });
module.exports = router;

31
z2/src/server.js Normal file
View File

@ -0,0 +1,31 @@
const express = require('express');
const mongoose = require('mongoose');
const path = require('path');
const contactsRouter = require('./routes/contacts');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware to parse JSON bodies
app.use(express.json());
// Serve static files from the public directory
app.use(express.static(path.join(__dirname, 'public')));
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/contactlist';
// Connect to the database
mongoose.connect(MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log('Connected to the database'))
.catch(err => console.error('Database connection error:', err));
// Use contacts routes
app.use('/api/contacts', contactsRouter);
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});

View File

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

@ -0,0 +1,7 @@
#!/bin/bash
# filepath: start-app.sh
kubectl apply -f namespace.yaml
kubectl apply -f statefulset.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

View File

73
z2/statefulset.yaml Normal file
View File

@ -0,0 +1,73 @@
apiVersion: v1
kind: Service
metadata:
name: mongo
namespace: contact-app
labels:
app: mongo
spec:
ports:
- port: 27017
name: mongo
clusterIP: None
selector:
app: mongo
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongo-pv
namespace: contact-app
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/data/mongo"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongo-pvc
namespace: contact-app
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mongo
namespace: contact-app
spec:
serviceName: "mongo"
replicas: 1
selector:
matchLabels:
app: mongo
template:
metadata:
labels:
app: mongo
spec:
containers:
- name: mongo
image: mongo:6.0
ports:
- containerPort: 27017
volumeMounts:
- name: mongo-storage
mountPath: /data/db
volumeClaimTemplates:
- metadata:
name: mongo-storage
namespace: contact-app
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi

View File

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

@ -0,0 +1,7 @@
#!/bin/bash
# filepath: stop-app.sh
kubectl delete -f service.yaml
kubectl delete -f deployment.yaml
kubectl delete -f statefulset.yaml
kubectl delete -f namespace.yaml

View File