Initial commit of the k8-mini-app project with backend, frontend, worker, and RabbitMQ integration. Added Dockerfiles, Kubernetes manifests, and necessary configurations for deployment.
This commit is contained in:
parent
4d4868dd06
commit
fe9c240033
5
backend/Dockerfile
Normal file
5
backend/Dockerfile
Normal file
@ -0,0 +1,5 @@
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN npm install
|
||||
CMD ["node", "index.js"]
|
||||
145
backend/index.js
Normal file
145
backend/index.js
Normal file
@ -0,0 +1,145 @@
|
||||
const express = require('express');
|
||||
const amqp = require('amqplib');
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// Variabili globali
|
||||
let channel = null;
|
||||
let connection = null;
|
||||
|
||||
// Funzione per connettersi a RabbitMQ
|
||||
async function connectRabbitMQ() {
|
||||
try {
|
||||
console.log('Tentativo di connessione a RabbitMQ...');
|
||||
connection = await amqp.connect('amqp://rabbitmq');
|
||||
console.log('Connessione a RabbitMQ stabilita');
|
||||
|
||||
// Gestione della chiusura della connessione
|
||||
connection.on('close', () => {
|
||||
console.warn('Connessione a RabbitMQ chiusa, tentativo di riconnessione tra 5 secondi...');
|
||||
setTimeout(connectRabbitMQ, 5000);
|
||||
});
|
||||
|
||||
// Gestione errori
|
||||
connection.on('error', (err) => {
|
||||
console.error('Errore nella connessione a RabbitMQ:', err);
|
||||
if (!connection.closed) connection.close();
|
||||
});
|
||||
|
||||
// Crea canale
|
||||
channel = await connection.createChannel();
|
||||
console.log('Canale creato');
|
||||
|
||||
// NUOVO: Tenta di cancellare tutti i consumatori esistenti
|
||||
try {
|
||||
await channel.cancel('');
|
||||
console.log('Tentativo di cancellazione consumatori esistenti');
|
||||
} catch (err) {
|
||||
console.log('Nessun consumatore da cancellare o errore:', err.message);
|
||||
}
|
||||
|
||||
// Configura la coda con durabilità (nome modificato)
|
||||
await channel.assertQueue('messages_v2', {
|
||||
durable: true, // La coda sopravvive al riavvio del broker
|
||||
autoDelete: false // La coda non viene eliminata quando non ha più consumatori
|
||||
});
|
||||
console.log('Coda "messages_v2" verificata/creata');
|
||||
|
||||
return channel;
|
||||
} catch (error) {
|
||||
console.error('Errore nella connessione a RabbitMQ:', error);
|
||||
setTimeout(connectRabbitMQ, 5000);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Inizializza il server dopo la connessione a RabbitMQ
|
||||
async function startServer() {
|
||||
try {
|
||||
await connectRabbitMQ();
|
||||
|
||||
// Endpoint per inviare messaggi alla coda
|
||||
app.post('/api/enqueue', async (req, res) => {
|
||||
try {
|
||||
const msg = JSON.stringify(req.body);
|
||||
console.log('Invio messaggio alla coda:', msg);
|
||||
|
||||
// Invia il messaggio con persistenza (nome coda modificato)
|
||||
channel.sendToQueue('messages_v2', Buffer.from(msg), {
|
||||
persistent: true // Il messaggio è persistente
|
||||
});
|
||||
|
||||
console.log('Messaggio inviato con successo');
|
||||
res.json({ status: 'success', message: 'Messaggio inviato alla coda', data: req.body });
|
||||
} catch (error) {
|
||||
console.error('Errore nell\'invio del messaggio:', error);
|
||||
res.status(500).json({ status: 'error', message: 'Errore nell\'invio del messaggio' });
|
||||
}
|
||||
});
|
||||
|
||||
// Endpoint per recuperare messaggi dalla coda
|
||||
app.get('/api/messages', async (req, res) => {
|
||||
try {
|
||||
console.log('Tentativo di recupero messaggio con GET...');
|
||||
|
||||
// Usa il metodo get invece di consume (nome coda modificato)
|
||||
const msg = await channel.get('messages_v2', { noAck: false });
|
||||
|
||||
if (msg) {
|
||||
console.log('Messaggio trovato:', msg.content.toString());
|
||||
// Conferma esplicita che il messaggio è stato ricevuto
|
||||
channel.ack(msg);
|
||||
const content = JSON.parse(msg.content.toString());
|
||||
res.json({ status: 'success', message: content });
|
||||
} else {
|
||||
console.log('Nessun messaggio trovato nella coda con GET');
|
||||
res.json({ status: 'empty', message: 'Nessun messaggio nella coda' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Errore nel recupero del messaggio:', error);
|
||||
res.status(500).json({ status: 'error', message: `Errore nel recupero: ${error.message}` });
|
||||
}
|
||||
});
|
||||
|
||||
// Endpoint di debug per verificare lo stato della coda
|
||||
app.get('/api/queue-info', async (req, res) => {
|
||||
try {
|
||||
// Nome coda modificato
|
||||
const queueInfo = await channel.assertQueue('messages_v2', { passive: true });
|
||||
console.log('Informazioni coda:', queueInfo);
|
||||
res.json({
|
||||
status: 'success',
|
||||
queue: 'messages_v2',
|
||||
messageCount: queueInfo.messageCount,
|
||||
consumerCount: queueInfo.consumerCount,
|
||||
properties: queueInfo
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Errore nel recupero info coda:', error);
|
||||
res.status(500).json({ status: 'error', message: 'Errore nel recupero info coda' });
|
||||
}
|
||||
});
|
||||
|
||||
// Avvia il server
|
||||
app.listen(3000, () => console.log('Backend in ascolto sulla porta 3000'));
|
||||
} catch (error) {
|
||||
console.error('Errore nell\'avvio del server:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Avvia il server
|
||||
startServer();
|
||||
|
||||
// Gestione della chiusura pulita
|
||||
process.on('SIGINT', async () => {
|
||||
try {
|
||||
if (channel) await channel.close();
|
||||
if (connection) await connection.close();
|
||||
} catch (error) {
|
||||
console.error('Errore nella chiusura della connessione:', error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
9
backend/package.json
Normal file
9
backend/package.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"amqplib": "^0.10.3"
|
||||
}
|
||||
}
|
||||
43
ci-build.sh
Executable file
43
ci-build.sh
Executable file
@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
# Script per buildare e pushare le immagini al registry locale
|
||||
|
||||
# Variabili
|
||||
REGISTRY="localhost:5000"
|
||||
TAG=$(date +%Y%m%d-%H%M%S) # Usa timestamp come tag
|
||||
|
||||
# Verifica che il registry locale sia in esecuzione
|
||||
if ! curl -s http://localhost:5000/v2/ > /dev/null; then
|
||||
echo "❌ Registry localhost:5000 non raggiungibile. Avvialo con: docker run -d -p 5000:5000 registry:2"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build e push delle immagini
|
||||
echo "🔨 Building backend image..."
|
||||
docker build -t $REGISTRY/backend:$TAG ./backend
|
||||
docker push $REGISTRY/backend:$TAG
|
||||
|
||||
echo "🔨 Building frontend image..."
|
||||
docker build -t $REGISTRY/frontend:$TAG ./frontend
|
||||
docker push $REGISTRY/frontend:$TAG
|
||||
|
||||
echo "🔨 Building worker image..."
|
||||
docker build -t $REGISTRY/worker:$TAG ./worker
|
||||
docker push $REGISTRY/worker:$TAG
|
||||
|
||||
# Aggiorna i file YAML di Kubernetes
|
||||
echo "📝 Updating Kubernetes manifests..."
|
||||
sed -i "s|localhost:5000/backend:latest|$REGISTRY/backend:$TAG|g" k8s/backend.yaml
|
||||
sed -i "s|localhost:5000/frontend:latest|$REGISTRY/frontend:$TAG|g" k8s/frontend.yaml
|
||||
sed -i "s|localhost:5000/worker:latest|$REGISTRY/worker:$TAG|g" k8s/worker.yaml
|
||||
|
||||
# Commit e push a Gitea se necessario
|
||||
if [ "$1" == "--push" ]; then
|
||||
echo "🚀 Pushing changes to Gitea..."
|
||||
git add k8s/
|
||||
git commit -m "Update images to tag $TAG"
|
||||
git push
|
||||
fi
|
||||
|
||||
echo "✅ Build completato. Immagini taggate come: $TAG"
|
||||
echo "📋 Per applicare le modifiche manualmente: kubectl apply -f k8s/"
|
||||
echo "🔄 Per sincronizzare con Argo CD: argocd app sync k8-mini-app"
|
||||
26
docker-compose.yml
Normal file
26
docker-compose.yml
Normal file
@ -0,0 +1,26 @@
|
||||
version: '3'
|
||||
services:
|
||||
rabbitmq:
|
||||
image: rabbitmq:3-management
|
||||
ports:
|
||||
- "15672:15672"
|
||||
- "5672:5672"
|
||||
|
||||
backend:
|
||||
build: ./backend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- rabbitmq
|
||||
|
||||
frontend:
|
||||
build: ./frontend
|
||||
ports:
|
||||
- "8080:80"
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
worker:
|
||||
build: ./worker
|
||||
depends_on:
|
||||
- rabbitmq
|
||||
5
frontend/Dockerfile
Normal file
5
frontend/Dockerfile
Normal file
@ -0,0 +1,5 @@
|
||||
# Dockerfile - Frontend
|
||||
FROM nginx:alpine
|
||||
COPY ./index.html /usr/share/nginx/html/index.html
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
105
frontend/index.html
Normal file
105
frontend/index.html
Normal file
@ -0,0 +1,105 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Frontend</title>
|
||||
<style>
|
||||
#messageDisplay {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
min-height: 50px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
button {
|
||||
margin-right: 10px;
|
||||
padding: 8px 12px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Mini Frontend</h1>
|
||||
<button onclick="sendMessage()">Invia Messaggio</button>
|
||||
<button onclick="retrieveMessage()">Recupera Messaggio</button>
|
||||
<button onclick="checkQueueInfo()">Verifica Stato Coda</button>
|
||||
|
||||
<div id="messageDisplay">
|
||||
<p>I messaggi recuperati appariranno qui...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function sendMessage() {
|
||||
try {
|
||||
const response = await fetch('/api/enqueue', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ message: 'Hello from frontend!' })
|
||||
});
|
||||
const data = await response.json();
|
||||
alert('Messaggio inviato alla coda!');
|
||||
} catch (error) {
|
||||
console.error('Errore nell\'invio del messaggio:', error);
|
||||
alert('Errore nell\'invio del messaggio!');
|
||||
}
|
||||
}
|
||||
|
||||
async function retrieveMessage() {
|
||||
try {
|
||||
const response = await fetch('/api/messages');
|
||||
const data = await response.json();
|
||||
|
||||
const displayDiv = document.getElementById('messageDisplay');
|
||||
|
||||
if (data.status === 'success') {
|
||||
displayDiv.innerHTML = `
|
||||
<h3>Messaggio recuperato:</h3>
|
||||
<pre>${JSON.stringify(data.message, null, 2)}</pre>
|
||||
`;
|
||||
} else if (data.status === 'empty') {
|
||||
displayDiv.innerHTML = `<p>Nessun messaggio disponibile nella coda.</p>`;
|
||||
} else {
|
||||
displayDiv.innerHTML = `<p>Errore: ${data.message}</p>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Errore nel recupero del messaggio:', error);
|
||||
document.getElementById('messageDisplay').innerHTML =
|
||||
`<p>Errore nella connessione al backend: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function checkQueueInfo() {
|
||||
try {
|
||||
const response = await fetch('/api/queue-info');
|
||||
const data = await response.json();
|
||||
|
||||
const displayDiv = document.getElementById('messageDisplay');
|
||||
|
||||
if (data.status === 'success') {
|
||||
displayDiv.innerHTML = `
|
||||
<h3>Informazioni Coda:</h3>
|
||||
<p>Nome: ${data.queue}</p>
|
||||
<p>Messaggi in coda: ${data.messageCount}</p>
|
||||
<p>Consumatori attivi: ${data.consumerCount}</p>
|
||||
<pre>Proprietà complete: ${JSON.stringify(data.properties, null, 2)}</pre>
|
||||
`;
|
||||
} else {
|
||||
displayDiv.innerHTML = `<p>Errore: ${data.message}</p>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Errore nel recupero info coda:', error);
|
||||
document.getElementById('messageDisplay').innerHTML =
|
||||
`<p>Errore nella connessione al backend: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
21
k8s/argocd/application.yaml
Normal file
21
k8s/argocd/application.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: k8-mini-app
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://git.algios.dev/francescoalbano/k8-mini-app.git
|
||||
targetRevision: HEAD
|
||||
path: k8s
|
||||
# Se Gitea richiede autenticazione, aggiungi:
|
||||
# username: git
|
||||
# password: <vuoto, verrà usato un segreto>
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
34
k8s/backend.yaml
Normal file
34
k8s/backend.yaml
Normal file
@ -0,0 +1,34 @@
|
||||
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: localhost:5000/backend:20250523-010002
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
tolerations:
|
||||
- key: "node.kubernetes.io/disk-pressure"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: backend
|
||||
spec:
|
||||
selector:
|
||||
app: backend
|
||||
ports:
|
||||
- port: 3000
|
||||
targetPort: 3000
|
||||
38
k8s/frontend.yaml
Normal file
38
k8s/frontend.yaml
Normal file
@ -0,0 +1,38 @@
|
||||
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: localhost:5000/frontend:20250523-010002
|
||||
ports:
|
||||
- containerPort: 80
|
||||
tolerations:
|
||||
- key: "node.kubernetes.io/disk-pressure"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
tolerations:
|
||||
- key: "node.kubernetes.io/disk-pressure"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: frontend
|
||||
spec:
|
||||
selector:
|
||||
app: frontend
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
40
k8s/ingress.yaml
Normal file
40
k8s/ingress.yaml
Normal file
@ -0,0 +1,40 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: app-ingress
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: local.test
|
||||
http:
|
||||
paths:
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: backend
|
||||
port:
|
||||
number: 3000
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: frontend
|
||||
port:
|
||||
number: 80
|
||||
- http: # Regola senza host per accesso via IP
|
||||
paths:
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: backend
|
||||
port:
|
||||
number: 3000
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: frontend
|
||||
port:
|
||||
number: 80
|
||||
37
k8s/rabbitmq.yaml
Normal file
37
k8s/rabbitmq.yaml
Normal file
@ -0,0 +1,37 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: rabbitmq
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: rabbitmq
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: rabbitmq
|
||||
spec:
|
||||
containers:
|
||||
- name: rabbitmq
|
||||
image: rabbitmq:3-management
|
||||
ports:
|
||||
- containerPort: 5672
|
||||
- containerPort: 15672
|
||||
tolerations:
|
||||
- key: "node.kubernetes.io/disk-pressure"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: rabbitmq
|
||||
spec:
|
||||
ports:
|
||||
- port: 5672
|
||||
name: amqp
|
||||
- port: 15672
|
||||
name: management
|
||||
selector:
|
||||
app: rabbitmq
|
||||
21
k8s/worker.yaml
Normal file
21
k8s/worker.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: worker
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: worker
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: worker
|
||||
spec:
|
||||
containers:
|
||||
- name: worker
|
||||
image: localhost:5000/worker:20250523-010340
|
||||
tolerations:
|
||||
- key: "node.kubernetes.io/disk-pressure"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
5
worker/Dockerfile
Normal file
5
worker/Dockerfile
Normal file
@ -0,0 +1,5 @@
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN npm install
|
||||
CMD ["node", "worker.js"]
|
||||
8
worker/package.json
Normal file
8
worker/package.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "worker",
|
||||
"version": "1.0.0",
|
||||
"main": "worker.js",
|
||||
"dependencies": {
|
||||
"amqplib": "^0.10.3"
|
||||
}
|
||||
}
|
||||
14
worker/worker.js
Normal file
14
worker/worker.js
Normal file
@ -0,0 +1,14 @@
|
||||
const amqp = require('amqplib');
|
||||
|
||||
(async () => {
|
||||
const conn = await amqp.connect('amqp://rabbitmq');
|
||||
const ch = await conn.createChannel();
|
||||
await ch.assertQueue('messages');
|
||||
console.log('Worker started, waiting for messages...');
|
||||
ch.consume('messages', (msg) => {
|
||||
if (msg !== null) {
|
||||
console.log('Received:', msg.content.toString());
|
||||
ch.ack(msg);
|
||||
}
|
||||
});
|
||||
})();
|
||||
Loading…
x
Reference in New Issue
Block a user