Restructure project and update Helm chart for microservices
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

Directory Changes:
- Moved old unused files to 'old/' directory:
  - server.js, package.json, package-lock.json, node_modules
  - data/, public/, run_ora_map.sh, README copy.md
- Clean root structure with only backend/ and frontend/ dirs
- Updated .gitignore to ignore old directories

Helm Chart Updates (oramap/):
- Updated Chart.yaml to v0.2.0 with proper description
- Split values.yaml for separate backend and frontend configurations
- Created separate deployments for backend and frontend
- Created separate services for backend (port 3000) and frontend (port 80)
- Added ConfigMap for nginx configuration in Kubernetes
- Added volume mounts for nginx config in frontend deployment
- Updated ingress to route to frontend service
- Added proper health checks for both services
- Added resource limits and requests

Frontend Updates:
- Enhanced nginx.conf with dynamic backend routing
- Supports both Docker Compose and Kubernetes service discovery

This enables proper microservices deployment in Kubernetes with:
- Separate scaling for frontend and backend
- Independent deployments and rollbacks
- Proper service discovery and load balancing
This commit is contained in:
dvirlabs 2026-03-24 10:25:23 +02:00
parent fccd8a0166
commit c457db534b
17 changed files with 227 additions and 329 deletions

16
.gitignore vendored
View File

@ -1,3 +1,19 @@
node_modules/
package-lock.json
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
nul
# Old files
old/
old-project/
oramap-chart/

View File

@ -1,6 +0,0 @@
# How to Run
```
npm init -y
npm install express
node server.js
```

View File

@ -1,10 +0,0 @@
[
{ "family": "Kafe (קאפח)", "city": "Sana'a (צנעא)", "lat": 15.3545, "lng": 44.2064 },
{ "family": "Shiheb (שחב-שבח)", "city": "Sana'a (צנעא)", "lat": 15.3545, "lng": 44.2064 },
{ "family": "Uzeyri (עזירי-עוזרי)", "city": "Sana'a (צנעא)", "lat": 15.3545, "lng": 44.2064 },
{ "family": "Uzeyri (עזירי-עוזרי)", "city": "Manakhah (מנאכה)", "lat": 15.3019, "lng": 43.5983 },
{ "family": "Uzeyri (עזירי-עוזרי)", "city": "Dhamar (ד'מאר)", "lat": 14.5424, "lng": 44.4056 },
{ "family": "Salumi (סלומי-שלומי)", "city": "Al Kafla (אל קפלה)", "lat": 16.0240, "lng": 43.9790 },
{ "family": "Afgin (עפג'ין)", "city": "Sa'dah (צעדה)", "lat": 16.9402, "lng": 43.7639 },
{ "family": "Eraki (עראקי)", "city": "Sana'a (צנעא)", "lat": 15.3545, "lng": 44.2064 }
]

View File

