Helm Charts¶
Das Kamerplanter Helm-Chart basiert auf der bjw-s common library und definiert alle Kubernetes-Ressourcen in einem einzigen Chart. Container-Images und das Chart selbst werden als OCI-Artefakte über die GitHub Container Registry bereitgestellt.
Registry-Übersicht¶
| Artefakt | OCI-URL |
|---|---|
| Helm-Chart | oci://ghcr.io/nolte/kamerplanter-helm/kamerplanter |
| Backend-Image | ghcr.io/nolte/kamerplanter-backend |
| Frontend-Image | ghcr.io/nolte/kamerplanter-frontend |
Chart-Informationen¶
name: kamerplanter
type: application
version: 0.2.0 # Chart-Version (Helm-spezifisch)
appVersion: "1.0.0" # Anwendungs-Version
Abhängigkeiten¶
| Dependency | Version | Quelle | Zweck |
|---|---|---|---|
| common (bjw-s) | 4.6.2 | bjw-s-labs Helm-Charts | Library-Chart für einheitliche Kubernetes-Ressourcen |
| valkey | 0.9.3 | OCI: ghcr.io/valkey-io/valkey-helm | Redis-kompatibler Cache + Celery-Broker |
Chart-Struktur¶
helm/kamerplanter/
├── Chart.yaml # Chart-Metadaten und Abhängigkeiten
├── Chart.lock # Pinned Dependency-Versionen
├── values.yaml # Standard-Werte (Produktion)
├── values-dev.yaml # Override für Entwicklung
├── templates/
│ └── common.yaml # bjw-s Library-Loader
└── charts/
├── common-4.6.2.tgz # bjw-s Common Library
└── valkey-0.9.3.tgz # Valkey Sub-Chart
Das Chart nutzt den bjw-s common.loader.all-Ansatz: Alle Kubernetes-Ressourcen (Deployments, StatefulSets, Services, ConfigMaps, Ingress) werden deklarativ über values.yaml definiert — es gibt keine eigenen Templates.
Konfigurationsreferenz¶
Controller (Deployments & StatefulSets)¶
Backend¶
controllers:
backend:
type: deployment
replicas: 2 # Anpassbar
strategy: RollingUpdate
containers:
main:
image:
repository: ghcr.io/nolte/kamerplanter-backend
tag: latest # In Produktion: feste Version verwenden
env:
ARANGODB_HOST: "..."
ARANGODB_PORT: "8529"
ARANGODB_DATABASE: "kamerplanter"
ARANGODB_USERNAME: "root"
ARANGODB_PASSWORD: "..."
REDIS_URL: "redis://kamerplanter-valkey:6379/0"
CORS_ORIGINS: '["..."]'
DEBUG: "false"
KAMERPLANTER_MODE: "light" # oder "standard"
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
Frontend¶
controllers:
frontend:
type: deployment
replicas: 2
containers:
main:
image:
repository: ghcr.io/nolte/kamerplanter-frontend
tag: latest
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
Das Frontend wird hinter nginx ausgeliefert. Die nginx-Konfiguration wird automatisch als ConfigMap gemountet und leitet /api/-Anfragen an das Backend weiter.
ArangoDB¶
controllers:
arangodb:
type: statefulset
replicas: 1 # Single-Node (kein Cluster)
containers:
main:
image:
repository: arangodb
tag: "3.11"
env:
ARANGO_ROOT_PASSWORD: "..."
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
statefulset:
volumeClaimTemplates:
- name: data
accessMode: ReadWriteOnce
size: 5Gi # Anpassbar
globalMounts:
- path: /var/lib/arangodb3
Services¶
service:
backend:
controller: backend
ports:
http:
port: 8000
frontend:
controller: frontend
ports:
http:
port: 80
arangodb:
controller: arangodb
ports:
http:
port: 8529
Ingress¶
ingress:
main:
enabled: true # Standard: deaktiviert
hosts:
- host: pflanzen.example.com
paths:
- path: /api
pathType: Prefix
service:
identifier: backend
- path: /
pathType: Prefix
service:
identifier: frontend
TLS
Für HTTPS füge eine tls-Sektion hinzu und verwende z.B. cert-manager mit Let's Encrypt:
Valkey (Redis-kompatibler Cache)¶
Umgebungsvariablen¶
| Variable | Pflicht | Standard | Beschreibung |
|---|---|---|---|
ARANGODB_HOST | Ja | — | Hostname des ArangoDB-Service |
ARANGODB_PORT | Ja | 8529 | Port des ArangoDB-Service |
ARANGODB_DATABASE | Ja | kamerplanter | Datenbankname |
ARANGODB_USERNAME | Ja | root | Datenbank-Benutzer |
ARANGODB_PASSWORD | Ja | — | Datenbank-Passwort |
ARANGO_ROOT_PASSWORD | Ja | — | ArangoDB Root-Passwort (muss identisch mit ARANGODB_PASSWORD sein) |
REDIS_URL | Ja | — | Valkey/Redis-Verbindungs-URL |
CORS_ORIGINS | Ja | — | Erlaubte Origins als JSON-Array |
DEBUG | Nein | false | Debug-Modus aktivieren |
KAMERPLANTER_MODE | Nein | light | light (ohne Auth) oder standard (mit Auth) |
REQUIRE_EMAIL_VERIFICATION | Nein | false | E-Mail-Verifikation bei Registrierung |
Entwicklungs-Overrides (values-dev.yaml)¶
Für die lokale Entwicklung existiert eine separate Values-Datei, die Skaffold automatisch verwendet:
| Einstellung | Produktion | Entwicklung |
|---|---|---|
| Replicas (Backend/Frontend) | 2 | 1 |
| Update-Strategie | RollingUpdate | Recreate |
| DEBUG | false | true |
| Resource Limits | Streng | Großzügig |
| Frontend-Port | 80 (nginx) | 5173 (Vite Dev Server) |
| ArangoDB PVC | 5 Gi | 2 Gi |
| Ingress-Host | (konfigurierbar) | kamerplanter.local |
Häufige Anpassungen¶
Ressourcen reduzieren (kleiner Cluster / Raspberry Pi)¶
controllers:
backend:
replicas: 1
containers:
main:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
frontend:
replicas: 1
containers:
main:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 250m
memory: 128Mi
arangodb:
containers:
main:
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
Bestimmte Image-Version pinnen¶
controllers:
backend:
containers:
main:
image:
tag: "1.2.3" # Statt "latest"
frontend:
containers:
main:
image:
tag: "1.2.3"
Image-Tags
Verwende in Produktion immer feste Versions-Tags statt latest. So stellst du sicher, dass ein helm upgrade die erwartete Version deployt.
Storage-Konfiguration (NFR-013)¶
Kamerplanter speichert alle Binärdaten (Fotos, Importe, Exporte) über einen austauschbaren Storage-Adapter. Die Wahl des Backends und die zugehörige Kubernetes-Persistenz werden vollständig über values.yaml gesteuert.
Local Filesystem (Standard)¶
Im Default-Betrieb legt das Chart automatisch das PVC backend-attachments an und mountet es in den Backend- und Celery-Worker-Pods unter /data/attachments.
storage:
backend: local-fs # Standard; kein externes Storage nötig
maxFileSizeMb: 25
presignTtlSeconds: 900
virusScan:
enabled: false
endpoint: ""
localFs:
root: /data/attachments # Container-interner Mount-Pfad
pvc:
size: 100Gi
accessMode: ReadWriteOnce # Für Single-Replica (Standard)
storageClass: "" # Leer = Cluster-Default
Multi-Replica-Betrieb (Backend-Replicas > 1):
storage:
localFs:
pvc:
accessMode: ReadWriteMany # RWX-fähige StorageClass erforderlich
storageClass: longhorn # Oder: nfs, cephfs, etc.
Signing-Secret bei RWX zwingend
Bei mehr als einer Backend-Replica muss STORAGE_LOCALFS_SIGNING_SECRET als stabiles Kubernetes-Secret gesetzt sein. Ohne dieses Secret generiert jeder Pod ein eigenes ephemeres Signing-Secret — Token-Downloads schlagen fehl, wenn die Validierungsanfrage einen anderen Pod erreicht als die Signierung.
kubectl create secret generic kamerplanter-storage-signing \
--from-literal=STORAGE_LOCALFS_SIGNING_SECRET="$(openssl rand -hex 32)" \
--namespace kamerplanter
Im Chart über envFrom referenzieren:
S3-kompatibel (Production)¶
Nicht-geheime S3-Parameter werden direkt in values.yaml gesetzt. Die Credentials kommen ausschließlich aus dem External Secrets Operator (ESO) — nie als Klartext in Git.
storage:
backend: s3
maxFileSizeMb: 25
presignTtlSeconds: 900
s3:
endpointUrl: https://s3.eu-central-1.amazonaws.com
region: eu-central-1
bucket: kamerplanter-prod
usePathStyle: false # true für MinIO und Nicht-AWS-Anbieter
forceTls: true
kmsKeyId: "" # Optional: Customer-Managed Key (SSE-KMS)
# S3-Credentials via External Secrets Operator (NIEMALS Klartext)
credentialsRef:
secretName: storage-s3-credentials
accessKeyIdKey: STORAGE_S3_ACCESS_KEY_ID
secretAccessKeyKey: STORAGE_S3_SECRET_ACCESS_KEY
External Secrets Operator — ESO-Secret:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: storage-s3-credentials
namespace: kamerplanter
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend # Oder AWS Secrets Manager, etc.
kind: ClusterSecretStore
target:
name: storage-s3-credentials
creationPolicy: Owner
data:
- secretKey: STORAGE_S3_ACCESS_KEY_ID
remoteRef:
key: kamerplanter/storage
property: access_key_id
- secretKey: STORAGE_S3_SECRET_ACCESS_KEY
remoteRef:
key: kamerplanter/storage
property: secret_access_key
Ohne ESO: manuelles Kubernetes-Secret
Wenn kein External Secrets Operator verfügbar ist, lege das Secret manuell an:
Das Secret sollte aus einem sicheren Vault kommen und niemals in Git gespeichert werden.NetworkPolicy für S3-Endpoints¶
Das Chart enthält eine NetworkPolicy, die ausgehende Verbindungen auf den konfigurierten S3-Endpunkt beschränkt und den Zugriff auf die Cloud-Metadata-IP (169.254.169.254) blockiert (SSRF-Schutz):
networkPolicies:
storage:
enabled: true
blockMetadataEndpoint: true # Blockiert 169.254.169.254 (Default: true)
MinIO im Cluster¶
storage:
backend: s3
s3:
endpointUrl: http://minio.kamerplanter.svc:9000
region: us-east-1
bucket: kamerplanter
usePathStyle: true
forceTls: false
allowPrivateEndpoint: true # Erlaubt nicht öffentlich erreichbaren Endpunkt
credentialsRef:
secretName: storage-s3-credentials
accessKeyIdKey: STORAGE_S3_ACCESS_KEY_ID
secretAccessKeyKey: STORAGE_S3_SECRET_ACCESS_KEY
Virenscan (optional)¶
ClamAV muss als separates Deployment im Cluster laufen. Das Backend blockiert einen Upload, wenn der Scanner einen Fund meldet.
Häufige Provider-Konfigurationen¶
storage:
backend: s3
s3:
endpointUrl: https://fsn1.your-objectstorage.com
region: eu-central
bucket: mein-kamerplanter-bucket
usePathStyle: false
forceTls: true
credentialsRef:
secretName: storage-s3-credentials
accessKeyIdKey: STORAGE_S3_ACCESS_KEY_ID
secretAccessKeyKey: STORAGE_S3_SECRET_ACCESS_KEY
Siehe auch¶
- Kubernetes-Deployment — Schritt-für-Schritt-Anleitung
- Umgebungsvariablen — Vollständige Referenz aller Umgebungsvariablen
- Speicher konfigurieren (Object Storage) — Admin-UI und Migration