feat(roswell): despliegue GitOps del corpus — bot+scheduler, backup pg_dump diario, postgres adoptado
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
# Se aplica una vez imperativamente (kubectl apply -f); después ArgoCD
|
||||||
|
# se autogestiona porque este fichero también vive en k8s-manifests/roswell/.
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: roswell
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: default
|
||||||
|
source:
|
||||||
|
repoURL: https://git.chemavx.xyz/chemavx/k8s-manifests
|
||||||
|
targetRevision: HEAD
|
||||||
|
path: roswell
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: roswell
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Backup diario de la BD del corpus con retención 7 (PLAN §Fase 6).
|
||||||
|
# pg_dump en formato custom (-Fc, comprimido) verificado con pg_restore --list.
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
# ArgoCD no debe podar este PVC: contiene los backups de la BD.
|
||||||
|
argocd.argoproj.io/sync-options: Prune=false
|
||||||
|
name: roswell-backups
|
||||||
|
namespace: roswell
|
||||||
|
spec:
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: roswell-db-backup
|
||||||
|
namespace: roswell
|
||||||
|
labels:
|
||||||
|
app: roswell
|
||||||
|
spec:
|
||||||
|
schedule: "30 3 * * *"
|
||||||
|
timeZone: "Europe/Madrid"
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
successfulJobsHistoryLimit: 3
|
||||||
|
failedJobsHistoryLimit: 3
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
backoffLimit: 2
|
||||||
|
activeDeadlineSeconds: 900
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: roswell-db-backup
|
||||||
|
spec:
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: backup
|
||||||
|
# Misma imagen que el postgres del corpus: pg_dump 16 garantizado
|
||||||
|
# y ya está en la caché del nodo.
|
||||||
|
image: pgvector/pgvector:pg16
|
||||||
|
env:
|
||||||
|
- name: PGPASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: roswell-pg-secret
|
||||||
|
key: password
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
STAMP=$(date +%Y%m%d)
|
||||||
|
DEST="/backups/roswell-${STAMP}.dump"
|
||||||
|
echo "pg_dump roswell -> ${DEST}"
|
||||||
|
pg_dump -h postgres.roswell.svc.cluster.local -U roswell \
|
||||||
|
-d roswell -Fc -f "${DEST}"
|
||||||
|
# Verificación: un dump corrupto no lista su TOC.
|
||||||
|
pg_restore --list "${DEST}" > /dev/null
|
||||||
|
echo "Dump verificado ($(du -h ${DEST} | cut -f1))"
|
||||||
|
# Retención: conservar los 7 más recientes
|
||||||
|
for f in $(ls -1t /backups/roswell-*.dump | tail -n +8); do
|
||||||
|
echo "Podando backup antiguo ${f}"
|
||||||
|
rm -f "${f}"
|
||||||
|
done
|
||||||
|
ls -lh /backups/
|
||||||
|
volumeMounts:
|
||||||
|
- name: backups
|
||||||
|
mountPath: /backups
|
||||||
|
volumes:
|
||||||
|
- name: backups
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: roswell-backups
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
# Bot + scheduler en un pod (PLAN §Fase 6). El Secret roswell-secrets se crea
|
||||||
|
# imperativamente (nunca en git):
|
||||||
|
# kubectl create secret generic roswell-secrets -n roswell \
|
||||||
|
# --from-literal=telegram-bot-token=... \
|
||||||
|
# --from-literal=telegram-allowed-users=... \
|
||||||
|
# --from-literal=anthropic-api-key=... \
|
||||||
|
# --from-literal=database-url=postgresql://roswell:<pass>@postgres.roswell.svc.cluster.local:5432/roswell \
|
||||||
|
# --from-literal=podcastindex-key=... \
|
||||||
|
# --from-literal=podcastindex-secret=...
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
# ArgoCD no debe podar este PVC: guarda la caché del modelo Whisper (~3GB)
|
||||||
|
# y los PDFs subidos por Telegram.
|
||||||
|
argocd.argoproj.io/sync-options: Prune=false
|
||||||
|
name: roswell-data
|
||||||
|
namespace: roswell
|
||||||
|
spec:
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: roswell
|
||||||
|
namespace: roswell
|
||||||
|
labels:
|
||||||
|
app: roswell
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
# Dos pods haciendo polling del mismo token de Telegram entran en conflicto:
|
||||||
|
# Recreate garantiza que el viejo muere antes de arrancar el nuevo.
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: roswell
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: roswell
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: roswell
|
||||||
|
# Tag placeholder: el workflow de Gitea Actions lo sustituye por el
|
||||||
|
# sha8 real en cada build (ver .gitea/workflows/build.yml del repo).
|
||||||
|
image: git.chemavx.xyz/chemavx/roswell-corpus:00000000
|
||||||
|
imagePullPolicy: Always
|
||||||
|
env:
|
||||||
|
- name: TELEGRAM_BOT_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: roswell-secrets
|
||||||
|
key: telegram-bot-token
|
||||||
|
- name: TELEGRAM_ALLOWED_USERS
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: roswell-secrets
|
||||||
|
key: telegram-allowed-users
|
||||||
|
- name: ANTHROPIC_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: roswell-secrets
|
||||||
|
key: anthropic-api-key
|
||||||
|
- name: DATABASE_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: roswell-secrets
|
||||||
|
key: database-url
|
||||||
|
- name: PODCASTINDEX_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: roswell-secrets
|
||||||
|
key: podcastindex-key
|
||||||
|
- name: PODCASTINDEX_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: roswell-secrets
|
||||||
|
key: podcastindex-secret
|
||||||
|
- name: OLLAMA_URL
|
||||||
|
value: "http://ollama.ollama.svc.cluster.local:11434"
|
||||||
|
- name: SEARXNG_URL
|
||||||
|
value: "http://searxng-svc.researchowl.svc.cluster.local:8080"
|
||||||
|
- name: WHISPER_MODEL
|
||||||
|
value: "large-v3"
|
||||||
|
- name: WHISPER_DEVICE
|
||||||
|
value: "cpu"
|
||||||
|
- name: DATA_DIR
|
||||||
|
value: "/data"
|
||||||
|
- name: FEED_CHECK_HOURS
|
||||||
|
value: "24"
|
||||||
|
# Caché de modelos Whisper en el PVC: la descarga (~3GB) sobrevive
|
||||||
|
# a los redeploys.
|
||||||
|
- name: HF_HOME
|
||||||
|
value: "/data/hf-cache"
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "512Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
limits:
|
||||||
|
# Whisper large-v3 en CPU ≈ 3-4 GB (PLAN §10). Sin límite de CPU:
|
||||||
|
# la transcripción aprovecha los cores libres del nodo.
|
||||||
|
memory: "4Gi"
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: roswell-data
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: gitea-registry
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
apiVersion: secrets.infisical.com/v1beta1
|
||||||
|
kind: InfisicalStaticSecret
|
||||||
|
metadata:
|
||||||
|
name: gitea-registry
|
||||||
|
namespace: roswell
|
||||||
|
spec:
|
||||||
|
# Referencia la capa de auth COMPARTIDA en 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
|
||||||
|
namespace: roswell
|
||||||
|
creationPolicy: Owner
|
||||||
|
secretType: kubernetes.io/dockerconfigjson
|
||||||
|
template:
|
||||||
|
engineVersion: v1
|
||||||
|
data:
|
||||||
|
# Reconstruye el pull-secret docker desde los dos campos de /registry.
|
||||||
|
.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 }}"}}}'
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Postgres 16 + pgvector para el corpus (namespace roswell).
|
||||||
|
# El Secret roswell-pg-secret se crea imperativamente (PLAN Fase 6):
|
||||||
|
# kubectl create secret generic roswell-pg-secret -n roswell \
|
||||||
|
# --from-literal=password=<pass>
|
||||||
|
# NodePort 30432 para acceso desde la LAN/nodos durante el desarrollo local.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: roswell
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
# ArgoCD no debe podar nunca este PVC: contiene la BD del corpus.
|
||||||
|
argocd.argoproj.io/sync-options: Prune=false
|
||||||
|
name: roswell-pgdata
|
||||||
|
namespace: roswell
|
||||||
|
spec:
|
||||||
|
accessModes: ["ReadWriteOnce"]
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 20Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
namespace: roswell
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: roswell-postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: roswell-postgres
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: pgvector/pgvector:pg16
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_DB
|
||||||
|
value: roswell
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: roswell
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: roswell-pg-secret
|
||||||
|
key: password
|
||||||
|
- name: PGDATA
|
||||||
|
value: /var/lib/postgresql/data/pgdata
|
||||||
|
ports:
|
||||||
|
- containerPort: 5432
|
||||||
|
volumeMounts:
|
||||||
|
- name: pgdata
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 256Mi
|
||||||
|
cpu: 100m
|
||||||
|
limits:
|
||||||
|
memory: 1Gi
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command: ["pg_isready", "-U", "roswell", "-d", "roswell"]
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 5
|
||||||
|
volumes:
|
||||||
|
- name: pgdata
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: roswell-pgdata
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
namespace: roswell
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
selector:
|
||||||
|
app: roswell-postgres
|
||||||
|
ports:
|
||||||
|
- port: 5432
|
||||||
|
targetPort: 5432
|
||||||
|
nodePort: 30432
|
||||||
Reference in New Issue
Block a user