Introduction
This project was part of my MAALSI master's degree at CESI. The assignment required building a second-hand marketplace application, but the real challenge lay elsewhere: demonstrating mastery of a microservices architecture deployed on a full Kubernetes infrastructure.
I chose to go far beyond the assignment's requirements by building a production-realistic infrastructure on my personal Mac mini, with two Kubernetes clusters (staging and production), a reverse proxy, a backup VM, and a complete CI/CD pipeline. The jury commended this work with their congratulations and one of the highest grades in the class.
Application Architecture
The application is built on four Laravel microservices, each responsible for a specific functional domain:
- UserService: user management and data storage
- AuthService: centralized authentication with JWT token generation
- MessageService: user-to-user messaging
- WebSocketService: real-time instant messaging via Laravel Echo
Since Laravel is not designed for microservices, I had to deeply adapt the framework. I created "starter pack" repositories serving as reusable templates for each microservice, including a custom ValidateJWT middleware that bypasses Laravel's native auth:api system — which requires a local User model, whereas the User only exists in the UserService.
Inter-service communications use HTTP/REST via Kubernetes ClusterIP Services, without going through the Nginx Ingress Controller. This deliberate choice prevents the Ingress from becoming a single point of failure and reduces network load on that component.
Decentralized Authentication
Authentication is based on an asymmetric JWT system (RS256):
- The client sends credentials to the AuthService
- The AuthService queries the UserService to validate credentials
- A JWT is signed with the RS256 private key (held only by the AuthService)
- The token is returned in a secure cookie
- Each microservice verifies incoming requests using the public key
- An additional check against Redis ensures the token hasn't been revoked (centralized blacklist)
I initially planned to use edDSA encryption, but a Laravel incompatibility forced me to switch to RS256.
Infrastructure
The entire infrastructure runs on Multipass VMs hosted on a Mac mini, totaling approximately ten virtual machines:
- 1 reverse proxy VM: Nginx, fail2ban, TLS certificate management
- 1 staging Kubernetes cluster: control plane + workers
- 1 production Kubernetes cluster: control plane + workers
- 1 backup VM: receiving ETCD snapshots and backups from other VMs
Each VM has its own IP address on the network. The MySQL database runs in a cluster pod with a dedicated schema per microservice, ensuring data isolation. Redis is also deployed within the cluster for JWT blacklist management.
Infrastructure as Code
I developed Ansible playbooks to manage the infrastructure automatically:
- Cluster creation: a single playbook taking cluster name, number of workers, and resources (RAM, CPU) as parameters automatically creates all Multipass VMs, installs Kubernetes, and configures the complete cluster
- Scaling up: adding a worker to an existing cluster (VM creation, configuration, automatic join)
- Scaling down: clean worker removal (pod draining, cluster removal, VM deletion)
Automatic pod scaling is handled by Kubernetes' native HPA (Horizontal Pod Autoscaler), configured on the RAM metric.
CI/CD
The CI/CD pipeline is built on GitHub Actions and GitHub Container Registry:
- CI: triggered on each pull request to the staging and pre-production branches (tests, checks)
- CD: on merge, Docker image build, versioned tag, push to GHCR, then an Ansible playbook connects to the target cluster's control plane to deploy the new image
Versioned tags on the registry enable quick rollback if needed.
Observability and Load Testing
A custom Grafana dashboard provides real-time infrastructure monitoring: node count, CPU and RAM load per node, MySQL status, and most importantly MessageService metrics (load per pod, active pod count).
Load testing with K6, targeting the MessageService and its WebSocket, demonstrated automatic scaling up to 10 pods and 5,000 simultaneous users before cluster crash — limited only by the Mac mini's hardware resources. After load reduction, services recovered automatically.
A DAST analysis with ZAP was also performed manually to assess infrastructure security.
Challenges Encountered
This project was by far the most complex in my career. Major obstacles included:
- Adapting Laravel for microservices, particularly bypassing the native authentication system
- Discovering edDSA incompatibility with Laravel, requiring migration to RS256
- A missing StorageClass configuration that kept PersistentVolumeClaims stuck in pending state
- Cluster corruption following a Multipass crash that corrupted some VM kernels beyond recovery
- GitHub Runner blocks caused by macOS Keychain