Skip to content

Latest commit

 

History

History
541 lines (409 loc) · 14.8 KB

File metadata and controls

541 lines (409 loc) · 14.8 KB

DevOps Stack

API REST construite avec FastAPI et PostgreSQL pour gérer une liste de courses. Déployée sur Kubernetes (k3s) via un pipeline GitOps complet : Gitea → Gitea Actions → ArgoCD → k3s.

Architecture


Prérequis — environnement de lab

Ce lab a été réalisé sur une VM Ubuntu Desktop importée depuis osboxes.org (OVA Ubuntu 24.04) et lancée sur VMware Workstation.

Configuration VM recommandée

Ressource Minimum Recommandé Lab utilisé
CPU 2 vCPU 4 vCPU 4 vCPU
RAM 6 Go 8 Go 8 Go
Stockage 30 Go 50 Go 50 Go
Réseau NAT NAT ou Bridged NAT

Avec moins de 6 Go de RAM, les pods Prometheus + Grafana peuvent être en OOMKilled. Si les ressources sont limitées, garder le namespace monitoring scalé à 0 et ne le démarrer qu'au moment des tests.

Logiciels requis sur la VM

# Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER   # puis se reconnecter
 
# Git
sudo apt-get install -y git
 
# curl, jq (utilisés dans les commandes de ce README)
sudo apt-get install -y curl jq

Importer l'OVA sur VMware Workstation

1. Télécharge l'OVA Ubuntu 24.04 sur https://www.osboxes.org/ubuntu/
2. VMware Workstation → File → Open → sélectionne le fichier .ova
3. Ajuste les ressources (CPU / RAM / Disk) avant de démarrer
4. Démarre la VM — login : osboxes / mot de passe : osboxes.org

Stack technique

Couche Technologie
API FastAPI (Python 3.12)
Base de données PostgreSQL 16
ORM SQLAlchemy
Serveur ASGI Uvicorn
Conteneurisation Docker
Orchestration k3s (Kubernetes)
Packaging K8s Helm
GitOps / CD ArgoCD
CI Gitea Actions
Monitoring Prometheus + Grafana
Tests de charge k6

Structure du projet

devops-stack/
├── app/                            ← code source de l'API
│   ├── main.py                     ← point d'entrée FastAPI + exposition /metrics
│   ├── db.py                       ← config SQLAlchemy (pool de connexions)
│   ├── Dockerfile                  ← image de production
│   ├── docker-compose.yml          ← dev local avec PostgreSQL
│   ├── requirements.txt
│   ├── models/
│   │   └── item.py                 ← modèle SQLAlchemy (table products)
│   ├── schemas/
│   │   └── item.py                 ← schémas Pydantic (validation entrée/sortie)
│   ├── routers/
│   │   └── items.py                ← endpoints /products
│   ├── services/
│   │   └── shopping_service.py     ← logique métier
│   └── utils/
├── helm/
│   └── shopping-api/               ← Helm chart de l'application
│       ├── Chart.yaml
│       ├── values.yaml             ← config (image tag, replicas, ressources...)
│       └── templates/
│           ├── deployment.yaml
│           ├── service.yaml
│           ├── ingress.yaml
│           ├── middleware.yaml     ← Traefik StripPrefix /api
│           └── secret.yaml        ← DATABASE_URL injectée via K8s Secret
├── argocd/
│   └── application.yaml            ← manifeste ArgoCD (source Gitea → cluster)
├── k6/
│   └── shopping-api-test.js        ← scénario de test de charge
├── runner/                         ← configuration du runner Gitea Actions
│   ├── install-runner.sh           ← script d'installation automatisé
│   ├── act-runner.service          ← service systemd (démarrage au boot)
│   └── README.md                   ← documentation du runner
└── .gitea/
    └── workflows/
        └── ci.yaml                 ← pipeline CI (build → push → deploy)

Endpoints

Méthode URL Description
GET / Healthcheck
GET /docs Swagger UI
GET /metrics Métriques Prometheus
POST /products Créer un produit
GET /products Lister les produits (filtres : category, bought)
GET /products/{id} Récupérer un produit
PATCH /products/{id} Modifier un produit (quantité, acheté)
DELETE /products/{id} Supprimer un produit
POST /products/{id}/favorite Ajouter aux favoris
DELETE /products/{id}/favorite Retirer des favoris
POST /products/from-favorites Ajouter les favoris à la liste
GET /products/favorites Lister les favoris
GET /products/history Historique des achats
GET /products/categories Lister les catégories disponibles

Lancer en local (Docker Compose)

cd app/
docker compose up --build

L'API est disponible sur http://localhost:8000. Swagger UI sur http://localhost:8000/docs.

Variables d'environnement utilisées :

