From fe9c2400339271dd40da43e9076fe114ace23caf Mon Sep 17 00:00:00 2001 From: Francesco Albano Date: Fri, 23 May 2025 01:05:47 +0200 Subject: [PATCH] Initial commit of the k8-mini-app project with backend, frontend, worker, and RabbitMQ integration. Added Dockerfiles, Kubernetes manifests, and necessary configurations for deployment. --- backend/Dockerfile | 5 ++ backend/index.js | 145 ++++++++++++++++++++++++++++++++++++ backend/package.json | 9 +++ ci-build.sh | 43 +++++++++++ docker-compose.yml | 26 +++++++ frontend/Dockerfile | 5 ++ frontend/index.html | 105 ++++++++++++++++++++++++++ k8s/argocd/application.yaml | 21 ++++++ k8s/backend.yaml | 34 +++++++++ k8s/frontend.yaml | 38 ++++++++++ k8s/ingress.yaml | 40 ++++++++++ k8s/rabbitmq.yaml | 37 +++++++++ k8s/worker.yaml | 21 ++++++ worker/Dockerfile | 5 ++ worker/package.json | 8 ++ worker/worker.js | 14 ++++ 16 files changed, 556 insertions(+) create mode 100644 backend/Dockerfile create mode 100644 backend/index.js create mode 100644 backend/package.json create mode 100755 ci-build.sh create mode 100644 docker-compose.yml create mode 100644 frontend/Dockerfile create mode 100644 frontend/index.html create mode 100644 k8s/argocd/application.yaml create mode 100644 k8s/backend.yaml create mode 100644 k8s/frontend.yaml create mode 100644 k8s/ingress.yaml create mode 100644 k8s/rabbitmq.yaml create mode 100644 k8s/worker.yaml create mode 100644 worker/Dockerfile create mode 100644 worker/package.json create mode 100644 worker/worker.js diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..20f1ba5 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,5 @@ +FROM node:20-alpine +WORKDIR /app +COPY . . +RUN npm install +CMD ["node", "index.js"] diff --git a/backend/index.js b/backend/index.js new file mode 100644 index 0000000..755261f --- /dev/null +++ b/backend/index.js @@ -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); + } +}); \ No newline at end of file diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..4ef2294 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,9 @@ +{ + "name": "backend", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "express": "^4.18.2", + "amqplib": "^0.10.3" + } +} diff --git a/ci-build.sh b/ci-build.sh new file mode 100755 index 0000000..168357f --- /dev/null +++ b/ci-build.sh @@ -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" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..631f5e4 --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..859e313 --- /dev/null +++ b/frontend/Dockerfile @@ -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;"] \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..0bbf233 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,105 @@ + + + + + Frontend + + + +

Mini Frontend

+ + + + +
+

I messaggi recuperati appariranno qui...

+
+ + + + \ No newline at end of file diff --git a/k8s/argocd/application.yaml b/k8s/argocd/application.yaml new file mode 100644 index 0000000..4a74db8 --- /dev/null +++ b/k8s/argocd/application.yaml @@ -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: + destination: + server: https://kubernetes.default.svc + namespace: default + syncPolicy: + automated: + prune: true + selfHeal: true \ No newline at end of file diff --git a/k8s/backend.yaml b/k8s/backend.yaml new file mode 100644 index 0000000..5e844a0 --- /dev/null +++ b/k8s/backend.yaml @@ -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 diff --git a/k8s/frontend.yaml b/k8s/frontend.yaml new file mode 100644 index 0000000..9dbfdb2 --- /dev/null +++ b/k8s/frontend.yaml @@ -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 diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 0000000..ec65357 --- /dev/null +++ b/k8s/ingress.yaml @@ -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 \ No newline at end of file diff --git a/k8s/rabbitmq.yaml b/k8s/rabbitmq.yaml new file mode 100644 index 0000000..b7f441f --- /dev/null +++ b/k8s/rabbitmq.yaml @@ -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 diff --git a/k8s/worker.yaml b/k8s/worker.yaml new file mode 100644 index 0000000..9035684 --- /dev/null +++ b/k8s/worker.yaml @@ -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" diff --git a/worker/Dockerfile b/worker/Dockerfile new file mode 100644 index 0000000..82153cb --- /dev/null +++ b/worker/Dockerfile @@ -0,0 +1,5 @@ +FROM node:20-alpine +WORKDIR /app +COPY . . +RUN npm install +CMD ["node", "worker.js"] diff --git a/worker/package.json b/worker/package.json new file mode 100644 index 0000000..501a98f --- /dev/null +++ b/worker/package.json @@ -0,0 +1,8 @@ +{ + "name": "worker", + "version": "1.0.0", + "main": "worker.js", + "dependencies": { + "amqplib": "^0.10.3" + } +} diff --git a/worker/worker.js b/worker/worker.js new file mode 100644 index 0000000..5ac961b --- /dev/null +++ b/worker/worker.js @@ -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); + } + }); +})();