Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3158f2d013 | ||
|
|
edc3a73351 | ||
|
|
6d09adab0c |
+35
-12
@@ -78,20 +78,43 @@ l'URL signée et de sa durée de vie courte (15 min pour déposer, 1 h pour lire
|
||||
jamais `*` : une URL présignée qui fuiterait serait sinon rejouable depuis
|
||||
n'importe quel site.
|
||||
|
||||
### ⚠ À vérifier avant de s'y fier : la taille maximale d'une requête
|
||||
### La taille maximale d'une requête — **mesurée le 2026-08-20**
|
||||
|
||||
Le trafic public passe par un **tunnel Cloudflare**. Les offres gratuites de
|
||||
Cloudflare plafonnent la taille du corps d'une requête proxifiée (de l'ordre de
|
||||
**100 Mo**) — ce plafond n'a **pas** été mesuré ici, il doit l'être avec un vrai
|
||||
téléversement avant d'annoncer une limite aux utilisateurs.
|
||||
Le trafic public passe par un **tunnel Cloudflare**, qui plafonne la taille du
|
||||
corps d'une requête proxifiée. Ce plafond était marqué ici « à vérifier avant de
|
||||
s'y fier ». Il l'est.
|
||||
|
||||
Ce qu'on sait, en revanche, et qui rend le sujet peu urgent : sur le corpus réel
|
||||
du fondateur (707 vidéos, ~2 ans), **la durée moyenne est de 53 secondes** et
|
||||
**deux vidéos seulement dépassent 5 minutes**. Au palier « travail » de l'ADR-018
|
||||
(360p ≈ 3,4 Mo/min), 100 Mo représentent ~29 minutes de cours : le corpus entier
|
||||
passe très largement. Si la limite se confirme, le plafond de 200 Mio annoncé
|
||||
côté API mérite d'être ramené sous celle du tunnel — mieux vaut refuser tôt, avec
|
||||
une phrase claire, qu'échouer au milieu d'un téléversement.
|
||||
Sonde : un PUT **non signé** vers le bucket. Rien ne s'écrit, et les deux
|
||||
réponses se distinguent proprement — un 403 vient de MinIO (donc le corps a
|
||||
traversé le tunnel), un 413 vient du tunnel lui-même.
|
||||
|
||||
| Corps | Réponse | Lecture |
|
||||
|---:|---|---|
|
||||
| 50 Mio | `403` | corps passé, refus de signature |
|
||||
| **100 Mio** | `403` | **corps passé** |
|
||||
| **101 Mio** | `413` | **refusé par le tunnel** |
|
||||
| 200 Mio | `413` | refusé par le tunnel |
|
||||
|
||||
⚠ **Le plafond est exactement 100 Mio.** Or `kadans-api` annonçait un gabarit de
|
||||
200 Mio — le **double**. Une vidéo entre les deux était acceptée, signée,
|
||||
téléversée pendant ~100 Mo… puis coupée.
|
||||
|
||||
⚠ **La parade retenue n'est PAS celle que cette section recommandait.** Ramener
|
||||
le gabarit sous le plafond du tunnel aurait aussi fermé les cours longs (au
|
||||
palier « travail » de l'ADR-018, 360p ≈ 3,4 Mo/min, 100 Mio ≈ 29 min). C'est le
|
||||
**téléversement en plusieurs parts** qui a été livré (kadans-api PR #188) : des
|
||||
parts de 8 Mio passent chacune très en dessous du plafond, sans rétrécir la
|
||||
promesse.
|
||||
|
||||
Le corpus, lui, reste largement sous la limite : sur les 707 vidéos du fondateur
|
||||
(~2 ans), **la durée moyenne est de 53 secondes** et **deux seulement dépassent
|
||||
5 minutes**. C'est ce qui explique que le défaut n'ait jamais été rencontré — et
|
||||
pourquoi il attendait le premier cours entier.
|
||||
|
||||
⚠ **Conséquence pour le bucket, et elle est ici :** un téléversement en parts
|
||||
jamais refermé laisse des parts que *rien ne montre*. Le module `minio_app` pose
|
||||
donc sur chaque bucket une règle de cycle de vie
|
||||
`AbortIncompleteMultipartUpload` à **1 jour** — voir son `main.tf`.
|
||||
|
||||
|
||||
## Donner à une app l'accès au stockage
|
||||
|
||||
@@ -38,6 +38,8 @@ des variables dans le Deployment.
|
||||
## Ce que le module garantit
|
||||
|
||||
- les buckets sont **privés** — l'accès passe par des URL présignées ;
|
||||
- les **téléversements abandonnés** sont ramassés au bout d'un jour (voir plus
|
||||
bas : c'est de l'hygiène de protocole, pas un choix de l'app) ;
|
||||
- le compte de service ne peut **rien** toucher d'autre que ces buckets-là ;
|
||||
- ses clés vont dans `kvv2/minio/<app>`, que le module Vault central autorise
|
||||
déjà l'app à lire (règle **inconditionnelle** : le chemin porte le nom de
|
||||
@@ -51,6 +53,48 @@ l'accès, **sans nouvelle clé**.
|
||||
|
||||
## Ce que le module ne fait PAS
|
||||
|
||||
Il ne pose ni quota, ni règle de cycle de vie, ni versioning : ces choix
|
||||
appartiennent à l'app et varient d'un bucket à l'autre. À ajouter le jour où
|
||||
un besoin réel apparaît, pas avant.
|
||||
Il ne pose ni quota, ni **expiration de contenu**, ni versioning : ces choix
|
||||
appartiennent à l'app et varient d'un bucket à l'autre. « Ces vidéos se purgent
|
||||
à 90 jours » est une décision de produit, elle se prend dans le dépôt du
|
||||
produit.
|
||||
|
||||
### ⚠ L'exception, et la ligne qu'elle trace
|
||||
|
||||
Ce module pose **une seule** règle de cycle de vie :
|
||||
`AbortIncompleteMultipartUpload` à 1 jour, sur chaque bucket qu'il crée.
|
||||
|
||||
Elle a l'air de contredire le paragraphe ci-dessus. Elle ne le contredit pas —
|
||||
elle en précise la frontière, et c'est la question « à QUOI cette connaissance
|
||||
appartient-elle ? » qui tranche :
|
||||
|
||||
- une **expiration de contenu** porte sur des objets que l'app a voulus, qu'elle
|
||||
montre, et dont elle seule sait combien de temps ils valent. Elle varie d'une
|
||||
app à l'autre : elle est chez l'app ;
|
||||
- un **téléversement en plusieurs parts jamais refermé** n'est le contenu de
|
||||
personne. Ce sont des morceaux qu'aucune API ne montre — ni `mc ls`, ni la
|
||||
console — laissés par un navigateur qui a fermé l'onglet. **Aucune app ne veut
|
||||
les garder**, et aucune ne peut les voir depuis son propre code. C'est un
|
||||
déchet de PROTOCOLE, produit par le mécanisme même du bucket : il est chez
|
||||
celui qui crée les buckets.
|
||||
|
||||
Le test pratique : si la réponse à « combien de temps ? » demande de connaître le
|
||||
produit, c'est chez l'app. Ici, la réponse ne demande que de connaître S3 — passé
|
||||
l'expiration des URL signées, un téléversement en cours ne peut plus rien
|
||||
recevoir, il occupe seulement.
|
||||
|
||||
⚠ Le délai n'est **pas** paramétrable, et c'est voulu (YAGNI) : un seul cas
|
||||
existe. Le jour où une app signe des parts pour plus de 24 h, ce sera le
|
||||
déclencheur pour en faire une variable — pas avant.
|
||||
|
||||
### Plancher de version
|
||||
|
||||
⚠ Ce module exige **`aminueza/minio >= 3.10.0`** : `abort_incomplete_multipart_upload`
|
||||
y est apparu (mesuré en interrogeant le schéma du provider version par version —
|
||||
3.9.0 ne l'a pas). Un appelant resté plus bas se verra dire, dès `tofu init` :
|
||||
|
||||
```
|
||||
no available releases match the given constraints 3.3.0, >= 3.10.0
|
||||
```
|
||||
|
||||
… ce qui NOMME la version à atteindre, au lieu d'un « unsupported block type »
|
||||
au plan, qui ne l'aurait pas dite.
|
||||
|
||||
@@ -8,6 +8,48 @@ resource "minio_s3_bucket" "app" {
|
||||
force_destroy = false # détruire un bucket doit être un geste explicite
|
||||
}
|
||||
|
||||
# ── Les téléversements ABANDONNÉS ne s'accumulent pas ────────────────────────
|
||||
#
|
||||
# Un téléversement S3 en plusieurs parts qui n'est jamais refermé laisse ses
|
||||
# parts dans le bucket : elles occupent de l'espace et AUCUN objet ne les
|
||||
# montre. `mc ls` n'en dit rien, la console non plus. C'est donc une fuite qui
|
||||
# ne se voit qu'à la facture — ou à la saturation du volume, qui est ici de
|
||||
# 50 Gi et déjà dimensionné à 250 h de cours.
|
||||
#
|
||||
# L'application abandonne ce qu'elle ouvre quand elle échoue en route. Ce
|
||||
# qu'elle ne peut PAS rattraper : un navigateur qui ferme l'onglet, perd le
|
||||
# réseau, ou expire. Ce cas-là appartient au BUCKET, pas au code applicatif —
|
||||
# c'est la seule place d'où l'on voit un téléversement que plus personne ne
|
||||
# suit.
|
||||
#
|
||||
# ⚠ POURQUOI UN JOUR, ET PAS SEPT. Les URL de parts sont signées pour quelques
|
||||
# heures (2 h côté kadans-api). Passé ce délai, un téléversement en cours ne
|
||||
# peut PLUS rien recevoir : il est mort, il occupe seulement. Un jour laisse
|
||||
# douze fois la marge nécessaire au plus long téléversement possible, sans
|
||||
# garder des déchets une semaine.
|
||||
#
|
||||
# ⚠ CE N'EST PAS PARAMÉTRABLE, ET C'EST VOULU (YAGNI) : un seul cas existe. Le
|
||||
# jour où une app signe des parts pour plus de 24 h, ce sera le déclencheur pour
|
||||
# en faire une variable — pas avant.
|
||||
#
|
||||
# ⚠ CE RÉGLAGE-CI FONCTIONNE VRAIMENT, contrairement au CORS par bucket. MinIO
|
||||
# édition communautaire stubbe `PutBucketCors` (501, cf. `cmd/dummy-handlers.go`
|
||||
# du serveur), ce qui a déjà coûté une tentative d'IaC ; les handlers de CYCLE DE
|
||||
# VIE, eux, n'y figurent PAS — vérifié à la source le 2026-08-20.
|
||||
resource "minio_ilm_policy" "app" {
|
||||
for_each = minio_s3_bucket.app
|
||||
bucket = each.value.bucket
|
||||
|
||||
rule {
|
||||
id = "abandon-televersements-incomplets"
|
||||
status = "Enabled"
|
||||
|
||||
abort_incomplete_multipart_upload {
|
||||
days_after_initiation = "1d"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "minio_iam_policy" "app" {
|
||||
name = "${var.app}-app"
|
||||
policy = jsonencode({
|
||||
|
||||
@@ -2,6 +2,13 @@ terraform {
|
||||
required_providers {
|
||||
minio = {
|
||||
source = "aminueza/minio"
|
||||
# ⚠ PLANCHER, ET IL N'EST PAS DÉCORATIF : `abort_incomplete_multipart_upload`
|
||||
# est apparu en 3.10.0 (mesuré en interrogeant le schéma du provider,
|
||||
# version par version : 3.9.0 ne l'a pas, 3.10.0 l'a). Sans cette
|
||||
# contrainte, un appelant resté sur 3.3.0 échouerait au plan avec un
|
||||
# « unsupported block type » qui ne dit pas qu'il faut monter de version.
|
||||
# Le plancher fait dire à tofu la vraie phrase, au bon moment.
|
||||
version = ">= 3.10.0"
|
||||
configuration_aliases = [minio]
|
||||
}
|
||||
vault = {
|
||||
|
||||
Reference in New Issue
Block a user