diff --git a/README.md b/README.md index 14cf0f0..28d8dc7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,102 @@ -# restaurant-ordering-system +# Restaurant Ordering System 🍽️ -Watch Youtube video for set up, run, and demo +This is a containerized and Kubernetes-ready **Restaurant Ordering System** application. +📺 **Watch Setup, Run, and Demo Video:** https://www.youtube.com/watch?v=Yf8zB4dXp7I -Give a star if you like it! +⭐ **Give a star if you like it!** + +--- + +## 🧰 Features & Technologies + +- Frontend (Vue.js) served via Docker on port **80** +- Backend (Node.js/Express) served via Docker on port **8001** +- PostgreSQL database with init script for auto table creation +- Kubernetes manifests for: + - Frontend + - Backend (with HPA) + - Database (with Persistent Volume and Claim) + - Monitoring using Prometheus and Grafana + +--- + +## 📦 Docker Images + +- Docker images are available at Docker Hub: [`sharmaaakash170`](https://hub.docker.com/u/sharmaaakash170) + +--- + +## 🚀 Kubernetes Setup + +Project structure for Kubernetes manifests: + +``` +k8s/ +├── backend/ +│ ├── deployment.yaml +│ ├── service.yaml +│ └── hpa.yaml +├── frontend/ +│ ├── deployment.yaml +│ └── service.yaml +├── database/ +│ ├── deployment.yaml +│ ├── service.yaml +│ ├── pv.yaml +│ └── pvc.yaml +├── init/ +│ └── init-db.sql +├── monitoring/ +│ ├── grafana/ +│ │ ├── deployment.yaml +│ │ └── service.yaml +│ └── prometheus/ +│ ├── deployment.yaml +│ ├── service.yaml +│ └── prometheus-config.yaml +``` + +### Apply Kubernetes Resources + +```bash +kubectl apply -f k8s/ +``` + +> Make sure your cluster is up and running and you have access to required Docker images. + +--- + +## 🗂️ Init DB Script + +Located at `k8s/init/init-db.sql` +Automatically creates necessary tables when the database pod is started. + +--- + +## 📊 Monitoring + +- **Prometheus**: Deployed with its configuration via `prometheus-config.yaml` +- **Grafana**: Deployed with service and deployment manifests + +--- + +## ✅ To-Do (Optional Enhancements) + +- Add Ingress controller for external access +- CI/CD integration with GitHub Actions or Jenkins +- Secrets management for DB credentials + +--- + +## 🙌 Contributing + +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +--- + +## 📄 License + +This project is licensed under the terms of the LICENSE file. + diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..a2f2201 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,11 @@ +FROM node:latest + +WORKDIR /app + +COPY . . + +RUN npm install + +RUN npm install prom-client + +CMD [ "npm", "start" ] \ No newline at end of file diff --git a/backend/config/database.js b/backend/config/database.js index ef1d62c..1a68759 100644 --- a/backend/config/database.js +++ b/backend/config/database.js @@ -3,13 +3,12 @@ import mysql from "mysql2"; // create the connection to database const db = mysql.createConnection({ - host: "localhost", - user: "root", - password: "", - database: "db_restaurant" + host: process.env.MYSQL_HOST || "mysql", + user: process.env.MYSQL_USER || "root", + password: process.env.MYSQL_PASSWORD || "root", + database: process.env.MYSQL_DATABASE || "db_restaurant" }); - db.connect(error => { if (error) throw error; console.log("Successfully connected to the database."); diff --git a/backend/index.js b/backend/index.js index 3d6d08e..3b47e5e 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,67 +1,81 @@ -// using nodemon so that you do not need to type node index.js every time new code saved - -// import express - is for building the Rest apis import express from "express"; - -// import body-parser - helps to parse the request and create the req.body object import bodyParser from "body-parser"; - -// import cors - provides Express middleware to enable CORS with various options, connect frontend import cors from "cors"; - -// import routes +import path from "path"; import router from "./routes/routes.js"; -// import path -import path from "path"; +// Prometheus client setup +import client from "prom-client"; -// use path +// Initialize path for static files const __dirname = path.resolve(); -// init express +// Initialize express const app = express(); -// use express json +// Prometheus setup +const register = new client.Registry(); +client.collectDefaultMetrics({ register }); + +// Custom metrics: HTTP Request Counter and Response Time Histogram +const httpRequestCounter = new client.Counter({ + name: "http_requests_total", + help: "Total number of HTTP requests", + labelNames: ["method", "route", "status_code"], +}); +register.registerMetric(httpRequestCounter); + +const responseTimeHistogram = new client.Histogram({ + name: "http_response_time_seconds", + help: "HTTP response duration in seconds", + labelNames: ["method", "route"], +}); +register.registerMetric(responseTimeHistogram); + +// Middleware to track HTTP requests and response times +app.use((req, res, next) => { + const end = responseTimeHistogram.startTimer(); + res.on("finish", () => { + // Increment the request counter + httpRequestCounter.labels(req.method, req.path, res.statusCode).inc(); + // Track response time + end({ method: req.method, route: req.route?.path || req.path }); + }); + next(); +}); + +// Middleware setup app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); - -//use cors app.use(cors()); -// use router -app.use(router); +// Metrics endpoint for Prometheus scraping (Make sure this is defined early) +app.get('/metrics', async (req, res) => { + try { + res.set('Content-Type', register.contentType); + res.end(await register.metrics()); + } catch (err) { + res.status(500).send(err); + } +}); -// // Handle production -// if (process.env.NODE_ENV === 'production'){ -// // Static folder -// app.use(express.static(__dirname + '/public/')); +// Routes setup (Make sure other routes are defined after /metrics) +app.use(router); -// // Handle SPA -// app.get(/.*/, (req,res)=> res.sendFile(__dirname + '/public/index.html')); -// } +// Static files and SPA (if needed) +app.use(express.static(path.join(__dirname, './restaurant_management/'))); -app.get('/api', function(req, res){ - res.json({ message: 'Welcome to restaurant api' }); +app.get('/*', (req, res) => { + res.sendFile(path.join(__dirname, './restaurant_management/index.html')); }); -app.use(express.static(path.join(__dirname, './restaurant_management/'))); -app.get('/*', function (req, res) { - res.sendFile(path.join(__dirname, './restaurant_management/index.html')) +// API Test endpoint (optional) +app.get('/api', (req, res) => { + res.json({ message: 'Welcome to the restaurant API' }); }); - - -// PORT +// Set up server port const PORT = process.env.PORT || 8001; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}.`); }); - -// https://www.youtube.com/watch?v=GK2TiAAxmQ0 -// https://www.bezkoder.com/node-js-rest-api-express-mysql/ -// https://www.bezkoder.com/serve-vue-app-express/ -// https://www.bezkoder.com/deploy-node-js-app-heroku-cleardb-mysql/ -// https://www.youtube.com/watch?v=W-b9KGwVECs -// https://stackoverflow.com/questions/43362014/heroku-no-default-language-could-be-detected-for-this-app-error-thrown-for-no -// https://stackoverflow.com/questions/16128395/what-is-procfile-and-web-and-worker -// https://www.youtube.com/watch?v=lwOsI8LtVEQ \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..289939b --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,21 @@ +FROM node:18-alpine AS build + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install + +COPY . . + +RUN npm run build + +FROM nginx:stable-alpine + +RUN rm -rf /usr/share/nginx/html/* + +COPY --from=build /app/dist /usr/share/nginx/html + +EXPOSE 80 + +CMD [ "nginx", "-g", "daemon off;" ] \ No newline at end of file diff --git a/frontend/src/pages/Home.vue b/frontend/src/pages/Home.vue index e4ebdf8..bb859d8 100644 --- a/frontend/src/pages/Home.vue +++ b/frontend/src/pages/Home.vue @@ -3,6 +3,7 @@
welcome foodies +

