shortsmith: deployment, service, PVC y Application
Renderizador determinista JSON->MP4 para Shorts. API interna sin Ingress, en shortsmith-svc:8080; la cola es por proceso y la SQLite va en RWO, de ahí la replica unica con estrategia Recreate. Los recursos van medidos, no estimados, y la tabla con las cifras queda en el propio manifiesto: el pico son 522 MB de anon y lo pone entero el encode, no el render. Ojo al comentario sobre por que los workers salen del limite de CPU y no del request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: shortsmith
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
notifications.argoproj.io/subscribe.on-sync-failed.telegram: "5138407666"
|
||||||
|
notifications.argoproj.io/subscribe.on-health-degraded.telegram: "5138407666"
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: https://git.chemavx.xyz/chemavx/k8s-manifests
|
||||||
|
targetRevision: HEAD
|
||||||
|
path: shortsmith
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: shortsmith
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
---
|
||||||
|
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:2dc2a8bb
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
env:
|
||||||
|
- name: SHORTSMITH_DATA_DIR
|
||||||
|
value: "/data"
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
# Measured on chemavx-k8 under `systemd-run --scope -p CPUQuota=300%`,
|
||||||
|
# sampling the cgroup's memory.stat anon every 20 ms — the figure that
|
||||||
|
# cannot be reclaimed. Rendering is this service's normal mode, not a
|
||||||
|
# burst, so the request comes from the peak and not from the resting 22 MB.
|
||||||
|
#
|
||||||
|
# at rest (imports + FastAPI app) 22 MB
|
||||||
|
# frames, 3 workers, 1260 of them 147 MB
|
||||||
|
# audio synthesis and WAV 71 MB
|
||||||
|
# encode, -threads 3 480 MB <- the whole cost
|
||||||
|
# full render of examples/jal1628.json 522 MB, 32 s
|
||||||
|
# the same at the spec's 180 s ceiling 511 MB, 138 s
|
||||||
|
#
|
||||||
|
# The peak is the encode's and does not grow with duration: at 5400 frames
|
||||||
|
# anon is unchanged and only the page cache grows (64 -> 275 MB), which is
|
||||||
|
# reclaimable. That is why these are sized against anon and not against
|
||||||
|
# memory.current, which reaches 808 MB at the ceiling.
|
||||||
|
#
|
||||||
|
# 512Mi does survive the reference render, but only by evicting page cache
|
||||||
|
# to sit exactly on the cap with no margin. 640Mi covers the measured peak
|
||||||
|
# with ~20% headroom; the 1Gi limit lets the page cache stay cached instead
|
||||||
|
# of being re-read during the encode.
|
||||||
|
#
|
||||||
|
# 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 a real cgroup (300%->3, 200%->2, 100%->1).
|
||||||
|
# 3 CPUs render in 32 s against 48 s at 2, for 22 MB less, so the third is
|
||||||
|
# worth more than the extra worker costs.
|
||||||
|
#
|
||||||
|
# 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: "640Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "3"
|
||||||
|
memory: "1Gi"
|
||||||
|
# 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.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
apiVersion: secrets.infisical.com/v1beta1
|
||||||
|
kind: InfisicalStaticSecret
|
||||||
|
metadata:
|
||||||
|
name: gitea-registry
|
||||||
|
namespace: shortsmith
|
||||||
|
spec:
|
||||||
|
# References the SHARED auth layer in infisical-operator (cross-namespace).
|
||||||
|
infisicalAuthRef:
|
||||||
|
name: infisical-auth
|
||||||
|
namespace: infisical-operator
|
||||||
|
sources:
|
||||||
|
- projectId: 17e98e9d-70f5-43d1-8382-7da818dfcdd0 # project "homelab"
|
||||||
|
environmentSlug: prod
|
||||||
|
secretPath: /registry
|
||||||
|
syncOptions:
|
||||||
|
refreshInterval: 60s
|
||||||
|
targets:
|
||||||
|
- kind: Secret
|
||||||
|
name: gitea-registry-infisical
|
||||||
|
namespace: shortsmith
|
||||||
|
creationPolicy: Owner
|
||||||
|
secretType: kubernetes.io/dockerconfigjson
|
||||||
|
template:
|
||||||
|
engineVersion: v1
|
||||||
|
data:
|
||||||
|
# Reconstruct the docker pull-secret blob from the two discrete /registry fields.
|
||||||
|
.dockerconfigjson: '{"auths":{"git.chemavx.xyz":{"username":"{{ .GITEA_REGISTRY_USERNAME.Value }}","password":"{{ .GITEA_REGISTRY_TOKEN.Value }}","auth":"{{ printf "%s:%s" .GITEA_REGISTRY_USERNAME.Value .GITEA_REGISTRY_TOKEN.Value | encodeBase64 }}"}}}'
|
||||||
Reference in New Issue
Block a user