From f28e5e9d84d581bf63514b83706b5bfc41772fd2 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Sat, 10 Oct 2026 08:15:51 +0200 Subject: [PATCH] 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) --- .gitea/workflows/docker-build.yml | 24 ++++++++++++++ Dockerfile | 20 ++++++++++++ k8s/07-backup-cronjob.yaml | 53 ++++++++++++++++++++++++------- 3 files changed, 85 insertions(+), 12 deletions(-) diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml index cc3017b..363435b 100644 --- a/.gitea/workflows/docker-build.yml +++ b/.gitea/workflows/docker-build.yml @@ -77,6 +77,16 @@ jobs: type=sha,format=short 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 + # MySQL, waehlt zur Laufzeit per DATABASE_PROVIDER, siehe # src/lib/prisma.ts) UND die Migrations-/Seed-Tools (siehe Dockerfile- @@ -86,8 +96,22 @@ jobs: uses: docker/build-push-action@v6 with: context: . + target: runner push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha 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 diff --git a/Dockerfile b/Dockerfile index 293ffce..9021834 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,6 +52,26 @@ RUN pnpm add --save-prod \ @prisma/client@^7.9.1 @prisma/adapter-pg@^7.9.1 \ @prisma/adapter-mariadb@^7.9.1 mariadb@^3.5.3 +# --- Werkzeuge (CronJobs) ------------------------------------------------- +# Eigenes Image -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 -------------------------------------------------------------- FROM base AS runner ENV NODE_ENV=production diff --git a/k8s/07-backup-cronjob.yaml b/k8s/07-backup-cronjob.yaml index 39d5d32..92386c7 100644 --- a/k8s/07-backup-cronjob.yaml +++ b/k8s/07-backup-cronjob.yaml @@ -8,9 +8,9 @@ # ausserdem einer zehnjaehrigen Aufbewahrungspflicht (§ 147 AO) — ein # Datenverlust ist hier nicht nur aergerlich, sondern ein Rechtsproblem. # -# Diese Sicherung liegt bewusst NUR im Cluster. Sie schuetzt gegen -# Volume-Verlust und Bedienfehler, nicht gegen den Ausfall des gesamten -# Clusters oder Standorts. Fuer eine vollstaendige Absicherung die +# Diese Sicherung liegt auf derselben Storage Box wie die Datenbank. Sie +# schuetzt gegen Volume-Verlust und Bedienfehler, nicht gegen den Ausfall +# der Storage Box oder des Standorts. Fuer eine vollstaendige Absicherung die # erzeugten .sql.gz-Dateien zusaetzlich regelmaessig aus dem Cluster # herausziehen (Beispielbefehl siehe k8s/README.md, Abschnitt "Sicherung # und Wiederherstellung"). @@ -20,6 +20,33 @@ # pg_dump-Aufruf unten ersetzen durch: # mysqldump -h wabenhain-mysql -u wabenhain -p"$PGPASSWORD" \ # --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 kind: PersistentVolumeClaim metadata: @@ -28,7 +55,8 @@ metadata: spec: accessModes: - ReadWriteOnce - # storageClassName: "dein-storage-class" + storageClassName: "" + volumeName: wabenhain-db-backup resources: requests: # Eine gzip-komprimierte Sicherung dieses Datenbestandes liegt im @@ -136,6 +164,7 @@ spec: volumeMounts: - name: backup mountPath: /backup + subPath: db-backup # Nur lesend: dieser Job darf Belege sichern, niemals aendern. - name: invoices mountPath: /invoices @@ -209,17 +238,17 @@ spec: spec: restartPolicy: OnFailure # Gleiche Kennung wie der App-Container (Dockerfile: adduser --uid - # 1001 nextjs) — dasselbe Image, derselbe Nutzer. + # 1001 nextjs) — beide Images legen denselben Nutzer an. securityContext: runAsUser: 1001 runAsGroup: 1001 containers: - name: release-stale-orders - # DASSELBE Image und derselbe Tag wie in 05-app-deployment.yaml. - # Beide zusammen aktualisieren: der Lauf nutzt denselben - # Prisma-Client wie die App und muss deshalb zum selben - # Datenbankschema passen. - image: git.encry.de/nick.rosenloecher/wabenhain:latest + # Werkzeug-Image (Dockerfile "AS tools") mit DEMSELBEN Tag wie + # das App-Image in 05-app-deployment.yaml. Beide zusammen + # aktualisieren: der Lauf nutzt denselben Prisma-Client wie die + # App und muss deshalb zum selben Datenbankschema passen. + image: git.encry.de/nick.rosenloecher/wabenhain-tools:latest imagePullPolicy: IfNotPresent # Braucht DATABASE_URL, DATABASE_PROVIDER und STRIPE_SECRET_KEY # — die stehen bereits in denselben beiden Quellen wie fuer die @@ -278,8 +307,8 @@ spec: runAsGroup: 1001 containers: - name: apicore-stock - # DASSELBE Image und derselbe Tag wie in 05-app-deployment.yaml. - image: git.encry.de/nick.rosenloecher/wabenhain:latest + # Werkzeug-Image mit demselben Tag wie in 05-app-deployment.yaml. + image: git.encry.de/nick.rosenloecher/wabenhain-tools:latest imagePullPolicy: IfNotPresent # Braucht DATABASE_URL, DATABASE_PROVIDER, APICORE_API_URL und # APICORE_API_KEY — dieselben Quellen wie die App.