feat: Abschreiben im Cluster mit Whisper-Image und Voice-CronJob (T7 L3 Task 13)
ApiCore-Whisper: transcribe.py liest das Manifest im RAM-Arbeitsordner, dekodiert mit festem Demuxer je Endung, misst die Länge vor dem Abschreiben (TOO_LONG > 240 s, DECODE_FAILED bei Länge 0), sperrt das Netz vor dem Modell-Laden und schreibt nur Zähler auf stdout. Image: python:3.12-slim per Digest, Nutzer 1000, alle Pakete mit Hash (faster-whisper 1.2.1, av 18.1.0 wegen Inkompatibilität mit av 19), Modell faster-whisper-small auf fester Revision mit SHA-256 je Datei. CI-Workflow nur bei Änderungen unter ApiCore-Whisper/, Actions per SHA. deploy/k8s/components/voice: CronJob alle 2 Minuten mit fetch, whisper, ingest (gleiche UID, getrennte /tmp-Volumes, damit der Decoder dem Container mit den Secrets nichts unterschieben kann; whisper ohne Secrets und Umgebungsvariablen außer WHISPER_CPU_THREADS), Overlay mit Platzhalter-Digest, ConfigMap-Werte für Sprache, Policy-Tests (55). Die Anbindung des Anthropic-Schlüssels bleibt offen (Ruling 9). Refs #27 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
e614ded967
commit
c53810b8a6
16 files changed
+1448
-5
No files matched your search
@@ -0,0 +1,165 @@
|
||||
# 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:
|
||||
# Der ganze Mount, Schreibrecht fuer /work/out.
|
||||
- name: work
|
||||
mountPath: /work
|
||||
- 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
|
||||
Reference in new issue
Block a user