feat(k8s): Sicherung und Hintergrund-Jobs lauffaehig machen

- Werkzeug-Image wabenhain-tools (Dockerfile "AS tools", Muster wie
  artista-modelcamp-tools): Im App-Image fehlten tsconfig.json und Pakete
  wie stripe, Bestandsabgleich und Bestellfreigabe brachen mit
  "Cannot find module" ab. Die CI baut es neben dem App-Image.
- Datenbank-Sicherung: PV auf der Storage Box (Customer/Wabenhain), der
  Unterordner db-backup entsteht per subPath. Vorher blieb der PVC ohne
  StorageClass haengen.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-10 08:15:51 +02:00
1 parent b246c9ff4e
commit f28e5e9d84
3 files changed
+85 -12

No files matched your search

+24
View File
@@ -77,6 +77,16 @@ jobs:
type=sha,format=short type=sha,format=short
type=semver,pattern={{version}} type=semver,pattern={{version}}
- name: Metadaten Werkzeuge
id: meta-tools
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_PUSH }}/${{ steps.image.outputs.name }}-tools
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=sha,format=short
type=semver,pattern={{version}}
# Ein einzelnes Image traegt beide Prisma-Client-Varianten (Postgres + # Ein einzelnes Image traegt beide Prisma-Client-Varianten (Postgres +
# MySQL, waehlt zur Laufzeit per DATABASE_PROVIDER, siehe # MySQL, waehlt zur Laufzeit per DATABASE_PROVIDER, siehe
# src/lib/prisma.ts) UND die Migrations-/Seed-Tools (siehe Dockerfile- # src/lib/prisma.ts) UND die Migrations-/Seed-Tools (siehe Dockerfile-
@@ -86,8 +96,22 @@ jobs:
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
target: runner
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
# Werkzeug-Image fuer die CronJobs (Dockerfile "AS tools"); nutzt die
# gemeinsamen Build-Stufen aus dem Cache des ersten Builds.
- name: Werkzeuge bauen und pushen
uses: docker/build-push-action@v6
with:
context: .
target: tools
push: true
tags: ${{ steps.meta-tools.outputs.tags }}
labels: ${{ steps.meta-tools.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
+20
View File
@@ -52,6 +52,26 @@ RUN pnpm add --save-prod \
@prisma/client@^7.9.1 @prisma/adapter-pg@^7.9.1 \ @prisma/client@^7.9.1 @prisma/adapter-pg@^7.9.1 \
@prisma/adapter-mariadb@^7.9.1 mariadb@^3.5.3 @prisma/adapter-mariadb@^7.9.1 mariadb@^3.5.3
# --- Werkzeuge (CronJobs) -------------------------------------------------
# Eigenes Image <repo>-tools fuer die Skripte unter scripts/, die als
# CronJob laufen (Bestandsabgleich mit ApiCore, Freigabe haengender
# Bestellungen), nach dem Muster von artista-modelcamp-tools. Das runner-
# Image enthaelt nur den Standalone-Output: dort fehlen tsconfig.json (der
# Pfad-Alias "@/") und Pakete wie stripe, die Skripte brechen dort mit
# "Cannot find module" ab. Steht VOR runner, damit ein Build ohne --target
# weiterhin das App-Image liefert.
FROM base AS tools
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 --ingroup nodejs nextjs
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/src ./src
COPY --from=builder --chown=nextjs:nodejs /app/scripts ./scripts
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
COPY --from=builder --chown=nextjs:nodejs /app/prisma.config.ts /app/package.json /app/tsconfig.json ./
ENV PATH="/app/node_modules/.bin:${PATH}"
USER 1001
CMD ["tsx", "scripts/apicore-stock-sync.ts"]
# --- Runtime -------------------------------------------------------------- # --- Runtime --------------------------------------------------------------
FROM base AS runner FROM base AS runner
ENV NODE_ENV=production ENV NODE_ENV=production
+41 -12
View File
@@ -8,9 +8,9 @@
# ausserdem einer zehnjaehrigen Aufbewahrungspflicht (§ 147 AO) — ein # ausserdem einer zehnjaehrigen Aufbewahrungspflicht (§ 147 AO) — ein
# Datenverlust ist hier nicht nur aergerlich, sondern ein Rechtsproblem. # Datenverlust ist hier nicht nur aergerlich, sondern ein Rechtsproblem.
# #
# Diese Sicherung liegt bewusst NUR im Cluster. Sie schuetzt gegen # Diese Sicherung liegt auf derselben Storage Box wie die Datenbank. Sie
# Volume-Verlust und Bedienfehler, nicht gegen den Ausfall des gesamten # schuetzt gegen Volume-Verlust und Bedienfehler, nicht gegen den Ausfall
# Clusters oder Standorts. Fuer eine vollstaendige Absicherung die # der Storage Box oder des Standorts. Fuer eine vollstaendige Absicherung die
# erzeugten .sql.gz-Dateien zusaetzlich regelmaessig aus dem Cluster # erzeugten .sql.gz-Dateien zusaetzlich regelmaessig aus dem Cluster
# herausziehen (Beispielbefehl siehe k8s/README.md, Abschnitt "Sicherung # herausziehen (Beispielbefehl siehe k8s/README.md, Abschnitt "Sicherung
# und Wiederherstellung"). # und Wiederherstellung").
@@ -20,6 +20,33 @@
# pg_dump-Aufruf unten ersetzen durch: # pg_dump-Aufruf unten ersetzen durch:
# mysqldump -h wabenhain-mysql -u wabenhain -p"$PGPASSWORD" \ # mysqldump -h wabenhain-mysql -u wabenhain -p"$PGPASSWORD" \
# --single-transaction --routines wabenhain | gzip -9 > "$file.partial" # --single-transaction --routines wabenhain | gzip -9 > "$file.partial"
# Ziel ist die Storage Box wie bei den uebrigen Wabenhain-Volumes. Das PV
# zeigt auf den vorhandenen Ordner Customer/Wabenhain; der Unterordner
# db-backup entsteht ueber subPath beim ersten Lauf.
apiVersion: v1
kind: PersistentVolume
metadata:
name: wabenhain-db-backup
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
mountOptions: ["vers=3.0", "uid=70", "gid=70", "dir_mode=0700", "file_mode=0600", "noperm"]
csi:
driver: smb.csi.k8s.io
volumeHandle: wabenhain-db-backup
volumeAttributes:
source: //u652065.your-storagebox.de/backup/Server/Disks/Customer/Wabenhain
nodeStageSecretRef:
name: smb-credentials
namespace: wabenhain
claimRef:
namespace: wabenhain
name: wabenhain-db-backup
---
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
@@ -28,7 +55,8 @@ metadata:
spec: spec:
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
# storageClassName: "dein-storage-class" storageClassName: ""
volumeName: wabenhain-db-backup
resources: resources:
requests: requests:
# Eine gzip-komprimierte Sicherung dieses Datenbestandes liegt im # Eine gzip-komprimierte Sicherung dieses Datenbestandes liegt im
@@ -136,6 +164,7 @@ spec:
volumeMounts: volumeMounts:
- name: backup - name: backup
mountPath: /backup mountPath: /backup
subPath: db-backup
# Nur lesend: dieser Job darf Belege sichern, niemals aendern. # Nur lesend: dieser Job darf Belege sichern, niemals aendern.
- name: invoices - name: invoices
mountPath: /invoices mountPath: /invoices
@@ -209,17 +238,17 @@ spec:
spec: spec:
restartPolicy: OnFailure restartPolicy: OnFailure
# Gleiche Kennung wie der App-Container (Dockerfile: adduser --uid # Gleiche Kennung wie der App-Container (Dockerfile: adduser --uid
# 1001 nextjs) — dasselbe Image, derselbe Nutzer. # 1001 nextjs) — beide Images legen denselben Nutzer an.
securityContext: securityContext:
runAsUser: 1001 runAsUser: 1001
runAsGroup: 1001 runAsGroup: 1001
containers: containers:
- name: release-stale-orders - name: release-stale-orders
# DASSELBE Image und derselbe Tag wie in 05-app-deployment.yaml. # Werkzeug-Image (Dockerfile "AS tools") mit DEMSELBEN Tag wie
# Beide zusammen aktualisieren: der Lauf nutzt denselben # das App-Image in 05-app-deployment.yaml. Beide zusammen
# Prisma-Client wie die App und muss deshalb zum selben # aktualisieren: der Lauf nutzt denselben Prisma-Client wie die
# Datenbankschema passen. # App und muss deshalb zum selben Datenbankschema passen.
image: git.encry.de/nick.rosenloecher/wabenhain:latest image: git.encry.de/nick.rosenloecher/wabenhain-tools:latest
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
# Braucht DATABASE_URL, DATABASE_PROVIDER und STRIPE_SECRET_KEY # Braucht DATABASE_URL, DATABASE_PROVIDER und STRIPE_SECRET_KEY
# — die stehen bereits in denselben beiden Quellen wie fuer die # — die stehen bereits in denselben beiden Quellen wie fuer die
@@ -278,8 +307,8 @@ spec:
runAsGroup: 1001 runAsGroup: 1001
containers: containers:
- name: apicore-stock - name: apicore-stock
# DASSELBE Image und derselbe Tag wie in 05-app-deployment.yaml. # Werkzeug-Image mit demselben Tag wie in 05-app-deployment.yaml.
image: git.encry.de/nick.rosenloecher/wabenhain:latest image: git.encry.de/nick.rosenloecher/wabenhain-tools:latest
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
# Braucht DATABASE_URL, DATABASE_PROVIDER, APICORE_API_URL und # Braucht DATABASE_URL, DATABASE_PROVIDER, APICORE_API_URL und
# APICORE_API_KEY — dieselben Quellen wie die App. # APICORE_API_KEY — dieselben Quellen wie die App.