From 18f4edbf1b67265ce8de9b700388acaacef0511b Mon Sep 17 00:00:00 2001 From: Sayed Abubaker Hashimi Date: Tue, 15 Apr 2025 15:49:52 +0200 Subject: [PATCH] Second assignment --- z1/deployment.yaml | 36 ++++++++++ z1/docker-compose.yml | 32 +++++++-- z1/namespace.yaml | 5 ++ z1/service.yaml | 15 +++++ z1/statefulset-service.yaml | 16 +++++ z1/statefulset.yaml | 60 +++++++++++++++++ z2/Dockerfile | 20 ++++++ z2/Dockerfile:Zone.Identifier | 0 z2/README.md | 81 +++++++++++++++++++++++ z2/README.md:Zone.Identifier | 0 z2/deployment.yaml | 24 +++++++ z2/deployment.yaml:Zone.Identifier | 0 z2/namespace.yaml | 4 ++ z2/namespace.yaml:Zone.Identifier | 0 z2/package.json | 15 +++++ z2/package.json:Zone.Identifier | 0 z2/prepare-app.sh | 11 +++ z2/prepare-app.sh:Zone.Identifier | 0 z2/service.yaml | 13 ++++ z2/service.yaml:Zone.Identifier | 0 z2/src/models/contact.js | 8 +++ z2/src/models/contact.js:Zone.Identifier | 0 z2/src/public/app.js | 44 ++++++++++++ z2/src/public/app.js:Zone.Identifier | 0 z2/src/public/index.html | 21 ++++++ z2/src/public/index.html:Zone.Identifier | 0 z2/src/public/style.css | 50 ++++++++++++++ z2/src/public/style.css:Zone.Identifier | 0 z2/src/routes/contacts.js | 64 ++++++++++++++++++ z2/src/routes/contacts.js:Zone.Identifier | 0 z2/src/server.js | 31 +++++++++ z2/src/server.js:Zone.Identifier | 0 z2/start-app.sh | 7 ++ z2/start-app.sh:Zone.Identifier | 0 z2/statefulset.yaml | 73 ++++++++++++++++++++ z2/statefulset.yaml:Zone.Identifier | 0 z2/stop-app.sh | 7 ++ z2/stop-app.sh:Zone.Identifier | 0 38 files changed, 630 insertions(+), 7 deletions(-) create mode 100644 z1/deployment.yaml create mode 100644 z1/namespace.yaml create mode 100644 z1/service.yaml create mode 100644 z1/statefulset-service.yaml create mode 100644 z1/statefulset.yaml create mode 100644 z2/Dockerfile create mode 100644 z2/Dockerfile:Zone.Identifier create mode 100644 z2/README.md create mode 100644 z2/README.md:Zone.Identifier create mode 100644 z2/deployment.yaml create mode 100644 z2/deployment.yaml:Zone.Identifier create mode 100644 z2/namespace.yaml create mode 100644 z2/namespace.yaml:Zone.Identifier create mode 100644 z2/package.json create mode 100644 z2/package.json:Zone.Identifier create mode 100644 z2/prepare-app.sh create mode 100644 z2/prepare-app.sh:Zone.Identifier create mode 100644 z2/service.yaml create mode 100644 z2/service.yaml:Zone.Identifier create mode 100644 z2/src/models/contact.js create mode 100644 z2/src/models/contact.js:Zone.Identifier create mode 100644 z2/src/public/app.js create mode 100644 z2/src/public/app.js:Zone.Identifier create mode 100644 z2/src/public/index.html create mode 100644 z2/src/public/index.html:Zone.Identifier create mode 100644 z2/src/public/style.css create mode 100644 z2/src/public/style.css:Zone.Identifier create mode 100644 z2/src/routes/contacts.js create mode 100644 z2/src/routes/contacts.js:Zone.Identifier create mode 100644 z2/src/server.js create mode 100644 z2/src/server.js:Zone.Identifier create mode 100644 z2/start-app.sh create mode 100644 z2/start-app.sh:Zone.Identifier create mode 100644 z2/statefulset.yaml create mode 100644 z2/statefulset.yaml:Zone.Identifier create mode 100644 z2/stop-app.sh create mode 100644 z2/stop-app.sh:Zone.Identifier diff --git a/z1/deployment.yaml b/z1/deployment.yaml new file mode 100644 index 0000000..22008cf --- /dev/null +++ b/z1/deployment.yaml @@ -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 + + diff --git a/z1/docker-compose.yml b/z1/docker-compose.yml index a703184..27a9c47 100644 --- a/z1/docker-compose.yml +++ b/z1/docker-compose.yml @@ -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 diff --git a/z1/namespace.yaml b/z1/namespace.yaml new file mode 100644 index 0000000..4b20133 --- /dev/null +++ b/z1/namespace.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: sayedhashimi + diff --git a/z1/service.yaml b/z1/service.yaml new file mode 100644 index 0000000..e149f39 --- /dev/null +++ b/z1/service.yaml @@ -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 + + + diff --git a/z1/statefulset-service.yaml b/z1/statefulset-service.yaml new file mode 100644 index 0000000..5d527ea --- /dev/null +++ b/z1/statefulset-service.yaml @@ -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 + diff --git a/z1/statefulset.yaml b/z1/statefulset.yaml new file mode 100644 index 0000000..1a22580 --- /dev/null +++ b/z1/statefulset.yaml @@ -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' + diff --git a/z2/Dockerfile b/z2/Dockerfile new file mode 100644 index 0000000..639c6fb --- /dev/null +++ b/z2/Dockerfile @@ -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"] diff --git a/z2/Dockerfile:Zone.Identifier b/z2/Dockerfile:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/README.md b/z2/README.md new file mode 100644 index 0000000..f229908 --- /dev/null +++ b/z2/README.md @@ -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://:` + - 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. \ No newline at end of file diff --git a/z2/README.md:Zone.Identifier b/z2/README.md:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/deployment.yaml b/z2/deployment.yaml new file mode 100644 index 0000000..18512d7 --- /dev/null +++ b/z2/deployment.yaml @@ -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" diff --git a/z2/deployment.yaml:Zone.Identifier b/z2/deployment.yaml:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/namespace.yaml b/z2/namespace.yaml new file mode 100644 index 0000000..b5e5e3c --- /dev/null +++ b/z2/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: contact-app diff --git a/z2/namespace.yaml:Zone.Identifier b/z2/namespace.yaml:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/package.json b/z2/package.json new file mode 100644 index 0000000..c04b3c4 --- /dev/null +++ b/z2/package.json @@ -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" +} diff --git a/z2/package.json:Zone.Identifier b/z2/package.json:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/prepare-app.sh b/z2/prepare-app.sh new file mode 100644 index 0000000..e662bed --- /dev/null +++ b/z2/prepare-app.sh @@ -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 diff --git a/z2/prepare-app.sh:Zone.Identifier b/z2/prepare-app.sh:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/service.yaml b/z2/service.yaml new file mode 100644 index 0000000..021221c --- /dev/null +++ b/z2/service.yaml @@ -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 diff --git a/z2/service.yaml:Zone.Identifier b/z2/service.yaml:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/src/models/contact.js b/z2/src/models/contact.js new file mode 100644 index 0000000..4ea8743 --- /dev/null +++ b/z2/src/models/contact.js @@ -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); \ No newline at end of file diff --git a/z2/src/models/contact.js:Zone.Identifier b/z2/src/models/contact.js:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/src/public/app.js b/z2/src/public/app.js new file mode 100644 index 0000000..78f0c71 --- /dev/null +++ b/z2/src/public/app.js @@ -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); + } +}); \ No newline at end of file diff --git a/z2/src/public/app.js:Zone.Identifier b/z2/src/public/app.js:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/src/public/index.html b/z2/src/public/index.html new file mode 100644 index 0000000..3ab0513 --- /dev/null +++ b/z2/src/public/index.html @@ -0,0 +1,21 @@ + + + + + + Contact List + + + +

