Files
ApiCore/deploy/k8s/components/voice/cronjob-voice-transcribe.yaml
Nick SzaunigandClaude Opus 5.5 484d799a36 fix: Whisper liest /work/in nur, Restrisiko des Abschreib-Pods dokumentiert
Der Decoder-Container mountet /work/in schreibgeschützt und nur
/work/out beschreibbar (Policy-Test); ingest ignoriert Ergebnisse zu
Aufnahmen außerhalb des Manifests (Test). ARCHITECTURE Annahme 106
beschreibt das getragene Restrisiko (DNS im Pod-Netz, Sicht auf den
eigenen Lauf) und stärkere Optionen für später.

Refs #27

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 11:16:59 +02:00

173 lines
6.3 KiB
YAML

# Abschreiben im Cluster (Spec T7 6.3 und 11.1): alle 2 Minuten ein Pod mit
# drei Containern, die nacheinander laufen:
# fetch (initContainer) claimt Aufnahmen, entschluesselt, legt Ton nach /work/in
# whisper (initContainer) liest /work/in, schreibt /work/out; ohne Netz,
# ohne Schluessel, ohne Datenbank-Zugang
# ingest (Hauptcontainer) liest /work/out, verschluesselt, speichert, loescht Ton
#
# Der gemeinsame CronJob-Patch aus components/backend-roles trifft alle
# CronJobs mit app=apicore-job und wuerde diesem Pod einen Container `job`
# (Backend-Image, alle Secrets) hinzufuegen. Darum traegt das CronJob-Objekt
# app=apicore-voice-job; nur das Pod-Template traegt app=apicore-job (damit die
# NetworkPolicy apicore-jobs gilt: Egress nur Postgres und DNS). Haertung,
# Zeitzone und Forbid stehen deshalb hier vollstaendig.
#
# fetch, whisper und ingest laufen mit derselben uid 1000 und fsGroup 1000:
# /work/in und /work/out sind 0700, die Dateien 0600.
apiVersion: batch/v1
kind: CronJob
metadata:
name: apicore-job-voice-transcribe
labels:
app: apicore-voice-job
apicore.io/job: voice-transcribe
spec:
schedule: "*/2 * * * *"
timeZone: Europe/Berlin
concurrencyPolicy: Forbid
startingDeadlineSeconds: 120
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 0
activeDeadlineSeconds: 1500
ttlSecondsAfterFinished: 86400
template:
metadata:
labels:
app: apicore-job
apicore.io/job: voice-transcribe
db-client: "true"
spec:
restartPolicy: Never
automountServiceAccountToken: false
enableServiceLinks: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
volumes:
# Entschluesselter Ton und Abschriften liegen nur im RAM, nie auf
# einer Platte. Das Backend rechnet mit diesen 32Mi (work-dir.ts).
- name: work
emptyDir:
medium: Memory
sizeLimit: 32Mi
# /tmp von fetch und ingest (HOME=/tmp). Whisper bekommt ein eigenes
# Volume: ein gemeinsames /tmp liesse einen kompromittierten
# Decoder Code unter $HOME/.node_modules ablegen, den ingest mit
# allen Secrets laedt.
- name: tmp
emptyDir:
sizeLimit: 64Mi
- name: whisper-tmp
emptyDir:
medium: Memory
sizeLimit: 16Mi
initContainers:
- name: fetch
image: git.encry.de/nick.rosenloecher/apicore-backend
imagePullPolicy: IfNotPresent
command: ["node", "dist/cli.js", "voice-transcribe", "--phase=fetch"]
env:
- name: APICORE_ROLE
value: job
- name: HOME
value: /tmp
envFrom:
- configMapRef:
name: apicore-backend-config
- secretRef:
name: apicore-backend-secret
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: work
mountPath: /work
- name: tmp
mountPath: /tmp
resources:
requests:
cpu: 50m
memory: 192Mi
limits:
cpu: 500m
memory: 512Mi
# Kein envFrom, kein secretKeyRef, kein Service-Account-Token. Nur
# die Thread-Zahl kommt von aussen; alles andere steht im Image.
- name: whisper
image: git.encry.de/nick.rosenloecher/apicore-whisper
imagePullPolicy: IfNotPresent
env:
- name: WHISPER_CPU_THREADS
value: "2"
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
volumeMounts:
# Eingabe nur lesbar (Manifest und Ton kann der Decoder nicht
# veraendern), Ausgabe schreibbar. fetch legt in/ und out/ an
# (clearWorkDir, Modus 0700, uid 1000), bevor dieser Container startet.
- name: work
mountPath: /work/in
subPath: in
readOnly: true
- name: work
mountPath: /work/out
subPath: out
- name: whisper-tmp
mountPath: /tmp
resources:
requests:
cpu: 50m
memory: 1280Mi
limits:
cpu: 2000m
memory: 1280Mi
containers:
- name: ingest
image: git.encry.de/nick.rosenloecher/apicore-backend
imagePullPolicy: IfNotPresent
command: ["node", "dist/cli.js", "voice-transcribe", "--phase=ingest"]
env:
- name: APICORE_ROLE
value: job
- name: HOME
value: /tmp
envFrom:
- configMapRef:
name: apicore-backend-config
- secretRef:
name: apicore-backend-secret
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: work
mountPath: /work
- name: tmp
mountPath: /tmp
resources:
requests:
cpu: 50m
memory: 192Mi
limits:
cpu: 500m
memory: 512Mi