- URLs images versionnées (?v=mtime) : une tuile recalculée change d'URL et force le rechargement navigateur (cache heuristique contourné), y compris en plein run via /api/tiles ; veille permanente 15 s sur la carte - simulation locale du mode deux machines (docker-compose.local-2m.yml) : worker GPU :8974 + webapp légère :8973 au cache séparé output-webapp/ - override webapp pour le Pi 5 (192.168.3.3) : volume /srv/lidar/output, rsync vers le worker, labels Traefik (proxy/websecure/myresolver) - purge 0,5 m : worker/process et politique générale passés à 0,2 m seul - intègre le travail parallèle non commité : export mosaïque multi-dalles (export.py + /api/export), sous-tuilage intégral des couches, légendes VIZ_LEGENDS, docs et tests associés (213 tests verts)
280 lines
12 KiB
Markdown
280 lines
12 KiB
Markdown
# Déploiement webapp légère + machine de traitement
|
||
|
||
Architecture deux machines : la **webapp** (carte interactive, vignettes,
|
||
API) tourne sur un Raspberry Pi ; la **génération de tuiles** (téléchargement
|
||
IGN, PDAL, GPU) tourne sur une machine puissante. Les deux exécutent le même
|
||
code (`lidar_pipeline.webapp`), dans deux images Docker différentes.
|
||
|
||
```
|
||
Navigateur ──HTTP──▶ Raspberry Pi (image légère, Dockerfile.webapp)
|
||
│ sert carte + vignettes (régénérées localement)
|
||
│ /api/generate, /api/preview, /api/status
|
||
│ └─ transmis à ──▶ machine de traitement
|
||
│ /api/sync : rsync output/ ──◀── machine puissante
|
||
▼
|
||
Machine de traitement (image complète,
|
||
docker-compose.worker.yml service `worker`) :
|
||
téléchargement IGN + pipeline GPU (générateur de tuiles)
|
||
```
|
||
|
||
## Machine de traitement (générateur de tuiles)
|
||
|
||
Le service `worker` joue le rôle de générateur : il accepte les demandes
|
||
de génération envoyées par les webapp distantes (téléchargement IGN +
|
||
traitement PDAL/GPU) et expose les tuiles produites pour le rsync.
|
||
|
||
```bash
|
||
docker compose -f docker-compose.worker.yml up -d --build # API sur http://<ip-machine>:8973
|
||
docker compose -f docker-compose.worker.yml logs -f worker
|
||
```
|
||
|
||
Traitement batch ponctuel des dalles déjà présentes dans `input/` :
|
||
|
||
```bash
|
||
docker compose -f docker-compose.worker.yml run --rm --build process [-r 0.5,0.2 | --force]
|
||
```
|
||
|
||
Sur une machine sans GPU : retirer les lignes `gpus: all` (traitement CPU,
|
||
plus lent). Optionnel mais recommandé si le réseau n'est pas de confiance :
|
||
protéger l'API avec un jeton partagé — décommenter dans
|
||
`docker-compose.worker.yml` :
|
||
|
||
```yaml
|
||
environment:
|
||
- LIDAR_API_TOKEN=un-secret-à-partager
|
||
```
|
||
|
||
## Raspberry Pi (webapp légère)
|
||
|
||
### 0. Prérequis sur le Pi
|
||
|
||
- **Architecture** : image construite nativement sur la machine (ARM64 ou
|
||
x86_64, vérifier avec `uname -m`). Le build se fait sur le Pi lui-même
|
||
(`Dockerfile.webapp` : base `python:3.12-slim`, ~200 Mo, sans PDAL/GDAL).
|
||
- **Docker + plugin compose** : installation officielle
|
||
[docs.docker.com/engine/install](https://docs.docker.com/engine/install/)
|
||
(tester avec `docker compose version`).
|
||
- **rsync/ssh côté client** : inutile sur l'hôte — ils sont dans l'image —
|
||
mais la **clé SSH** de l'hôte est montée dans le conteneur.
|
||
- **Espace disque** : prévoir la taille du cache de tuiles (compter la
|
||
taille de `output/` sur la machine de traitement, ~quelques dizaines de
|
||
Mo par dalle et par résolution).
|
||
|
||
### 1. Copier le code sur le Pi (git clone)
|
||
|
||
```bash
|
||
git clone ssh://git@git.example.fr:2222/code_public/lidar_rendu.git lidar
|
||
cd lidar
|
||
```
|
||
|
||
Le clone SSH utilise la même clé que le rsync (`~/.ssh`, voir ci-dessous) ;
|
||
si le serveur git n'est pas encore connu, faire une première connexion pour
|
||
accepter son empreinte. `output/` et `input/` sont ignorés par git : le
|
||
dépôt ne contient que le code, le cache de tuiles se remplit ensuite par
|
||
rsync (premier chargement, section 4).
|
||
|
||
### 2. Accès SSH pour le rsync (la Pi tire les tuiles traitées)
|
||
|
||
```bash
|
||
ssh-keygen -t ed25519 # si pas encore de clé
|
||
ssh-copy-id lidar@<ip-machine> # compte lecture sur output/
|
||
ssh lidar@<ip-machine> exit # 1re connexion : enregistre known_hosts
|
||
```
|
||
|
||
La dernière commande évite le prompt « authenticity of host » pendant le
|
||
rsync (le conteneur ne peut pas répondre interactivement).
|
||
|
||
Sur la machine puissante, le dossier `output/` doit être lisible par ce
|
||
compte (ex. `/srv/lidar/output` si vous préférez un chemin dédié — adaptez
|
||
LIDAR_SYNC_CMD).
|
||
|
||
Avec `serve-webapp.sh` et `run.sh`, `~/.ssh` (clé + known_hosts) est monté
|
||
automatiquement en lecture seule dans le conteneur ; avec `docker compose`,
|
||
le montage équivalent est à décommenter dans `docker-compose.webapp.yml`
|
||
(voir l'option c ci-dessous).
|
||
|
||
### 3. Configurer et lancer
|
||
|
||
Trois façons, au choix :
|
||
|
||
**a. `serve-webapp.sh` (recommandé)** — script de gestion de la stack
|
||
(arrière-plan, redémarrage automatique au reboot, sous-commandes). La
|
||
configuration vit dans `webapp.env` (copie du modèle, non versionné) :
|
||
|
||
```bash
|
||
cp webapp.env.example webapp.env
|
||
nano webapp.env # LIDAR_GENERATION_URL, LIDAR_SYNC_CMD, jeton...
|
||
./serve-webapp.sh # démarre (build au premier lancement) et attend le serveur
|
||
```
|
||
|
||
Sous-commandes : `stop`, `restart` (relit `webapp.env`), `status` (conteneur
|
||
+ état de la sync), `sync` (force rsync + vignettes), `logs`, `update`
|
||
(`git pull` + rebuild + redémarrage). Port hôte via `WEBAPP_PORT` dans
|
||
`webapp.env` ou l'environnement (`WEBAPP_PORT=9000 ./serve-webapp.sh`).
|
||
|
||
**b. `run.sh`** — conteneur au premier plan (Ctrl-C arrête tout) : pratique
|
||
pour tester, les variables se passent depuis l'environnement hôte, `~/.ssh`
|
||
est monté automatiquement :
|
||
|
||
```bash
|
||
LIDAR_GENERATION_URL=http://192.168.1.50:8973 \
|
||
LIDAR_REMOTE_TOKEN=un-secret-à-partager \
|
||
LIDAR_SYNC_CMD="rsync -a --delete --exclude=*.tif --exclude=.generation* --exclude=index_thumbs --exclude=index_subtiles lidar@192.168.1.50:/srv/lidar/output/ /data/output/" \
|
||
LIDAR_AUTO_SYNC_SECONDS=600 \
|
||
./run.sh --serve-webapp # port 8973, ou --serve-webapp 9000
|
||
```
|
||
|
||
`LIDAR_AUTO_SYNC_SECONDS` entretient le cache tout seul : toutes les N
|
||
secondes, rsync ramène les nouvelles tuiles et les vignettes manquantes
|
||
sont régénérées (les mtimes évitent tout recalcul inutile). Sans cette
|
||
variable, le cache se rafraîchit à la demande : bouton ↻, fin d'un run,
|
||
ou `POST /api/sync`.
|
||
|
||
`LIDAR_REGEN_CIDR` restreint le lancement des générations (`POST
|
||
/api/generate` : zones, sélection, complétion, régénération) aux clients
|
||
dont l'IP est dans un des réseaux indiqués — liste de CIDR séparés par
|
||
virgules, chaîne vide pour lever la restriction. Défaut :
|
||
`127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` (boucle locale
|
||
+ plages privées RFC1918 : LAN, hôte Docker via la passerelle 172.x ; les
|
||
IP publiques restent refusées). L'IP est celle de la connexion (conservée
|
||
par le DNAT Docker pour les clients du LAN) ; derrière un reverse proxy
|
||
local (dans le réseau autorisé), `X-Forwarded-For` désigne le client réel.
|
||
Les autres clients consultent la carte normalement : les boutons de
|
||
génération sont masqués et l'API répond 403.
|
||
|
||
**c. `docker compose`** — éditer `docker-compose.webapp.yml` :
|
||
|
||
- `LIDAR_GENERATION_URL` : `http://<ip-machine>:8973`
|
||
- `LIDAR_REMOTE_TOKEN` : la valeur de `LIDAR_API_TOKEN` de la machine
|
||
(inutile si aucun token là-bas)
|
||
- `LIDAR_SYNC_CMD` : la commande rsync qui copie `output/` distant vers
|
||
`/data/output/` local, ex :
|
||
|
||
```yaml
|
||
- LIDAR_SYNC_CMD=rsync -a --delete --exclude=*.tif --exclude=.generation* --exclude=index_thumbs --exclude=index_subtiles lidar@192.168.1.50:/srv/lidar/output/ /data/output/
|
||
```
|
||
|
||
Décommenter aussi le montage de la clé SSH (rsync) :
|
||
|
||
```yaml
|
||
volumes:
|
||
- ./output:/data/output
|
||
- ~/.ssh:/home/lidar/.ssh:ro
|
||
```
|
||
|
||
Les vignettes (`index_thumbs/`, `index_subtiles/`) ne se synchronisent pas :
|
||
elles sont régénérées sur place par le Pi (`/api/sync` → `build_index`),
|
||
c'est le seul travail lourd qu'il fait. Exclure `*.tif` évite de copier des
|
||
intermédiaires éventuels ; les sidecars `output/DTM/*_method.txt` servent au
|
||
panneau d'infos et sont synchronisés.
|
||
|
||
```bash
|
||
mkdir -p output
|
||
docker compose -f docker-compose.webapp.yml up -d --build
|
||
```
|
||
|
||
La carte est sur `http://<ip-pi>:8973/`.
|
||
|
||
`run.sh` lance le conteneur au premier plan (Ctrl-C arrête tout) : pratique
|
||
pour tester. Pour une installation permanente, `serve-webapp.sh` (option a)
|
||
ou `docker compose` (option c) redémarrent la carte après un reboot du Pi
|
||
(`--restart unless-stopped`).
|
||
|
||
### 4. Premier chargement
|
||
|
||
Si aucune tuile n'a encore été synchronisée, déclencher une première fois :
|
||
|
||
```bash
|
||
curl -X POST http://<ip-pi>:8973/api/sync
|
||
```
|
||
|
||
(puis attendre la fin : `curl http://<ip-pi>:8973/api/sync` → `"running":
|
||
false`.) Le bouton ↻ de la carte fait la même chose (sync + vignettes).
|
||
|
||
## Mise à jour
|
||
|
||
Avec `serve-webapp.sh`, une seule commande (`git pull`, rebuild de l'image,
|
||
redémarrage) :
|
||
|
||
```bash
|
||
./serve-webapp.sh update
|
||
```
|
||
|
||
Sinon, sur le Pi, après un `git pull` (ou rsync du code) :
|
||
|
||
```bash
|
||
docker compose -f docker-compose.webapp.yml up -d --build # rebuild + redémarrage
|
||
```
|
||
|
||
Avec `run.sh --serve-webapp`, l'image est reconstruite automatiquement à
|
||
chaque lancement ; il suffit de relancer la commande. Le cache de tuiles
|
||
(`output/`) n'est pas affecté par les mises à jour d'image.
|
||
|
||
## Dépannage
|
||
|
||
- **État d'une sync** : `curl http://<ip-pi>:8973/api/sync` → `running`,
|
||
`phase` (`sync` puis `index`) et `error` (dernière erreur, ex. fin de
|
||
journal rsync en cas d'échec). Les logs du conteneur :
|
||
`docker logs lidar-webapp` (ou `docker compose -f docker-compose.webapp.yml
|
||
logs -f webapp`).
|
||
- **`Permission denied (publickey)` pendant la sync** : refaire
|
||
`ssh-copy-id lidar@<ip-machine>` et une connexion manuelle pour
|
||
renseigner `known_hosts` ; vérifier que `~/.ssh` est monté dans le
|
||
conteneur (automatique avec `run.sh`, à décommenter en compose).
|
||
- **Boutons de génération masqués / `403` sur `/api/generate`** : l'IP du
|
||
client n'est dans aucun des réseaux de `LIDAR_REGEN_CIDR` (défaut :
|
||
localhost + plages privées). Adapter la variable ou la vider pour lever
|
||
la restriction.
|
||
- **« Machine de traitement injoignable »** : vérifier le service `worker`
|
||
(`docker compose -f docker-compose.worker.yml logs -f worker`) et
|
||
`LIDAR_GENERATION_URL` (IP + port 8973). La consultation de la carte
|
||
reste possible avec le cache local.
|
||
- **Carte vide après sync** : attendre la fin du rebuild (`GET /api/sync` →
|
||
`"running": false`, ou `./serve-webapp.sh status`) puis recharger la
|
||
page ; le bouton ↻ de la carte enchaîne sync + vignettes
|
||
automatiquement.
|
||
- **Port 8973 déjà pris** : `WEBAPP_PORT=9000 ./serve-webapp.sh` (ou la
|
||
variable dans `webapp.env`), `--serve-webapp 9000` avec `run.sh`, ou
|
||
changer le mapping `ports:` en compose (le conteneur écoute toujours sur
|
||
8973).
|
||
|
||
## Fonctionnement
|
||
|
||
- Dessiner une zone (bouton « + Zone ») sur la carte du Pi envoie la demande
|
||
à la machine de traitement, qui télécharge les dalles IGN puis les traite.
|
||
La file de génération (progression tuile par tuile) est lue depuis la
|
||
machine distante en direct.
|
||
- À la fin du run, le navigateur appelle `/api/sync` : rsync ramène les
|
||
images, le Pi régénère vignettes/sous-tuiles/index.html, puis recharge la
|
||
carte. Le bouton ↻ relance le même cycle à tout moment. Avec
|
||
`LIDAR_AUTO_SYNC_SECONDS`, le conteneur webapp seul maintient aussi son
|
||
cache périodiquement (utile quand personne ne consulte la carte).
|
||
- Sans machine de traitement configurée, `./run.sh --serve-webapp` sert la
|
||
carte en lecture seule depuis `output/` (cache figé, aucun traitement
|
||
possible depuis l'interface).
|
||
- **Autonomie** : une fois les tuiles chargées dans le cache local, la
|
||
webapp fonctionne seule — Leaflet est vendorisé dans `assets/vendor/`
|
||
(aucun CDN), tuiles/vignettes/infos sont lues dans `output/`, et un
|
||
backend injoignable masque simplement les boutons de génération sans
|
||
affecter la consultation.
|
||
- Machine de traitement seule (sans Pi) : rien ne change, `--serve` /
|
||
`docker compose up -d --build serve` fonctionne comme avant en local (tout-en-un), et `docker-compose.worker.yml` installe le générateur seul.
|
||
- Si la machine de traitement est éteinte, la carte du Pi reste consultable
|
||
(données synchronisées) ; seuls les nouveaux traitements sont indisponibles
|
||
(message « machine de traitement injoignable »).
|
||
|
||
## Références
|
||
|
||
- `lidar_pipeline/webapp.py` : proxy distant (`LIDAR_GENERATION_URL`),
|
||
`/api/sync`, cache local périodique (`LIDAR_AUTO_SYNC_SECONDS`), jeton
|
||
`LIDAR_API_TOKEN`/`LIDAR_REMOTE_TOKEN`, restriction des générations au
|
||
réseau local (`LIDAR_REGEN_CIDR`).
|
||
- `lidar_pipeline/index.py` : vignettes/index sans GDAL (pyproj ou repli
|
||
affine), recharge après sync.
|
||
- `docker-compose.worker.yml` : générateur de tuiles (machine puissante).
|
||
- `Dockerfile.webapp`, `docker-compose.webapp.yml` : webapp légère ARM64
|
||
(FastAPI + Pillow AVIF natif + pyproj, ~200 Mo).
|
||
- `serve-webapp.sh`, `webapp.env.example` : gestion de la stack webapp sur
|
||
le Pi (démarrage/arrêt/sync/logs/update), configuration locale dans
|
||
`webapp.env` (ignoré par git).
|