Variable Valeur par défaut Description
DATABASE_URL postgresql+psycopg2://shopping:shopping@localhost:5432/shopping_db URL de connexion PostgreSQL
PYTHONUNBUFFERED 1 Logs Python non bufférisés

Pipeline CI/CD

Schéma du flux

git push app/**
       │
       ▼
   Gitea (repo)
       │  déclenche
       ▼
Gitea Actions (runner local)
       │
       ├─ docker build -t localhost:5000/shopping-api:{tag}
       ├─ docker push localhost:5000/shopping-api:{tag}
       ├─ sed values.yaml → tag: "{tag}"
       └─ git commit + push
                │
                ▼
           ArgoCD détecte le changement dans values.yaml
                │
                ▼
        kubectl apply (RollingUpdate — zéro downtime)

Installation du runner CI

Le runner (act_runner) doit être installé une seule fois sur la VM host. Il tourne directement sur le host — pas dans un container — pour avoir accès au daemon Docker local et pouvoir builder des images.

1. Récupère le token d'enregistrement sur Gitea :

http://<GITEA_IP>:30300/<user>/<repo>/settings/actions/runners
→ "Create new runner" → copie le token affiché

2. Lance le script d'installation :

chmod +x runner/install-runner.sh
./runner/install-runner.sh <GITEA_IP> <TOKEN>

# Exemple :
./runner/install-runner.sh 192.168.1.100 ClOEHoZ1PAJHFNvR...

Le script télécharge le binaire act_runner, l'enregistre sur Gitea avec le label ubuntu-latest:host et installe un service systemd pour le démarrer automatiquement au boot.

3. Vérifie que le runner est actif :

sudo systemctl status act-runner

Puis sur Gitea : http://<GITEA_IP>:30300/<user>/<repo>/settings/actions/runnerslocal-runner doit apparaître avec un point vert.

Commandes utiles :

sudo journalctl -u act-runner -f    # logs en temps réel
sudo systemctl restart act-runner   # redémarrer
sudo systemctl stop act-runner      # arrêter (désactive le CI)

Déclencher le CI

Le CI se déclenche automatiquement sur tout push sur main qui modifie un fichier sous app/. Les commits qui touchent uniquement helm/ sont ignorés (ce sont les commits automatiques du CI lui-même — évite la boucle infinie).

# Exemple : modifier l'app et pousser
vim app/main.py
git add app/
git commit -m "feat: ma modification"
git push

Suivre l'exécution : http://localhost:30300/asadiakhou/devops-stack/actions


Installation de l'infrastructure

Cette section couvre l'installation complète du stack de zéro sur une VM Linux (Ubuntu 24).

1. k3s

curl -sfL https://get.k3s.io | sh -

# Configure kubectl sans sudo
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
export KUBECONFIG=~/.kube/config

# Vérifie
kubectl get nodes   # doit afficher le nœud en Ready

2. Helm

curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Ajoute les repos utilisés
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add gitea-charts https://dl.gitea.com/charts/
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

3. Registry Docker local

k3s a besoin d'un registry accessible pour puller les images buildées par le CI.

# Lance le registry
docker run -d -p 5000:5000 --restart=always --name registry registry:2

# Configure k3s pour faire confiance à ce registry (HTTP)
sudo mkdir -p /etc/rancher/k3s
sudo tee /etc/rancher/k3s/registries.yaml <<EOF
mirrors:
  "localhost:5000":
    endpoint:
      - "http://localhost:5000"
EOF

sudo systemctl restart k3s

4. Gitea

helm install gitea gitea-charts/gitea \
  --set service.http.type=NodePort \
  --set service.http.nodePort=30300

# Attends que les pods soient Running
kubectl get pods -l app.kubernetes.io/name=gitea -w

Accès : http://localhost:30300 — crée un compte admin, puis un repo devops-stack.

Clone et pousse le contenu de ce repo :

git remote set-url origin http://localhost:30300/<user>/devops-stack.git
git push -u origin main

5. PostgreSQL

helm install shopping-db bitnami/postgresql \
  --set auth.username=shopping \
  --set auth.password=shopping \
  --set auth.database=shopping_db

# Vérifie
kubectl get pods -l app.kubernetes.io/name=postgresql -w

Le Service créé s'appelle shopping-db-postgresql — c'est le hostname utilisé dans DATABASE_URL.

6. ArgoCD

kubectl create namespace argocd
kubectl apply -n argocd \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Expose l'UI en NodePort
kubectl patch svc argocd-server -n argocd \
  -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "nodePort": 30443, "targetPort": 8080}]}}'

# Récupère le mot de passe admin
kubectl get secret argocd-initial-admin-secret -n argocd \
  -o jsonpath="{.data.password}" | base64 -d && echo

# Attends que tous les pods ArgoCD soient Running
kubectl get pods -n argocd -w

Accès UI : https://localhost:30443 (login : admin).

Crée le projet par défaut et déploie l'application :

kubectl apply -f - <<EOF
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: default
  namespace: argocd
spec:
  sourceRepos: ['*']
  destinations:
    - namespace: '*'
      server: '*'
  clusterResourceWhitelist:
    - group: '*'
      kind: '*'
EOF

# Adapte le repoURL avec l'IP de ta VM
kubectl apply -f argocd/application.yaml

7. Runner CI (Gitea Actions)

Voir la section Installation du runner CI ci-dessus.

8. Prometheus + Grafana

helm install monitoring prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --set grafana.service.type=NodePort \
  --set grafana.service.nodePort=30900 \
  --set prometheus.service.type=NodePort \
  --set prometheus.service.nodePort=30090 \
  --set alertmanager.enabled=false

# Attends que tous les pods soient Running (~2-3 min)
kubectl get pods -n monitoring -w

Applique le ServiceMonitor pour scraper l'API :

kubectl apply -f - <<EOF
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: shopping-api
  namespace: monitoring
  labels:
    release: monitoring
spec:
  namespaceSelector:
    matchNames:
      - default
  selector:
    matchLabels:
      app: shopping-api
  endpoints:
    - port: "http"
      path: /metrics
      interval: 15s
EOF

Récupère le mot de passe Grafana :

kubectl get secret monitoring-grafana -n monitoring \
  -o jsonpath="{.data.admin-password}" | base64 -d && echo

Accès Grafana : http://localhost:30900 (login : admin).

9. k6

sudo gpg --no-default-keyring \
  --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
  --keyserver hkp://keyserver.ubuntu.com:80 \
  --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69

echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \
  | sudo tee /etc/apt/sources.list.d/k6.list

sudo apt-get update && sudo apt-get install k6 -y

Déploiement Kubernetes

Prérequis

  • Tous les composants de la section précédente installés
  • Registry local sur localhost:5000
  • Gitea sur http://localhost:30300
  • ArgoCD sur https://localhost:30443
  • Runner CI actif (sudo systemctl status act-runner)

Commandes utiles

# État de l'application ArgoCD
kubectl get application shopping-api -n argocd

# Pods de l'app
kubectl get pods -l app=shopping-api

# Logs en temps réel
kubectl logs -l app=shopping-api -f

# Forcer une resynchronisation ArgoCD
kubectl patch application shopping-api -n argocd \
  --type merge -p '{"operation":{"sync":{"revision":"HEAD"}}}'

# Rollback : changer le tag dans values.yaml et pousser
vim helm/shopping-api/values.yaml   # modifier image.tag
git add helm/shopping-api/values.yaml
git commit -m "revert: rollback vers <ancien-tag>"
git push

Accès à l'API en cluster

Accès URL
NodePort direct http://localhost:30800
Via Ingress Traefik http://localhost/api/products
Swagger UI http://localhost/docs

Tests de charge (k6)

# Lancer le test complet (4 stages, jusqu'à 50 VUs)
k6 run k6/shopping-api-test.js

Le scénario simule un cycle utilisateur complet : liste → création → lecture → patch → suppression.

Seuils définis :

Métrique Seuil
http_req_duration p(95) < 1500ms
duration_create_product p(95) < 2000ms
http_req_failed < 1%

Monitoring

# Démarrer Prometheus + Grafana (si scaled down)
kubectl scale deployment --all -n monitoring --replicas=1
kubectl scale statefulset --all -n monitoring --replicas=1

# Accès Grafana
http://localhost:30900   # admin / <mot de passe dans le secret>

# Récupérer le mot de passe Grafana
kubectl get secret monitoring-grafana -n monitoring \
  -o jsonpath="{.data.admin-password}" | base64 -d

Queries Prometheus utiles :

# Taux de requêtes par endpoint
rate(http_requests_total{job="shopping-api"}[1m])

# Latence p95
histogram_quantile(0.95,
  rate(http_request_duration_seconds_bucket{job="shopping-api"}[1m])
)

# Taux d'erreurs 5xx
rate(http_requests_total{job="shopping-api", status_code=~"5.."}[1m])

Dashboard Grafana importé : ID 17175


Configuration du pool de connexions

Le fichier db.py configure SQLAlchemy avec un pool dimensionné pour tenir sous charge :

engine = create_engine(
    DATABASE_URL,
    pool_size=20,       # connexions permanentes
    max_overflow=10,    # connexions bonus en pic
    pool_timeout=30,    # timeout d'attente
    pool_pre_ping=True, # vérifie les connexions avant usage
)

Sans ce pool, à 50 VUs simultanés la latence p95 dépasse 1.5s. Avec, elle reste sous 500ms en conditions normales.