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]>
173 lines
6.3 KiB
YAML
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
|