Remplacer MinIO par SeaweedFS — cadrage : trois bancs mesurés sur le homelab, SeaweedFS contre MinIO à charge égale, et le MinIO de prod est EN PANNE
#48
Cadrage seulement. Aucun code ni aucune configuration de prod n'ont été modifiés : pas de Tofu distant, pas de Helm, pas d'ArgoCD. Le MinIO de prod n'a été qu'observé. Trois bancs jetables ont tourné dans le namespace essai-seaweedfs, puis ont été supprimés, PV vérifiés :
B1 (2026-09-14) : l'API S3 de SeaweedFS ;
B2 (2026-09-15) : provisionnement, charge, bascule et purge ;
B3 (2026-09-15) : MinIO de prod rejoué à charge égale, après l'arbitrage « mesurer MinIO d'abord ».
⚠⚠ Constat hors banc : le MinIO de prod est EN PANNE
Relevé le 2026-09-15 vers 09:24, en lecture seule (rien n'a été touché) :
Podtools/minio-56975fd45-s8j8k sur pi2 : CrashLoopBackOff, 311 puis 312 redémarrages en 26 h.
Dernière sortie : code 1, reason: Error.
Journal du conteneur précédent : unable to rename (/export/.minio.sys/tmp -> /export/.minio.sys/tmp-old/…) drive is faulty, puis FATAL Unable to initialize backend: drive is faulty.
Volume Longhornpvc-ebb2605f-0597-43ef-a3d0-6b3c7c8fe3b2 : attached, healthy, 3 réplicas running (pi1, pi2, pi3). Longhorn dit le volume sain ; c'est MinIO qui refuse son disque.
pi2 à 109-110 % de mémoire pendant tous les relevés.
Conséquence : aucune vidéo ne peut être stockée ni relue depuis au moins 26 h. C'est la même famille de symptômes que tools#31 et kadans-api#107 (Input/output error). Pour la même raison, la référence prod « au repos » n'existe pas : kubectl top pod → « Metrics not available », et le compte d'objets et d'octets est inaccessible (serveur éteint). Le diagnostic n'était pas dans le périmètre de ce cadrage (Q1).
TL;DR
MinIO communautaire est mort (archivé, plus d'images, plus de correctifs), et notre instance l'est aussi depuis au moins 26 h.
À charge égale sur le même Pi, SeaweedFS 4.46 et MinIO de prod font 0 échec tous les deux. SeaweedFS envoie un peu plus vite (24,8 contre 20,9 Mio/s), mais consomme ×2,1 de mémoire (1188 contre 567 Mi) et ×2 de CPU (547 contre 271 m). Détail en §4.
Le critère « < 512 Mi » écrit en §5.4 était trop sévère : MinIO lui-même le dépasse (567 Mi). L'écart réel est ×2,1, et SeaweedFS reste sous la limite de prod de MinIO (2 Gi).
L'API S3, le provisionnement Tofu idempotent, le parcours de bout en bout et la bascule sont mesurés verts pour SeaweedFS (§3, §5).
Recommandation révisée : SeaweedFS, avec quatre conditions de configuration et une limite mémoire de 1,5 Gi, hors pi2 (§4).
Arbitré : renommer MINIO_* en S3_* pendant la migration (lot M3).
1. Pourquoi
1.1 L'état réel de MinIO communautaire
Date
Fait
Source
mai 2025
Console d'administration retirée de l'édition communautaire
mesuré B2 : le défaut -s3.allowedOrigins=*accepte toute origine → à poser explicitement
banc ; weed/command/server.go l.172
Droits par identité
mesuré B1 et B2 : autre bucket → AccessDenied ; créer un bucket → AccessDenied ; GET anonyme → 403
banc
Granularité
source : toutes les actions multipart se ramènent à Write (weed/iam/helpers.go)
source
⚠ Noms d'actions IAM
mesuré B2 : s3:ListMultipartUploadParts refusé (MalformedPolicyDocument) ; il faut s3:ListParts
banc
API IAM en écriture
mesuré : refusée avec -s3.config ; acceptée avec -s3.iam.readOnly=false ; admin amorcé par AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
banc ; s3api_embedded_iam.go l.2596
Provisionnement Tofu
mesuré B2 : apply → plan -detailed-exitcode = 0 « No changes » → apply = 0 changement → encore No changes après redémarrage. ⚠ JonasKop/seaweedfs 0.2.0 absent du registre OpenTofu : registry.terraform.io/… obligatoire. hashicorp/aws pour CORS et cycle de vie (55 s pour la ressource cycle de vie)
banc
Cycle de vie AbortIncompleteMultipartUpload
mesuré en partie : règle acceptée et persistante ; run-shard avant l'échéance ne purge pas, ce qui est correct. Exécution à J+1 non observée (délai de 24 h en dur)
mesuré B2 : volumes pré-alloués par collection. Deux épisodes de 500 « No writable volumes » (disque à 11 % puis 64 %). Les suppressions ne rendent rien avant volume.vacuum (Free 0 → 13)
banc
Empreinte à charge égale
mesuré B2 et B3, voir §4 : 1188 Mi, contre 567 Mi pour MinIO
banc
Persistance Longhorn
mesuré B1 (objet, CORS, cycle de vie) et B2 (identités dynamiques) après redémarrage
banc
Métriques et sondes
mesuré : 607 séries SeaweedFS_* ; /healthz → 200
banc
Chart Helm
doc : officiel ; chart autonome probable (leçon de tools#31)
4. SeaweedFS contre MinIO à charge égale, alternatives et recommandation
4.1 À charge égale (B2 contre B3)
Même nœud serveur (pi3), même chargeur (pod alpine:3.20 sur pi1, même binaire), même Longhorn 5 Gi. Même charge : 8 envois de 320 Mio, 4 en parallèle, parts de 16 Mio, séquence de kadans-api (part présignée, PUT nu avec Origin, ListObjectParts, Complete, Stat, Remove). kubectl top toutes les 3 s.
SeaweedFS 4.46 (B2)
MinIO RELEASE.2024-12-18T13-15-44Z (B3, image et config de prod)
Code de sortie / échecs
0 / 0
0 / 0
Octets envoyés, durée
2560 Mio en 103,4 s
2560 Mio en 122,3 s
Débit
24,8 Mio/s
20,9 Mio/s
PUT de part 16 Mio, p50 / p95 / max
2,18 s / 5,82 s / 8,03 s
2,89 s / 3,99 s / 5,10 s
NewMultipartUpload p50 / p95
2 ms / 6 ms
55 ms / 141 ms
ListObjectParts p50 / p95 / max
18 ms / 29 ms / 2,34 s
117 ms / 283 ms / 304 ms
CompleteMultipartUpload p50 / p95 / max
6 ms / 1,56 s / 4,54 s
23 ms / 30 ms / 31 ms
StatObject p50 / p95
2 ms / 17 ms
1 ms / 3 ms
RemoveObject p50 / p95
2 ms / 13 ms
6 ms / 11 ms
Mémoire du pod : repos → pic
130 Mi → 1188 Mi
75 Mi → 567 Mi
CPU du pod, pic
547 m
271 m
CPU de pi3, pic
1767 m
1194 m
Mémoire de pi3 pendant l'essai
62 % → 71 %
60 % → 61 %
Redémarrages
0
0
Parcours stockage.go (PUT nu, Origin, Complete, GET présigné, Remove)
code 0, 7 OK
code 0, 7 OK
Limite mémoire du banc
1536 Mi
2 Gi (celle de prod)
Lecture :
Aucun des deux ne décroche. SeaweedFS est plus rapide en médiane et en débit. MinIO est plus régulier : p95 et max plus bas sur les PUT de part et sur Complete.
SeaweedFS paie ×2,1 en mémoire et ×2 en CPU. Son pic (1188 Mi) reste sous la limite de prod de MinIO (2 Gi).
Le critère « < 512 Mi » était mal posé : écrit sans référence, MinIO lui-même le dépasse.
⚠ Écart de banc : MinIO a été chargé avec l'identité root ; SeaweedFS avec une identité applicative bornée (B2). L'évaluation d'une politique pèse peu face au transfert, mais l'écart est écrit.
4.2 La référence prod
Ce qui était demandé
Relevé
kubectl top pod au repos, plusieurs fois
impossible : 5 relevés à 15 s d'écart → Metrics not available (pod en CrashLoopBackOff)
SeaweedFS. À charge égale, il fait le même travail sans échec, un peu plus vite. Il règle deux dettes : le CORS global partagé et les stubs 501. Il remplace un logiciel mort dont l'instance est tombée. Son surcoût mémoire est réel (×2,1), mais tient sous la limite qu'on donnait déjà à MinIO. Conditions, toutes mesurées comme nécessaires :
-s3.allowedOrigins explicite, jamais *, plus un CORS par bucket posé par Tofu.
-s3.iam.readOnly=false et admin amorcé depuis Vault. Politiques en noms SeaweedFS (s3:ListParts), provider épinglé registry.terraform.io/….
Capacité : -volume.max et -master.volumeSizeLimitMB dimensionnés sur le PVC, plus un CronJob volume.vacuum.
Filet de purge : un CronJob s3.clean.uploads -timeAgo 24h.
Ressources : requests 512Mi, limit 1536Mi à 2Gi, et nodeSelector sur pi1 ou pi3, jamais pi2 (109-111 % de mémoire à chaque relevé, et nœud du MinIO en panne).
5. Les bancs
5.1 B1 (2026-09-14) : l'API S3
weed server tout-en-un sur pi3, Longhorn 2 Gi, identités en fichier, accès par port-forward.
Passage
Code
Résultat
Nominal (minio-go v7.2.1)
0
38 OK
Sabotage : faux secret
28
403 et SignatureDoesNotMatch ; ⚠ le port-forward meurt quand le serveur refuse un corps en cours d'envoi (KO suivants = harnais)
Persistance / témoin
0 / 1
relu / KO attendu
IAM en écriture avec -s3.config
254
écritures désactivées
Créé puis supprimé : ns essai-seaweedfs, Secret seaweedfs-s3, PVC seaweedfs-banc (pvc-59a2afd6-…), Deployment. delete namespace → 0 ; PV et volume Longhorn → NotFound ; diff des PV → 0.
MalformedPolicyDocument: not a valid action: 'ListMultipartUploadParts'
apply n° 2 (s3:ListParts)
0
1 ajoutée
plan -detailed-exitcode
0
No changes
apply n° 3
0
0 changement
plan après redémarrage
0
No changes
Charge. Essai n° 1, code 146 : 146 PUT en 500, dus au dimensionnement de volumes du harnais (-volume.max=8). Essai n° 2, code 0 : chiffres au §4.1.
Parcours et sabotage. Nominal : code 0. Faux secret : code 8 (403 SignatureDoesNotMatch, puis port-forward mort). Cloison : AccessDenied ×2. Bucket sans règle CORS : code 1 (origine étrangère acceptée).
Bascule.
URL de l'ancien magasin : clé inconnue → 403 InvalidAccessKeyId ; même clé, autre secret → 403 SignatureDoesNotMatch. Tous portent l'ACAO, donc le XHR lit bien 403.
uploadId de l'ancien magasin → NoSuchUpload 404.
Côté front (lecture de code, ea56aac5) :
depot-video.ts l.120-128 transforme ce 403 en ErreurApiCoeur(403), que classerEchec (moteur-sauvegarde.ts l.169) range en serveur, donc rejoué en backoff.
Au rejeu, televersementId est renvoyé (l.322-326). L'API reçoit NoSuchUpload et rouvre un envoi, et le front remet les parts à zéro (l.332-337).
Rattrapage en un essai, à une condition : l'API signe avec les nouvelles clés dans le même geste que la bascule de l'hôte.
Créé puis supprimé : ns, Secret seaweedfs-admin, PVC seaweedfs-banc 5 Gi (pvc-87fa5604-…), Deployment et Service seaweedfs, Pod chargeur (deux fois), buckets et identités internes au banc. delete namespace → 0 ; PV et volume Longhorn → NotFound ; diff des PV → 0 ; 0 pod restant.
5.3 B3 (2026-09-15) : la référence MinIO
Montage, calqué sur tools/minio/templates/deployment.yaml et values.yaml (6913e5d, lus en lecture seule) :
Identique à la prod : image, commande minio server /export -S /etc/minio/certs/ --address :9000 --console-address :9001, MINIO_PROMETHEUS_AUTH_TYPE=public, MINIO_API_CORS_ALLOW_ORIGIN (les deux origines), sondes startup, readiness et liveness, securityContext 1000:1000, requests 512Mi/100m, limit 2Gi.
Écarts : nodeSelector pi3 (comme SeaweedFS) ; PVC Longhorn 5 Gi au lieu de 50 ; Secret root local (sans Vault ni ServiceAccount).
Passage
Code
Résultat
create-bucket kadans-videos
0
—
Charge (même binaire, mêmes paramètres, depuis pi1)
0
0 échec ; chiffres au §4.1
Parcours stockage.go
0
7 OK, dont PUT nus 200 avec ACAO, GET présigné sha identique, Stat 404 après Remove
Prod, lecture seule : get deploy, get pod, top pod (×5), get events, logs --previous, get pvc, get volumes.longhorn.io et replicas.longhorn.io. Aucune écriture, aucun redémarrage, aucun port-forward vers la prod. ArgoCD, Vault, DNS et Cloudflare intacts.
5.4 Ce qui n'a pas été mesuré
L'exécution réelle de l'Abort multipart par le worker de cycle de vie, à J+1.
La copie rclone et son débit.
kadans-api elle-même déployée contre l'essai (seule sa séquence d'appels a été rejouée).
La référence prod au repos (serveur en panne).
6. Plan de migration en lots
👤 = exige un humain.
Lot
Contenu
Humain
M0 — la panne de prod (hors migration, préalable)
Décider : réparer MinIO, ou accélérer M1-M5 (Q1). Tant que MinIO ne démarre pas, les objets existants (~7,7 Go) ne sont lisibles par aucune voie S3 : M4 en dépend
👤 fondateur
M1 — le serveur à côté
Chart tools/seaweedfs/ autonome, avec toutes les conditions du §4.4 (allowedOrigins, iam.readOnly, volumes, vacuum, clean.uploads, 512Mi/1536Mi-2Gi, hors pi2). Service interne seulement
👤 synchronisation ArgoCD si elle n'est pas automatique
M2 — le provisionnement
Pipeline seaweedfs : admin dans kvv2/seaweedfs/config. Module s3_app : registry.terraform.io/JonasKop/seaweedfs pour bucket, identité, clé et politique (s3:ListParts) ; hashicorp/aws pour CORS et cycle de vie. Secret écrit dans kvv2/s3/<app> avec des clés S3_*. app_policy : nouveaux chemins
👤apply Vault par OIDC : hashicorp-vault, seaweedfs, front/iac
M3 — renommage MINIO_* → S3_* (arbitré le 2026-09-15)
kadans-api lit S3_ENDPOINT, S3_ENDPOINT_INTERNE, S3_BUCKET, S3_REGION, S3_ACCESS_KEY, S3_SECRET_KEYavec repli sur MINIO_* le temps de la bascule, retiré en M6. Idem pour outils/import-direct et outils/verifier-import. Chart : Secret kadans-api-s3, VaultStaticSecret → s3/kadans, endpointInterne → SeaweedFS. Tests (stockage_test.go, TestMagasinMemoireNEcrasePasMinio), textes et doctrine renommés dans les quatre dépôts, dont check-cors-magasin.mjs et CLAUDE.md du front
—
M4 — la copie
Job rclone sync puis rclone check, comptes d'objets et d'octets des deux côtés. Exige un MinIO qui démarre (M0)
👤 lancer et lire le rapport
M5 — la bascule
Ingress s3.arcodange.fr et .lab vers SeaweedFS dans le même geste que le redéploiement de kadans-api sur les nouvelles clés. Contrôles : bun run check:cors, bun run test:import-reel
👤 Cloudflare si le tunnel ou le DNS change
M6 — le retrait
Suppression du repli MINIO_*, du chart, du module, du provider, du workflow et de kvv2/minio/*
👤 secrets Vault ; PVC MinIO (Prune=false) supprimé à la main
Retour arrière, jusqu'à M6 : ingress et VaultStaticSecret vers MinIO (s'il démarre), plus rclone copy inverse. Les masters restent sur les appareils, et la file ne retire aucune tâche : au pire, le moteur redépose.
7. Questions ouvertes au fondateur
Q1 — la panne de prod. MinIO ne démarre plus (« drive is faulty »), alors que Longhorn dit le volume sain. Ouvrir un incident à part pour le diagnostiquer et récupérer les ~7,7 Go, ou traiter la migration comme la réparation ?
Q2 — la limite mémoire de SeaweedFS : 1536 Mi (pic mesuré + 30 %) ou 2 Gi (la limite actuelle de MinIO) ?
(Tranchés le 2026-09-15 : « mesurer MinIO d'abord » → §4.1 ; renommer MINIO_* en S3_* → M3.)
Renvoi : tools#31, tools#37, kadans-api#107, kadans-api#217, incident du 2026-09-07 (front/docs/memoire/stockage/sauvegarde.md#droit-manque-magasin).
> **Cadrage seulement.** Aucun code ni aucune configuration de prod n'ont été modifiés : pas de Tofu distant, pas de Helm, pas d'ArgoCD. Le MinIO de prod n'a été **qu'observé**. Trois bancs **jetables** ont tourné dans le namespace `essai-seaweedfs`, puis ont été **supprimés**, PV vérifiés :
> - B1 (2026-09-14) : l'API S3 de SeaweedFS ;
> - B2 (2026-09-15) : provisionnement, charge, bascule et purge ;
> - B3 (2026-09-15) : MinIO de prod rejoué à charge égale, après l'arbitrage « mesurer MinIO d'abord ».
## ⚠⚠ Constat hors banc : le MinIO de prod est EN PANNE
Relevé le 2026-09-15 vers 09:24, **en lecture seule** (rien n'a été touché) :
- **Pod** `tools/minio-56975fd45-s8j8k` sur **pi2** : **`CrashLoopBackOff`, 311 puis 312 redémarrages en 26 h**.
- **Dernière sortie** : code 1, `reason: Error`.
- **Journal du conteneur précédent** : `unable to rename (/export/.minio.sys/tmp -> /export/.minio.sys/tmp-old/…) drive is faulty`, puis **`FATAL Unable to initialize backend: drive is faulty`**.
- **Volume Longhorn** `pvc-ebb2605f-0597-43ef-a3d0-6b3c7c8fe3b2` : `attached`, **`healthy`**, 3 réplicas `running` (pi1, pi2, pi3). **Longhorn dit le volume sain ; c'est MinIO qui refuse son disque.**
- **pi2** à **109-110 % de mémoire** pendant tous les relevés.
**Conséquence :** aucune vidéo ne peut être stockée ni relue depuis au moins 26 h. C'est la même famille de symptômes que tools#31 et kadans-api#107 (`Input/output error`). Pour la même raison, **la référence prod « au repos » n'existe pas** : `kubectl top pod` → « Metrics not available », et le compte d'objets et d'octets est inaccessible (serveur éteint). Le diagnostic n'était pas dans le périmètre de ce cadrage (Q1).
## TL;DR
- **MinIO communautaire est mort** (archivé, plus d'images, plus de correctifs), **et notre instance l'est aussi** depuis au moins 26 h.
- **À charge égale sur le même Pi, SeaweedFS 4.46 et MinIO de prod font 0 échec tous les deux.** SeaweedFS envoie un peu plus vite (24,8 contre 20,9 Mio/s), mais consomme **×2,1 de mémoire** (1188 contre 567 Mi) et **×2 de CPU** (547 contre 271 m). Détail en §4.
- **Le critère « < 512 Mi » écrit en §5.4 était trop sévère** : MinIO lui-même le dépasse (567 Mi). L'écart réel est ×2,1, et SeaweedFS reste **sous la limite de prod de MinIO (2 Gi)**.
- **L'API S3, le provisionnement Tofu idempotent, le parcours de bout en bout et la bascule** sont mesurés verts pour SeaweedFS (§3, §5).
- **Recommandation révisée : SeaweedFS**, avec quatre conditions de configuration et une limite mémoire de 1,5 Gi, hors pi2 (§4).
- **Arbitré** : renommer `MINIO_*` en `S3_*` pendant la migration (lot M3).
---
## 1. Pourquoi
### 1.1 L'état réel de MinIO communautaire
| Date | Fait | Source |
|---|---|---|
| mai 2025 | Console d'administration retirée de l'édition communautaire | [vonng, « MinIO is Dead », 2025-12-04](https://blog.vonng.com/en/db/minio-is-dead/) |
| 2025-10-15 | Plus d'images sur Docker Hub ni Quay, au moment d'un avis de sécurité (GHSA-jjjj-jwhf-8rgr) | même source ; [lobehub#9845](https://github.com/lobehub/lobehub/issues/9845) |
| 2025-12-03 | « maintenance mode » | [minio/minio#21714](https://github.com/minio/minio/issues/21714) |
| 2026-02 | README : « THIS REPOSITORY IS NO LONGER MAINTAINED », renvoi vers AIStor | [github.com/minio/minio](https://github.com/minio/minio) |
| 2026-04-25 | Dépôt **archivé**, en lecture seule | bandeau GitHub |
### 1.2 Ce que coûte de rester figé
- **Aucun correctif de sécurité**, alors que `s3.arcodange.fr` est **public**.
- **Des stubs jamais corrigés** : `PutBucketCors` en 501, d'où un CORS global partagé entre toutes les apps.
- **Plus de version à suivre** : le tag est à notre charge depuis que le chart est autonome (tools#31).
- **Aucun recours amont** pour nos pannes : tools#31, le 403 `ListObjectParts` du 2026-09-07, kadans-api#107, et la panne « drive is faulty » d'aujourd'hui.
---
## 2. Inventaire de ce qui dépend de MinIO
Relevé par `git grep -i minio` : front `87de2fb2`, kadans-api `7354087`, kadans-jobs `b7fe972`, dossier `4f0c9d0`, kadans-admin `218614e`, tools `6913e5d`.
### 2.1 Ce qui SURVIT : le client S3 standard
**`kadans-api` garde minio-go v7** : les trois bancs l'utilisent, contre SeaweedFS comme contre MinIO.
| Dépôt | Fichier | Usage |
|---|---|---|
| kadans-api | `stockage.go`, `internal/solo/stockage*.go` | `PresignedPutObject`, `Presign` (PUT de part), `NewMultipartUpload`, `ListObjectParts`, `CompleteMultipartUpload`, `AbortMultipartUpload`, `PresignedGetObject`, `StatObject`, `GetObject`, `RemoveObject`, `ToErrorResponse` |
| kadans-api | `outils/import-direct/main.go`, `outils/verifier-import/main.go` | `StatObject`, `PutObject`, `FGetObject` |
| front | `app/services/depot-video.ts` | PUT présigné par XHR, `withCredentials = false` |
| kadans-jobs | `cmd/worker/main.go` | lit par URL signée par l'API : aucun identifiant du magasin |
| kadans-admin | — | aucune occurrence |
### 2.2 Ce qui est SPÉCIFIQUE à MinIO et doit être réécrit ou renommé
- **tools**
- `minio/` en entier : chart, `values.yaml` (`MINIO_API_CORS_ALLOW_ORIGIN`, `MINIO_PROMETHEUS_AUTH_TYPE`), sondes `/minio/health/*`.
- `minio/iac/*` : provider `aminueza/minio` 3.3.0, root `kvv2/minio/config`, provisionneur `admin:*`.
- `minio/iac/modules/minio_app/*` : clés `MINIO_*` dans `kvv2/minio/<app>`.
- Workflows `.gitea/workflows/minio.yaml` et `helmcharts.yaml`.
- `hashicorp-vault/iac/modules/app_policy/main.tf` et `terraform.tfvars`.
- `chart/values.yaml` et `doc/ce-que-traefik-voit.md`.
- **kadans-api**
- Variables `MINIO_ENDPOINT`, `MINIO_ENDPOINT_INTERNE`, `MINIO_BUCKET`, `MINIO_REGION`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY` : `stockage.go`, `main.go`, `outils/*`, `stockage_test.go`.
- Chart : `deployment.yaml` l.195-214, `vaultstaticsecret-stockage.yaml` (Secret `kadans-api-minio`, chemin `minio/kadans`), `values.yaml` l.208-285.
- Textes et tests qui disent « MinIO » : `AGENTS.md`, `README.md`, `features/stockage.feature`, `openapi.yaml`, `magasin_memoire*.go`, `garde_erreur_du_magasin_test.go`, `magasin_dit_son_erreur_test.go`, `rgpd.go`, `recette_octets*.go`, `vignette_rattrapage.go`, `dimensions.go`, `environnement.go`, migrations 0011 et 0014 (commentaires), ADR-026, `outils/import-collection/*`, `outils/collection/ffmpeg.go`, les autres `vaultstaticsecret-*.yaml`.
- **front**
- `iac/main.tf` et `iac/providers.tf`.
- `scripts/check-cors-magasin.mjs` : son explication et le piège `mc cors get` deviennent faux ; le préflight reste.
- Commentaires et messages : `app/services/depot-video.ts`, `app/lib/sauvegarde/{contrat-stockage,type-contenu}.ts`, `app/lib/video-storage.ts`, `app/composables/useReconciliation.ts`, `scripts/generer-contrat-stockage.mjs`, `tests/e2e/parcours-{import-reel,nouvel-appareil,televersement}.spec.ts`, `tests/e2e/parcours.config.ts`, `tests/fixtures/api-reelle.mjs`, trois tests unitaires.
- Doctrine : `README.md`, `docs/STORAGE_STRATEGY.md`, ADR 012, 013, 014, 018, 021, 022, `docs/memoire/stockage/{sauvegarde,transport}.md`, `docs/cadrage/*`, `docs/notes/2026-07-13-*`, `CLAUDE.md`.
- **dossier**
- `04-architecture/composants/{socle-homelab,api-coeur,README}.md`, `adr/0002-pipeline-video-protege.md`, `runbooks/mvp-bloc4-extrait-protege.md`, `02-prd/{choix-anticipes,seuil-du-gratuit-mesure}.md`, `02-prd/detail/24-sauvegarde-et-compte.md`, `05-roadmap/{03,04,05}-*.md`, `05-roadmap/plan/etat.json`.
---
## 3. Matrice de compatibilité SeaweedFS 4.46
Légende : **mesuré** (B1, B2, B3) ; **doc** ; **incertain** ; **absent**.
| Exigence | Statut | Preuve |
|---|---|---|
| PUT et GET présignés SigV4 | **mesuré** B1 : 200, ETag, `Content-Type` enregistré, octets identiques ; URL altérée → 403 | banc |
| PUT de part présigné, par client HTTP nu avec `Origin` de la PWA | **mesuré** B1 et B2 : 200, ETag, ACAO exact | banc |
| Multipart complet, dont `ListObjectParts` et `Abort` | **mesuré** B1 et B2 : recollé dans l'ordre (sha256) ; terminé ou abandonné → `NoSuchUpload` 404 | banc |
| `StatObject` / `RemoveObject` | **mesuré** : taille et type ; puis 404 `NoSuchKey` | banc |
| CORS par bucket | **mesuré** B1 (préflight `.fr` et `.lab`, ETag exposé, origine étrangère → 403) ; B2 (posé par Tofu) | banc ; [wiki S3 CORS](https://github.com/seaweedfs/seaweedfs/wiki/S3-CORS) |
| ⚠ CORS d'un bucket **sans** règle | **mesuré** B2 : le défaut `-s3.allowedOrigins=*` **accepte toute origine** → à poser explicitement | banc ; `weed/command/server.go` l.172 |
| Droits par identité | **mesuré** B1 et B2 : autre bucket → `AccessDenied` ; créer un bucket → `AccessDenied` ; GET anonyme → 403 | banc |
| Granularité | **source** : toutes les actions multipart se ramènent à `Write` (`weed/iam/helpers.go`) | source |
| ⚠ Noms d'actions IAM | **mesuré** B2 : `s3:ListMultipartUploadParts` refusé (`MalformedPolicyDocument`) ; il faut **`s3:ListParts`** | banc |
| API IAM en écriture | **mesuré** : refusée avec `-s3.config` ; acceptée avec **`-s3.iam.readOnly=false`** ; admin amorcé par `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | banc ; `s3api_embedded_iam.go` l.2596 |
| Provisionnement Tofu | **mesuré** B2 : `apply` → `plan -detailed-exitcode` = **0 « No changes »** → `apply` = 0 changement → encore No changes après redémarrage. ⚠ [`JonasKop/seaweedfs` 0.2.0](https://github.com/JonasKop/terraform-provider-seaweedfs) absent du registre OpenTofu : `registry.terraform.io/…` obligatoire. `hashicorp/aws` pour CORS et cycle de vie (55 s pour la ressource cycle de vie) | banc |
| Cycle de vie `AbortIncompleteMultipartUpload` | **mesuré en partie** : règle acceptée et persistante ; `run-shard` avant l'échéance ne purge pas, ce qui est correct. **Exécution à J+1 non observée** (délai de 24 h en dur) | banc ; `constants_lifecycle_interval_*.go` |
| Purge manuelle | **mesuré** B2 : `s3.clean.uploads -timeAgo 1m` → envois inachevés 3 → 0 | banc |
| ⚠ Volumes et capacité | **mesuré** B2 : volumes pré-alloués par collection. Deux épisodes de 500 « No writable volumes » (disque à 11 % puis 64 %). Les suppressions ne rendent rien avant `volume.vacuum` (Free 0 → 13) | banc |
| **Empreinte à charge égale** | **mesuré** B2 et B3, voir §4 : **1188 Mi, contre 567 Mi pour MinIO** | banc |
| Persistance Longhorn | **mesuré** B1 (objet, CORS, cycle de vie) et B2 (identités dynamiques) après redémarrage | banc |
| Métriques et sondes | **mesuré** : 607 séries `SeaweedFS_*` ; `/healthz` → 200 | banc |
| Chart Helm | **doc** : officiel ; chart autonome probable (leçon de tools#31) | [k8s/charts](https://github.com/seaweedfs/seaweedfs/tree/master/k8s/charts/seaweedfs) |
| Maturité | **doc** : une version par semaine environ ; Apache-2.0 | [releases](https://api.github.com/repos/seaweedfs/seaweedfs/releases) |
---
## 4. SeaweedFS contre MinIO à charge égale, alternatives et recommandation
### 4.1 À charge égale (B2 contre B3)
Même nœud serveur (**pi3**), même chargeur (pod `alpine:3.20` sur **pi1**, même binaire), même Longhorn 5 Gi. Même charge : **8 envois de 320 Mio, 4 en parallèle, parts de 16 Mio**, séquence de kadans-api (part présignée, PUT nu avec `Origin`, `ListObjectParts`, `Complete`, `Stat`, `Remove`). `kubectl top` toutes les 3 s.
| | **SeaweedFS 4.46** (B2) | **MinIO `RELEASE.2024-12-18T13-15-44Z`** (B3, image et config de prod) |
|---|---|---|
| Code de sortie / échecs | **0 / 0** | **0 / 0** |
| Octets envoyés, durée | 2560 Mio en 103,4 s | 2560 Mio en 122,3 s |
| **Débit** | **24,8 Mio/s** | 20,9 Mio/s |
| PUT de part 16 Mio, p50 / p95 / max | **2,18 s** / 5,82 s / 8,03 s | 2,89 s / **3,99 s** / 5,10 s |
| `NewMultipartUpload` p50 / p95 | **2 ms / 6 ms** | 55 ms / 141 ms |
| `ListObjectParts` p50 / p95 / max | **18 ms / 29 ms** / 2,34 s | 117 ms / 283 ms / **304 ms** |
| `CompleteMultipartUpload` p50 / p95 / max | **6 ms** / 1,56 s / 4,54 s | 23 ms / **30 ms / 31 ms** |
| `StatObject` p50 / p95 | 2 ms / 17 ms | **1 ms / 3 ms** |
| `RemoveObject` p50 / p95 | **2 ms** / 13 ms | 6 ms / **11 ms** |
| Mémoire du pod : repos → **pic** | 130 Mi → **1188 Mi** | 75 Mi → **567 Mi** |
| CPU du pod, pic | 547 m | **271 m** |
| CPU de pi3, pic | 1767 m | **1194 m** |
| Mémoire de pi3 pendant l'essai | 62 % → 71 % | **60 % → 61 %** |
| Redémarrages | 0 | 0 |
| Parcours `stockage.go` (PUT nu, `Origin`, `Complete`, GET présigné, `Remove`) | code 0, 7 OK | code 0, 7 OK |
| Limite mémoire du banc | 1536 Mi | **2 Gi** (celle de prod) |
**Lecture :**
- **Aucun des deux ne décroche.** SeaweedFS est plus rapide en médiane et en débit. MinIO est plus régulier : p95 et max plus bas sur les PUT de part et sur `Complete`.
- **SeaweedFS paie ×2,1 en mémoire et ×2 en CPU.** Son pic (1188 Mi) reste **sous la limite de prod de MinIO (2 Gi)**.
- **Le critère « < 512 Mi » était mal posé** : écrit sans référence, MinIO lui-même le dépasse.
- ⚠ **Écart de banc** : MinIO a été chargé avec l'identité **root** ; SeaweedFS avec une identité applicative bornée (B2). L'évaluation d'une politique pèse peu face au transfert, mais l'écart est écrit.
### 4.2 La référence prod
| Ce qui était demandé | Relevé |
|---|---|
| `kubectl top pod` au repos, plusieurs fois | **impossible** : 5 relevés à 15 s d'écart → `Metrics not available` (pod en `CrashLoopBackOff`) |
| Requêtes et limites | image `quay.io/minio/minio:RELEASE.2024-12-18T13-15-44Z` ; requests `cpu: 100m`, `memory: 512Mi` ; limits `memory: 2Gi` (aucune limite CPU) ; PVC 50 Gi |
| Objets et octets de `kadans-videos` | **inaccessibles** : serveur éteint. Dernier chiffre connu : ~7,7 Go le 2026-08-07 (kadans-api#107) |
### 4.3 Les alternatives
| | **Garage** | **RustFS** | **Cloudflare R2** |
|---|---|---|---|
| PUT de part présigné (notre chemin principal) | incertain | incertain | incertain : [la doc](https://developers.cloudflare.com/r2/api/s3/presigned-urls/) ne cite pas UploadPart |
| Multipart, CORS par bucket, cycle de vie Abort | doc ✅ | incertain | doc ✅ |
| Droits | une clé × un bucket | IAM et politiques | jetons d'API |
| Maturité | v2.4.1 stable | 1.0.0-rc.6 (2026-09-11), prerelease | commercial ; ADR-012 : aucun seuil atteint |
| Mesuré ici | non | non | non |
### 4.4 Recommandation révisée
**SeaweedFS.** À charge égale, il fait le même travail sans échec, un peu plus vite. Il règle deux dettes : le CORS global partagé et les stubs 501. Il remplace un logiciel mort **dont l'instance est tombée**. Son surcoût mémoire est réel (×2,1), mais tient sous la limite qu'on donnait déjà à MinIO. Conditions, toutes mesurées comme nécessaires :
1. **`-s3.allowedOrigins` explicite**, jamais `*`, plus un CORS par bucket posé par Tofu.
2. **`-s3.iam.readOnly=false`** et admin amorcé depuis Vault. Politiques en noms SeaweedFS (`s3:ListParts`), provider épinglé `registry.terraform.io/…`.
3. **Capacité** : `-volume.max` et `-master.volumeSizeLimitMB` dimensionnés sur le PVC, plus un **CronJob `volume.vacuum`**.
4. **Filet de purge** : un CronJob `s3.clean.uploads -timeAgo 24h`.
5. **Ressources** : requests `512Mi`, **limit `1536Mi` à `2Gi`**, et `nodeSelector` sur **pi1 ou pi3, jamais pi2** (109-111 % de mémoire à chaque relevé, et nœud du MinIO en panne).
---
## 5. Les bancs
### 5.1 B1 (2026-09-14) : l'API S3
`weed server` tout-en-un sur pi3, Longhorn 2 Gi, identités en fichier, accès par `port-forward`.
| Passage | Code | Résultat |
|---|---|---|
| Nominal (minio-go v7.2.1) | **0** | 38 OK |
| Sabotage : faux secret | **28** | 403 et `SignatureDoesNotMatch` ; ⚠ le `port-forward` meurt quand le serveur refuse un corps en cours d'envoi (KO suivants = harnais) |
| Persistance / témoin | **0** / **1** | relu / KO attendu |
| IAM en écriture avec `-s3.config` | **254** | écritures désactivées |
Créé puis supprimé : ns `essai-seaweedfs`, Secret `seaweedfs-s3`, PVC `seaweedfs-banc` (`pvc-59a2afd6-…`), Deployment. `delete namespace` → 0 ; PV et volume Longhorn → NotFound ; `diff` des PV → 0.
### 5.2 B2 (2026-09-15) : provisionnement, charge, bascule, purge
**Montage** : `weed server -s3 -s3.iam.readOnly=false`, Longhorn 5 Gi, pi3 ; chargeur sur pi1.
**Provisionnement** (OpenTofu 1.10.5, état local) :
| Étape | Code | Sortie |
|---|---|---|
| `init` `JonasKop/seaweedfs` | 1 | absent de `registry.opentofu.org` |
| `init` `registry.terraform.io/JonasKop/seaweedfs` + `hashicorp/aws` 5.100.0 | 0 | installés, signés |
| `apply` n° 1 | 1 | `MalformedPolicyDocument: not a valid action: 'ListMultipartUploadParts'` |
| `apply` n° 2 (`s3:ListParts`) | 0 | 1 ajoutée |
| `plan -detailed-exitcode` | **0** | No changes |
| `apply` n° 3 | 0 | 0 changement |
| `plan` après redémarrage | **0** | No changes |
**Charge.** Essai n° 1, code **146** : 146 PUT en 500, dus au dimensionnement de volumes du harnais (`-volume.max=8`). Essai n° 2, code **0** : chiffres au §4.1.
**Parcours et sabotage.** Nominal : code **0**. Faux secret : code **8** (403 `SignatureDoesNotMatch`, puis `port-forward` mort). Cloison : `AccessDenied` ×2. Bucket sans règle CORS : code **1** (origine étrangère acceptée).
**Bascule.**
- URL de l'ancien magasin : clé inconnue → 403 `InvalidAccessKeyId` ; même clé, autre secret → 403 `SignatureDoesNotMatch`. Tous portent l'ACAO, donc le XHR lit bien 403.
- `uploadId` de l'ancien magasin → `NoSuchUpload` 404.
**Côté front** (lecture de code, `ea56aac5`) :
- `depot-video.ts` l.120-128 transforme ce 403 en `ErreurApiCoeur(403)`, que `classerEchec` (`moteur-sauvegarde.ts` l.169) range en **`serveur`**, donc rejoué en backoff.
- Au rejeu, `televersementId` est renvoyé (l.322-326). L'API reçoit `NoSuchUpload` et rouvre un envoi, et le front remet les parts à zéro (l.332-337).
- **Rattrapage en un essai**, à une condition : l'API signe avec les nouvelles clés **dans le même geste** que la bascule de l'hôte.
**Purge.**
- Envois posés : code 1 (le 3ᵉ en 500, volumes pleins) ; `ListMultipartUploads` = 3.
- `s3.lifecycle.run-shard` avant l'échéance : 0, toujours 3.
- `s3.clean.uploads -timeAgo 1m` : 0, puis **0 envoi inachevé**.
- `volume.vacuum` + `volume.deleteEmpty` : Free 0 → 13, disque 64 % → 0 %.
Créé puis supprimé : ns, Secret `seaweedfs-admin`, PVC `seaweedfs-banc` 5 Gi (`pvc-87fa5604-…`), Deployment et Service `seaweedfs`, Pod `chargeur` (deux fois), buckets et identités internes au banc. `delete namespace` → 0 ; PV et volume Longhorn → NotFound ; `diff` des PV → 0 ; 0 pod restant.
### 5.3 B3 (2026-09-15) : la référence MinIO
**Montage**, calqué sur `tools/minio/templates/deployment.yaml` et `values.yaml` (`6913e5d`, lus en lecture seule) :
- **Identique à la prod** : image, commande `minio server /export -S /etc/minio/certs/ --address :9000 --console-address :9001`, `MINIO_PROMETHEUS_AUTH_TYPE=public`, `MINIO_API_CORS_ALLOW_ORIGIN` (les deux origines), sondes startup, readiness et liveness, `securityContext` 1000:1000, requests 512Mi/100m, limit 2Gi.
- **Écarts** : `nodeSelector` pi3 (comme SeaweedFS) ; PVC Longhorn 5 Gi au lieu de 50 ; Secret root local (sans Vault ni ServiceAccount).
| Passage | Code | Résultat |
|---|---|---|
| `create-bucket kadans-videos` | 0 | — |
| Charge (même binaire, mêmes paramètres, depuis pi1) | **0** | 0 échec ; chiffres au §4.1 |
| Parcours `stockage.go` | **0** | 7 OK, dont PUT nus 200 avec ACAO, GET présigné sha identique, Stat 404 après `Remove` |
**Créé puis supprimé :**
```
kubectl --context=default apply -f minio-banc.yaml # ns essai-seaweedfs, PVC minio-banc (longhorn 5Gi), Deployment minio (pi3), Service minio
kubectl --context=default -n essai-seaweedfs create secret generic minio-banc --from-literal=rootUser=… --from-literal=rootPassword=…
kubectl --context=default apply -f chargeur.yaml # Pod chargeur (alpine:3.20, pi1)
kubectl --context=default -n essai-seaweedfs port-forward svc/minio 39000:9000
kubectl --context=default delete namespace essai-seaweedfs --wait=true --timeout=300s # code 0
kubectl --context=default get ns essai-seaweedfs # NotFound
kubectl --context=default get pv pvc-439e7a80-ebf1-4ba4-bfe0-f4d6637f3293 # NotFound
kubectl --context=default -n longhorn-system get volumes.longhorn.io pvc-439e7a80-ebf1-4ba4-bfe0-f4d6637f3293 # NotFound
diff pv-avant.txt pv-apres.txt # code 0
```
**Prod, lecture seule** : `get deploy`, `get pod`, `top pod` (×5), `get events`, `logs --previous`, `get pvc`, `get volumes.longhorn.io` et `replicas.longhorn.io`. **Aucune** écriture, **aucun** redémarrage, aucun `port-forward` vers la prod. ArgoCD, Vault, DNS et Cloudflare intacts.
### 5.4 Ce qui n'a pas été mesuré
- L'exécution réelle de l'Abort multipart par le worker de cycle de vie, à J+1.
- La copie `rclone` et son débit.
- kadans-api elle-même déployée contre l'essai (seule sa séquence d'appels a été rejouée).
- La référence prod au repos (serveur en panne).
---
## 6. Plan de migration en lots
👤 = exige un humain.
| Lot | Contenu | Humain |
|---|---|---|
| **M0** — la panne de prod (hors migration, préalable) | Décider : réparer MinIO, ou accélérer M1-M5 (Q1). Tant que MinIO ne démarre pas, les objets existants (~7,7 Go) ne sont lisibles par aucune voie S3 : **M4 en dépend** | 👤 fondateur |
| **M1** — le serveur à côté | Chart `tools/seaweedfs/` autonome, avec toutes les conditions du §4.4 (allowedOrigins, iam.readOnly, volumes, vacuum, clean.uploads, 512Mi/1536Mi-2Gi, hors pi2). Service interne seulement | 👤 synchronisation ArgoCD si elle n'est pas automatique |
| **M2** — le provisionnement | Pipeline `seaweedfs` : admin dans `kvv2/seaweedfs/config`. Module `s3_app` : `registry.terraform.io/JonasKop/seaweedfs` pour bucket, identité, clé et politique (`s3:ListParts`) ; `hashicorp/aws` pour CORS et cycle de vie. Secret écrit dans **`kvv2/s3/<app>`** avec des clés **`S3_*`**. `app_policy` : nouveaux chemins | 👤 **apply Vault par OIDC** : `hashicorp-vault`, `seaweedfs`, `front/iac` |
| **M3** — **renommage `MINIO_*` → `S3_*`** (arbitré le 2026-09-15) | kadans-api lit `S3_ENDPOINT`, `S3_ENDPOINT_INTERNE`, `S3_BUCKET`, `S3_REGION`, `S3_ACCESS_KEY`, `S3_SECRET_KEY` **avec repli sur `MINIO_*`** le temps de la bascule, retiré en M6. Idem pour `outils/import-direct` et `outils/verifier-import`. Chart : Secret `kadans-api-s3`, `VaultStaticSecret` → `s3/kadans`, `endpointInterne` → SeaweedFS. Tests (`stockage_test.go`, `TestMagasinMemoireNEcrasePasMinio`), textes et doctrine renommés dans les quatre dépôts, dont `check-cors-magasin.mjs` et `CLAUDE.md` du front | — |
| **M4** — la copie | Job `rclone sync` puis `rclone check`, comptes d'objets et d'octets des deux côtés. **Exige un MinIO qui démarre** (M0) | 👤 lancer et lire le rapport |
| **M5** — la bascule | Ingress `s3.arcodange.fr` et `.lab` vers SeaweedFS **dans le même geste** que le redéploiement de kadans-api sur les nouvelles clés. Contrôles : `bun run check:cors`, `bun run test:import-reel` | 👤 Cloudflare si le tunnel ou le DNS change |
| **M6** — le retrait | Suppression du repli `MINIO_*`, du chart, du module, du provider, du workflow et de `kvv2/minio/*` | 👤 secrets Vault ; PVC MinIO (`Prune=false`) supprimé à la main |
**Retour arrière**, jusqu'à M6 : ingress et `VaultStaticSecret` vers MinIO (s'il démarre), plus `rclone copy` inverse. Les masters restent sur les appareils, et la file ne retire aucune tâche : au pire, le moteur redépose.
---
## 7. Questions ouvertes au fondateur
1. **Q1 — la panne de prod.** MinIO ne démarre plus (« drive is faulty »), alors que Longhorn dit le volume sain. Ouvrir un incident à part pour le diagnostiquer et récupérer les ~7,7 Go, ou traiter la migration comme la réparation ?
2. **Q2 — la limite mémoire de SeaweedFS** : 1536 Mi (pic mesuré + 30 %) ou 2 Gi (la limite actuelle de MinIO) ?
*(Tranchés le 2026-09-15 : « mesurer MinIO d'abord » → §4.1 ; renommer `MINIO_*` en `S3_*` → M3.)*
---
Renvoi : tools#31, tools#37, kadans-api#107, kadans-api#217, incident du 2026-09-07 (`front/docs/memoire/stockage/sauvegarde.md#droit-manque-magasin`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FDDndoBCcpef952NdHDgaQ
La seconde moitié du banc (§5.4) est lancée : provisionnement déclaratif et identités dynamiques, purge réelle des envois multipart abandonnés, charge réaliste depuis le cluster, parcours de bout en bout façon kadans-api, et comportement d'une URL présignée à la bascule. Mêmes règles qu'au premier banc : contexte default explicite, namespace jetable, prod intacte, tout supprimé à la fin. La décision de migrer revient au fondateur après ces mesures.
## Arbitrage fondateur — 2026-09-15 (outil question) : **FINIR LE BANC D'ABORD**
La seconde moitié du banc (§5.4) est lancée : provisionnement déclaratif et identités dynamiques, purge réelle des envois multipart abandonnés, charge réaliste depuis le cluster, parcours de bout en bout façon kadans-api, et comportement d'une URL présignée à la bascule. Mêmes règles qu'au premier banc : contexte `default` explicite, namespace jetable, prod intacte, tout supprimé à la fin. La décision de migrer revient au fondateur après ces mesures.
arcodange
changed title from Remplacer MinIO par SeaweedFS — cadrage : banc mesuré sur le homelab (nos opérations S3 passent toutes), et ce qui reste à trancher avant de migrer to Remplacer MinIO par SeaweedFS — cadrage : banc complet mesuré sur le homelab, quatre conditions de configuration et un critère mémoire manqué2026-09-15 00:31:53 +02:00
Second banc joué (2026-09-15), corps de l'issue mis à jour : matrice §3, recommandation §4, mesures §5.4-5.6, plan §6, questions §7.
✅ Provisionnement par Tofu idempotent, y compris après redémarrage. Il faut deux providers, s3:ListParts au lieu du nom AWS, et registry.terraform.io/… écrit en toutes lettres.
✅ Parcours de bout en bout avec l'Origin de la PWA. Le sabotage rougit (code 8).
✅ Bascule : les 403 sont lisibles par le XHR, et NoSuchUpload fait rouvrir un téléversement. Le front se rattrape en un essai, si l'API change de clés en même temps que l'hôte.
✅ Charge depuis pi1 : 0 échec, 24,8 Mio/s. ❌ Pic mémoire 1188 Mi, pour un critère écrit à < 512 Mi → Q1.
Purge : la voie manuelle s3.clean.uploads fonctionne. L'exécution réelle de la règle de cycle de vie reste non observée (délai de 24 h en dur).
⚠ Deux pièges trouvés : le défaut -s3.allowedOrigins=* accepte toute origine sur un bucket sans règle, et les suppressions ne rendent pas la place avant volume.vacuum (deux épisodes de 500).
Tout ce qui a été créé est supprimé : namespace, PVC, PV et volume Longhorn en NotFound, liste des PV identique (§5.6). Décision au fondateur.
**Second banc joué (2026-09-15), corps de l'issue mis à jour** : matrice §3, recommandation §4, mesures §5.4-5.6, plan §6, questions §7.
- **✅ Provisionnement par Tofu idempotent**, y compris après redémarrage. Il faut deux providers, `s3:ListParts` au lieu du nom AWS, et `registry.terraform.io/…` écrit en toutes lettres.
- **✅ Parcours de bout en bout** avec l'`Origin` de la PWA. Le sabotage rougit (code 8).
- **✅ Bascule** : les 403 sont lisibles par le XHR, et `NoSuchUpload` fait rouvrir un téléversement. Le front se rattrape en un essai, **si** l'API change de clés en même temps que l'hôte.
- **✅ Charge** depuis pi1 : 0 échec, 24,8 Mio/s. **❌ Pic mémoire 1188 Mi**, pour un critère écrit à < 512 Mi → Q1.
- **Purge** : la voie manuelle `s3.clean.uploads` fonctionne. L'exécution réelle de la règle de cycle de vie reste non observée (délai de 24 h en dur).
- ⚠ Deux pièges trouvés : le défaut **`-s3.allowedOrigins=*`** accepte toute origine sur un bucket sans règle, et **les suppressions ne rendent pas la place avant `volume.vacuum`** (deux épisodes de 500).
Tout ce qui a été créé est supprimé : namespace, PVC, PV et volume Longhorn en NotFound, liste des PV identique (§5.6). Décision au fondateur.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FDDndoBCcpef952NdHDgaQ
Arbitrages fondateur — 2026-09-15 (outil question), après le second banc
Mémoire (pic SeaweedFS 1188 Mi contre un critère écrit de 512 Mi) → MESURER MINIO D'ABORD. Référence à charge égale : même image MinIO que la prod, montée dans le namespace jetable sur le même nœud, même charge. Prod observée en lecture seule (mémoire au repos, objets, octets). Décision ensuite.
Noms → MINIO_* devient S3_* pendant la migration, dans le même lot : chart, secrets Vault, API, jobs.
## Arbitrages fondateur — 2026-09-15 (outil question), après le second banc
1. **Mémoire (pic SeaweedFS 1188 Mi contre un critère écrit de 512 Mi) → MESURER MINIO D'ABORD.** Référence à charge égale : même image MinIO que la prod, montée dans le namespace jetable sur le même nœud, même charge. Prod observée en lecture seule (mémoire au repos, objets, octets). Décision ensuite.
2. **Noms → `MINIO_*` devient `S3_*`** pendant la migration, dans le même lot : chart, secrets Vault, API, jobs.
arcodange
changed title from Remplacer MinIO par SeaweedFS — cadrage : banc complet mesuré sur le homelab, quatre conditions de configuration et un critère mémoire manqué to Remplacer MinIO par SeaweedFS — cadrage : trois bancs mesurés sur le homelab, SeaweedFS contre MinIO à charge égale, et le MinIO de prod est EN PANNE2026-09-15 09:33:34 +02:00
Référence MinIO mesurée (B3, 2026-09-15), corps de l'issue mis à jour (§4.1, §4.2, §4.4, §5.3, §6 M0 et M3, §7).
⚠⚠ Le MinIO de prod est en panne. Pod tools/minio-… en CrashLoopBackOff sur pi2, 312 redémarrages en 26 h, avec FATAL Unable to initialize backend: drive is faulty. Le volume Longhorn est pourtant healthy avec 3 réplicas. Relevé en lecture seule, rien touché. Aucune vidéo ne se stocke ni ne se relit → Q1.
À charge égale (image et configuration de prod, même Pi, 8 × 320 Mio, 4 en parallèle) :
SeaweedFS
MinIO
échecs
0
0
débit
24,8 Mio/s
20,9 Mio/s
PUT de part p50 / p95
2,18 s / 5,82 s
2,89 s / 3,99 s
pic mémoire
1188 Mi
567 Mi
pic CPU du pod
547 m
271 m
Le critère « < 512 Mi » était mal posé : MinIO le dépasse lui-même. La recommandation reste SeaweedFS, avec une limite de 1536 Mi à 2 Gi, hors pi2. Le renommage MINIO_* → S3_* est inscrit en M3.
Ménage fait : namespace, PV et volume Longhorn en NotFound, liste des PV identique.
**Référence MinIO mesurée (B3, 2026-09-15), corps de l'issue mis à jour** (§4.1, §4.2, §4.4, §5.3, §6 M0 et M3, §7).
**⚠⚠ Le MinIO de prod est en panne.** Pod `tools/minio-…` en `CrashLoopBackOff` sur pi2, 312 redémarrages en 26 h, avec `FATAL Unable to initialize backend: drive is faulty`. Le volume Longhorn est pourtant `healthy` avec 3 réplicas. Relevé en lecture seule, rien touché. Aucune vidéo ne se stocke ni ne se relit → Q1.
**À charge égale** (image et configuration de prod, même Pi, 8 × 320 Mio, 4 en parallèle) :
| | SeaweedFS | MinIO |
|---|---|---|
| échecs | 0 | 0 |
| débit | 24,8 Mio/s | 20,9 Mio/s |
| PUT de part p50 / p95 | 2,18 s / 5,82 s | 2,89 s / 3,99 s |
| pic mémoire | 1188 Mi | 567 Mi |
| pic CPU du pod | 547 m | 271 m |
Le critère « < 512 Mi » était mal posé : MinIO le dépasse lui-même. **La recommandation reste SeaweedFS**, avec une limite de 1536 Mi à 2 Gi, hors pi2. Le renommage `MINIO_*` → `S3_*` est inscrit en M3.
Ménage fait : namespace, PV et volume Longhorn en NotFound, liste des PV identique.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FDDndoBCcpef952NdHDgaQ
Relais : réduire l'empreinte mémoire de SeaweedFS (Q2). Arrêté à la demande du fondateur, forfait épuisé. AUCUNE mesure valide.
Les leviers trouvés, et leur prix
-volume.index=leveldb (ou leveldbMedium, leveldbLarge) à la place de l'index en mémoire, qui coûte ~20 octets par fichier. L'index passe à 4, 8 ou 12 Mo au total. Prix : accès un peu plus lent. Optimization, Production Setup. Gain probablement faible ici : peu d'objets.
-s3.concurrentUploadLimitMB, -filer.concurrentUploadLimitMB, -volume.concurrentUploadLimitMB (défaut 0, illimité). Une écriture ATTEND tant que les octets en vol dépassent la limite (source 4.46 : weed/s3api/s3api_circuit_breaker.go). Prix : débit et latence sous charge. Le mainteneur conseille plutôt un filer store partagé et plusieurs instances filer/S3 (discussion #6012).
Taille des chunks -filer.maxMB (4 Mio sous weed server). Au plus 4 tampons par requête (weed/operation/upload_chunked.go). Plus petit = moins de mémoire en vol, mais plus de chunks et de métadonnées. Optimization : « 1000 x 32MB memory is needed » pour 1000 lectures concurrentes.
-s3.cacheCapacityMB : cache de chunks pour les GET, désactivé par défaut (0). Rien à gagner.
GOMEMLIMIT (~90-95 % de la limite du conteneur) et GOGC plus bas. Prix : plus de CPU consacré au GC, et un risque d'emballement près de la limite (Go GC guide). Hypothèse non mesurée : c'est le levier le plus probable, le pic de 1188 Mi ressemblant à du tas Go non rendu.
-volume.readBufferSizeMB : plus grand = moins de verrous, mais plus de mémoire en concurrence (Optimization).
Mesures faites : aucune. Trois configurations avaient été déclarées avant d'être jouées :
C1 = index leveldb + concurrentUploadLimitMB=64 ;
C2 = C1 + GOMEMLIMIT=512MiB et GOGC=50 ;
C3 = C2 sous une limite dure de 768 Mi, avec GOMEMLIMIT=690MiB.
La première tentative (C1) n'est pas un verdict. Le PVC a été refusé à cause de son nom en majuscules (seaweedfs-C1, invalide en RFC 1123), donc le pod n'a jamais démarré (apply 1, rollout 1, charge code 200).
Reste à mesurer : C1, C2 et C3 sous la même charge (8 × 320 Mio, 4 en parallèle, parts de 16 Mio ; serveur sur pi3, chargeur sur pi1). Il faudra corriger le nom du PVC en minuscules dans jouer.sh. Ensuite, le parcours stockage.go sur la configuration retenue, puis le choix de la limite. La comparaison reste : SeaweedFS B2 à 1188 Mi, MinIO B3 à 567 Mi.
Ménage fait : delete namespace essai-seaweedfs → code 0 ; namespace NotFound ; aucun PVC n'avait été créé ; liste des PV identique à l'avant-banc (diff code 0). Prod et pi2 non touchés.
**Relais : réduire l'empreinte mémoire de SeaweedFS (Q2). Arrêté à la demande du fondateur, forfait épuisé. AUCUNE mesure valide.**
**Les leviers trouvés, et leur prix**
- **`-volume.index=leveldb`** (ou `leveldbMedium`, `leveldbLarge`) à la place de l'index en mémoire, qui coûte ~20 octets par fichier. L'index passe à 4, 8 ou 12 Mo au total. Prix : accès un peu plus lent. [Optimization](https://github.com/seaweedfs/seaweedfs/wiki/Optimization), [Production Setup](https://github.com/seaweedfs/seaweedfs/wiki/Production-Setup). Gain probablement faible ici : peu d'objets.
- **`-s3.concurrentUploadLimitMB`**, `-filer.concurrentUploadLimitMB`, `-volume.concurrentUploadLimitMB` (défaut 0, illimité). Une écriture ATTEND tant que les octets en vol dépassent la limite (source 4.46 : `weed/s3api/s3api_circuit_breaker.go`). Prix : débit et latence sous charge. Le mainteneur conseille plutôt un filer store partagé et plusieurs instances filer/S3 ([discussion #6012](https://github.com/seaweedfs/seaweedfs/discussions/6012)).
- **Taille des chunks `-filer.maxMB`** (4 Mio sous `weed server`). Au plus 4 tampons par requête (`weed/operation/upload_chunked.go`). Plus petit = moins de mémoire en vol, mais plus de chunks et de métadonnées. [Optimization](https://github.com/seaweedfs/seaweedfs/wiki/Optimization) : « 1000 x 32MB memory is needed » pour 1000 lectures concurrentes.
- **`-s3.cacheCapacityMB`** : cache de chunks pour les GET, désactivé par défaut (0). Rien à gagner.
- **`GOMEMLIMIT`** (~90-95 % de la limite du conteneur) et **`GOGC`** plus bas. Prix : plus de CPU consacré au GC, et un risque d'emballement près de la limite ([Go GC guide](https://go.dev/doc/gc-guide)). **Hypothèse non mesurée : c'est le levier le plus probable**, le pic de 1188 Mi ressemblant à du tas Go non rendu.
- **`-volume.readBufferSizeMB`** : plus grand = moins de verrous, mais plus de mémoire en concurrence ([Optimization](https://github.com/seaweedfs/seaweedfs/wiki/Optimization)).
**Mesures faites : aucune.** Trois configurations avaient été déclarées avant d'être jouées :
- C1 = index leveldb + `concurrentUploadLimitMB=64` ;
- C2 = C1 + `GOMEMLIMIT=512MiB` et `GOGC=50` ;
- C3 = C2 sous une limite dure de 768 Mi, avec `GOMEMLIMIT=690MiB`.
La première tentative (C1) **n'est pas un verdict**. Le PVC a été refusé à cause de son nom en majuscules (`seaweedfs-C1`, invalide en RFC 1123), donc le pod n'a jamais démarré (apply 1, rollout 1, charge code 200).
**Reste à mesurer** : C1, C2 et C3 sous la même charge (8 × 320 Mio, 4 en parallèle, parts de 16 Mio ; serveur sur pi3, chargeur sur pi1). Il faudra corriger le nom du PVC en minuscules dans `jouer.sh`. Ensuite, le parcours `stockage.go` sur la configuration retenue, puis le choix de la limite. La comparaison reste : SeaweedFS B2 à 1188 Mi, MinIO B3 à 567 Mi.
**Ménage fait** : `delete namespace essai-seaweedfs` → code 0 ; namespace NotFound ; aucun PVC n'avait été créé ; liste des PV identique à l'avant-banc (`diff` code 0). Prod et pi2 non touchés.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FDDndoBCcpef952NdHDgaQ
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
⚠⚠ Constat hors banc : le MinIO de prod est EN PANNE
Relevé le 2026-09-15 vers 09:24, en lecture seule (rien n'a été touché) :
tools/minio-56975fd45-s8j8ksur pi2 :CrashLoopBackOff, 311 puis 312 redémarrages en 26 h.reason: Error.unable to rename (/export/.minio.sys/tmp -> /export/.minio.sys/tmp-old/…) drive is faulty, puisFATAL Unable to initialize backend: drive is faulty.pvc-ebb2605f-0597-43ef-a3d0-6b3c7c8fe3b2:attached,healthy, 3 réplicasrunning(pi1, pi2, pi3). Longhorn dit le volume sain ; c'est MinIO qui refuse son disque.Conséquence : aucune vidéo ne peut être stockée ni relue depuis au moins 26 h. C'est la même famille de symptômes que tools#31 et kadans-api#107 (
Input/output error). Pour la même raison, la référence prod « au repos » n'existe pas :kubectl top pod→ « Metrics not available », et le compte d'objets et d'octets est inaccessible (serveur éteint). Le diagnostic n'était pas dans le périmètre de ce cadrage (Q1).TL;DR
MINIO_*enS3_*pendant la migration (lot M3).1. Pourquoi
1.1 L'état réel de MinIO communautaire
1.2 Ce que coûte de rester figé
s3.arcodange.frest public.PutBucketCorsen 501, d'où un CORS global partagé entre toutes les apps.ListObjectPartsdu 2026-09-07, kadans-api#107, et la panne « drive is faulty » d'aujourd'hui.2. Inventaire de ce qui dépend de MinIO
Relevé par
git grep -i minio: front87de2fb2, kadans-api7354087, kadans-jobsb7fe972, dossier4f0c9d0, kadans-admin218614e, tools6913e5d.2.1 Ce qui SURVIT : le client S3 standard
kadans-apigarde minio-go v7 : les trois bancs l'utilisent, contre SeaweedFS comme contre MinIO.stockage.go,internal/solo/stockage*.goPresignedPutObject,Presign(PUT de part),NewMultipartUpload,ListObjectParts,CompleteMultipartUpload,AbortMultipartUpload,PresignedGetObject,StatObject,GetObject,RemoveObject,ToErrorResponseoutils/import-direct/main.go,outils/verifier-import/main.goStatObject,PutObject,FGetObjectapp/services/depot-video.tswithCredentials = falsecmd/worker/main.go2.2 Ce qui est SPÉCIFIQUE à MinIO et doit être réécrit ou renommé
minio/en entier : chart,values.yaml(MINIO_API_CORS_ALLOW_ORIGIN,MINIO_PROMETHEUS_AUTH_TYPE), sondes/minio/health/*.minio/iac/*: provideraminueza/minio3.3.0, rootkvv2/minio/config, provisionneuradmin:*.minio/iac/modules/minio_app/*: clésMINIO_*danskvv2/minio/<app>..gitea/workflows/minio.yamlethelmcharts.yaml.hashicorp-vault/iac/modules/app_policy/main.tfetterraform.tfvars.chart/values.yamletdoc/ce-que-traefik-voit.md.MINIO_ENDPOINT,MINIO_ENDPOINT_INTERNE,MINIO_BUCKET,MINIO_REGION,MINIO_ACCESS_KEY,MINIO_SECRET_KEY:stockage.go,main.go,outils/*,stockage_test.go.deployment.yamll.195-214,vaultstaticsecret-stockage.yaml(Secretkadans-api-minio, cheminminio/kadans),values.yamll.208-285.AGENTS.md,README.md,features/stockage.feature,openapi.yaml,magasin_memoire*.go,garde_erreur_du_magasin_test.go,magasin_dit_son_erreur_test.go,rgpd.go,recette_octets*.go,vignette_rattrapage.go,dimensions.go,environnement.go, migrations 0011 et 0014 (commentaires), ADR-026,outils/import-collection/*,outils/collection/ffmpeg.go, les autresvaultstaticsecret-*.yaml.iac/main.tfetiac/providers.tf.scripts/check-cors-magasin.mjs: son explication et le piègemc cors getdeviennent faux ; le préflight reste.app/services/depot-video.ts,app/lib/sauvegarde/{contrat-stockage,type-contenu}.ts,app/lib/video-storage.ts,app/composables/useReconciliation.ts,scripts/generer-contrat-stockage.mjs,tests/e2e/parcours-{import-reel,nouvel-appareil,televersement}.spec.ts,tests/e2e/parcours.config.ts,tests/fixtures/api-reelle.mjs, trois tests unitaires.README.md,docs/STORAGE_STRATEGY.md, ADR 012, 013, 014, 018, 021, 022,docs/memoire/stockage/{sauvegarde,transport}.md,docs/cadrage/*,docs/notes/2026-07-13-*,CLAUDE.md.04-architecture/composants/{socle-homelab,api-coeur,README}.md,adr/0002-pipeline-video-protege.md,runbooks/mvp-bloc4-extrait-protege.md,02-prd/{choix-anticipes,seuil-du-gratuit-mesure}.md,02-prd/detail/24-sauvegarde-et-compte.md,05-roadmap/{03,04,05}-*.md,05-roadmap/plan/etat.json.3. Matrice de compatibilité SeaweedFS 4.46
Légende : mesuré (B1, B2, B3) ; doc ; incertain ; absent.
Content-Typeenregistré, octets identiques ; URL altérée → 403Originde la PWAListObjectPartsetAbortNoSuchUpload404StatObject/RemoveObjectNoSuchKey.fret.lab, ETag exposé, origine étrangère → 403) ; B2 (posé par Tofu)-s3.allowedOrigins=*accepte toute origine → à poser explicitementweed/command/server.gol.172AccessDenied; créer un bucket →AccessDenied; GET anonyme → 403Write(weed/iam/helpers.go)s3:ListMultipartUploadPartsrefusé (MalformedPolicyDocument) ; il fauts3:ListParts-s3.config; acceptée avec-s3.iam.readOnly=false; admin amorcé parAWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYs3api_embedded_iam.gol.2596apply→plan -detailed-exitcode= 0 « No changes » →apply= 0 changement → encore No changes après redémarrage. ⚠JonasKop/seaweedfs0.2.0 absent du registre OpenTofu :registry.terraform.io/…obligatoire.hashicorp/awspour CORS et cycle de vie (55 s pour la ressource cycle de vie)AbortIncompleteMultipartUploadrun-shardavant l'échéance ne purge pas, ce qui est correct. Exécution à J+1 non observée (délai de 24 h en dur)constants_lifecycle_interval_*.gos3.clean.uploads -timeAgo 1m→ envois inachevés 3 → 0volume.vacuum(Free 0 → 13)SeaweedFS_*;/healthz→ 2004. SeaweedFS contre MinIO à charge égale, alternatives et recommandation
4.1 À charge égale (B2 contre B3)
Même nœud serveur (pi3), même chargeur (pod
alpine:3.20sur pi1, même binaire), même Longhorn 5 Gi. Même charge : 8 envois de 320 Mio, 4 en parallèle, parts de 16 Mio, séquence de kadans-api (part présignée, PUT nu avecOrigin,ListObjectParts,Complete,Stat,Remove).kubectl toptoutes les 3 s.RELEASE.2024-12-18T13-15-44Z(B3, image et config de prod)NewMultipartUploadp50 / p95ListObjectPartsp50 / p95 / maxCompleteMultipartUploadp50 / p95 / maxStatObjectp50 / p95RemoveObjectp50 / p95stockage.go(PUT nu,Origin,Complete, GET présigné,Remove)Lecture :
Complete.4.2 La référence prod
kubectl top podau repos, plusieurs foisMetrics not available(pod enCrashLoopBackOff)quay.io/minio/minio:RELEASE.2024-12-18T13-15-44Z; requestscpu: 100m,memory: 512Mi; limitsmemory: 2Gi(aucune limite CPU) ; PVC 50 Gikadans-videos4.3 Les alternatives
4.4 Recommandation révisée
SeaweedFS. À charge égale, il fait le même travail sans échec, un peu plus vite. Il règle deux dettes : le CORS global partagé et les stubs 501. Il remplace un logiciel mort dont l'instance est tombée. Son surcoût mémoire est réel (×2,1), mais tient sous la limite qu'on donnait déjà à MinIO. Conditions, toutes mesurées comme nécessaires :
-s3.allowedOriginsexplicite, jamais*, plus un CORS par bucket posé par Tofu.-s3.iam.readOnly=falseet admin amorcé depuis Vault. Politiques en noms SeaweedFS (s3:ListParts), provider épingléregistry.terraform.io/….-volume.maxet-master.volumeSizeLimitMBdimensionnés sur le PVC, plus un CronJobvolume.vacuum.s3.clean.uploads -timeAgo 24h.512Mi, limit1536Mià2Gi, etnodeSelectorsur pi1 ou pi3, jamais pi2 (109-111 % de mémoire à chaque relevé, et nœud du MinIO en panne).5. Les bancs
5.1 B1 (2026-09-14) : l'API S3
weed servertout-en-un sur pi3, Longhorn 2 Gi, identités en fichier, accès parport-forward.SignatureDoesNotMatch; ⚠ leport-forwardmeurt quand le serveur refuse un corps en cours d'envoi (KO suivants = harnais)-s3.configCréé puis supprimé : ns
essai-seaweedfs, Secretseaweedfs-s3, PVCseaweedfs-banc(pvc-59a2afd6-…), Deployment.delete namespace→ 0 ; PV et volume Longhorn → NotFound ;diffdes PV → 0.5.2 B2 (2026-09-15) : provisionnement, charge, bascule, purge
Montage :
weed server -s3 -s3.iam.readOnly=false, Longhorn 5 Gi, pi3 ; chargeur sur pi1.Provisionnement (OpenTofu 1.10.5, état local) :
initJonasKop/seaweedfsregistry.opentofu.orginitregistry.terraform.io/JonasKop/seaweedfs+hashicorp/aws5.100.0applyn° 1MalformedPolicyDocument: not a valid action: 'ListMultipartUploadParts'applyn° 2 (s3:ListParts)plan -detailed-exitcodeapplyn° 3planaprès redémarrageCharge. Essai n° 1, code 146 : 146 PUT en 500, dus au dimensionnement de volumes du harnais (
-volume.max=8). Essai n° 2, code 0 : chiffres au §4.1.Parcours et sabotage. Nominal : code 0. Faux secret : code 8 (403
SignatureDoesNotMatch, puisport-forwardmort). Cloison :AccessDenied×2. Bucket sans règle CORS : code 1 (origine étrangère acceptée).Bascule.
InvalidAccessKeyId; même clé, autre secret → 403SignatureDoesNotMatch. Tous portent l'ACAO, donc le XHR lit bien 403.uploadIdde l'ancien magasin →NoSuchUpload404.Côté front (lecture de code,
ea56aac5) :depot-video.tsl.120-128 transforme ce 403 enErreurApiCoeur(403), queclasserEchec(moteur-sauvegarde.tsl.169) range enserveur, donc rejoué en backoff.televersementIdest renvoyé (l.322-326). L'API reçoitNoSuchUploadet rouvre un envoi, et le front remet les parts à zéro (l.332-337).Purge.
ListMultipartUploads= 3.s3.lifecycle.run-shardavant l'échéance : 0, toujours 3.s3.clean.uploads -timeAgo 1m: 0, puis 0 envoi inachevé.volume.vacuum+volume.deleteEmpty: Free 0 → 13, disque 64 % → 0 %.Créé puis supprimé : ns, Secret
seaweedfs-admin, PVCseaweedfs-banc5 Gi (pvc-87fa5604-…), Deployment et Serviceseaweedfs, Podchargeur(deux fois), buckets et identités internes au banc.delete namespace→ 0 ; PV et volume Longhorn → NotFound ;diffdes PV → 0 ; 0 pod restant.5.3 B3 (2026-09-15) : la référence MinIO
Montage, calqué sur
tools/minio/templates/deployment.yamletvalues.yaml(6913e5d, lus en lecture seule) :minio server /export -S /etc/minio/certs/ --address :9000 --console-address :9001,MINIO_PROMETHEUS_AUTH_TYPE=public,MINIO_API_CORS_ALLOW_ORIGIN(les deux origines), sondes startup, readiness et liveness,securityContext1000:1000, requests 512Mi/100m, limit 2Gi.nodeSelectorpi3 (comme SeaweedFS) ; PVC Longhorn 5 Gi au lieu de 50 ; Secret root local (sans Vault ni ServiceAccount).create-bucket kadans-videosstockage.goRemoveCréé puis supprimé :
Prod, lecture seule :
get deploy,get pod,top pod(×5),get events,logs --previous,get pvc,get volumes.longhorn.ioetreplicas.longhorn.io. Aucune écriture, aucun redémarrage, aucunport-forwardvers la prod. ArgoCD, Vault, DNS et Cloudflare intacts.5.4 Ce qui n'a pas été mesuré
rcloneet son débit.6. Plan de migration en lots
👤 = exige un humain.
tools/seaweedfs/autonome, avec toutes les conditions du §4.4 (allowedOrigins, iam.readOnly, volumes, vacuum, clean.uploads, 512Mi/1536Mi-2Gi, hors pi2). Service interne seulementseaweedfs: admin danskvv2/seaweedfs/config. Modules3_app:registry.terraform.io/JonasKop/seaweedfspour bucket, identité, clé et politique (s3:ListParts) ;hashicorp/awspour CORS et cycle de vie. Secret écrit danskvv2/s3/<app>avec des clésS3_*.app_policy: nouveaux cheminshashicorp-vault,seaweedfs,front/iacMINIO_*→S3_*(arbitré le 2026-09-15)S3_ENDPOINT,S3_ENDPOINT_INTERNE,S3_BUCKET,S3_REGION,S3_ACCESS_KEY,S3_SECRET_KEYavec repli surMINIO_*le temps de la bascule, retiré en M6. Idem pouroutils/import-directetoutils/verifier-import. Chart : Secretkadans-api-s3,VaultStaticSecret→s3/kadans,endpointInterne→ SeaweedFS. Tests (stockage_test.go,TestMagasinMemoireNEcrasePasMinio), textes et doctrine renommés dans les quatre dépôts, dontcheck-cors-magasin.mjsetCLAUDE.mddu frontrclone syncpuisrclone check, comptes d'objets et d'octets des deux côtés. Exige un MinIO qui démarre (M0)s3.arcodange.fret.labvers SeaweedFS dans le même geste que le redéploiement de kadans-api sur les nouvelles clés. Contrôles :bun run check:cors,bun run test:import-reelMINIO_*, du chart, du module, du provider, du workflow et dekvv2/minio/*Prune=false) supprimé à la mainRetour arrière, jusqu'à M6 : ingress et
VaultStaticSecretvers MinIO (s'il démarre), plusrclone copyinverse. Les masters restent sur les appareils, et la file ne retire aucune tâche : au pire, le moteur redépose.7. Questions ouvertes au fondateur
(Tranchés le 2026-09-15 : « mesurer MinIO d'abord » → §4.1 ; renommer
MINIO_*enS3_*→ M3.)Renvoi : tools#31, tools#37, kadans-api#107, kadans-api#217, incident du 2026-09-07 (
front/docs/memoire/stockage/sauvegarde.md#droit-manque-magasin).🤖 Generated with Claude Code
https://claude.ai/code/session_01FDDndoBCcpef952NdHDgaQ
Arbitrage fondateur — 2026-09-15 (outil question) : FINIR LE BANC D'ABORD
La seconde moitié du banc (§5.4) est lancée : provisionnement déclaratif et identités dynamiques, purge réelle des envois multipart abandonnés, charge réaliste depuis le cluster, parcours de bout en bout façon kadans-api, et comportement d'une URL présignée à la bascule. Mêmes règles qu'au premier banc : contexte
defaultexplicite, namespace jetable, prod intacte, tout supprimé à la fin. La décision de migrer revient au fondateur après ces mesures.Remplacer MinIO par SeaweedFS — cadrage : banc mesuré sur le homelab (nos opérations S3 passent toutes), et ce qui reste à trancher avant de migrerto Remplacer MinIO par SeaweedFS — cadrage : banc complet mesuré sur le homelab, quatre conditions de configuration et un critère mémoire manquéSecond banc joué (2026-09-15), corps de l'issue mis à jour : matrice §3, recommandation §4, mesures §5.4-5.6, plan §6, questions §7.
s3:ListPartsau lieu du nom AWS, etregistry.terraform.io/…écrit en toutes lettres.Originde la PWA. Le sabotage rougit (code 8).NoSuchUploadfait rouvrir un téléversement. Le front se rattrape en un essai, si l'API change de clés en même temps que l'hôte.s3.clean.uploadsfonctionne. L'exécution réelle de la règle de cycle de vie reste non observée (délai de 24 h en dur).-s3.allowedOrigins=*accepte toute origine sur un bucket sans règle, et les suppressions ne rendent pas la place avantvolume.vacuum(deux épisodes de 500).Tout ce qui a été créé est supprimé : namespace, PVC, PV et volume Longhorn en NotFound, liste des PV identique (§5.6). Décision au fondateur.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FDDndoBCcpef952NdHDgaQ
Arbitrages fondateur — 2026-09-15 (outil question), après le second banc
MINIO_*devientS3_*pendant la migration, dans le même lot : chart, secrets Vault, API, jobs.Remplacer MinIO par SeaweedFS — cadrage : banc complet mesuré sur le homelab, quatre conditions de configuration et un critère mémoire manquéto Remplacer MinIO par SeaweedFS — cadrage : trois bancs mesurés sur le homelab, SeaweedFS contre MinIO à charge égale, et le MinIO de prod est EN PANNERéférence MinIO mesurée (B3, 2026-09-15), corps de l'issue mis à jour (§4.1, §4.2, §4.4, §5.3, §6 M0 et M3, §7).
⚠⚠ Le MinIO de prod est en panne. Pod
tools/minio-…enCrashLoopBackOffsur pi2, 312 redémarrages en 26 h, avecFATAL Unable to initialize backend: drive is faulty. Le volume Longhorn est pourtanthealthyavec 3 réplicas. Relevé en lecture seule, rien touché. Aucune vidéo ne se stocke ni ne se relit → Q1.À charge égale (image et configuration de prod, même Pi, 8 × 320 Mio, 4 en parallèle) :
Le critère « < 512 Mi » était mal posé : MinIO le dépasse lui-même. La recommandation reste SeaweedFS, avec une limite de 1536 Mi à 2 Gi, hors pi2. Le renommage
MINIO_*→S3_*est inscrit en M3.Ménage fait : namespace, PV et volume Longhorn en NotFound, liste des PV identique.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FDDndoBCcpef952NdHDgaQ
Relais : réduire l'empreinte mémoire de SeaweedFS (Q2). Arrêté à la demande du fondateur, forfait épuisé. AUCUNE mesure valide.
Les leviers trouvés, et leur prix
-volume.index=leveldb(ouleveldbMedium,leveldbLarge) à la place de l'index en mémoire, qui coûte ~20 octets par fichier. L'index passe à 4, 8 ou 12 Mo au total. Prix : accès un peu plus lent. Optimization, Production Setup. Gain probablement faible ici : peu d'objets.-s3.concurrentUploadLimitMB,-filer.concurrentUploadLimitMB,-volume.concurrentUploadLimitMB(défaut 0, illimité). Une écriture ATTEND tant que les octets en vol dépassent la limite (source 4.46 :weed/s3api/s3api_circuit_breaker.go). Prix : débit et latence sous charge. Le mainteneur conseille plutôt un filer store partagé et plusieurs instances filer/S3 (discussion #6012).-filer.maxMB(4 Mio sousweed server). Au plus 4 tampons par requête (weed/operation/upload_chunked.go). Plus petit = moins de mémoire en vol, mais plus de chunks et de métadonnées. Optimization : « 1000 x 32MB memory is needed » pour 1000 lectures concurrentes.-s3.cacheCapacityMB: cache de chunks pour les GET, désactivé par défaut (0). Rien à gagner.GOMEMLIMIT(~90-95 % de la limite du conteneur) etGOGCplus bas. Prix : plus de CPU consacré au GC, et un risque d'emballement près de la limite (Go GC guide). Hypothèse non mesurée : c'est le levier le plus probable, le pic de 1188 Mi ressemblant à du tas Go non rendu.-volume.readBufferSizeMB: plus grand = moins de verrous, mais plus de mémoire en concurrence (Optimization).Mesures faites : aucune. Trois configurations avaient été déclarées avant d'être jouées :
concurrentUploadLimitMB=64;GOMEMLIMIT=512MiBetGOGC=50;GOMEMLIMIT=690MiB.La première tentative (C1) n'est pas un verdict. Le PVC a été refusé à cause de son nom en majuscules (
seaweedfs-C1, invalide en RFC 1123), donc le pod n'a jamais démarré (apply 1, rollout 1, charge code 200).Reste à mesurer : C1, C2 et C3 sous la même charge (8 × 320 Mio, 4 en parallèle, parts de 16 Mio ; serveur sur pi3, chargeur sur pi1). Il faudra corriger le nom du PVC en minuscules dans
jouer.sh. Ensuite, le parcoursstockage.gosur la configuration retenue, puis le choix de la limite. La comparaison reste : SeaweedFS B2 à 1188 Mi, MinIO B3 à 567 Mi.Ménage fait :
delete namespace essai-seaweedfs→ code 0 ; namespace NotFound ; aucun PVC n'avait été créé ; liste des PV identique à l'avant-banc (diffcode 0). Prod et pi2 non touchés.🤖 Generated with Claude Code
https://claude.ai/code/session_01FDDndoBCcpef952NdHDgaQ