From c29c7cf1b4eb4bb4e3ab28501e0dbe9e72fa533d Mon Sep 17 00:00:00 2001 From: "Ahmed, Mushtaq" Date: Fri, 25 Sep 2026 23:01:33 -0600 Subject: [PATCH] feat(langfuse): back object storage with SeaweedFS + restricted PSS Companion to the enterprise-ai-solutions MinIO->SeaweedFS object_store migration. - Point langfuse S3 (event upload, batch export, media) at the SeaweedFS S3 endpoint with path-style addressing; read scoped creds from the object-store-credentials secret. - Set the chart's shared podSecurityContext/securityContext (uid 1001, seccomp RuntimeDefault, drop ALL) so web/worker pass restricted PSS. --- roles/langfuse/defaults/main.yaml | 12 +++-- roles/langfuse/tasks/install.yaml | 78 ++++++++++++++----------------- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/roles/langfuse/defaults/main.yaml b/roles/langfuse/defaults/main.yaml index 029cf2e..a2f09c5 100644 --- a/roles/langfuse/defaults/main.yaml +++ b/roles/langfuse/defaults/main.yaml @@ -7,9 +7,9 @@ # routed through LiteLLM. # # Architecture: -# LiteLLM → Langfuse (OTEL/HTTP callback) → PostgreSQL + Redis + MinIO(S3) +# LiteLLM → Langfuse (OTEL/HTTP callback) → PostgreSQL + Redis + object store (S3) # -# We reuse the existing PostgreSQL and MinIO from the platform layer. +# We reuse the existing PostgreSQL and object store from the platform layer. # A lightweight Redis (Valkey) is deployed inside the Langfuse namespace. # # Single-node: 1 replica each, minimal resources @@ -36,9 +36,13 @@ langfuse_admin_password: "{{ lookup('password', '/dev/null length=24 chars=ascii langfuse_db_name: "langfuse" langfuse_db_user: "langfuse" -# MinIO — reuse platform MinIO for blob storage (traces, media) +# Object store (S3) — reuse the platform object store via the backend-agnostic +# object_store interface (SeaweedFS by default). Langfuse is registered as an +# object_store consumer, so the active backend role creates its bucket and a +# scoped S3 user, and writes the shared credentials Secret we read at install. langfuse_s3_bucket: "langfuse" -minio_namespace: "minio" +object_store_namespace: "monitoring" +object_store_credentials_secret: "object-store-credentials" # checkov:skip=CKV_SECRET_6: Secret name, not a value # ClickHouse — deployed by Helm subchart (bitnami/clickhouse) diff --git a/roles/langfuse/tasks/install.yaml b/roles/langfuse/tasks/install.yaml index d936c54..4cd1e6d 100644 --- a/roles/langfuse/tasks/install.yaml +++ b/roles/langfuse/tasks/install.yaml @@ -2,7 +2,7 @@ # ============================================================================= # Langfuse — Install via Helm # ============================================================================= -# Uses existing platform PostgreSQL and MinIO. +# Uses existing platform PostgreSQL and object store (S3). # Redis (Valkey) and ClickHouse deployed by Helm subcharts. # Single-node: 1 replica, minimal resources. # Multi-node: 2 replicas, HPA enabled. @@ -85,59 +85,36 @@ _langfuse_db_pass: "{{ _langfuse_db_secret.resources[0].data.password | b64decode }}" no_log: true - # ── MinIO S3 credentials ────────────────────────────────────────────────── + # ── Object store (S3) credentials ───────────────────────────────────────── + # Langfuse is an object_store consumer: the active backend role (SeaweedFS) + # creates the `langfuse` bucket + a scoped S3 user and writes the shared + # credentials Secret. We read the langfuse key pair from it — no bucket + # creation here (the backend already made it, and the scoped user has no + # permission to create buckets). - - name: "langfuse | Load MinIO credentials" + - name: "langfuse | Load object store credentials" kubernetes.core.k8s_info: api_version: v1 kind: Secret - name: minio-credentials - namespace: "{{ minio_namespace | default('minio') }}" - register: _langfuse_minio_secret + name: "{{ object_store_credentials_secret }}" + namespace: "{{ object_store_namespace }}" + register: _langfuse_os_secret failed_when: false - - name: "langfuse | Extract MinIO credentials" + - name: "langfuse | Extract object store S3 credentials" ansible.builtin.set_fact: - _langfuse_s3_endpoint: "{{ _langfuse_minio_secret.resources[0].data.endpoint | b64decode }}" - _langfuse_s3_access_key: "{{ _langfuse_minio_secret.resources[0].data.rootUser | b64decode }}" - _langfuse_s3_secret_key: "{{ _langfuse_minio_secret.resources[0].data.rootPassword | b64decode }}" + _langfuse_s3_endpoint: "{{ _langfuse_os_secret.resources[0].data.endpoint | b64decode }}" + _langfuse_s3_access_key: "{{ _langfuse_os_secret.resources[0].data.langfuseAccessKey | b64decode }}" + _langfuse_s3_secret_key: "{{ _langfuse_os_secret.resources[0].data.langfuseSecretKey | b64decode }}" no_log: true - when: _langfuse_minio_secret.resources | default([]) | length > 0 + when: _langfuse_os_secret.resources | default([]) | length > 0 - - name: "langfuse | Fallback S3 to empty (MinIO not available)" + - name: "langfuse | Fallback S3 to empty (object store not available)" ansible.builtin.set_fact: _langfuse_s3_endpoint: "" _langfuse_s3_access_key: "" _langfuse_s3_secret_key: "" - when: _langfuse_minio_secret.resources | default([]) | length == 0 - - # ── Create MinIO bucket for Langfuse ───────────────────────────────────── - - - name: "langfuse | Create MinIO bucket" - when: _langfuse_s3_endpoint | length > 0 - kubernetes.core.k8s: - state: present - definition: - apiVersion: batch/v1 - kind: Job - metadata: - name: langfuse-create-bucket - namespace: "{{ langfuse_namespace }}" - spec: - ttlSecondsAfterFinished: 60 - template: - spec: - restartPolicy: OnFailure - containers: - - name: mc - image: minio/mc:RELEASE.2025-07-21T05-28-08Z - command: - - /bin/sh - - -c - - | - mc alias set minio {{ _langfuse_s3_endpoint }} {{ _langfuse_s3_access_key }} {{ _langfuse_s3_secret_key }} && - mc mb --ignore-existing minio/{{ langfuse_s3_bucket }} - failed_when: false + when: _langfuse_os_secret.resources | default([]) | length == 0 # ── Langfuse secrets (stable across restarts — generated once) ─────────── @@ -262,6 +239,23 @@ name: langfuse-secrets key: nextauth_secret + # PSS restricted — chart v1.5.x reads these shared web+worker keys, + # not the web/worker subkeys below. Image runs as uid 1001 (nextjs). + podSecurityContext: + runAsNonRoot: true + runAsUser: 1001 + fsGroup: 1001 + seccompProfile: + type: RuntimeDefault + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 1001 + capabilities: + drop: ["ALL"] + seccompProfile: + type: RuntimeDefault + # Web UI sizing web: resources: "{{ _langfuse_web_resources }}" @@ -411,7 +405,7 @@ capabilities: drop: ["ALL"] - # ── S3/MinIO — use our existing MinIO ───────────────────────────── + # ── S3 — use the platform object store (SeaweedFS via object_store) ─ s3: deploy: false bucket: "{{ langfuse_s3_bucket }}"