@ -13,9 +13,19 @@ server {
try_files $uri $uri/ /index.html;
}
# API proxy to backend
# API proxy to backend service
# For Kubernetes: set BACKEND_HOST env var or use service name
# For Docker Compose: backend service name is 'backend'
location /api/ {
proxy_pass http://backend:3000;
# In Kubernetes, this will be: oramap-backend:3000
# In Docker Compose, this will be: backend:3000
# Default to backend:3000
set $backend_host backend;
if ($http_x_backend_host != "") {
set $backend_host $http_x_backend_host;
}
proxy_pass http://$backend_host:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';

View File

@ -1,24 +1,11 @@
apiVersion: v2
name: oramap
description: A Helm chart for Kubernetes
description: Ora Map - Family Location Mapping Application (Microservices Architecture)
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# Chart version
version: 0.2.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.16.0"
# Application version
appVersion: "1.0.0"

View File

@ -0,0 +1,45 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "oramap.fullname" . }}-nginx-config
labels:
app: {{ include "oramap.name" . }}-frontend
chart: {{ include "oramap.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
data:
default.conf: |
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# Main location
location / {
try_files $uri $uri/ /index.html;
}
# API proxy to backend service in Kubernetes
location /api/ {
proxy_pass http://{{ include "oramap.fullname" . }}-backend:{{ .Values.service.backend.port }};
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

View File

@ -1,38 +1,107 @@
---
# Backend Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "oramap.fullname" . }}
name: {{ include "oramap.fullname" . }}-backend
labels:
app: {{ include "oramap.name" . }}
app: {{ include "oramap.name" . }}-backend
chart: {{ include "oramap.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
component: backend
spec:
replicas: {{ .Values.replicaCount }}
replicas: {{ .Values.backend.replicaCount }}
selector:
matchLabels:
app: {{ include "oramap.name" . }}
app: {{ include "oramap.name" . }}-backend
release: {{ .Release.Name }}
component: backend
template:
metadata:
labels:
app: {{ include "oramap.name" . }}
app: {{ include "oramap.name" . }}-backend
release: {{ .Release.Name }}
component: backend
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
- name: backend
image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}"
imagePullPolicy: {{ .Values.backend.image.pullPolicy }}
ports:
- containerPort: {{ .Values.containerPort }}
- containerPort: {{ .Values.backend.containerPort }}
name: http
env:
- name: NODE_ENV
value: "production"
- name: PORT
value: "{{ .Values.backend.containerPort }}"
livenessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 5
periodSeconds: 10
resources:
{{- toYaml .Values.backend.resources | nindent 12 }}
---
# Frontend Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "oramap.fullname" . }}-frontend
labels:
app: {{ include "oramap.name" . }}-frontend
chart: {{ include "oramap.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
component: frontend
spec:
replicas: {{ .Values.frontend.replicaCount }}
selector:
matchLabels:
app: {{ include "oramap.name" . }}-frontend
release: {{ .Release.Name }}
component: frontend
template:
metadata:
labels:
app: {{ include "oramap.name" . }}-frontend
release: {{ .Release.Name }}
component: frontend
spec:
containers:
- name: frontend
image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}"
imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
ports:
- containerPort: {{ .Values.frontend.containerPort }}
name: http
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 10
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- toYaml .Values.frontend.resources | nindent 12 }}
volumes:
- name: nginx-config
configMap:
name: {{ include "oramap.fullname" . }}-nginx-config

View File

@ -21,9 +21,9 @@ spec:
pathType: {{ .pathType }}
backend:
service:
name: {{ include "oramap.fullname" $ }}
name: {{ include "oramap.fullname" $ }}-frontend
port:
number: {{ $.Values.service.port }}
number: {{ $.Values.service.frontend.port }}
{{- end }}
{{- end }}
{{- if .Values.ingress.tls }}

View File

@ -1,19 +1,46 @@
---
# Backend Service
apiVersion: v1
kind: Service
metadata:
name: {{ include "oramap.fullname" . }}
name: {{ include "oramap.fullname" . }}-backend
labels:
app: {{ include "oramap.name" . }}
app: {{ include "oramap.name" . }}-backend
chart: {{ include "oramap.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
component: backend
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.containerPort }}
- port: {{ .Values.service.backend.port }}
targetPort: {{ .Values.service.backend.targetPort }}
protocol: TCP
name: http
selector:
app: {{ include "oramap.name" . }}
app: {{ include "oramap.name" . }}-backend
release: {{ .Release.Name }}
component: backend
---
# Frontend Service
apiVersion: v1
kind: Service
metadata:
name: {{ include "oramap.fullname" . }}-frontend
labels:
app: {{ include "oramap.name" . }}-frontend
chart: {{ include "oramap.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
component: frontend
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.frontend.port }}
targetPort: {{ .Values.service.frontend.targetPort }}
protocol: TCP
name: http
selector:
app: {{ include "oramap.name" . }}-frontend
release: {{ .Release.Name }}
component: frontend

View File

@ -1,15 +1,45 @@
replicaCount: 1
image:
repository: harbor.dvirlabs.com/shay/oramap
tag: "1"
# Backend API configuration
backend:
image:
repository: harbor.dvirlabs.com/my-apps/oramap-backend
tag: "latest"
pullPolicy: IfNotPresent
containerPort: 3000
replicaCount: 1
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
containerPort: 3000
# Frontend Nginx configuration
frontend:
image:
repository: harbor.dvirlabs.com/my-apps/oramap-frontend
tag: "latest"
pullPolicy: IfNotPresent
containerPort: 80
replicaCount: 1
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 50m
memory: 64Mi
service:
type: ClusterIP
backend:
port: 3000
targetPort: 3000
frontend:
port: 80
targetPort: 80
ingress:
enabled: true

View File

@ -1,26 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>🗺️ Ora</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
</head>
<body>
<header>
<h1>🗺️ Ora</h1>
<div class="search-bar">
<input type="text" id="searchInput" placeholder="Enter family name..." />
<button onclick="searchFamily()">Search</button>
</div>
</header>
<main>
<div id="map"></div>
</main>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.min.js"></script>
<script src="script.js"></script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 975 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,124 +0,0 @@
let map = L.map('map').setView([15.5527, 48.5164], 6);
// Define the two tile layers
const voyager = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
attribution: '&copy; OpenStreetMap contributors &copy; CARTO',
subdomains: 'abcd',
maxZoom: 19
});
const openStreetMap = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Map data © OpenStreetMap contributors',
maxZoom: 19
});
// Add the default layer (Voyager)
voyager.addTo(map);
// Group the layers for switching
const baseMaps = {
"English Map (CartoDB Voyager)": voyager,
"Original Map (OpenStreetMap)": openStreetMap
};
// Add the layer control to the map
L.control.layers(baseMaps).addTo(map);
async function searchFamily() {
const familyName = document.getElementById('searchInput').value.trim();
if (!familyName) {
alert('Please enter a family name.');
return;
}
try {
const response = await fetch(`/api/search?family=${encodeURIComponent(familyName)}`);
const familyResult = await response.json();
if (!familyResult.length) {
alert('No matching family found.');
return;
}
clearMarkers();
familyResult.forEach(record => {
L.marker([record.lat, record.lng]).addTo(map)
.bindPopup(`<strong>${record.family}</strong><br>City: ${record.city}`)
.openPopup();
});
const first = familyResult[0];
map.setView([first.lat, first.lng], 7);
} catch (error) {
console.error('Search error:', error);
alert('Something went wrong while searching. Please try again later.');
}
}
function clearMarkers() {
map.eachLayer(layer => {
if (layer instanceof L.Marker) {
map.removeLayer(layer);
}
});
}
const familyNames = [
"Kafe (קאפח)", "Shiheb (שחב-שבח)", "Eraki (עראקי)", "Salumi (סלומי-שלומי)",
"Afgin (עפג'ין)", "Uzeyri (עזירי-עוזרי)"
// Add more families here if you like
];
// Initialize Fuse.js
const fuse = new Fuse(familyNames, {
includeScore: true,
threshold: 0.4 // Lower = stricter matching
});
const searchInput = document.getElementById('searchInput');
const suggestionsBox = document.createElement('div');
suggestionsBox.classList.add('suggestions');
searchInput.parentNode.style.position = 'relative'; // Make parent relative
searchInput.parentNode.appendChild(suggestionsBox);
searchInput.addEventListener('input', () => {
const value = searchInput.value.trim();
suggestionsBox.innerHTML = '';
if (!value) return;
const results = fuse.search(value);
results.forEach(result => {
const option = document.createElement('div');
option.textContent = result.item;
option.onclick = () => {
searchInput.value = result.item;
suggestionsBox.innerHTML = '';
};
suggestionsBox.appendChild(option);
});
});
L.control.logo = function (opts) {
return new L.Control.Logo(opts);
};
L.Control.Logo = L.Control.extend({
onAdd: function () {
const div = L.DomUtil.create('div', 'custom-logo');
div.innerHTML = `
<img src="logo.png" alt="Logo" style="height: 40px; vertical-align: middle;">
<span style="margin-left: 8px; font-weight: bold; color: #333;">Shevach</span>
`;
return div;
},
onRemove: function () {
// Nothing to clean up
}
});
L.control.logo({ position: 'bottomleft' }).addTo(map);

