caddy-fs-s3cache — Dateien aus S3 ausliefern, ohne für jedes Bild dreimal zu fragen.
Viele Websites speichern Bilder und Downloads nicht mehr auf der Festplatte des Servers, sondern in einem Objektspeicher wie Amazon S3. Der Webserver Caddy kann solche Dateien direkt von dort ausliefern. Mit dem bisher verfügbaren Modul kostet das pro Datei drei Anfragen an S3, und beim nächsten Aufruf beginnt alles von vorn. caddy-fs-s3cache holt eine Datei einmal, behält eine Kopie auf der lokalen Platte und beantwortet alle weiteren Aufrufe von dort. Nur in Abständen fragt es bei S3 nach, ob sich die Datei geändert hat.
- Was es ist: ein Erweiterungsmodul für Caddy (
caddy.fs.s3cache), das an die Stelle des bisherigen S3-Moduls tritt - Für wen: Betreiber, die Caddy oder FrankenPHP einsetzen und ihre Medien in S3 halten, zum Beispiel in Kubernetes
- Technik: Go-Modul · Caddy 2.11+ · Go 1.26+ · MIT-Lizenz · Quellcode auf GitHub
TL;DR — Passt das zu meinem Setup?
Nutze es wenn …
- deine Website Bilder oder Downloads aus einem S3-Bucket ausliefert und die Adressen der Dateien gleich bleiben sollen
- die Server keine eingehängten Netzlaufwerke (FUSE-Mounts) und keine gemeinsame Festplatte haben sollen, etwa Pods in Kubernetes
- dieselben Dateien oft abgerufen werden; dann kommen sie fast immer von der lokalen Platte
Nutze es nicht wenn …
- du Ordnerinhalte als Liste anzeigen willst; das Modul kennt einzelne Dateien, keine Verzeichnisse
- eine ersetzte Datei sofort überall sichtbar sein muss; das Modul fragt frühestens nach einer Minute wieder bei S3 nach, die Frist ist einstellbar
- gar kein lokaler Speicherplatz vorhanden ist; ein kleiner temporärer Bereich reicht
Aufwand
Eine Zeile beim Bau von Caddy und ein Block in der Konfiguration. Zugangsdaten für S3 kommen aus der üblichen AWS-Kette, in Kubernetes aus der Identität des Pods. In der Konfigurationsdatei steht kein Passwort.
Warum drei Anfragen pro Datei, und wie der Cache sie spart
Das Problem
Caddys Dateiserver behandelt S3 wie eine Festplatte. Für jede Datei fragt er erst nach, ob sie existiert und wie groß sie ist. Dann öffnet er sie. Zum Schluss springt er an den Anfang zurück, um sie auszuliefern. Eine Festplatte erledigt das in Mikrosekunden. S3 kennt kein Zurückspringen, also wird die Datei ein zweites Mal geholt. Drei Anfragen, jede 30 bis 40 Millisekunden, für ein winziges Bild. Und beim nächsten Aufruf wieder von vorn.
Die Lösung
caddy-fs-s3cache merkt sich, was es schon geholt hat. Die erste Anfrage lädt die Datei und legt sie auf die lokale Platte. Existenz, Größe, Öffnen und Ausliefern kommen danach von dort. Nach einer einstellbaren Frist, standardmäßig eine Minute, fragt das Modul bei S3 nach, ob sich die Datei geändert hat. Unverändert heißt eine kurze Antwort ohne Download.
| Situation | Anfragen an S3 |
|---|---|
| Datei kürzlich geholt | keine |
| Datei älter als die Frist, aber unverändert | eine kurze Nachfrage, kein Download |
| Datei neu oder geändert | ein Download |
| Datei existiert nicht | eine Nachfrage, danach 30 Sekunden lang keine |
| Viele Besucher rufen eine neue Datei gleichzeitig ab | ein Download für alle |
Fällt S3 aus, liefert Caddy weiter aus, was es schon hat, und schreibt eine Warnung ins Protokoll. Nur eine Datei, die nie geholt wurde, meldet dann einen Serverfehler statt eines falschen „nicht gefunden“.
Gemessen im Betrieb
Gemessen auf einer TYPO3-Website in Kubernetes. Gleiche Server, gleiche Dateien, einmal mit dem bisherigen Modul und einmal mit caddy-fs-s3cache. Die Zeiten sind die Antwortzeit des Webservers selbst, ohne den Weg zum Browser.
| Was abgerufen wurde | bisher | mit Cache |
|---|---|---|
| Kleines Bild (1,8 kB), schon im Cache | 72 bis 100 ms | 3 bis 5 ms |
| Kleines Bild, erster Abruf | 250 ms | 44 ms |
| Mittleres Bild (92 kB), schon im Cache | 70 bis 180 ms | 4 bis 6 ms |
| Datei, die es nicht gibt | 40 ms | 12 bis 27 ms |
| Erster Abruf über den vorgeschalteten Cache-Proxy | 70 bis 88 ms | 1 bis 12 ms |
Zur Einordnung: Ein einzelner S3-Zugriff aus dem Pod dauert in derselben Region 28 bis 42 Millisekunden. Drei davon ergaben die 70 bis 100 Millisekunden. Der Cache macht daraus den Preis einer lokalen Datei.
Medien ohne FUSE-Mount aus S3 ausliefern? Ich helfe bei Caddy-Build, Caddyfile und der Pod-Identität in Kubernetes. Anfragen →
Einbau und Konfiguration
Für Betreiber, die Caddy oder FrankenPHP selbst bauen. Zwei Schritte.
1. Modul beim Bau einbinden
xcaddy build --with github.com/ohartwig/caddy-fs-s3cache@v1.0.1
Bei FrankenPHP steht die Zeile als weiteres --with neben github.com/dunglas/frankenphp.
2. Dateisystem in der Caddyfile anlegen
Der erste Block benennt den Bucket und den Cache. Der zweite liefert alles unter /media/ daraus aus.
{
filesystem media s3cache {
bucket my-media-bucket
region eu-north-1
cache_dir /var/cache/caddy/media
cache_max_size 512MiB
revalidate_after 60s
negative_ttl 30s
keepalive 25s
}
}
example.org {
handle /media/* {
route {
uri strip_prefix /media
root * .
fs media
file_server {
pass_thru
}
respond "Not Found" 404
}
}
}
Die Optionen
| Option | Standard | Bedeutung |
|---|---|---|
bucket | Pflicht | Name des S3-Buckets |
region, endpoint, use_path_style | aus der AWS-Konfiguration | AWS-Region; eigener Endpunkt und Pfad-Adressierung für S3-kompatible Speicher wie MinIO oder RustFS |
prefix | leer | Nur ein Unterordner des Buckets wird sichtbar |
cache_dir | temporäres Verzeichnis | Wo die Kopien liegen; wird beim Start geleert |
cache_max_size | 512MiB | Obergrenze für den Cache; die am längsten nicht genutzten Dateien weichen zuerst |
revalidate_after | 60s | So lange gilt eine Kopie ohne Nachfrage bei S3 |
negative_ttl | 30s | So lange bleibt eine fehlende Datei fehlend, ohne neue Nachfrage |
keepalive | 25s | Hält die Verbindung zu S3 im Hintergrund offen, damit der erste Abruf nach einer Pause nicht auf den Verbindungsaufbau wartet; off schaltet ab |
max_idle_conns | 16 | Wie viele Verbindungen zu S3 offen gehalten werden |
Rechte
Das Modul braucht Leserechte auf die Dateien im Bucket (s3:GetObject). Ist keepalive an, zusätzlich das Recht, den Bucket zu sehen (s3:ListBucket). Zugangsdaten stehen nicht in der Caddyfile. Sie kommen wie bei allen AWS-Werkzeugen aus Umgebungsvariablen, der AWS-Konfigurationsdatei oder in Kubernetes aus der Identität des Pods.
Häufige Fallstricke
- Der Cache-Ordner wird beim Start geleert. Das Modul führt sein Verzeichnis im Arbeitsspeicher, und das ist nach einem Neustart leer. Alte Dateien auf der Platte wären unerreichbar, also räumt es sie weg. In Kubernetes ist ein temporäres Volume pro Pod (
emptyDir) der richtige Ort, kein geteiltes Laufwerk. - Eine ersetzte Datei erscheint mit Verzögerung. Wer eine Datei unter gleichem Namen austauscht, sieht die neue Fassung erst nach der nächsten Nachfrage bei S3, also spätestens nach
revalidate_after. Dateien mit unveränderlichen Namen, etwa Bildvarianten mit Prüfsumme im Namen, kennen das Problem nicht. - Ein vorgeschalteter Cache ersetzt den lokalen nicht. Ein HTTP-Cache vor dem Webserver hilft nur, wenn dieselbe Adresse mehrfach abgerufen wird. Der erste Abruf jeder Adresse erreicht immer den Webserver. Gerade bei vielen selten abgerufenen Bildern lohnt der lokale Cache deshalb am meisten.
- Ungültige Pfade erreichen S3 nicht. Pfade mit
..oder mit führendem oder abschließendem Schrägstrich werden abgewiesen, bevor eine Anfrage entsteht.
Medien aus S3 ohne Netzlaufwerk ausliefern?
Wenn jedes Bild deiner Website drei Anfragen an S3 kostet oder ein eingehängtes Netzlaufwerk im Weg steht, hilft caddy-fs-s3cache. Ich unterstütze beim Bau von Caddy, bei der Konfiguration und bei der Pod-Identität in Kubernetes.
Oder direkt schreiben: mail@ole-hartwig.eu