GREEN Deployment

Original taste from Mexico 😋

We guarantee to use fresh food with the best quality. Customers will enjoy Mexican cuisine with explosive, sophisticated flavors.

diff --git a/frontend/vue.config.js b/frontend/vue.config.js index 18f9fe6..95085b1 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -5,11 +5,11 @@ const path = require('path'); // }); module.exports = { - outputDir: path.resolve(__dirname, '../backend/restaurant_management'), + // outputDir: path.resolve(__dirname, '../backend/restaurant_management'), devServer: { proxy: { '/': { - target: 'http://localhost:8001' + target: 'http://backend:8001' } } } diff --git a/k8s/backend/deployment-blue.yaml b/k8s/backend/deployment-blue.yaml new file mode 100644 index 0000000..ab3ce77 --- /dev/null +++ b/k8s/backend/deployment-blue.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend-blue +spec: + replicas: 2 + selector: + matchLabels: + app: backend + version: blue + template: + metadata: + labels: + app: backend + version: blue + spec: + containers: + - name: backend + image: sharmaaakash170/node-backend:latest + resources: + limits: + memory: "256Mi" + cpu: "500m" + requests: + memory: "128Mi" + cpu: "200m" + ports: + - containerPort: 8001 + env: + - name: MYSQL_HOST + value: "mysql" + - name: MYSQL_ROOT_USER + value: "root" + - name: MYSQL_ROOT_PASSWORD + value: "root" + - name: MYSQL_DATABASE + value: "db_restaurant" + diff --git a/k8s/backend/deployment-green.yaml b/k8s/backend/deployment-green.yaml new file mode 100644 index 0000000..faa27d6 --- /dev/null +++ b/k8s/backend/deployment-green.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend-green +spec: + replicas: 2 + selector: + matchLabels: + app: backend + version: green + template: + metadata: + labels: + app: backend + version: green + spec: + containers: + - name: backend + image: sharmaaakash170/node-backend:new-version + resources: + limits: + memory: "256Mi" + cpu: "500m" + requests: + memory: "128Mi" + cpu: "200m" + ports: + - containerPort: 8001 + env: + - name: MYSQL_HOST + value: "mysql" + - name: MYSQL_ROOT_USER + value: "root" + - name: MYSQL_ROOT_PASSWORD + value: "root" + - name: MYSQL_DATABASE + value: "db_restaurant" + diff --git a/k8s/backend/hpa.yaml b/k8s/backend/hpa.yaml new file mode 100644 index 0000000..23b2323 --- /dev/null +++ b/k8s/backend/hpa.yaml @@ -0,0 +1,18 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: myapp +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: backend + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 50 diff --git a/k8s/backend/service.yaml b/k8s/backend/service.yaml new file mode 100644 index 0000000..298735d --- /dev/null +++ b/k8s/backend/service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: backend +spec: + selector: + app: backend + ports: + - port: 8001 + targetPort: 8001 diff --git a/k8s/database/deployment.yaml b/k8s/database/deployment.yaml new file mode 100644 index 0000000..fa1f2ff --- /dev/null +++ b/k8s/database/deployment.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mysql +spec: + selector: + matchLabels: + app: mysql + template: + metadata: + labels: + app: mysql + spec: + containers: + - name: mysql + image: mysql:latest + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + ports: + - containerPort: 3306 + env: + - name: MYSQL_HOST + value: "mysql" + - name: MYSQL_ROOT_USER + value: "root" + - name: MYSQL_ROOT_PASSWORD + value: "root" + - name: MYSQL_DATABASE + value: "db_restaurant" + volumeMounts: + - name: mysql-persistent-storage + mountPath: /var/lib/mysql + - name: init-script + mountPath: /docker-entrypoint-initdb.d + volumes: + - name: mysql-persistent-storage + persistentVolumeClaim: + claimName: mysql-pvc + - name: init-script + configMap: + name: mysql-initdb-script + \ No newline at end of file diff --git a/k8s/database/pv.yaml b/k8s/database/pv.yaml new file mode 100644 index 0000000..3f4e693 --- /dev/null +++ b/k8s/database/pv.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: mysql-pv +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/data/mysql" + storageClassName: "" + persistentVolumeReclaimPolicy: Retain \ No newline at end of file diff --git a/k8s/database/pvc.yaml b/k8s/database/pvc.yaml new file mode 100644 index 0000000..1c6eb0c --- /dev/null +++ b/k8s/database/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mysql-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: "" \ No newline at end of file diff --git a/k8s/database/service.yaml b/k8s/database/service.yaml new file mode 100644 index 0000000..ab75962 --- /dev/null +++ b/k8s/database/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: mysql +spec: + selector: + app: mysql + ports: + - port: 3306 + targetPort: 3306 + \ No newline at end of file diff --git a/k8s/frontend/deployment-blue.yaml b/k8s/frontend/deployment-blue.yaml new file mode 100644 index 0000000..b39cd63 --- /dev/null +++ b/k8s/frontend/deployment-blue.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend-blue +spec: + replicas: 2 + selector: + matchLabels: + app: frontend + version: blue + template: + metadata: + labels: + app: frontend + version: blue + spec: + containers: + - name: frontend + image: sharmaaakash170/frontend-vue:latest + resources: + limits: + memory: "256Mi" + cpu: "500m" + requests: + memory: "128Mi" + cpu: "200m" + ports: + - containerPort: 80 diff --git a/k8s/frontend/deployment-green.yaml b/k8s/frontend/deployment-green.yaml new file mode 100644 index 0000000..8c3e6e7 --- /dev/null +++ b/k8s/frontend/deployment-green.yaml @@ -0,0 +1,28 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend-green +spec: + replicas: 2 + selector: + matchLabels: + app: frontend + version: green + template: + metadata: + labels: + app: frontend + version: green + spec: + containers: + - name: frontend + image: sharmaaakash170/frontend-vue:v1 + resources: + limits: + memory: "256Mi" + cpu: "500m" + requests: + memory: "128Mi" + cpu: "200m" + ports: + - containerPort: 80 diff --git a/k8s/frontend/service.yaml b/k8s/frontend/service.yaml new file mode 100644 index 0000000..9f96877 --- /dev/null +++ b/k8s/frontend/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: frontend +spec: + selector: + app: frontend + version: blue + ports: + - port: 80 + targetPort: 80 diff --git a/k8s/init/init-db.sql b/k8s/init/init-db.sql new file mode 100644 index 0000000..8013515 --- /dev/null +++ b/k8s/init/init-db.sql @@ -0,0 +1,46 @@ +CREATE DATABASE IF NOT EXISTS db_restaurant; +USE db_restaurant; + +CREATE TABLE IF NOT EXISTS user ( + user_id INT AUTO_INCREMENT PRIMARY KEY, + user_name VARCHAR(100), + user_email VARCHAR(100) UNIQUE, + user_password VARCHAR(255), + user_phone VARCHAR(20), + user_birth DATE, + user_gender VARCHAR(10) +); + +CREATE TABLE IF NOT EXISTS food ( + food_id INT AUTO_INCREMENT PRIMARY KEY, + food_name VARCHAR(100), + food_price DECIMAL(10, 2) +); + +CREATE TABLE IF NOT EXISTS cart ( + user_id INT, + food_id INT, + item_qty INT, + PRIMARY KEY (user_id, food_id) +); + +CREATE TABLE IF NOT EXISTS billstatus ( + bill_id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT, + bill_status INT DEFAULT 0, + bill_paid VARCHAR(10) DEFAULT 'false' +); + +CREATE TABLE IF NOT EXISTS billdetails ( + billdetail_id INT AUTO_INCREMENT PRIMARY KEY, + bill_id INT, + food_id INT, + item_qty INT +); + +CREATE TABLE IF NOT EXISTS booktable ( + booking_id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT, + table_number INT, + booking_time DATETIME +); diff --git a/k8s/monitoring/grafana/deployment.yaml b/k8s/monitoring/grafana/deployment.yaml new file mode 100644 index 0000000..9d08e2a --- /dev/null +++ b/k8s/monitoring/grafana/deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grafana +spec: + selector: + matchLabels: + app: grafana + template: + metadata: + labels: + app: grafana + spec: + containers: + - name: grafana + image: grafana/grafana + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + ports: + - containerPort: 3000 + \ No newline at end of file diff --git a/k8s/monitoring/grafana/service.yaml b/k8s/monitoring/grafana/service.yaml new file mode 100644 index 0000000..3dc4a88 --- /dev/null +++ b/k8s/monitoring/grafana/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: grafana +spec: + selector: + app: grafana + ports: + - port: 3000 + targetPort: 3000 + \ No newline at end of file diff --git a/k8s/monitoring/prometheus/deployment.yaml b/k8s/monitoring/prometheus/deployment.yaml new file mode 100644 index 0000000..8c0656b --- /dev/null +++ b/k8s/monitoring/prometheus/deployment.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: prometheus +spec: + selector: + matchLabels: + app: prometheus + template: + metadata: + labels: + app: prometheus + spec: + containers: + - name: prometheus + image: prom/prometheus + ports: + - containerPort: 9090 + args: + - "--config.file=/etc/prometheus/prometheus.yml" + volumeMounts: + - name: config-volume + mountPath: /etc/prometheus/ + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + volumes: + - name: config-volume + configMap: + name: prometheus-config diff --git a/k8s/monitoring/prometheus/prometheus-config.yaml b/k8s/monitoring/prometheus/prometheus-config.yaml new file mode 100644 index 0000000..c29f783 --- /dev/null +++ b/k8s/monitoring/prometheus/prometheus-config.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: prometheus-config +data: + prometheus.yml: | + global: + scrape_interval: 15s + scrape_configs: + - job_name: 'backend' + metrics_path: /metrics + static_configs: + - targets: ['backend:8001'] + diff --git a/k8s/monitoring/prometheus/service.yaml b/k8s/monitoring/prometheus/service.yaml new file mode 100644 index 0000000..a0bd272 --- /dev/null +++ b/k8s/monitoring/prometheus/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: prometheus +spec: + selector: + app: prometheus + ports: + - port: 9090 + targetPort: 9090 + \ No newline at end of file