EN
FR

Infrastructure distribuée Kubernetes

Sep 2025 — May 2026 (8 months)

Multi-cluster K8s infrastructure on Mac mini: Laravel microservices, decentralized JWT auth, Ansible IaC and Grafana observability.

Project cover
Share on LinkedIn

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):

  1. The client sends credentials to the AuthService
  2. The AuthService queries the UserService to validate credentials
  3. A JWT is signed with the RS256 private key (held only by the AuthService)
  4. The token is returned in a secure cookie
  5. Each microservice verifies incoming requests using the public key
  6. 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
Recommendations
Project cover Cloudy Pocket
React
Laravel
Git & Github

Cloudy Pocket

Budget management app in production for 2+ years. AES-256 encryption, CI/CD on 4 repos, deployment via secure bastion.

Project cover Pipeline DevSecOps — Conduite du changement

Pipeline DevSecOps — Conduite du changement

mplementing DevSecOps practices in a B2B SaaS: audit, CI/CD, 1-year change management. Results: 5+ deployments/day.

Project cover Cluvo — SaaS B2B pour clubs sportifs

Cluvo — SaaS B2B pour clubs sportifs

Co-founder & Lead Tech of a B2B SaaS: Laravel/React architecture, GitHub Actions CI/CD, dockerized VPS infra and product management.