View File

@ -1,98 +0,0 @@
/* Reset some basic styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f4f4f4;
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* Header styles */
header {
background-color: #2c3e50;
padding: 20px;
text-align: center;
color: white;
}
h1 {
margin-bottom: 10px;
}
.search-bar {
margin-top: 10px;
}
.search-bar input {
padding: 10px;
width: 250px;
border: none;
border-radius: 5px;
margin-right: 8px;
}
.search-bar button {
padding: 10px 15px;
border: none;
border-radius: 5px;
background-color: #3498db;
color: white;
cursor: pointer;
font-weight: bold;
}
.suggestions {
background: #363251;
border: 1px solid #ccc;
position: absolute;
top: 110%; /* Just below the input */
left: 48%;
transform: translateX(-50%);
max-width: fit-content;
min-width: 200px; /* Optional: minimum size */
max-height: 200px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 8px;
transition: all 0.2s ease-in-out;
}
.suggestions div {
padding: 10px;
cursor: pointer;
}
.suggestions div:hover {
background-color: #95a1a8;
}
.custom-logo {
background: rgba(255, 255, 255, 0.9);
padding: 6px 10px;
border-radius: 8px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
font-family: sans-serif;
}
/* Map container */
#map {
flex-grow: 1;
height: 80vh;
width: 100%;
}
/* Main container */
main {
flex: 1;
display: flex;
flex-direction: column;
}

View File

@ -1,3 +0,0 @@
npm init -y
npm install express
node server.js

View File

@ -1,19 +0,0 @@
const express = require('express');
const app = express();
const families = require('./data/families.json');
app.use(express.static('public'));
app.get('/search', (req, res) => {
const query = req.query.family?.toLowerCase();
if (!query) {
return res.json([]);
}
const matches = families.filter(fam => fam.family.toLowerCase().includes(query));
res.json(matches);
});
const port = 3000;
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});