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]>
166 lines
5.9 KiB
YAML
166 lines
5.9 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:
|
|
# 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
|