Files
Wabenhain/k8s/06-ingress.yaml
T
Nick Szaunig cd8e307faf perf: hochgeladene Bilder und Videos schnell und vollstaendig ausliefern
Vier Befunde aus der Pruefung der Upload-Kette, alle am gemeinsamen
Datenpfad lib/uploads.ts und app/uploads/[...path]/route.ts:

1. Kein Range-Support. Die Ausliefer-Route beantwortete auch einen
   Range-Request mit einer vollstaendigen 200-Antwort. Ein <video> laedt
   sein Material grundsaetzlich per Range; iOS-Safari schickt zuerst die
   Probe "bytes=0-1" und spielt bei einer 200-Antwort gar nicht ab.
   Springen im Hero-Video war ebenfalls unmoeglich. Die Route beantwortet
   Bereichsanfragen jetzt mit 206 samt Content-Range, meldet
   Accept-Ranges und antwortet auf einen unerfuellbaren Bereich mit 416.

2. Ganze Datei im Speicher. readFile() las jede Datei vollstaendig in den
   RAM — bei einem Hero-Video bis MAX_UPLOAD_VIDEO_BYTES pro
   gleichzeitigem Zugriff. Ersetzt durch einen Stream von der Platte,
   inklusive Content-Length (vorher chunked, ohne Groessenangabe).

3. Keine bedingten Anfragen. Ohne ETag lud der Browser bei jedem Reload
   den vollen Bildinhalt erneut. ETag aus Groesse + Aenderungszeit, 304
   auf If-None-Match.

4. Bilder wurden in Originalaufloesung gespeichert. Ein 24-MP-Foto lag
   mit 6000x4000 und 9 MB auf dem Volume, und next/image musste dieses
   Bild fuer JEDE ausgelieferte Breite komplett neu dekodieren.
   reencodeImage() begrenzt jetzt auf MAX_IMAGE_DIMENSION (2560px lange
   Kante, Seitenverhaeltnis erhalten, kleinere Bilder werden nie
   hochskaliert).

Zusaetzlich MAX_UPLOAD_BYTES von 8 auf 25 MB angehoben: 8 MB lagen unter
dem, was aktuelle Handykameras routinemaessig erzeugen — ein normales
Foto brach mit "Die Datei ist zu gross" ab, obwohl es serverseitig
ohnehin verkleinert wird. Gegen eine Dekompressionsbombe (kleine Datei,
die sich zu Milliarden Pixeln entpackt) bekommt sharp mit
MAX_INPUT_PIXELS eine ausdrueckliche Obergrenze von 100 MP, die die
Byte-Grenze allein nicht abdeckt.

Gemessen an einem 10,17-MB-Foto mit 6000x4000: vorher mit 413 abgelehnt,
jetzt als 2560x1707 / 1,88 MB gespeichert; next/image kalt fuer 640px von
180 ms auf 65 ms.

Der Kommentar zur proxy-body-size in k8s/06-ingress.yaml nennt jetzt das
neue Bild-Limit; die Annotation selbst (110m) deckt es unveraendert ab.
2026-09-13 23:16:23 +02:00

47 lines
1.9 KiB
YAML

# Setzt einen bereits im Cluster laufenden Ingress-Controller voraus (z.B.
# ingress-nginx) sowie optional cert-manager fuer automatisches TLS. Host
# unten anpassen (muss zu NEXT_PUBLIC_SITE_URL in 01-configmap.yaml passen).
#
# X-Forwarded-For: ingress-nginx setzt den Header standardmaessig korrekt
# (verwirft einen vom Client mitgeschickten Wert), lib/rate-limit.ts vertraut
# genau darauf — bei einem ANDEREN Ingress-Controller/zusaetzlichen Proxy
# davor unbedingt pruefen, dass er sich genauso verhaelt, siehe Kommentar in
# lib/rate-limit.ts.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wabenhain
namespace: wabenhain
annotations:
# Nur relevant, falls cert-manager installiert ist — sonst diese beiden
# Zeilen und den "tls:"-Block unten entfernen und Host per HTTP betreiben
# oder TLS auf andere Weise terminieren.
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
# ingress-nginx laesst ohne diese Annotation standardmaessig nur 1 MB pro
# Request durch — weit unter dem Bild- (25 MB, lib/uploads.ts
# MAX_UPLOAD_BYTES) und erst recht dem Video-Limit (100 MB, lib/uploads.ts
# MAX_UPLOAD_VIDEO_BYTES) der Admin-Uploads. Ohne diese Zeile bricht
# nginx jeden Upload jenseits von 1 MB vor der App ab (Verbindungsabbruch
# ohne verwertbare Fehlermeldung im Browser) — 110m deckt das groessere
# der beiden Limits (100 MB) plus etwas Spielraum fuer den
# multipart/form-data-Overhead ab.
nginx.ingress.kubernetes.io/proxy-body-size: "110m"
spec:
ingressClassName: nginx
tls:
- hosts:
- shop.example.com
secretName: wabenhain-tls
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wabenhain-app
port:
number: 80