Initial commit: UmbraDatastore configuration and deployment scripts
- Database deployment script (scripts/deploy-database.sh) - Kubernetes manifests for CNPG PostgreSQL cluster (k8s/base/) - Documentation (docs/DEPLOYMENT_GUIDE.md, DATASTORE_SPEC.md, INTEGRATION_SPEC.md) - README with architecture overview and operations guide
This commit is contained in:
10
k8s/base/00-namespace.yaml
Normal file
10
k8s/base/00-namespace.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
# Copyright 2026 Umbra Tools (umbratools.com)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: umbratrans
|
||||
labels:
|
||||
name: umbratrans
|
||||
environment: production
|
||||
97
k8s/base/01-postgresql.yaml
Normal file
97
k8s/base/01-postgresql.yaml
Normal file
@@ -0,0 +1,97 @@
|
||||
# Copyright 2026 Umbra Tools (umbratools.com)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# CloudNativePG Cluster for UmbraTrans
|
||||
# Requires CNPG operator installed: kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.24/releases/cnpg-1.24.0.yaml
|
||||
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: umbratrans-db
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans-db
|
||||
environment: production
|
||||
spec:
|
||||
instances: 3
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16.4
|
||||
primaryUpdateStrategy: unsupervised
|
||||
postgresql:
|
||||
parameters:
|
||||
max_connections: "200"
|
||||
shared_buffers: "256MB"
|
||||
effective_cache_size: "1GB"
|
||||
work_mem: "16MB"
|
||||
maintenance_work_mem: "128MB"
|
||||
max_worker_processes: "8"
|
||||
max_parallel_workers_per_gather: "4"
|
||||
max_parallel_workers: "8"
|
||||
wal_buffers: "16MB"
|
||||
min_wal_size: "1GB"
|
||||
max_wal_size: "4GB"
|
||||
checkpoint_completion_target: "0.9"
|
||||
random_page_cost: "1.1"
|
||||
effective_io_concurrency: "200"
|
||||
log_destination: "stderr"
|
||||
logging_collector: "off"
|
||||
log_min_duration_statement: "1000"
|
||||
log_line_prefix: "%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h "
|
||||
log_checkpoints: "on"
|
||||
log_connections: "on"
|
||||
log_disconnections: "on"
|
||||
log_lock_waits: "on"
|
||||
log_temp_files: "10MB"
|
||||
log_autovacuum_min_duration: "1000"
|
||||
statement_timeout: "300000"
|
||||
idle_in_transaction_session_timeout: "300000"
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: umbratrans
|
||||
owner: umbratrans
|
||||
secret:
|
||||
name: umbratrans-db-credentials
|
||||
postInitApplicationSQL:
|
||||
- CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
- CREATE EXTENSION IF NOT EXISTS "citext";
|
||||
- CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
storage:
|
||||
storageClass: nfs-client
|
||||
size: 50Gi
|
||||
backup:
|
||||
barmanObjectStore:
|
||||
destinationPath: s3://umbra-backups/postgresql/umbratrans-db
|
||||
endpointURL: https://s3.example.com
|
||||
s3Credentials:
|
||||
accessKeyId:
|
||||
name: umbratrans-backup-credentials
|
||||
key: ACCESS_KEY_ID
|
||||
secretAccessKey:
|
||||
name: umbratrans-backup-credentials
|
||||
key: SECRET_ACCESS_KEY
|
||||
retentionPolicy: "30d"
|
||||
schedule: "0 2 * * *"
|
||||
proxy:
|
||||
pgBouncer:
|
||||
imageName: ghcr.io/cloudnative-pg/pgbouncer:1.23
|
||||
replicas: 2
|
||||
poolMode: transaction
|
||||
maxClientConn: 2000
|
||||
defaultPoolSize: 20
|
||||
maxDbConnections: 100
|
||||
admin:
|
||||
secret:
|
||||
name: umbratrans-pgbouncer-admin
|
||||
monitoring:
|
||||
enablePodMonitor: true
|
||||
customQueriesConfigMap:
|
||||
- name: umbratrans-db-queries
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
affinity:
|
||||
podAntiAffinityType: preferred
|
||||
topologyKey: topology.kubernetes.io/zone
|
||||
150
k8s/base/02-nfs-storage.yaml
Normal file
150
k8s/base/02-nfs-storage.yaml
Normal file
@@ -0,0 +1,150 @@
|
||||
# Copyright 2026 Umbra Tools (umbratools.com)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# NFS CSI Driver installation (apply once per cluster)
|
||||
# kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/v4.7.0/deploy/example/nfs-driver.yaml
|
||||
|
||||
---
|
||||
# StorageClass for NFS
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: nfs-client
|
||||
annotations:
|
||||
storageclass.kubernetes.io/is-default-class: "true"
|
||||
provisioner: nfs.csi.k8s.io
|
||||
parameters:
|
||||
server: 192.168.123.14 # NFS server (this machine)
|
||||
share: /mnt/umbra-data
|
||||
reclaimPolicy: Retain
|
||||
volumeBindingMode: Immediate
|
||||
mountOptions:
|
||||
- hard
|
||||
- nfsvers=4.1
|
||||
- rsize=1048576
|
||||
- wsize=1048576
|
||||
- timeo=600
|
||||
- retrans=2
|
||||
|
||||
---
|
||||
# PersistentVolume for existing productions data
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: umbratrans-productions
|
||||
labels:
|
||||
app: umbratrans
|
||||
data: productions
|
||||
spec:
|
||||
capacity:
|
||||
storage: 100Gi
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
storageClassName: nfs-client
|
||||
csi:
|
||||
driver: nfs.csi.k8s.io
|
||||
volumeHandle: umbratrans-productions
|
||||
volumeAttributes:
|
||||
server: 192.168.123.14 # NFS server (this machine)
|
||||
share: /mnt/umbra-data/productions
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: umbratrans-productions
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans
|
||||
data: productions
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
storageClassName: nfs-client
|
||||
resources:
|
||||
requests:
|
||||
storage: 100Gi
|
||||
volumeName: umbratrans-productions
|
||||
|
||||
---
|
||||
# PersistentVolume for artifacts (audio files, covers, etc.)
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: umbratrans-artifacts
|
||||
labels:
|
||||
app: umbratrans
|
||||
data: artifacts
|
||||
spec:
|
||||
capacity:
|
||||
storage: 500Gi
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
storageClassName: nfs-client
|
||||
csi:
|
||||
driver: nfs.csi.k8s.io
|
||||
volumeHandle: umbratrans-artifacts
|
||||
volumeAttributes:
|
||||
server: 192.168.123.14
|
||||
share: /mnt/umbra-data/artifacts
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: umbratrans-artifacts
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans
|
||||
data: artifacts
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
storageClassName: nfs-client
|
||||
resources:
|
||||
requests:
|
||||
storage: 500Gi
|
||||
volumeName: umbratrans-artifacts
|
||||
|
||||
---
|
||||
# PersistentVolume for database backups (optional, separate from PG backup)
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: umbratrans-backups
|
||||
labels:
|
||||
app: umbratrans
|
||||
data: backups
|
||||
spec:
|
||||
capacity:
|
||||
storage: 200Gi
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
storageClassName: nfs-client
|
||||
csi:
|
||||
driver: nfs.csi.k8s.io
|
||||
volumeHandle: umbratrans-backups
|
||||
volumeAttributes:
|
||||
server: 192.168.123.14
|
||||
share: /mnt/umbra-data/backups
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: umbratrans-backups
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans
|
||||
data: backups
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
storageClassName: nfs-client
|
||||
resources:
|
||||
requests:
|
||||
storage: 200Gi
|
||||
volumeName: umbratrans-backups
|
||||
284
k8s/base/03-web-deployment.yaml
Normal file
284
k8s/base/03-web-deployment.yaml
Normal file
@@ -0,0 +1,284 @@
|
||||
# Copyright 2026 Umbra Tools (umbratools.com)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
# ServiceAccount for web
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: umbratrans-web
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans-web
|
||||
|
||||
---
|
||||
# Role for web (minimal permissions)
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: umbratrans-web
|
||||
namespace: umbratrans
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods", "services", "configmaps"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["postgresql.cnpg.io"]
|
||||
resources: ["clusters", "backups"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: umbratrans-web
|
||||
namespace: umbratrans
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: umbratrans-web
|
||||
namespace: umbratrans
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: umbratrans-web
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
---
|
||||
# ConfigMap for static files (served by web)
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: umbratrans-static
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans-web
|
||||
data:
|
||||
# Static files are built into the image, this is for runtime config
|
||||
nginx.conf: |
|
||||
# Not used - FastAPI serves static files directly
|
||||
|
||||
---
|
||||
# Secret for web configuration (non-sensitive)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: umbratrans-web-config
|
||||
namespace: umbratrans
|
||||
type: Opaque
|
||||
stringData:
|
||||
UMBRA_ENV: "production"
|
||||
UMBRATRANS_REPOSITORY: "postgres"
|
||||
UMBRATRANS_PRODUCTIONS_ROOT: "/mnt/productions"
|
||||
UMBRATRANS_STATIC_ROOT: "/app/web"
|
||||
UMBRATRANS_ALLOWED_HOSTS: "umbratrans.bdmurphy.me,localhost"
|
||||
UMBRATRANS_VERIFICATION_BASE_URL: "https://umbratrans.bdmurphy.me"
|
||||
UMBRATRANS_COOKIE_SECURE: "true"
|
||||
UMBRATRANS_REQUIRE_CSRF: "true"
|
||||
UMBRATRANS_MAILER: "smtp"
|
||||
UMBRATRANS_SMTP_HOST: "smtp.example.com"
|
||||
UMBRATRANS_SMTP_PORT: "587"
|
||||
UMBRATRANS_SMTP_USERNAME: "umbra@example.com"
|
||||
UMBRATRANS_SMTP_SENDER: "umbratrans@umbra.example.com"
|
||||
UMBRATRANS_SMTP_STARTTLS: "true"
|
||||
# Sensitive values in separate secret
|
||||
# UMBRATRANS_DATABASE_DSN: from umbratrans-db-credentials
|
||||
# UMBRATRANS_SMTP_PASSWORD: from umbratrans-smtp
|
||||
# UMBRATRANS_TOKEN_PEPPER: from umbratrans-secrets
|
||||
|
||||
---
|
||||
# Deployment for web API + UI
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: umbratrans-web
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans-web
|
||||
component: web
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: umbratrans-web
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: umbratrans-web
|
||||
component: web
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8442"
|
||||
prometheus.io/path: "/api/metrics"
|
||||
spec:
|
||||
serviceAccountName: umbratrans-web
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
initContainers:
|
||||
- name: migrate
|
||||
image: umbratrans-web:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["python", "-m", "umbratrans_web.cli", "migrate"]
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: umbratrans-web-config
|
||||
- secretRef:
|
||||
name: umbratrans-db-credentials
|
||||
- secretRef:
|
||||
name: umbratrans-smtp
|
||||
- secretRef:
|
||||
name: umbratrans-secrets
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
containers:
|
||||
- name: web
|
||||
image: umbratrans-web:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["python", "-m", "umbratrans_web", "serve"]
|
||||
args:
|
||||
- "--host"
|
||||
- "0.0.0.0"
|
||||
- "--port"
|
||||
- "8442"
|
||||
- "--repository"
|
||||
- "postgres"
|
||||
- "--allow-production"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8442
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: umbratrans-web-config
|
||||
- secretRef:
|
||||
name: umbratrans-db-credentials
|
||||
- secretRef:
|
||||
name: umbratrans-smtp
|
||||
- secretRef:
|
||||
name: umbratrans-secrets
|
||||
volumeMounts:
|
||||
- name: productions
|
||||
mountPath: /mnt/productions
|
||||
readOnly: true
|
||||
- name: artifacts
|
||||
mountPath: /mnt/artifacts
|
||||
readOnly: false
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: 8442
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: 8442
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: 8442
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 30
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "1000m"
|
||||
volumes:
|
||||
- name: productions
|
||||
persistentVolumeClaim:
|
||||
claimName: umbratrans-productions
|
||||
- name: artifacts
|
||||
persistentVolumeClaim:
|
||||
claimName: umbratrans-artifacts
|
||||
- name: tmp
|
||||
emptyDir:
|
||||
sizeLimit: 1Gi
|
||||
|
||||
---
|
||||
# Service for web
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: umbratrans-web
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans-web
|
||||
spec:
|
||||
selector:
|
||||
app: umbratrans-web
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8442
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
|
||||
---
|
||||
# HorizontalPodAutoscaler for web
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: umbratrans-web
|
||||
namespace: umbratrans
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: umbratrans-web
|
||||
minReplicas: 2
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 300
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 10
|
||||
periodSeconds: 60
|
||||
scaleUp:
|
||||
stabilizationWindowSeconds: 60
|
||||
policies:
|
||||
- type: Percent
|
||||
value: 50
|
||||
periodSeconds: 60
|
||||
- type: Pods
|
||||
value: 2
|
||||
periodSeconds: 60
|
||||
selectPolicy: Max
|
||||
338
k8s/base/04-worker-deployments.yaml
Normal file
338
k8s/base/04-worker-deployments.yaml
Normal file
@@ -0,0 +1,338 @@
|
||||
# Copyright 2026 Umbra Tools (umbratools.com)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
# ServiceAccount for workers
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: umbratrans-worker
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans-worker
|
||||
|
||||
---
|
||||
# Role for workers
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: umbratrans-worker
|
||||
namespace: umbratrans
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods", "services", "configmaps", "secrets"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["batch"]
|
||||
resources: ["jobs", "cronjobs"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch"]
|
||||
- apiGroups: ["postgresql.cnpg.io"]
|
||||
resources: ["clusters", "backups"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: umbratrans-worker
|
||||
namespace: umbratrans
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: umbratrans-worker
|
||||
namespace: umbratrans
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: umbratrans-worker
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
|
||||
---
|
||||
# Secret for worker configuration
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: umbratrans-worker-config
|
||||
namespace: umbratrans
|
||||
type: Opaque
|
||||
stringData:
|
||||
UMBRA_ENV: "production"
|
||||
UMBRATRANS_REPOSITORY: "postgres"
|
||||
UMBRATRANS_PRODUCTIONS_ROOT: "/mnt/productions"
|
||||
UMBRATRANS_ALLOWED_HOSTS: "umbratrans.bdmurphy.me"
|
||||
OLLAMA_BASE_URL: "http://ollama:11434/v1"
|
||||
# UMBRATRANS_DATABASE_DSN: from umbratrans-db-credentials
|
||||
|
||||
---
|
||||
# Deployment for Translation Workers
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: umbratrans-worker-translation
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans-worker-translation
|
||||
component: worker
|
||||
workload: translation
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: umbratrans-worker-translation
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: umbratrans-worker-translation
|
||||
component: worker
|
||||
workload: translation
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "9090"
|
||||
prometheus.io/path: "/metrics"
|
||||
spec:
|
||||
serviceAccountName: umbratrans-worker
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
# Node affinity for GPU nodes
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: nvidia.com/gpu.present
|
||||
operator: In
|
||||
values: ["true"]
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
preference:
|
||||
matchExpressions:
|
||||
- key: workload
|
||||
operator: In
|
||||
values: ["translation"]
|
||||
tolerations:
|
||||
- key: "nvidia.com/gpu"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
containers:
|
||||
- name: worker
|
||||
image: umbratrans-worker:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["python", "-m", "umbratrans_web.cli", "worker", "translation"]
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: umbratrans-worker-config
|
||||
- secretRef:
|
||||
name: umbratrans-db-credentials
|
||||
- secretRef:
|
||||
name: umbratrans-secrets
|
||||
volumeMounts:
|
||||
- name: productions
|
||||
mountPath: /mnt/productions
|
||||
- name: artifacts
|
||||
mountPath: /mnt/artifacts
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
resources:
|
||||
requests:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
nvidia.com/gpu: "1"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "4000m"
|
||||
nvidia.com/gpu: "1"
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["python", "-c", "import psycopg; psycopg.connect('$UMBRATRANS_DATABASE_DSN', connect_timeout=3).close()"]
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 60
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["python", "-c", "import psycopg; psycopg.connect('$UMBRATRANS_DATABASE_DSN', connect_timeout=3).close()"]
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: productions
|
||||
persistentVolumeClaim:
|
||||
claimName: umbratrans-productions
|
||||
- name: artifacts
|
||||
persistentVolumeClaim:
|
||||
claimName: umbratrans-artifacts
|
||||
- name: tmp
|
||||
emptyDir:
|
||||
sizeLimit: 2Gi
|
||||
|
||||
---
|
||||
# HorizontalPodAutoscaler for translation workers
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: umbratrans-worker-translation
|
||||
namespace: umbratrans
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: umbratrans-worker-translation
|
||||
minReplicas: 1
|
||||
maxReplicas: 20
|
||||
metrics:
|
||||
- type: External
|
||||
external:
|
||||
metric:
|
||||
name: umbratrans_translation_queue_depth
|
||||
target:
|
||||
type: AverageValue
|
||||
averageValue: "10"
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 600
|
||||
scaleUp:
|
||||
stabilizationWindowSeconds: 60
|
||||
|
||||
---
|
||||
# Deployment for Audio Workers
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: umbratrans-worker-audio
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans-worker-audio
|
||||
component: worker
|
||||
workload: audio
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: umbratrans-worker-audio
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: umbratrans-worker-audio
|
||||
component: worker
|
||||
workload: audio
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "9090"
|
||||
prometheus.io/path: "/metrics"
|
||||
spec:
|
||||
serviceAccountName: umbratrans-worker
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: nvidia.com/gpu.present
|
||||
operator: In
|
||||
values: ["true"]
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
preference:
|
||||
matchExpressions:
|
||||
- key: workload
|
||||
operator: In
|
||||
values: ["audio"]
|
||||
tolerations:
|
||||
- key: "nvidia.com/gpu"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
containers:
|
||||
- name: worker
|
||||
image: umbratrans-worker:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["python", "-m", "umbratrans_web.cli", "worker", "audio"]
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: umbratrans-worker-config
|
||||
- secretRef:
|
||||
name: umbratrans-db-credentials
|
||||
- secretRef:
|
||||
name: umbratrans-secrets
|
||||
volumeMounts:
|
||||
- name: productions
|
||||
mountPath: /mnt/productions
|
||||
- name: artifacts
|
||||
mountPath: /mnt/artifacts
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
resources:
|
||||
requests:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
nvidia.com/gpu: "1"
|
||||
limits:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
nvidia.com/gpu: "1"
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["python", "-c", "import psycopg; psycopg.connect('$UMBRATRANS_DATABASE_DSN', connect_timeout=3).close()"]
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 60
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["python", "-c", "import psycopg; psycopg.connect('$UMBRATRANS_DATABASE_DSN', connect_timeout=3).close()"]
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: productions
|
||||
persistentVolumeClaim:
|
||||
claimName: umbratrans-productions
|
||||
- name: artifacts
|
||||
persistentVolumeClaim:
|
||||
claimName: umbratrans-artifacts
|
||||
- name: tmp
|
||||
emptyDir:
|
||||
sizeLimit: 4Gi
|
||||
|
||||
---
|
||||
# HorizontalPodAutoscaler for audio workers
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: umbratrans-worker-audio
|
||||
namespace: umbratrans
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: umbratrans-worker-audio
|
||||
minReplicas: 1
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: External
|
||||
external:
|
||||
metric:
|
||||
name: umbratrans_audio_queue_depth
|
||||
target:
|
||||
type: AverageValue
|
||||
averageValue: "5"
|
||||
behavior:
|
||||
scaleDown:
|
||||
stabilizationWindowSeconds: 900
|
||||
scaleUp:
|
||||
stabilizationWindowSeconds: 120
|
||||
169
k8s/base/05-ingress-network.yaml
Normal file
169
k8s/base/05-ingress-network.yaml
Normal file
@@ -0,0 +1,169 @@
|
||||
# Copyright 2026 Umbra Tools (umbratools.com)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
# Ingress for web UI and API
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: umbratrans-web
|
||||
namespace: umbratrans
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
|
||||
nginx.ingress.kubernetes.io/client-body-timeout: "300"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/hsts: "true"
|
||||
nginx.ingress.kubernetes.io/hsts-max-age: "31536000"
|
||||
nginx.ingress.kubernetes.io/hsts-include-subdomains: "true"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- umbratrans.bdmurphy.me
|
||||
secretName: umbratrans-tls
|
||||
rules:
|
||||
- host: umbratrans.bdmurphy.me
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: umbratrans-web
|
||||
port:
|
||||
number: 80
|
||||
|
||||
---
|
||||
# NetworkPolicy for web - allow ingress from nginx, egress to DB
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: umbratrans-web
|
||||
namespace: umbratrans
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: umbratrans-web
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: ingress-nginx
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8442
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: monitoring
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8442
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: umbratrans-db
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
- protocol: TCP
|
||||
port: 6432 # PgBouncer
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: kube-system
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- to: []
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443 # External APIs (Ollama, SMTP, etc.)
|
||||
|
||||
---
|
||||
# NetworkPolicy for workers - egress to DB and NFS
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: umbratrans-worker
|
||||
namespace: umbratrans
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
component: worker
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: umbratrans-db
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
- protocol: TCP
|
||||
port: 6432
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: kube-system
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- to: []
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 443
|
||||
- protocol: TCP
|
||||
port: 11434 # Ollama
|
||||
- protocol: TCP
|
||||
port: 2049 # NFS
|
||||
|
||||
---
|
||||
# NetworkPolicy for database - allow from web and workers only
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: umbratrans-db
|
||||
namespace: umbratrans
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: umbratrans-db
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: umbratrans-web
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
component: worker
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: monitoring
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
- protocol: TCP
|
||||
port: 6432
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: umbratrans-db
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
148
k8s/base/06-import-job.yaml
Normal file
148
k8s/base/06-import-job.yaml
Normal file
@@ -0,0 +1,148 @@
|
||||
# Copyright 2026 Umbra Tools (umbratools.com)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
# Job to import existing productions data into PostgreSQL
|
||||
# Run once after initial deployment: kubectl apply -f 06-import-job.yaml
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: umbratrans-import-productions
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans-import
|
||||
component: import
|
||||
annotations:
|
||||
# Allow job to run for a long time
|
||||
batch.kubernetes.io/job-tracking: "true"
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 86400 # Keep job for 24h after completion
|
||||
backoffLimit: 3
|
||||
activeDeadlineSeconds: 7200 # 2 hours max
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: umbratrans-import
|
||||
component: import
|
||||
annotations:
|
||||
prometheus.io/scrape: "false"
|
||||
spec:
|
||||
serviceAccountName: umbratrans-worker
|
||||
restartPolicy: OnFailure
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
initContainers:
|
||||
- name: wait-for-db
|
||||
image: postgres:16-alpine
|
||||
command: ["sh", "-c", "until pg_isready -h umbratrans-db-rw -U umbratrans -d umbratrans; do sleep 5; done"]
|
||||
env:
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: umbratrans-db-credentials
|
||||
key: password
|
||||
containers:
|
||||
- name: import
|
||||
image: umbratrans-worker:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["python", "-m", "umbratrans_web.cli", "import-productions"]
|
||||
args:
|
||||
- "--organization"
|
||||
- "umbra" # Organization slug
|
||||
- "--confirm"
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: umbratrans-worker-config
|
||||
- secretRef:
|
||||
name: umbratrans-db-credentials
|
||||
- secretRef:
|
||||
name: umbratrans-secrets
|
||||
volumeMounts:
|
||||
- name: productions
|
||||
mountPath: /mnt/productions
|
||||
readOnly: true
|
||||
- name: artifacts
|
||||
mountPath: /mnt/artifacts
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
volumes:
|
||||
- name: productions
|
||||
persistentVolumeClaim:
|
||||
claimName: umbratrans-productions
|
||||
- name: artifacts
|
||||
persistentVolumeClaim:
|
||||
claimName: umbratrans-artifacts
|
||||
|
||||
---
|
||||
# CronJob for periodic re-import (optional - for catching new productions)
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: umbratrans-import-productions-schedule
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
app: umbratrans-import
|
||||
component: import
|
||||
spec:
|
||||
schedule: "0 3 * * *" # Daily at 3 AM UTC
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 86400
|
||||
backoffLimit: 1
|
||||
activeDeadlineSeconds: 7200
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: umbratrans-worker
|
||||
restartPolicy: OnFailure
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
runAsGroup: 1000
|
||||
fsGroup: 1000
|
||||
containers:
|
||||
- name: import
|
||||
image: umbratrans-worker:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["python", "-m", "umbratrans_web.cli", "import-productions"]
|
||||
args:
|
||||
- "--organization"
|
||||
- "umbra"
|
||||
- "--incremental" # Only import new/changed
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: umbratrans-worker-config
|
||||
- secretRef:
|
||||
name: umbratrans-db-credentials
|
||||
- secretRef:
|
||||
name: umbratrans-secrets
|
||||
volumeMounts:
|
||||
- name: productions
|
||||
mountPath: /mnt/productions
|
||||
readOnly: true
|
||||
- name: artifacts
|
||||
mountPath: /mnt/artifacts
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
volumes:
|
||||
- name: productions
|
||||
persistentVolumeClaim:
|
||||
claimName: umbratrans-productions
|
||||
- name: artifacts
|
||||
persistentVolumeClaim:
|
||||
claimName: umbratrans-artifacts
|
||||
286
k8s/base/07-monitoring-secrets.yaml
Normal file
286
k8s/base/07-monitoring-secrets.yaml
Normal file
@@ -0,0 +1,286 @@
|
||||
# Copyright 2026 Umbra Tools (umbratools.com)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
---
|
||||
# Secret for database credentials (created by CNPG, but we reference it)
|
||||
# This is a template - CNPG creates the actual secret
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: umbratrans-db-credentials
|
||||
namespace: umbratrans
|
||||
type: kubernetes.io/basic-auth
|
||||
stringData:
|
||||
username: "umbratrans"
|
||||
password: "CHANGE_ME_GENERATED_BY_CNPG"
|
||||
# DSN format: postgresql://user:pass@host:port/db?sslmode=require
|
||||
dsn: "postgresql://umbratrans:CHANGE_ME@umbratrans-db-rw:5432/umbratrans?sslmode=require"
|
||||
|
||||
---
|
||||
# Secret for SMTP credentials
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: umbratrans-smtp
|
||||
namespace: umbratrans
|
||||
type: Opaque
|
||||
stringData:
|
||||
host: "smtp.example.com"
|
||||
port: "587"
|
||||
username: "umbra@example.com"
|
||||
password: "CHANGE_ME_SMTP_PASSWORD"
|
||||
sender: "umbratrans@umbra.example.com"
|
||||
|
||||
---
|
||||
# Secret for application secrets (token pepper, etc.)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: umbratrans-secrets
|
||||
namespace: umbratrans
|
||||
type: Opaque
|
||||
stringData:
|
||||
token_pepper: "CHANGE_ME_32_BYTES_MINIMUM_BASE64_ENCODED"
|
||||
|
||||
---
|
||||
# Secret for PgBouncer admin (generated by CNPG)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: umbratrans-pgbouncer-admin
|
||||
namespace: umbratrans
|
||||
type: Opaque
|
||||
stringData:
|
||||
username: "pgbouncer_admin"
|
||||
password: "CHANGE_ME_GENERATED_BY_CNPG"
|
||||
|
||||
---
|
||||
# Secret for backup credentials (S3)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: umbratrans-backup-credentials
|
||||
namespace: umbratrans
|
||||
type: Opaque
|
||||
stringData:
|
||||
ACCESS_KEY_ID: "CHANGE_ME"
|
||||
SECRET_ACCESS_KEY: "CHANGE_ME"
|
||||
|
||||
---
|
||||
# TLS certificate for ingress (managed by cert-manager)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: umbratrans-tls
|
||||
namespace: umbratrans
|
||||
type: kubernetes.io/tls
|
||||
data:
|
||||
# cert-manager will populate this
|
||||
tls.crt: ""
|
||||
tls.key: ""
|
||||
|
||||
---
|
||||
# ServiceMonitor for Prometheus (requires Prometheus Operator)
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: umbratrans-web
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
release: prometheus
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: umbratrans-web
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /api/metrics
|
||||
interval: 30s
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- umbratrans
|
||||
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: umbratrans-workers
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
release: prometheus
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
component: worker
|
||||
endpoints:
|
||||
- port: metrics
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- umbratrans
|
||||
|
||||
---
|
||||
# PodMonitor for PostgreSQL (CNPG)
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PodMonitor
|
||||
metadata:
|
||||
name: umbratrans-db
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
release: prometheus
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: umbratrans-db
|
||||
podMetricsEndpoints:
|
||||
- port: metrics
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
|
||||
---
|
||||
# ConfigMap for custom PostgreSQL queries
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: umbratrans-db-queries
|
||||
namespace: umbratrans
|
||||
data:
|
||||
umbratrans_queries.yaml: |
|
||||
- name: umbratrans_projects_total
|
||||
query: |
|
||||
SELECT count(*) as value FROM projects;
|
||||
metrics:
|
||||
- value
|
||||
- name: umbratrans_active_jobs
|
||||
query: |
|
||||
SELECT count(*) as value FROM jobs WHERE status IN ('queued', 'running', 'blocked');
|
||||
metrics:
|
||||
- value
|
||||
- name: umbratrans_translation_queue_depth
|
||||
query: |
|
||||
SELECT count(*) as value FROM jobs WHERE kind = 'translate' AND status = 'queued';
|
||||
metrics:
|
||||
- value
|
||||
- name: umbratrans_audio_queue_depth
|
||||
query: |
|
||||
SELECT count(*) as value FROM jobs WHERE kind LIKE 'audio%' AND status = 'queued';
|
||||
metrics:
|
||||
- value
|
||||
- name: umbratrans_artifacts_total
|
||||
query: |
|
||||
SELECT count(*) as value FROM artifacts;
|
||||
metrics:
|
||||
- value
|
||||
- name: umbratrans_artifacts_size_bytes
|
||||
query: |
|
||||
SELECT sum(byte_size) as value FROM artifacts;
|
||||
metrics:
|
||||
- value
|
||||
- name: umbratrans_import_runs_total
|
||||
query: |
|
||||
SELECT count(*) as value FROM translation_imports;
|
||||
metrics:
|
||||
- value
|
||||
|
||||
---
|
||||
# PrometheusRule for alerts
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: umbratrans-alerts
|
||||
namespace: umbratrans
|
||||
labels:
|
||||
release: prometheus
|
||||
spec:
|
||||
groups:
|
||||
- name: umbratrans.rules
|
||||
rules:
|
||||
- alert: UmbraTransWebDown
|
||||
expr: up{job="umbratrans-web"} == 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "UmbraTrans web is down"
|
||||
description: "UmbraTrans web has been down for more than 2 minutes."
|
||||
|
||||
- alert: UmbraTransHighErrorRate
|
||||
expr: |
|
||||
sum(rate(http_requests_total{job="umbratrans-web",code=~"5.."}[5m]))
|
||||
/
|
||||
sum(rate(http_requests_total{job="umbratrans-web"}[5m])) > 0.05
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "High error rate on UmbraTrans web"
|
||||
description: "Error rate is above 5% for the last 5 minutes."
|
||||
|
||||
- alert: UmbraTransDBConnectionsHigh
|
||||
expr: |
|
||||
pg_stat_database_numbackends / pg_settings_max_connections > 0.8
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "PostgreSQL connections near limit"
|
||||
description: "Database connections are above 80% of max_connections."
|
||||
|
||||
- alert: UmbraTransTranslationQueueBacklog
|
||||
expr: umbratrans_translation_queue_depth > 100
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Translation queue backlog"
|
||||
description: "Translation queue has more than 100 pending jobs for 10 minutes."
|
||||
|
||||
- alert: UmbraTransAudioQueueBacklog
|
||||
expr: umbratrans_audio_queue_depth > 50
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Audio queue backlog"
|
||||
description: "Audio queue has more than 50 pending jobs for 10 minutes."
|
||||
|
||||
- alert: UmbraTransDiskSpaceLow
|
||||
expr: |
|
||||
(kubelet_volume_stats_available_bytes / kubelet_volume_stats_capacity_bytes) < 0.1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Disk space low on UmbraTrans volumes"
|
||||
description: "Less than 10% disk space remaining on persistent volumes."
|
||||
|
||||
- alert: UmbraTransWorkerTranslationDown
|
||||
expr: |
|
||||
kube_deployment_status_replicas_available{deployment="umbratrans-worker-translation"} < 1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "No translation workers available"
|
||||
description: "All translation worker pods are down."
|
||||
|
||||
- alert: UmbraTransWorkerAudioDown
|
||||
expr: |
|
||||
kube_deployment_status_replicas_available{deployment="umbratrans-worker-audio"} < 1
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "No audio workers available"
|
||||
description: "All audio worker pods are down."
|
||||
|
||||
- alert: UmbraTransImportJobFailed
|
||||
expr: |
|
||||
kube_job_status_failed{job=~"umbratrans-import.*"} > 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "UmbraTrans import job failed"
|
||||
description: "Production import job has failed."
|
||||
61
k8s/base/kustomization.yaml
Normal file
61
k8s/base/kustomization.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
# Copyright 2026 Umbra Tools (umbratools.com)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: umbratrans
|
||||
|
||||
resources:
|
||||
- 00-namespace.yaml
|
||||
- 01-postgresql.yaml
|
||||
- 02-nfs-storage.yaml
|
||||
- 03-web-deployment.yaml
|
||||
- 04-worker-deployments.yaml
|
||||
- 05-ingress-network.yaml
|
||||
- 06-import-job.yaml
|
||||
- 07-monitoring-secrets.yaml
|
||||
|
||||
commonLabels:
|
||||
app.kubernetes.io/name: umbratrans
|
||||
app.kubernetes.io/part-of: umbratrans
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
|
||||
images:
|
||||
- name: umbratrans-web
|
||||
newTag: latest
|
||||
- name: umbratrans-worker
|
||||
newTag: latest
|
||||
|
||||
# Patches for different environments
|
||||
patches:
|
||||
- patch: |-
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: umbratrans-web
|
||||
spec:
|
||||
replicas: 1
|
||||
target:
|
||||
kind: Deployment
|
||||
name: umbratrans-web
|
||||
- patch: |-
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: umbratrans-worker-translation
|
||||
spec:
|
||||
replicas: 1
|
||||
target:
|
||||
kind: Deployment
|
||||
name: umbratrans-worker-translation
|
||||
- patch: |-
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: umbratrans-worker-audio
|
||||
spec:
|
||||
replicas: 0
|
||||
target:
|
||||
kind: Deployment
|
||||
name: umbratrans-worker-audio
|
||||
Reference in New Issue
Block a user