ErgoHub Ομαδικό ακαδημαϊκό έργο 2025
Από τον κώδικα της εφαρμογής σε ένα Kubernetes cluster
Το ErgoHub, μια πλατφόρμα freelance: μια εφαρμογή Spring Boot και React και η διαδρομή με Docker, Jenkins και Ansible που την έκανε deploy στο Azure Kubernetes Service
- Περίοδος
- Φεβρουάριος–Ιούλιος 2025
- Ομάδα
- Ομάδα δύο ατόμων, με την Αναστασία Ζέρβα
- Πλαίσιο
- Χαροκόπειο Πανεπιστήμιο: Κατανεμημένα Συστήματα και έπειτα Βασικές Έννοιες και Εργαλεία DevOps
Επισκόπηση
Το να γραφτεί η εφαρμογή ήταν το μισό πρόβλημα. Το άλλο μισό ήταν η διαδρομή από ένα commit ως ένα cluster σε λειτουργία, και να γίνεται κάθε φορά με τον ίδιο τρόπο.
Το ErgoHub είναι μια πλατφόρμα freelance με τρεις ρόλους. Οι πελάτες αναρτούν έργα και ένας διαχειριστής εγκρίνει ή απορρίπτει το καθένα πριν το δουν οι freelancers. Οι freelancers κάνουν αίτηση, αν θέλουν με το βιογραφικό τους, και ο πελάτης δέχεται μία από τις αιτήσεις· από εκεί ξεκινά η δουλειά.
Ξεκίνησε ως εργασία για το μάθημα Κατανεμημένα Συστήματα στο Χαροκόπειο Πανεπιστήμιο. Στο μάθημα Βασικές Έννοιες και Εργαλεία DevOps το πήγαμε μέχρι το τέλος: container images, Jenkins pipelines, Ansible playbooks και deployment στο Azure Kubernetes Service.
Ήμασταν ομάδα δύο ατόμων, η Αναστασία Ζέρβα κι εγώ, και δουλέψαμε και οι δύο σε όλα τα κομμάτια: στο API, στο frontend και στο deployment. Γι’ αυτό η σελίδα μιλά σε πρώτο πληθυντικό· ο απολογισμός στο τέλος είναι δικός μου.
Ήταν ακαδημαϊκό έργο: έγινε deploy και παρουσιάστηκε στο πλαίσιο του μαθήματος, δεν λειτούργησε ως υπηρεσία. Γι’ αυτό δεν υπάρχουν αριθμοί για χρήστες, κίνηση ή διαθεσιμότητα.
Με μια ματιά
- Ρόλοι χρηστών
- Πελάτης, freelancer, διαχειριστής
- Εφαρμογή
- Java 21, Spring Boot 3, Spring Security, JWT, React, Redux Toolkit, JUnit, Jest
- Δεδομένα
- PostgreSQL, Spring Data JPA, MinIO
- Παράδοση
- Docker, Jenkins, Ansible, Azure Container Registry, AKS
- Είσοδος
- NGINX Ingress, cert-manager, Let’s Encrypt TLS
Εφαρμογή
Ένα αίτημα, από τον browser ως τα δεδομένα
Χρήστης
Πελάτης, freelancer ή διαχειριστής, στον browser
Frontend
React και Redux Toolkit, που τα σερβίρει το NGINX
API
Spring Boot 3 σε Java 21, με JWT και έλεγχο ρόλων
Δεδομένα
PostgreSQL για τις εγγραφές, MinIO για τα βιογραφικά
Παράδοση
Μια αλλαγή, από το repository ως το cluster
Κώδικας
Ένα Git repository για το API και το frontend
CI
Το Jenkins τρέχει τα tests και φτιάχνει image
Registry
Azure Container Registry, με tag τον αριθμό του build
Kubernetes
AKS, με rollout μέσω kubectl ή Ansible
Ingress
NGINX Ingress, HTTPS με Let’s Encrypt
Οι δύο διαδρομές συναντιούνται στο cluster: το frontend και το API που απαντούν σε κάθε αίτημα είναι τα images που έφτιαξε και έκανε rollout η διαδρομή της παράδοσης.
Τρεις ρόλοι, μία ροή εργασίας
Το API είναι εφαρμογή Spring Boot 3 σε Java 21. Το frontend είναι εφαρμογή React με Redux Toolkit και ξεχωριστό dashboard για κάθε ρόλο, και μιλά με το API μέσω REST.
PENDING
Ο πελάτης αναρτά ένα έργο, που περιμένει τον διαχειριστή.
APPROVED ή DENIED
Ο διαχειριστής το εγκρίνει ή το απορρίπτει. Οι freelancers βλέπουν μόνο τα εγκεκριμένα έργα.
IN_PROGRESS
Οι freelancers κάνουν αίτηση, με βιογραφικό αν θέλουν. Ο πελάτης δέχεται μία αίτηση και οι υπόλοιπες απορρίπτονται.
COMPLETED
Ο freelancer ή ο διαχειριστής σημειώνει τη δουλειά ως ολοκληρωμένη.
Πρόσβαση ανά ρόλο
Με τη σύνδεση ο χρήστης παίρνει ένα JSON Web Token, υπογεγραμμένο με κοινό κλειδί HMAC και έγκυρο για 24 ώρες, που περιέχει τον ρόλο του και αν ο λογαριασμός έχει επαληθευτεί. Το API δεν κρατά session: το Spring Security ελέγχει το token σε κάθε αίτημα και εφαρμόζει κανόνες ρόλων ανά διαδρομή. Οι κωδικοί αποθηκεύονται ως hashes BCrypt.
Εγγραφές και αρχεία
Οι χρήστες, τα έργα και οι αιτήσεις αποθηκεύονται στην PostgreSQL μέσω Spring Data JPA. Το βιογραφικό που συνοδεύει μια αίτηση ανεβαίνει σε bucket του MinIO· η αίτηση κρατά μόνο τη διαδρομή του και το API το επιστρέφει μέσω ενός endpoint λήψης.
Email
Οι διαχειριστές επαληθεύουν τους λογαριασμούς των freelancers και το API τούς ενημερώνει με email. Στο τοπικό περιβάλλον του Docker Compose τα μηνύματα κατέληγαν στο MailHog, έναν δοκιμαστικό mail server.
Tests
Το API έχει JUnit tests για τα services του, που τρέχουν σε βάση H2 στη μνήμη. Το frontend έχει tests με Jest και Testing Library. Και τα δύο τρέχουν στα pipelines πριν φτιαχτεί το image.
Από τον κώδικα στο container
Το API και το frontend έχουν το καθένα δικό του image, χτισμένο σε δύο στάδια: το πρώτο περιέχει τα εργαλεία του build, το δεύτερο μόνο ό,τι χρειάζεται για να τρέξει.
API
Το πρώτο στάδιο εγκαθιστά το Maven σε image Java 21 και πακετάρει την εφαρμογή. Το δεύτερο αντιγράφει μόνο το jar σε ένα καθαρό slim image Java 21, που ακούει στη θύρα 8080. Το build του image παραλείπει τα tests, γιατί το pipeline τα έχει ήδη τρέξει.
Frontend
Το Node 21 κάνει build την εφαρμογή React· έπειτα το NGINX σε Alpine σερβίρει τα αρχεία του build, με δική του διαμόρφωση.
Το Docker Compose έτρεχε όλο το σύστημα σε ένα μηχάνημα: PostgreSQL 15, το API, το frontend, MinIO, MailHog και το ίδιο το Jenkins.
Και οι δύο διαδρομές προς το AKS ανεβάζουν κάθε image με δύο tags: τον αριθμό του build, που το συνδέει με την εκτέλεση του pipeline που το έφτιαξε, και το latest, στο οποίο παραπέμπουν τα manifests του Kubernetes.
Έξι ορισμοί pipeline, τρεις τρόποι για deployment
Το repository έχει έξι Jenkinsfiles: ένα για το API και ένα για το frontend, για καθέναν από τρεις τρόπους deployment.
Τρεις τρόποι για deployment
AKS με kubectl
Jenkinsfile-backendJenkinsfile-frontend
- Registry
- Azure Container Registry
- Deployment
kubectl set image, έπειτα αναμονή για το rollout και έλεγχος των pods
AKS με Ansible
Jenkinsfile-backend-ansibleJenkinsfile-frontend-ansible
- Registry
- Azure Container Registry
- Deployment
- Ένα Ansible playbook στήνει όλο το σύστημα με τη σειρά και έπειτα το ελέγχει
Minikube με Ansible
Jenkinsfile-backend-minikubeJenkinsfile-frontend-minikube
- Registry
- Docker Hub
- Deployment
- Ένα Ansible playbook κάνει deploy σε τοπικό cluster Minikube
API
Build και tests
- Compile
- Unit Tests
- Integration Tests
- Package
Δημοσίευση
- Azure Login & Setup
- Build & Push Docker Image
Deployment
- Deploy to Azure AKS
- Health Check
Frontend
Build και tests
- Checkout
- Install Dependencies
- Lint
- Unit Tests
- Integration Tests
- Build
- Security Audit
Δημοσίευση
- Azure Login & Setup
- Build & Push Docker Image
Deployment
- Deploy to Azure AKS
- Health Check
- Verify Application
Και τα δύο pipelines με kubectl συνδέονται στο Azure με service principal και στο registry με token, ανεβάζουν το image, στρέφουν σε αυτό το Deployment με kubectl set image και περιμένουν έως πέντε λεπτά για το rollout, πριν εμφανίσουν τα pods. Το pipeline του frontend τρέχει επίσης ESLint και npm audit, που αναφέρουν προβλήματα χωρίς να σταματούν το build.
Τα pipelines με Ansible αναθέτουν το deployment σε ένα playbook, που στήνει το σύστημα με τη σειρά: το namespace, τη διαμόρφωση και τα secrets· την PostgreSQL, το MailHog και το MinIO, με αναμονή ώσπου να είναι έτοιμα· το API και το frontend με το νέο image, μαζί με τα Services τους· και στο τέλος το Ingress, με ελέγχους για ό,τι τρέχει.
Τι έτρεχε μέσα στο cluster
Όλα έτρεχαν σε ένα namespace, το freelance, από απλά manifests που εφαρμόζονταν με kubectl, είτε από ένα script εγκατάστασης είτε από ένα pipeline είτε από το Ansible. Δεν υπάρχει Helm chart.
Ingress
- NGINX ingress controller
- TLS από το Let’s Encrypt, μέσω cert-manager
- Ανακατεύθυνση από HTTP σε HTTPS
Διαδρομές
- /frontend-service:80
- /apibackend-service:8080
Ξεχωριστά hostnames για το API, την κονσόλα του MinIO και το MailHog
Deployments, με ένα replica το καθένα
frontend
Το NGINX με το build του React
- Service
- frontend-service:80
- Αποθήκευση
- Κανένα
- Probes
- liveness, readiness
backend
Το API σε Spring Boot
- Service
- backend-service:8080
- Αποθήκευση
- Κανένα
- Probes
- startup, liveness, readiness
postgres
PostgreSQL 15
- Service
- postgres-service:5432
- Αποθήκευση
- Volume claim 5 Gi
- Probes
- liveness, readiness
minio
Object storage για τα βιογραφικά
- Service
- minio-service:9000 :9001NodePort
- Αποθήκευση
- Volume claim 10 Gi
- Probes
- Κανένα
mailhog
Δοκιμαστικός mail server
- Service
- mailhog:8025 :1025
- Αποθήκευση
- Κανένα
- Probes
- Κανένα
Διαμόρφωση
Ένα ConfigMap, το freelance-config, και ένα Secret, που τα διαβάζουν τα workloads ως μεταβλητές περιβάλλοντος
Η PostgreSQL και το MinIO κρατούσαν τα δεδομένα τους σε persistent volume claims των 5 Gi και 10 Gi. Το καθένα έτρεχε ως Deployment με ένα replica και όχι ως StatefulSet: αρκετό για ένα deployment μαθήματος, όχι σχεδιασμός για failover.
Κάθε container είχε requests και limits για CPU και μνήμη. Τα probes του API καλούσαν το /api/home, μια διαδρομή που δεν θέλει σύνδεση, ώστε το Kubernetes να ξεχωρίζει μια εφαρμογή που ακόμη ξεκινά από μια που έχει αποτύχει.
Ένα script εγκατάστασης προετοίμασε το cluster μία φορά: εγκατέστησε τον NGINX ingress controller και το cert-manager, δημιούργησε τον issuer για το Let’s Encrypt και εφάρμοσε τα manifests. Από εκεί και πέρα, μια εκτέλεση των pipelines με kubectl χρειαζόταν μόνο να κάνει rollout ένα νέο image.
Τι χάλασε όταν ενώθηκαν τα κομμάτια
Η τελευταία εβδομάδα του Ιουνίου 2025 πήγε λιγότερο σε λειτουργίες και περισσότερο σε προβλήματα που φαίνονται μόνο όταν τα κομμάτια τρέχουν μαζί. Τέσσερα από αυτά, από το ιστορικό των commits:
Το Jenkins δεν έβρισκε το cluster
Το Jenkins έτρεχε σε container και το τοπικό cluster του Minikube βρισκόταν έξω από αυτό. Η διεύθυνση στο kubeconfig πέρασε από το
host.docker.internal, την IP του Minikube, ένα kubectl proxy και το gateway της γέφυρας του Docker. Στα pipelines για το AKS το ζήτημα λύθηκε αλλιώς: παίρνουν kubeconfig από το Azure στην αρχή κάθε εκτέλεσης.Έλεγχοι υγείας σε ένα προστατευμένο API
Κάθε διαδρομή πίσω από το Spring Security θέλει token, και ένα probe του Kubernetes δεν έχει. Στρέψαμε τα probes στο
/api/home, μια διαδρομή χωρίς σύνδεση, και προσθέσαμε startup probe με μεγαλύτερες καθυστερήσεις, ώστε μια αργή εκκίνηση να μη θεωρείται αποτυχία.Το API έμενε χωρίς μνήμη
Το container του API τερματιζόταν επειδή ξεπερνούσε τη μνήμη του. Αυξήσαμε τη μνήμη του και δώσαμε στο JVM ρητό όριο heap μέσω
JAVA_TOOL_OPTIONS, ώστε να χωρά μέσα στο όριο του container.Ονόματα και tags που έπρεπε να ταιριάζουν
Το ConfigMap ανέφερε ονόματα services που δεν ταίριαζαν με τα Services των manifests, και στα pipelines με Ansible έπρεπε να δηλωθεί ποιο tag θα γίνει deploy: ο αριθμός build του image που μόλις ανέβηκε ή το
latestγια το κομμάτι που δεν είχε αλλάξει.
Τι κράτησα από το έργο
Το έργο μετακίνησε το σημείο όπου θεωρώ ότι τελειώνει μια εφαρμογή. Δεν τελειώνει όταν περνούν τα tests· τελειώνει όταν κάτι έξω από το πρόγραμμα μπορεί να την ξεκινήσει, να την ελέγξει και να τη φτάσει.
Ο έλεγχος υγείας είναι κι αυτός interface
Το Kubernetes γνωρίζει μια εφαρμογή μόνο μέσα από τα probes της. Η διαδρομή που καλούν είναι μέρος του API όπως όλες οι άλλες, με δικούς της κανόνες πρόσβασης, κι αν είναι λάθος, μια υπηρεσία που δουλεύει κάνει restart.
Η διαμόρφωση περνά από αρχείο σε αρχείο
Ένα όνομα service σε ένα ConfigMap, ένα tag που περνά σε ένα playbook: ό,τι χάλασε ήταν τιμές γραμμένες σε ένα αρχείο και διαβασμένες σε άλλο. Θέλουν την ίδια προσοχή με τον κώδικα και ένα μόνο σημείο όπου ορίζονται.
Τα όρια αφορούν και το πρόγραμμα
Ένα όριο μνήμης στο container είναι μισή ρύθμιση. Το πρόγραμμα μέσα του έχει τη δική του εικόνα για το πόση μνήμη μπορεί να χρησιμοποιήσει, και τα δύο πρέπει να επιλέγονται μαζί.
Γραμμένο, όχι από μνήμης
Στο τέλος, η σύνδεση στο registry, το rollout ενός image και η αναμονή του ήταν βήματα σε ένα pipeline ή ένα playbook, όχι εντολές που έπρεπε κάποιος να θυμάται. Αυτό έκανε το δεύτερο deployment ίδιο με το πρώτο.