infra/instagram-poster: shared CNPG-backed benchmark DB, no PVC for scores
The instagram_poster.benchmark CLI was writing scores to a sqlite file on the pod's data PVC. Moving it to the shared CNPG cluster so the benchmark scoring path is stateless on the pod, scores survive pod recreation, and the rotation/backup pipeline applies automatically. - dbaas: null_resource.pg_instagram_poster_db creates role + DB (idempotent CREATE IF NOT EXISTS, password placeholder) — same shape as pg_postiz_dbs / pg_wealthfolio_sync_db. - vault: vault_database_secret_backend_static_role.pg_instagram_poster + add to allowed_roles. 7d rotation_period. - instagram-poster: second ExternalSecret (vault-database store) → K8s Secret instagram-poster-benchmark-db with BENCHMARK_PG_HOST/ PORT/USER/PASSWORD/DATABASE. env_from on the deployment. reloader.stakater.com/match=true bounces the pod on rotation. Code-side: instagram_poster/benchmark.py now resolves the DB URL from BENCHMARK_DB_URL or BENCHMARK_PG_* env vars; falls back to sqlite for local DevVM scratch runs. Schema bootstraps via Base.metadata.create_all, no alembic step needed for the benchmark-only side-DB. Verified end-to-end via DevVM port-forward: ESO synced, K8s Secret has all 5 fields, pod env shows BENCHMARK_PG_*, smoke-test scoring 3 photos landed in the new PG table with subject_category populated. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
93ee45bd25
commit
dc87a9bffe
3 changed files with 96 additions and 1 deletions
|
|
@ -1296,6 +1296,35 @@ resource "null_resource" "pg_fire_planner_db" {
|
|||
}
|
||||
}
|
||||
|
||||
# Create instagram_poster database for the IG-curation pipeline. Initial use:
|
||||
# benchmark_score table written by `instagram_poster.benchmark` CLI (vision-LLM
|
||||
# scoring per Immich asset). Future: migrate story_queue/decision/ig_posted_media
|
||||
# off the pod's sqlite PVC into this DB so the pod is fully stateless.
|
||||
# Role password is managed by Vault Database Secrets Engine
|
||||
# (static role `pg-instagram-poster`, 7d rotation).
|
||||
resource "null_resource" "pg_instagram_poster_db" {
|
||||
depends_on = [null_resource.pg_cluster]
|
||||
|
||||
triggers = {
|
||||
db_name = "instagram_poster"
|
||||
username = "instagram_poster"
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = <<-EOT
|
||||
PRIMARY=$(kubectl --kubeconfig ${var.kube_config_path} get cluster -n dbaas pg-cluster -o jsonpath='{.status.currentPrimary}')
|
||||
kubectl --kubeconfig ${var.kube_config_path} exec -n dbaas $PRIMARY -c postgres -- \
|
||||
bash -c '
|
||||
psql -U postgres -tc "SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = '"'"'instagram_poster'"'"'" | grep -q 1 || \
|
||||
psql -U postgres -c "CREATE ROLE instagram_poster WITH LOGIN PASSWORD '"'"'changeme-vault-will-rotate'"'"'"
|
||||
psql -U postgres -tc "SELECT 1 FROM pg_catalog.pg_database WHERE datname = '"'"'instagram_poster'"'"'" | grep -q 1 || \
|
||||
psql -U postgres -c "CREATE DATABASE instagram_poster OWNER instagram_poster"
|
||||
psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE instagram_poster TO instagram_poster"
|
||||
'
|
||||
EOT
|
||||
}
|
||||
}
|
||||
|
||||
# Old PostgreSQL deployment — kept commented for rollback reference
|
||||
# resource "kubernetes_deployment" "postgres" {
|
||||
# metadata {
|
||||
|
|
|
|||
|
|
@ -129,6 +129,54 @@ resource "kubernetes_manifest" "external_secret" {
|
|||
depends_on = [kubernetes_namespace.instagram_poster]
|
||||
}
|
||||
|
||||
# Benchmark scoring DB — shared CNPG cluster, written by the
|
||||
# `instagram_poster.benchmark` CLI (vision-LLM scores per Immich asset).
|
||||
# Vault static role `pg-instagram-poster` rotates the password every 7 days;
|
||||
# ESO refreshes the K8s Secret every 15m. `reloader.stakater.com/match`
|
||||
# bounces the pod when the password changes.
|
||||
resource "kubernetes_manifest" "benchmark_db_external_secret" {
|
||||
manifest = {
|
||||
apiVersion = "external-secrets.io/v1beta1"
|
||||
kind = "ExternalSecret"
|
||||
metadata = {
|
||||
name = "instagram-poster-benchmark-db"
|
||||
namespace = local.namespace
|
||||
}
|
||||
spec = {
|
||||
refreshInterval = "15m"
|
||||
secretStoreRef = {
|
||||
name = "vault-database"
|
||||
kind = "ClusterSecretStore"
|
||||
}
|
||||
target = {
|
||||
name = "instagram-poster-benchmark-db"
|
||||
template = {
|
||||
metadata = {
|
||||
annotations = {
|
||||
"reloader.stakater.com/match" = "true"
|
||||
}
|
||||
}
|
||||
data = {
|
||||
BENCHMARK_PG_HOST = "pg-cluster-rw.dbaas.svc.cluster.local"
|
||||
BENCHMARK_PG_PORT = "5432"
|
||||
BENCHMARK_PG_DATABASE = "instagram_poster"
|
||||
BENCHMARK_PG_USER = "instagram_poster"
|
||||
BENCHMARK_PG_PASSWORD = "{{ .password }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
data = [{
|
||||
secretKey = "password"
|
||||
remoteRef = {
|
||||
key = "static-creds/pg-instagram-poster"
|
||||
property = "password"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
depends_on = [kubernetes_namespace.instagram_poster]
|
||||
}
|
||||
|
||||
# Persistent state: SQLite + image cache. Sensitive (API tokens may end up
|
||||
# in cached images / debug logs), but the proxmox-lvm-encrypted SC is for
|
||||
# user-data DBs; this is a small app cache so plain proxmox-lvm fits the
|
||||
|
|
@ -214,6 +262,15 @@ resource "kubernetes_deployment" "instagram_poster" {
|
|||
name = "instagram-poster-secrets"
|
||||
}
|
||||
}
|
||||
# Vault-rotated benchmark Postgres creds. Sources BENCHMARK_PG_*
|
||||
# env vars into the container; benchmark.py builds the SQLAlchemy
|
||||
# URL from them. Schema bootstraps via Base.metadata.create_all
|
||||
# on first use.
|
||||
env_from {
|
||||
secret_ref {
|
||||
name = "instagram-poster-benchmark-db"
|
||||
}
|
||||
}
|
||||
|
||||
env {
|
||||
name = "IMMICH_BASE_URL"
|
||||
|
|
@ -302,6 +359,7 @@ resource "kubernetes_deployment" "instagram_poster" {
|
|||
|
||||
depends_on = [
|
||||
kubernetes_manifest.external_secret,
|
||||
kubernetes_manifest.benchmark_db_external_secret,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -540,7 +540,7 @@ resource "vault_database_secret_backend_connection" "postgresql" {
|
|||
"pg-affine", "pg-woodpecker", "pg-claude-memory",
|
||||
"pg-terraform-state", "pg-payslip-ingest", "pg-job-hunter",
|
||||
"pg-wealthfolio-sync", "pg-fire-planner",
|
||||
"pg-postiz",
|
||||
"pg-postiz", "pg-instagram-poster",
|
||||
]
|
||||
|
||||
postgresql {
|
||||
|
|
@ -721,6 +721,14 @@ resource "vault_database_secret_backend_static_role" "pg_fire_planner" {
|
|||
rotation_period = 604800
|
||||
}
|
||||
|
||||
resource "vault_database_secret_backend_static_role" "pg_instagram_poster" {
|
||||
backend = vault_mount.database.path
|
||||
db_name = vault_database_secret_backend_connection.postgresql.name
|
||||
name = "pg-instagram-poster"
|
||||
username = "instagram_poster"
|
||||
rotation_period = 604800
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Kubernetes Secrets Engine — Dynamic K8s Credentials
|
||||
# =============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue