149 lines
5.6 KiB
YAML
149 lines
5.6 KiB
YAML
---
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: shortsmith
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
annotations:
|
|
# Never let ArgoCD prune this PVC: it holds the job store and the rendered videos.
|
|
argocd.argoproj.io/sync-options: Prune=false
|
|
name: shortsmith-data
|
|
namespace: shortsmith
|
|
spec:
|
|
accessModes:
|
|
- ReadWriteOnce
|
|
resources:
|
|
requests:
|
|
storage: 5Gi
|
|
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: shortsmith
|
|
namespace: shortsmith
|
|
labels:
|
|
app: shortsmith
|
|
spec:
|
|
replicas: 1
|
|
# Single replica on a RWO local-path volume holding a SQLite job store in WAL mode:
|
|
# Recreate keeps two pods from opening /data/shortsmith.db during a roll. The queue
|
|
# is per-process anyway, so a second replica would not share work, only the file.
|
|
strategy:
|
|
type: Recreate
|
|
selector:
|
|
matchLabels:
|
|
app: shortsmith
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: shortsmith
|
|
spec:
|
|
containers:
|
|
- name: shortsmith
|
|
image: git.chemavx.xyz/chemavx/shortsmith:43a26f60
|
|
imagePullPolicy: Always
|
|
ports:
|
|
- name: http
|
|
containerPort: 8080
|
|
env:
|
|
- name: SHORTSMITH_DATA_DIR
|
|
value: "/data"
|
|
volumeMounts:
|
|
- name: data
|
|
mountPath: /data
|
|
# Measured IN THIS POD, sampling the cgroup's own memory.stat every 30 ms.
|
|
# An earlier set of numbers taken on the dev host said 522 MB and produced a
|
|
# 1Gi limit; the first real render was OOMKilled at 1483 MB. The host runs
|
|
# ffmpeg 4.4.2 and the image ships 7.1.5, and 7.0 rewrote transcoding onto a
|
|
# threaded scheduler whose inter-component queues hold decoded frames. So:
|
|
# numbers measured outside the deployment target are a guess about it.
|
|
#
|
|
# Rendering is this service's normal mode, not a burst, so the request comes
|
|
# from the peak and not from the resting figure.
|
|
#
|
|
# at rest (imports + FastAPI app) 40 MB current
|
|
# frames, 3 workers, 1260 of them +145 MB anon
|
|
# audio synthesis and WAV +82 MB anon
|
|
# encode pass 1, video only, -threads 3 +562 MB anon <- the cost
|
|
# encode pass 2, mux the audio in +15 MB anon
|
|
# two renders back to back, peak 640 MB anon / 716 current
|
|
# one render at the spec's 180 s ceiling 620 MB anon / 923 current
|
|
#
|
|
# anon is flat with duration; what grows is the page cache holding the PNG
|
|
# frames (70 MB at 42 s, 287 MB at the 180 s ceiling). That is reclaimable,
|
|
# so the request is sized against anon and the limit against current.
|
|
#
|
|
# 768Mi covers the 640 MB anon peak with ~20% headroom. The limit is 1.5Gi
|
|
# and not 1Gi because of the ceiling case: at 1Gi that render peaked at
|
|
# 940 MB of 1024, i.e. 84 MB of margin, surviving only because the kernel
|
|
# had reclaimable cache to drop. Verified at 1.5Gi with memory.events
|
|
# reading `max 0` — the limit was never reached, not merely never fatal.
|
|
#
|
|
# The CPU limit is what sizes the worker pool, not the request — the code
|
|
# reads cpu.max, since cpu_count() reports the node's 16. At limits.cpu 3
|
|
# that is 3 workers, verified in this pod. 42 s of video renders in ~31 s,
|
|
# the 180 s ceiling in ~135 s.
|
|
#
|
|
# Consequence, deliberate: under sustained contention the pod runs 3 workers
|
|
# on the 1 CPU it is guaranteed and a render takes about 3x longer. That is
|
|
# benign here — POST /render returns a job id, the queue runs at concurrency
|
|
# 1 and the caller polls, so a slow render is a later `done`, never a
|
|
# timeout. Do not "fix" it by sizing the pool from the request: that would
|
|
# trade an occasional slowdown for a permanent 3x on every render, including
|
|
# the ones on an idle node, which is the common case.
|
|
resources:
|
|
requests:
|
|
cpu: "1"
|
|
memory: "768Mi"
|
|
limits:
|
|
cpu: "3"
|
|
memory: "1536Mi"
|
|
# The render runs off the event loop (asyncio.to_thread, and the frame
|
|
# workers are separate processes), so /healthz answers while a job is in
|
|
# flight. A probe timing out means the process is actually wedged.
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /healthz
|
|
port: http
|
|
initialDelaySeconds: 5
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /healthz
|
|
port: http
|
|
initialDelaySeconds: 15
|
|
periodSeconds: 30
|
|
failureThreshold: 3
|
|
volumes:
|
|
- name: data
|
|
persistentVolumeClaim:
|
|
claimName: shortsmith-data
|
|
imagePullSecrets:
|
|
- name: gitea-registry-infisical
|
|
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
# Must end in -svc. researchowl hit this: a Service named `searxng` collided with
|
|
# the SEARXNG_* env vars k8s injects into every pod in the namespace and returned
|
|
# 403s until it was renamed.
|
|
name: shortsmith-svc
|
|
namespace: shortsmith
|
|
spec:
|
|
selector:
|
|
app: shortsmith
|
|
ports:
|
|
- name: http
|
|
port: 8080
|
|
targetPort: http
|
|
|
|
# No Ingress on purpose: this is an internal API with no authentication, reached at
|
|
# shortsmith-svc.shortsmith.svc.cluster.local:8080 from inside the cluster. Ollama was
|
|
# exposed without auth once and had to be retrofitted with Authentik forward-auth.
|