Contact List

+
+ + + + + +
+
+ + + \ No newline at end of file diff --git a/z2/src/public/index.html:Zone.Identifier b/z2/src/public/index.html:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/src/public/style.css b/z2/src/public/style.css new file mode 100644 index 0000000..ba839c3 --- /dev/null +++ b/z2/src/public/style.css @@ -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); +} \ No newline at end of file diff --git a/z2/src/public/style.css:Zone.Identifier b/z2/src/public/style.css:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/src/routes/contacts.js b/z2/src/routes/contacts.js new file mode 100644 index 0000000..b473950 --- /dev/null +++ b/z2/src/routes/contacts.js @@ -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; \ No newline at end of file diff --git a/z2/src/routes/contacts.js:Zone.Identifier b/z2/src/routes/contacts.js:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/src/server.js b/z2/src/server.js new file mode 100644 index 0000000..ae465fa --- /dev/null +++ b/z2/src/server.js @@ -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}`); +}); \ No newline at end of file diff --git a/z2/src/server.js:Zone.Identifier b/z2/src/server.js:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/start-app.sh b/z2/start-app.sh new file mode 100644 index 0000000..0c21a5b --- /dev/null +++ b/z2/start-app.sh @@ -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 diff --git a/z2/start-app.sh:Zone.Identifier b/z2/start-app.sh:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/statefulset.yaml b/z2/statefulset.yaml new file mode 100644 index 0000000..ec0aac7 --- /dev/null +++ b/z2/statefulset.yaml @@ -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 diff --git a/z2/statefulset.yaml:Zone.Identifier b/z2/statefulset.yaml:Zone.Identifier new file mode 100644 index 0000000..e69de29 diff --git a/z2/stop-app.sh b/z2/stop-app.sh new file mode 100644 index 0000000..5033b1e --- /dev/null +++ b/z2/stop-app.sh @@ -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 diff --git a/z2/stop-app.sh:Zone.Identifier b/z2/stop-app.sh:Zone.Identifier new file mode 100644 index 0000000..e69de29