Scalabilité et haute disponibilité
Trois approches pour déployer une application web avec PostGIS, Flask et Nginx
Ce guide présente trois méthodes de plus en plus robustes pour déployer une application web utilisant Flask (backend), PostGIS (base de données spatiale), et Nginx (serveur HTTP).
1. Déploiement simple avec Docker Compose
Objectif :
Lancer les services Flask, PostGIS, et Nginx dans des conteneurs Docker, sans équilibrage de charge.
Structure :
docker-compose.yml
app/
→ code Flasknginx/
→ config Nginxdb/
→ PostGIS (image officielle)
Points clés :
- Facile à mettre en place.
- Adapté aux environnements de développement et aux petites applis.
Exemple du docker-compose.yml :
version: '3.8'
services:
flask:
build:
context: ./app
dockerfile: Dockerfile
container_name: flask
environment:
- DB_HOST=postgis
- DB_NAME=gisdb
- DB_USER=gisuser
- DB_PASSWORD=gispassword
- DB_PORT=5432
depends_on:
- postgis
networks:
- app-network
restart: unless-stopped
deploy:
replicas: 3 # 3 instances Flask pour la répartition de charge
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/points"]
interval: 30s
timeout: 10s
retries: 3
postgis:
image: postgis/postgis:15-3.4
container_name: postgis
environment:
- POSTGRES_DB=gisdb
- POSTGRES_USER=gisuser
- POSTGRES_PASSWORD=gispassword
volumes:
- postgis-data:/var/lib/postgresql/data
networks:
- app-network
restart: unless-stopped
nginx:
build:
context: ./nginx
dockerfile: Dockerfile
container_name: nginx
ports:
- "80:80"
depends_on:
- flask
networks:
- app-network
restart: unless-stopped
networks:
app-network:
driver: bridge
volumes:
postgis-data:
Cette approche assez simple à mettre en oeuvre fait tourner un service par container. On peut demander à Docker d’ajouter 3 services pour flask permettant d’absorber la demande grandissante.
Si on teste l’approche (sans duplication des services, donc un seul container flask ) avec un outil de tests (comme Locust), on va voir une saturation des ressources du système et une apparition de défauts dans les requêtes.
On a simulé la charge de l’arrivée de 3 utilisateurs à la seconde sur une API avec flask permettant de faire des GET et des POST. On pouvait ainsi ajouter des points dans une base de données PostGIS ou alors faire la liste des points. Des requêtes assez simples mais qui peuvent amener des défis pour un serveur lorsque le nombre d’utilisateurs augmente. L’approche de base (sans réplication) va soutenir assez bien 200 utilisateurs permettant d’avoir 95 % des requêtes avec une réponse en dessous de 1 000 ms.
Quand on ajoute l’option de dupliquer les services avec deploy.replicas
, on peut voir une légère amélioration de la solution. L’application va quand même saturer mais cela va se faire après 300 utilisateurs (95 % des requêtes en dessous de 1 000 ms de temps de réponse). De plus, il n’y aura pas d’apparition de défauts même lorsque l’application est saturée.
Ce genre d’approche peut convenir pour les petites applications.
2. Ajout de HAProxy pour la répartition de charge
Objectif :
On peut éviter d’utiliser nginx comme proxy mais plutôt utiliser HAProxy (High Availability Proxy). C’est un bon load balancer très performant et largement utilisé, notamment dans les environnements de production pour répartir la charge entre plusieurs instances d’un service web.
On va reposer sur 3 containers flask comme expliqué plus haut. Au lieu que ce soit nginx qui répartit la charge, on va définir haproxy en mode leastconn
, autrement dit on envoie la requête vers le container avec le moins de trafic. Il faut savoir qu’il existe de nombreuses stratégies d’équilibrage, car certaines offrent la possibilité d’orienter un même utilisateur toujours vers le même container. Ainsi l’utilisateur pourrait bénéficier de la cache des opérations passées. Il y aussi des stratégies plus aléatoires ou simplement séquentielles, on envoie sur le prochain container à chaque fois.
Modifications :
- Toujours plusieurs conteneurs Flask (
flask1
,flask2
, …) - HAProxy placé devant, pour dispatcher les requêtes.
- Toujours avec
docker-compose
.
Nouveau service dans docker-compose.yml
:
haproxy:
build:
context: ./haproxy
dockerfile: Dockerfile
container_name: haproxy
ports:
- "80:80" # Bind port 80 to host
depends_on:
- flask
networks:
- app-network
restart: unless-stopped
On peut définir un fichier Dockerfile
FROM haproxy:2.8
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
EXPOSE 80
Ensuite au niveau de la config, on a :
global
log /dev/log local0
maxconn 10000
tune.ssl.default-dh-param 2048
nbthread 4
defaults
log global
mode http
option httplog
timeout connect 5000
timeout client 30000
timeout server 30000
option forwardfor
option http-server-close
maxconn 5000
frontend http_front
bind *:80
default_backend flask_backend
log /dev/log local0 debug
backend flask_backend
balance leastconn
option httpchk GET /status
http-check expect status 200
server flask1 flask:5000 check inter 5000 rise 3 fall 5
server flask2 flask:5000 check inter 5000 rise 3 fall 5
server flask3 flask:5000 check inter 5000 rise 3 fall 5
haproxy va vérifier les containers pour chaque requête et orienter vers celui le moins occupé.
Cette approche donne un résultat intéressant car au lieu de saturer autour de 200 utilisateurs, l’application résiste bien jusqu’à 300 utilisateurs. On obtient encore 95 % des requêtes avec une réponse en dessous de 1 000 ms.
Cette approche se greffe assez bien sur une application déjà définie avec un docker-compose.yml, on remplace uniquement nginx par haproxy.
3. Déploiement avec MicroK8s (Kubernetes)
Objectif :
Si on souhaite aller à un niveau supérieur, on peut alors envisager une solution comme MicroK8s, une version légère et simplifiée de Kubernetes.
On est dans un autre univers, donc l’application a ses propres configurations et commandes.
Installation
Il faut passer par une étape d’installation. Je considère que vous disposez d’un système sous Debians avec Docker déjà installé.
Étape 1 : Mettre à jour le système et installer les dépendances
Mettez à jour le système et installez les outils essentiels nécessaires pour MicroK8s.
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git apt-transport-https ca-certificates gnupg lsb-release software-properties-common
Étape 2 : Installer Snapd
Installez et activez Snapd, nécessaire pour installer MicroK8s via Snap.
sudo apt install -y snapd
sudo systemctl enable --now snapd
Étape 3 : Installer MicroK8s
Installez MicroK8s avec Snap en utilisant l’option --classic
pour un accès complet au système.
sudo snap install microk8s --classic
Attention il est possible que cette opération ne fonctionne à cause de la version désuète de snap
. On peut simplement demander à snap de mettre à jour snap.
sudo snap install snapd
Étape 4 : Configurer les permissions utilisateur
Ajoutez l’utilisateur actuel au groupe microk8s
pour exécuter les commandes MicroK8s sans sudo, et activez l’appartenance au groupe.
sudo usermod -a -G microk8s $USER
newgrp microk8s
Étape 5 : Activer les add-ons MicroK8s
Activez les add-ons essentiels pour prendre en charge DNS, Ingress, le stockage, Helm, le tableau de bord et MetalLB pour l’équilibrage de charge.
microk8s enable dns ingress storage helm dashboard metallb
- Intérêts :
dns
: Fournit une résolution DNS interne.ingress
: Active l’Ingress pour le routage HTTP externe.storage
: Configure le stockage persistant.helm
: Installe Helm pour gérer les applications Kubernetes.dashboard
: Fournit l’interface web de Kubernetes.metallb
: Offre un équilibrage de charge pour les services.
Étape 6 : Vérifier le cluster MicroK8s
Vérifiez l’état du cluster MicroK8s et assurez-vous que tous les composants fonctionnent.
microk8s status --wait-ready
microk8s kubectl get nodes
microk8s kubectl get pods -A
Étape 7 : Construire et importer l’image de l’application Flask
Naviguez vers le répertoire de l’application, construisez l’image Docker Flask et importez-la dans MicroK8s.
cd kubernetes-k8s/app/
docker build -t my-flask-app:latest .
docker save my-flask-app:latest | microk8s ctr image import -
Étape 8 : Déployer l’application avec Helm
On doit créer des fichiers de configuration (Chart.yaml, values.yaml, templates/deployment.yaml, templates/ingress.yaml, templates/service.yaml) pour chaque composant.
Par exemple pour le composant flask, ce sera :
Pour values.yaml
replicaCount: 3
image:
repository: my-flask-app
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 5000
ingress:
enabled: true
hosts:
- host: flask.local
paths:
- path: /
pathType: Prefix
Pour Chart.yaml
apiVersion: v2
name: flask-app
description: A Helm chart for deploying a Flask application
version: 0.1.0
appVersion: "1.0"
Pour deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: -flask
labels:
app: flask
spec:
replicas:
selector:
matchLabels:
app: flask
template:
metadata:
labels:
app: flask
spec:
containers:
- name: flask
image: :
imagePullPolicy:
ports:
- containerPort: 5000
env:
- name: DB_HOST
value: "postgis-service.default.svc.cluster.local"
- name: DB_NAME
value: "gisdb"
- name: DB_USER
value: "gisuser"
- name: DB_PASS
value: "gispassword"
- name: DB_PORT
value: "5432"
Pour ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: -flask
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host:
http:
paths:
- path:
pathType:
backend:
service:
name: -flask
port:
number:
Pour service.yaml
apiVersion: v1
kind: Service
metadata:
name: -flask
spec:
selector:
app: flask
ports:
- protocol: TCP
port:
targetPort: 5000
type:
Ensuite naviguez vers le répertoire des charts Helm et déployez les composants PostGIS, Flask et Nginx avec Helm.
cd ../helm/
microk8s helm3 install postgis-release postgis/
microk8s helm3 install flask-release flask-app/
microk8s helm3 install nginx-release nginx/
Étape 9 : Initialiser la base de données PostGIS
Si on doit créer la base de données on peut accéder au pod PostGIS pour créer une table géospatiale pour stocker des points.
microk8s kubectl exec -it $(microk8s kubectl get pod -l app=postgis -o jsonpath="{.items[0].metadata.name}") -- psql -U gisuser -d gisdb
Dans l’interface psql, exécutez :
CREATE EXTENSION postgis;
CREATE TABLE points (id SERIAL PRIMARY KEY, name VARCHAR(100), geom GEOMETRY(POINT, 4326));
Étape 10 : Tester l’application
Vérifiez l’application en envoyant des requêtes HTTP au point de terminaison Flask via l’Ingress.
curl -X POST http://flask.local/point -H "Content-Type: application/json" -d '{"longitude": 2.3522, "latitude": 48.8566, "name": "Paris"}'
curl http://flask.local/points
Étape 11 : Surveiller et déboguer
Vérifiez l’état des pods, les journaux et les services pour s’assurer que tout fonctionne correctement.
microk8s kubectl get pods
microk8s kubectl get svc
microk8s kubectl logs <nom-du-pod-flask>
microk8s kubectl logs <nom-du-pod-nginx>
Étape 12 : Mettre à jour l’application Flask (facultatif)
Si des modifications sont nécessaires, mettez à jour les valeurs du chart Helm et redéployez.
nano helm/flask-app/values.yaml
microk8s helm3 upgrade flask-release helm/flask-app/
Étape 13 : Nettoyer MicroK8s
Désinstallez les releases Helm, désactivez les add-ons et arrêtez MicroK8s si vous passez à une autre configuration.
microk8s helm uninstall flask-release
microk8s helm uninstall postgis-release
microk8s helm uninstall nginx-release
microk8s disable metallb
microk8s disable ingress
microk8s stop
Architecture :
La solution de Microk8s repose sur des images de containers Docker. Il suffit de construire l’image et de demander à Microk8s de l’utiliser.
- Deployments : Flask, PostGIS, Nginx
- Services : ClusterIP + Ingress
- Replicas : paramétrables
- PersistentVolume pour PostGIS
En utilisant cette approche, on arrive à des résultats intéressants. Le serveur sature autour de 300 utilisateurs, mais il n’y aura aucun défaut. Le serveur répond toujours mais effectivement la réponse devient de plus en plus lente avec le nombre d’utilisateurs qui s’ajoute.
Les temps de réponse sont bien en dessous de ceux obtenus par les autres solutions, ce qui laisse sous-entendre que l’on pourrait trouver une configuration logicielle et matérielle qui donnerait de très bon résultat avec ce genre d’approche. L’inconvénient majeur est qu’il faut effectivement bien maîtriser Microk8s avec tous les fichiers config et les commandes.
Configuration matérielle
Tous les tests ont été effectués sur le même serveur, donc les ressources ont toujours été les mêmes (16 Gb de ram et 6 vCPUs). Si on souhaitait obtenir une application performante, on pourrait augmenter le nombre de processeurs et la mémoire.
De plus, le dimensionnement de l’application dépend grandement des besoins et des utilisateurs. Toutes les solutions présentées pourraient largement répondre aux besoins d’une application avec 50 utilisateurs faisant de multiples opérations en même temps. Afin d’être sûr que la solution répond bien aux attentes, on peut conseiller de simuler des tests de montée en charge avec Locust. Il suffirait d’identifier des opérations sur le système qui seraient faites souvent ou qui seraient gourmandes en ressources.