diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f1576d26..ce4c0955 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -23,7 +23,7 @@ jobs: - name: Set up Helm uses: azure/setup-helm@v4 with: - version: v3.13.2 + version: v3.20.2 - name: Install helm-unittest plugin run: | diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index e4182b9b..84efb6b5 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -20,7 +20,7 @@ jobs: - name: Set up Helm uses: azure/setup-helm@v4 with: - version: v3.13.2 + version: v3.20.2 - name: Install helm-docs run: | diff --git a/README.md b/README.md index bcd7c9d6..b38a01c4 100644 --- a/README.md +++ b/README.md @@ -14,37 +14,116 @@ Langfuse self-hosting documentation: https://langfuse.com/self-hosting - `examples` directory contains example `yaml` configurations - `charts/langfuse` directory contains Helm chart for deploying Langfuse with an associated database -## ⚠️ Important: Bitnami Registry Changes +## ⚠️ Breaking changes in v2.0.0 -**Effective August 28, 2025**, Bitnami will restructure its container registry. This chart now uses `bitnamilegacy/*` images by default to prevent deployment failures. +v2.0.0 replaces the Bitnami sub-charts with OSS-licensed alternatives and deploys ClickHouse via the upstream `ClickHouse/clickhouse-operator`: -**What changed:** -- Bitnami moved most container images to a paid "Secure Images" tier -- Free images are now limited to a small community subset -- Older/versioned images moved to the "Bitnami Legacy" repository +| Component | v1.x | v2.0.0 | +|-----------|------|--------| +| PostgreSQL | `bitnami/postgresql` | [`groundhog2k/postgres`](https://artifacthub.io/packages/helm/groundhog2k/postgres) | +| ClickHouse | `bitnami/clickhouse` | [`ClickHouse/clickhouse-operator`](https://github.com/Altinity/clickhouse-operator) (cluster-wide) | +| Redis | `bitnami/valkey` | [`valkey-io/valkey`](https://github.com/valkey-io/valkey-helm) | +| Object storage | `bitnami/minio` | [`seaweedfs/seaweedfs`](https://github.com/seaweedfs/seaweedfs) (allInOne) | -**Next steps:** -- For existing deployments: Ensure that you update your mirrors to clone from bitnamilegacy if applicable. -- We will investigate alternative image sources that are compliant with the Helm chart and roll them out over time. -- You _may_ upgrade to Bitnami Secure Images if desired in the meantime. In this case, set `global.security.allowInsecureImages: false` and configure image repositories to use `bitnami/*` instead of `bitnamilegacy/*` +In v2 the chart also auto-generates credential Secrets for Postgres, ClickHouse, Valkey, and SeaweedFS on first install (persisted across upgrades via `lookup`), so only the three Langfuse application secrets (`salt`, `encryptionKey`, `nextauth.secret`) need to be supplied. -See [Bitnami's announcement](https://github.com/bitnami/charts/issues/35164) for more details. +There is **no automatic data migration** from v1. To upgrade, dump v1 data, install v2 in a new namespace, and restore. ## Helm Chart We provide a Helm chart that helps you deploy Langfuse on Kubernetes. -### Installation +### Prerequisites + +- **Helm `v3.17` or newer.** The bundled `seaweedfs` sub-chart uses the `fromToml` template function, which was added in Helm 3.17.0. +- **Kubernetes `v1.28` or newer**, as required by the [ClickHouse operator](https://github.com/ClickHouse/clickhouse-operator). -Configure the required secrets and parameters as defined below in a new `values.yaml` file. -Then install the helm chart using the commands below: +The chart renders `ClickHouseCluster` / `KeeperCluster` CRs and cert-manager `Certificate` / `Issuer` resources. Both CRD sets must already exist in the cluster before `helm install`. Install these **once per cluster**: ```bash -helm repo add langfuse https://langfuse.github.io/langfuse-k8s -helm repo update -helm install langfuse langfuse/langfuse -f values.yaml +# 1. cert-manager (skip if already installed) +helm install cert-manager oci://quay.io/jetstack/charts/cert-manager \ + --version v1.20.2 \ + --namespace cert-manager --create-namespace \ + --set crds.enabled=true + +kubectl wait --for=condition=Established \ + crd/certificates.cert-manager.io crd/issuers.cert-manager.io \ + --timeout=120s + +# 2. clickhouse-operator +helm install ch-operator oci://ghcr.io/clickhouse/clickhouse-operator-helm \ + --version 0.0.4 \ + --namespace clickhouse-operator --create-namespace + +kubectl wait --for=condition=Established \ + crd/clickhouseclusters.clickhouse.com crd/keeperclusters.clickhouse.com \ + --timeout=120s ``` +### Installation + +The fastest path is to follow [`examples/minimal-installation`](./examples/minimal-installation/) — it installs Langfuse with all bundled sub-charts on a single `helm install`. + +1. Create a Secret containing the three Langfuse application secrets (`salt`, `encryption-key`, `nextauth-secret`). Generate them with: + + ```bash + openssl rand -hex 32 # salt + openssl rand -hex 32 # encryption-key + openssl rand -base64 32 # nextauth-secret + ``` + + ```yaml + apiVersion: v1 + kind: Secret + type: Opaque + metadata: + name: langfuse + stringData: + salt: "" + encryption-key: "" + nextauth-secret: "" + ``` + +2. Create a `values.yaml` that references those keys (sub-component credentials are auto-generated by the chart — no need to set them): + + ```yaml + langfuse: + salt: + secretKeyRef: + name: langfuse + key: salt + encryptionKey: + secretKeyRef: + name: langfuse + key: encryption-key + nextauth: + secret: + secretKeyRef: + name: langfuse + key: nextauth-secret + ``` + +3. Apply the Secret and install the chart: + + ```bash + kubectl create namespace langfuse + kubectl apply -n langfuse -f secret.yaml + + helm install langfuse oci://ghcr.io/langfuse/langfuse-k8s/langfuse \ + --version 2.0.0 \ + --namespace langfuse \ + -f values.yaml + ``` + + Alternatively, the chart is also published via the legacy Helm repo: + + ```bash + helm repo add langfuse https://langfuse.github.io/langfuse-k8s + helm repo update + helm install langfuse langfuse/langfuse -n langfuse -f values.yaml + ``` + ### Upgrading ```bash @@ -71,102 +150,94 @@ langfuse: cpu: "2" memory: "4Gi" -clickhouse: +postgresql: resources: limits: cpu: "2" - memory: "8Gi" + memory: "2Gi" requests: - cpu: "2" - memory: "8Gi" - - zookeeper: + cpu: "500m" + memory: "512Mi" + +clickhouse: + cluster: resources: limits: cpu: "2" - memory: "4Gi" + memory: "8Gi" requests: cpu: "2" - memory: "4Gi" - -redis: - primary: + memory: "8Gi" + keeper: resources: limits: cpu: "1" - memory: "1.5Gi" + memory: "1Gi" requests: - cpu: "1" - memory: "1.5Gi" + cpu: "500m" + memory: "512Mi" -s3: +redis: resources: limits: - cpu: "2" - memory: "4Gi" + cpu: "1" + memory: "1.5Gi" requests: - cpu: "2" - memory: "4Gi" + cpu: "1" + memory: "1.5Gi" + +s3: + allInOne: + resources: + limits: + cpu: "2" + memory: "4Gi" + requests: + cpu: "2" + memory: "4Gi" ``` ### Configuration -The required configuration options to set are: +The only **required** values are the three Langfuse application secrets — `salt`, `encryptionKey`, and `nextauth.secret`. Credentials for Postgres, ClickHouse, Valkey, and SeaweedFS are generated by the chart on first install (and persisted across upgrades via `lookup`). + +Provide the Langfuse secrets either inline: ```yaml -# Optional, but highly recommended. Generate via `openssl rand -hex 32`. -# langfuse: -# encryptionKey: -# value: "" -langfuse: +langfuse: salt: - value: secureSalt + value: "" + encryptionKey: + value: "" nextauth: secret: - value: "" - -postgresql: - auth: - # If you want to use `postgres` as the username, you need to provide postgresPassword instead of password. - username: langfuse - password: "" - -clickhouse: - auth: - password: "" - -redis: - auth: - password: "" - -s3: - auth: - rootPassword: "" + value: "" ``` -They can alternatively set via secret references (the secrets must exist): +…or as references to an existing Kubernetes Secret: ```yaml -# Optional, but highly recommended. Generate via `openssl rand -hex 32`. -# langfuse: -# encryptionKey: -# secretKeyRef: -# name: langfuse-encryption-key-secret -# key: encryptionKey -langfuse: +langfuse: salt: secretKeyRef: - name: langfuse-general + name: langfuse key: salt + encryptionKey: + secretKeyRef: + name: langfuse + key: encryption-key nextauth: secret: secretKeyRef: - name: langfuse-nextauth-secret + name: langfuse key: nextauth-secret +``` + +To pin the sub-component credentials instead of letting the chart generate them, set `.auth.password` (or `.auth.existingSecret`): +```yaml postgresql: auth: - # If you want to use `postgres` as the username, you need to provide a adminPasswordKey in secretKeys. username: langfuse existingSecret: langfuse-postgresql-auth secretKeys: @@ -189,7 +260,7 @@ s3: rootUserSecretKey: rootUser rootPasswordSecretKey: rootPassword ``` - + See the [Helm README](https://github.com/langfuse/langfuse-k8s/blob/main/charts/langfuse/README.md) for a full list of all configuration options. #### Storage Provider Options @@ -494,33 +565,44 @@ global: 2. **Component-specific Storage Classes**: Override the storage class for specific components. ```yaml postgresql: - primary: - persistence: - storageClass: "postgres-storage-class" - + storage: + className: "postgres-storage-class" + redis: - primary: - persistence: - storageClass: "redis-storage-class" + dataStorage: + className: "redis-storage-class" clickhouse: - persistence: - storageClass: "clickhouse-storage-class" + cluster: + storage: + className: "clickhouse-storage-class" + keeper: + storage: + className: "clickhouse-keeper-storage-class" s3: - persistence: - storageClass: "minio-storage-class" + allInOne: + data: + storageClass: "seaweedfs-storage-class" ``` If no storage class is specified, the cluster's default storage class will be used. ##### With an external Postgres server with client certificates using own secrets and additionalEnv for mappings +The Langfuse application reads `DATABASE_URL`, `SALT`, and `NEXTAUTH_SECRET` from environment variables, so an `additionalEnv` override takes precedence over the chart's defaults. Combine that with `extraVolumes` to mount client certificates from a Secret: + ```yaml langfuse: - salt: null - nextauth: - secret: null + salt: + secretKeyRef: + name: langfuse-general + key: salt + nextauth: + secret: + secretKeyRef: + name: langfuse-general + key: nextauth-secret extraVolumes: - name: db-keystore # referencing an existing secret to mount server/client certs for postgres secret: @@ -535,25 +617,15 @@ langfuse: secretKeyRef: name: langfuse-postgres # referencing an existing secret key: database-url - - name: NEXTAUTH_SECRET - valueFrom: - secretKeyRef: - name: langfuse-general # referencing an existing secret - key: nextauth-secret - - name: SALT - valueFrom: - secretKeyRef: - name: langfuse-general - key: salt -service: - [...] -ingress: - [...] + postgresql: deploy: false + # When DATABASE_URL is overridden via additionalEnv above, the auth block is only + # used to render the chart's connection-string env vars (which the additionalEnv + # entry replaces). Leave the defaults or set host/auth.username to match. + host: my-external-postgres-server.com auth: - password: null - username: null + username: langfuse ``` ##### With SSO provider configuration using secrets and additionalEnv diff --git a/charts/langfuse/Chart.yaml b/charts/langfuse/Chart.yaml index 7dc70285..6955fc42 100644 --- a/charts/langfuse/Chart.yaml +++ b/charts/langfuse/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: langfuse -version: 1.5.29 +version: 2.0.0 description: Open source LLM engineering platform - LLM observability, metrics, evaluations, prompt management. type: application keywords: @@ -14,27 +14,21 @@ sources: - https://github.com/langfuse/langfuse - https://github.com/langfuse/langfuse-k8s dependencies: - - name: postgresql - version: 16.4.9 - repository: oci://registry-1.docker.io/bitnamicharts + - name: postgres + version: 1.6.3 + repository: https://groundhog2k.github.io/helm-charts/ condition: postgresql.deploy - - name: clickhouse - version: 8.0.5 - repository: oci://registry-1.docker.io/bitnamicharts - condition: clickhouse.deploy + alias: postgresql - name: valkey - version: 2.2.4 - repository: oci://registry-1.docker.io/bitnamicharts + version: 0.9.4 + repository: https://valkey-io.github.io/valkey-helm condition: redis.deploy alias: redis - - name: minio - version: 14.10.5 - repository: oci://registry-1.docker.io/bitnamicharts + - name: seaweedfs + version: 4.23.0 + repository: https://seaweedfs.github.io/seaweedfs/helm condition: s3.deploy alias: s3 - - name: common - version: 2.30.0 - repository: oci://registry-1.docker.io/bitnamicharts maintainers: - name: langfuse email: contact@langfuse.com diff --git a/charts/langfuse/README.md b/charts/langfuse/README.md index 4f9df8ef..82780882 100644 --- a/charts/langfuse/README.md +++ b/charts/langfuse/README.md @@ -1,6 +1,6 @@ # langfuse -![Version: 1.5.29](https://img.shields.io/badge/Version-1.5.29-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.172.1](https://img.shields.io/badge/AppVersion-3.172.1-informational?style=flat-square) +![Version: 2.0.0](https://img.shields.io/badge/Version-2.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.172.1](https://img.shields.io/badge/AppVersion-3.172.1-informational?style=flat-square) Open source LLM engineering platform - LLM observability, metrics, evaluations, prompt management. @@ -21,37 +21,40 @@ Open source LLM engineering platform - LLM observability, metrics, evaluations, | Repository | Name | Version | |------------|------|---------| -| oci://registry-1.docker.io/bitnamicharts | clickhouse | 8.0.5 | -| oci://registry-1.docker.io/bitnamicharts | common | 2.30.0 | -| oci://registry-1.docker.io/bitnamicharts | s3(minio) | 14.10.5 | -| oci://registry-1.docker.io/bitnamicharts | postgresql | 16.4.9 | -| oci://registry-1.docker.io/bitnamicharts | redis(valkey) | 2.2.4 | +| https://groundhog2k.github.io/helm-charts/ | postgresql(postgres) | 1.6.3 | +| https://seaweedfs.github.io/seaweedfs/helm | s3(seaweedfs) | 4.23.0 | +| https://valkey-io.github.io/valkey-helm | redis(valkey) | 0.9.4 | ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| +| clickhouse | object | `{"auth":{"existingSecret":"","existingSecretKey":"","password":"","username":"default"},"cluster":{"affinity":{},"enabled":true,"image":{"repository":"clickhouse/clickhouse-server","tag":"26.3"},"nodeSelector":{},"profileSettings":{},"replicas":1,"resources":{},"settings":{},"storage":{"accessModes":["ReadWriteOnce"],"className":"","size":"100Gi"},"tolerations":[]},"database":"default","deploy":true,"host":"","httpPort":8123,"keeper":{"affinity":{},"enabled":true,"image":{"repository":"clickhouse/clickhouse-keeper","tag":"26.3"},"nodeSelector":{},"replicas":3,"resources":{},"storage":{"accessModes":["ReadWriteOnce"],"className":"","size":"10Gi"},"tolerations":[]},"migration":{"autoMigrate":true,"ssl":false,"url":""},"nativePort":9000}` | release notes before upgrading and pin the operator chart version explicitly. | | clickhouse.auth.existingSecret | string | `""` | If you want to use an existing secret for the ClickHouse password, set the name of the secret here. (`clickhouse.auth.password` will be ignored and picked up from this secret). | | clickhouse.auth.existingSecretKey | string | `""` | The key in the existing secret that contains the password. | -| clickhouse.auth.password | string | `""` | Password for the ClickHouse user. | +| clickhouse.auth.password | string | `""` | Leave empty to have the chart generate one. | | clickhouse.auth.username | string | `"default"` | Username for the ClickHouse user. | -| clickhouse.clusterEnabled | bool | `true` | Whether to run ClickHouse commands ON CLUSTER. Controls CLICKHOUSE_CLUSTER_ENABLED setting. | +| clickhouse.cluster | object | `{"affinity":{},"enabled":true,"image":{"repository":"clickhouse/clickhouse-server","tag":"26.3"},"nodeSelector":{},"profileSettings":{},"replicas":1,"resources":{},"settings":{},"storage":{"accessModes":["ReadWriteOnce"],"className":"","size":"100Gi"},"tolerations":[]}` | ------------------------------------------------------------------------- | +| clickhouse.cluster.enabled | bool | `true` | external non-clustered ClickHouse. | +| clickhouse.cluster.nodeSelector | object | `{}` | Pod scheduling. | +| clickhouse.cluster.profileSettings | object | `{}` | Extra ClickHouse profile settings (mounted into `users.xml`). | +| clickhouse.cluster.replicas | int | `1` | Number of ClickHouse replicas. 1 is a valid non-HA single-pod cluster; 2+ requires Keeper enabled. | +| clickhouse.cluster.resources | object | `{}` | Resource limits/requests for the ClickHouse pods. | +| clickhouse.cluster.settings | object | `{}` | Extra ClickHouse server settings (under `` config XML). | +| clickhouse.cluster.storage.className | string | `""` | StorageClass name. Leave empty to use the cluster's default. | +| clickhouse.cluster.storage.size | string | `"100Gi"` | Size of each ClickHouse pod's data PVC. | | clickhouse.database | string | `"default"` | ClickHouse database to use. | -| clickhouse.deploy | bool | `true` | Enable ClickHouse deployment (via Bitnami Helm Chart). If you want to use an external Clickhouse server (or a managed one), set this to false | -| clickhouse.host | string | `""` | ClickHouse host to connect to. If clickhouse.deploy is true, this will be set automatically based on the release name. | +| clickhouse.deploy | bool | `true` | Set to false to use an external ClickHouse server. | +| clickhouse.host | string | `""` | based on the cluster CR's headless Service. | | clickhouse.httpPort | int | `8123` | ClickHouse HTTP port to connect to. | -| clickhouse.image.repository | string | `"bitnamilegacy/clickhouse"` | Overwrite default repository of helm chart to point to non-paid bitnami images. | +| clickhouse.keeper.enabled | bool | `true` | Deploy a `KeeperCluster` CR alongside the ClickHouseCluster. Required for replicated mode. | +| clickhouse.keeper.replicas | int | `3` | Keeper replica count. Must be odd (1, 3, 5). Use 3 for production. | | clickhouse.migration.autoMigrate | bool | `true` | Whether to run automatic ClickHouse migrations on startup | | clickhouse.migration.ssl | bool | `false` | Set to true to establish SSL connection for migration | | clickhouse.migration.url | string | `""` | Migration URL (TCP protocol) for clickhouse | -| clickhouse.nativePort | int | `9000` | ClickHouse native port to connect to. | -| clickhouse.replicaCount | int | `3` | Number of replicas to use for the ClickHouse cluster. 1 corresponds to a single, non-HA deployment. | -| clickhouse.resourcesPreset | string | `"2xlarge"` | The resources preset to use for the ClickHouse cluster. | -| clickhouse.shards | int | `1` | Subchart specific settings | -| clickhouse.zookeeper.image.repository | string | `"bitnamilegacy/zookeeper"` | Overwrite default repository of helm chart to point to non-paid bitnami images. | +| clickhouse.nativePort | int | `9000` | ClickHouse native (TCP) port to connect to. | | extraManifests | list | `[]` | | | fullnameOverride | string | `""` | Override the full name of the deployed resources, defaults to a combination of the release name and the name for the selector labels | -| global.security.allowInsecureImages | bool | `true` | Allow insecure images to use bitnami legacy repository. Can be set to false if secure images are being used (Paid). | | langfuse.additionalEnv | list | `[]` | List of additional environment variables to be added to all langfuse deployments. See [documentation](https://langfuse.com/docs/deployment/self-host#configuring-environment-variables) for details. | | langfuse.additionalEnvFrom | list | `[]` | Secrets or ConfigMap of additional environment variables to be added to all langfuse deployments. See [documentation](https://langfuse.com/docs/deployment/self-host#configuring-environment-variables) for details. | | langfuse.affinity | object | `{}` | Affinity for all langfuse deployments | @@ -204,35 +207,57 @@ Open source LLM engineering platform - LLM observability, metrics, evaluations, | langfuse.worker.vpa.minAllowed | object | `{}` | The minimum allowed resources for the langfuse worker pods | | langfuse.worker.vpa.updatePolicy.updateMode | string | `"Auto"` | The update policy mode for the langfuse worker pods | | nameOverride | string | `""` | Override the name for the selector labels, defaults to the chart name | -| postgresql.architecture | string | `"standalone"` | | +| postgresql | object | `{"affinity":{},"args":"","auth":{"args":"","database":"langfuse","existingSecret":"","password":"","secretKeys":{"adminPasswordKey":"postgres-password","userPasswordKey":"password"},"username":"langfuse"},"deploy":true,"directUrl":"","host":"","image":{"pullPolicy":"IfNotPresent","repository":"postgres","tag":"18"},"livenessProbe":{},"migration":{"autoMigrate":true},"nodeSelector":{},"podSecurityContext":{"fsGroup":999},"port":null,"readinessProbe":{},"replicaCount":1,"resources":{},"securityContext":{"runAsGroup":999,"runAsUser":999},"service":{"port":5432,"type":"ClusterIP"},"settings":{"existingSecret":"langfuse-postgresql-auth","superuserPassword":{}},"shadowDatabaseUrl":"","startupProbe":{},"storage":{"className":"","persistentVolumeClaimRetentionPolicy":{"whenDeleted":"Retain","whenScaled":"Retain"},"requestedSize":"20Gi"},"tolerations":[],"userDatabase":{"existingSecret":"langfuse-postgresql-auth","name":{},"password":{},"user":{}}}` | For HA / managed Postgres, set `postgresql.deploy: false` and configure `host` + auth fields below. | | postgresql.args | string | `""` | Additional database connection arguments | | postgresql.auth.args | string | `""` | Additional database connection arguments | -| postgresql.auth.database | string | `"postgres_langfuse"` | Database name to use for Langfuse. | +| postgresql.auth.database | string | `"langfuse"` | database automatically and grants ownership to `auth.username`. | | postgresql.auth.existingSecret | string | `""` | If you want to use an existing secret for the postgres password, set the name of the secret here. (`postgresql.auth.password` will be ignored and picked up from this secret). | -| postgresql.auth.password | string | `""` | Password to use to connect to the postgres database deployed with Langfuse. In case `postgresql.deploy` is set to `true`, the password will be set automatically. | -| postgresql.auth.secretKeys | object | `{"adminPasswordKey":"password","userPasswordKey":"password"}` | The keys in the existing secret that contain the passwords. If using the default `postgres` user, both `userPasswordKey` and `adminPasswordKey` must be provided. | -| postgresql.auth.username | string | `"postgres"` | Username to use to connect to the postgres database deployed with Langfuse. In case `postgresql.deploy` is set to `true`, the user will be created automatically. | -| postgresql.deploy | bool | `true` | Enable PostgreSQL deployment (via Bitnami Helm Chart). If you want to use an external Postgres server (or a managed one), set this to false | +| postgresql.auth.password | string | `""` | Leave empty to have the chart generate one and store it in a release-managed Secret. | +| postgresql.auth.secretKeys | object | `{"adminPasswordKey":"postgres-password","userPasswordKey":"password"}` | when `postgresql.deploy: true` (used as the Postgres superuser password by the sub-chart). | +| postgresql.auth.username | string | `"langfuse"` | groundhog2k sub-chart creates this user automatically (see `userDatabase` below). | +| postgresql.deploy | bool | `true` | Set to false to use an external (managed) Postgres server. | | postgresql.directUrl | string | `""` | If `postgresql.deploy` is set to false, Connection string of your Postgres database used for database migrations. Use this if you want to use a different user for migrations or use connection pooling on DATABASE_URL. For large deployments, configure the database user with long timeouts as migrations might need a while to complete. | | postgresql.host | string | `""` | PostgreSQL host to connect to. If postgresql.deploy is true, this will be set automatically based on the release name. | -| postgresql.image.repository | string | `"bitnamilegacy/postgresql"` | Overwrite default repository of helm chart to point to non-paid bitnami images. | +| postgresql.image | object | `{"pullPolicy":"IfNotPresent","repository":"postgres","tag":"18"}` | ------------------------------------------------------------------------- | +| postgresql.image.repository | string | `"postgres"` | Postgres image. Defaults to the upstream Docker Hub image (Apache 2.0 / PostgreSQL license). | +| postgresql.livenessProbe | object | `{}` | Liveness / readiness probe customisations (see groundhog2k/postgres docs). | | postgresql.migration.autoMigrate | bool | `true` | Whether to run automatic migrations on startup | +| postgresql.nodeSelector | object | `{}` | Node selector / tolerations / affinity for the Postgres pod. | +| postgresql.podSecurityContext | object | `{"fsGroup":999}` | Pod security context. | | postgresql.port | string | `nil` | Port of the postgres server to use. Defaults to 5432. | -| postgresql.primary.service.ports.postgresql | int | `5432` | | +| postgresql.replicaCount | int | `1` | Number of replicas for the Postgres StatefulSet. Single-instance only — set to 0 to suspend. | +| postgresql.resources | object | `{}` | Resource limits/requests for the Postgres pod. Tune for your workload. | +| postgresql.securityContext | object | `{"runAsGroup":999,"runAsUser":999}` | Container security context. | +| postgresql.service.port | int | `5432` | Port the Postgres Service exposes. Must match `postgresql.port` (or the langfuse default 5432). | +| postgresql.settings | or `existingSecret` is provided | `{"existingSecret":"langfuse-postgresql-auth","superuserPassword":{}}` | , so users typically don't edit `settings` directly. | +| postgresql.settings.existingSecret | string | `"langfuse-postgresql-auth"` | Required keys: POSTGRES_USER, POSTGRES_PASSWORD. | +| postgresql.settings.superuserPassword | object | `{}` | chart-managed Secret unless `existingSecret` is supplied. | | postgresql.shadowDatabaseUrl | string | `""` | If your database user lacks the CREATE DATABASE permission, you must create a shadow database and configure the "SHADOW_DATABASE_URL". This is often the case if you use a Cloud database. Refer to the Prisma docs for detailed instructions. | -| redis.architecture | string | `"standalone"` | | -| redis.auth.database | int | `0` | | -| redis.auth.existingSecret | string | `""` | If you want to use an existing secret for the redis password, set the name of the secret here. (`redis.auth.password` will be ignored and picked up from this secret). | +| postgresql.storage | object | `{"className":"","persistentVolumeClaimRetentionPolicy":{"whenDeleted":"Retain","whenScaled":"Retain"},"requestedSize":"20Gi"}` | PVC settings for the Postgres data volume. | +| postgresql.storage.className | string | `""` | StorageClass name. Leave empty to use the cluster's default. | +| postgresql.storage.persistentVolumeClaimRetentionPolicy | object | `{"whenDeleted":"Retain","whenScaled":"Retain"}` | Keep the PVC after the release is uninstalled. | +| postgresql.userDatabase | object | `{"existingSecret":"langfuse-postgresql-auth","name":{},"password":{},"user":{}}` | `auth.database` and `auth.username` above (those values populate the Secret). | +| postgresql.userDatabase.existingSecret | string | `"langfuse-postgresql-auth"` | Required keys: USERDB_USER, USERDB_PASSWORD, POSTGRES_DB. | +| redis | object | `{"auth":{"aclConfig":"","aclUsers":{"default":{"permissions":"~* &* +@all"}},"database":0,"enabled":true,"existingSecret":"","existingSecretPasswordKey":"","password":"","username":"default","usersExistingSecret":"langfuse-redis-auth"},"cluster":{"enabled":false,"nodes":[]},"dataStorage":{"accessModes":["ReadWriteOnce"],"className":"","enabled":true,"keepPvc":false,"requestedSize":"8Gi"},"deploy":true,"extraFlags":["--maxmemory-policy","noeviction"],"host":"","image":{"pullPolicy":"IfNotPresent","registry":"docker.io","repository":"valkey/valkey","tag":"8.0"},"metrics":{"enabled":false},"podSecurityContext":{"fsGroup":1000,"runAsGroup":1000,"runAsUser":1000,"seccompProfile":{"type":"RuntimeDefault"}},"port":6379,"replica":{"enabled":false,"persistence":{"accessModes":["ReadWriteOnce"],"size":"8Gi","storageClass":""},"replicas":0,"service":{"enabled":true,"port":6379,"type":"ClusterIP"}},"resources":{},"sentinel":{"enabled":false,"existingSecret":"","existingSecretPasswordKey":"","masterName":"","nodes":"","password":"","username":""},"service":{"port":6379,"type":"ClusterIP"},"tls":{"caPath":"","certPath":"","enabled":false,"keyPath":""}}` | or a managed service such as ElastiCache). | +| redis.auth.aclConfig | string | `""` | Pass-through to valkey-helm: extra inline ACL config appended after `aclUsers`. | +| redis.auth.aclUsers | object | `{"default":{"permissions":"~* &* +@all"}}` | `enabled: true` (otherwise valkey rejects the config). `~* &* +@all` grants full access. | +| redis.auth.enabled | bool | `true` | Enable ACL-based authentication on the bundled Valkey sub-chart. | +| redis.auth.existingSecret | string | `""` | `existingSecretPasswordKey`. | | redis.auth.existingSecretPasswordKey | string | `""` | The key in the existing secret that contains the password. | -| redis.auth.password | string | `""` | Password for Redis authentication. Set to null to disable authentication (for passwordless Redis like AWS ElastiCache without auth). Use URL-encoded passwords or avoid special characters in the password. | -| redis.auth.username | string | `"default"` | Username for Redis authentication. Set to null to omit username from connection string entirely. In case `redis.deploy` is set to `true`, the user will be created automatically. | +| redis.auth.password | string | `""` | generate one (idempotent across upgrades via lookup). | +| redis.auth.username | string | `"default"` | When `redis.deploy: true`, an ACL user with this name is created automatically. | +| redis.auth.usersExistingSecret | string | `"langfuse-redis-auth"` | `fullnameOverride: langfuse` if your release name differs, or override here. | | redis.cluster.enabled | bool | `false` | Set to `true` to enable Redis Cluster mode. When enabled, you must set `redis.deploy` to `false` and provide cluster nodes. | | redis.cluster.nodes | list | `[]` | List of Redis cluster nodes in the format "host:port". Example: ["redis-1:6379", "redis-2:6379", "redis-3:6379"] | -| redis.deploy | bool | `true` | Enable valkey deployment (via Bitnami Helm Chart). If you want to use a Redis or Valkey server already deployed, set to false. | +| redis.dataStorage | object | `{"accessModes":["ReadWriteOnce"],"className":"","enabled":true,"keepPvc":false,"requestedSize":"8Gi"}` | Persistence for the primary node. | +| redis.deploy | bool | `true` | Set to false to use an existing Redis or Valkey deployment. | +| redis.extraFlags | list | `["--maxmemory-policy","noeviction"]` | Set the maxmemory eviction policy. Langfuse requires `noeviction` to avoid losing job data. | | redis.host | string | `""` | Redis host to connect to. If redis.deploy is true, this will be set automatically based on the release name. | -| redis.image.repository | string | `"bitnamilegacy/valkey"` | Overwrite default repository of helm chart to point to non-paid bitnami images. | +| redis.image | object | `{"pullPolicy":"IfNotPresent","registry":"docker.io","repository":"valkey/valkey","tag":"8.0"}` | ------------------------------------------------------------------------- | +| redis.metrics.enabled | bool | `false` | Enable the bundled Valkey Prometheus exporter sidecar. | | redis.port | int | `6379` | Redis port to connect to. | -| redis.primary.extraFlags | list | `["--maxmemory-policy noeviction"]` | Extra flags for the valkey deployment. Must include `--maxmemory-policy noeviction`. | +| redis.replica | object | `{"enabled":false,"persistence":{"accessModes":["ReadWriteOnce"],"size":"8Gi","storageClass":""},"replicas":0,"service":{"enabled":true,"port":6379,"type":"ClusterIP"}}` | `replicas` for a primary/replica topology — the Langfuse helpers always target the primary. | +| redis.resources | object | `{}` | Resource limits/requests for the Valkey pods. | | redis.sentinel.enabled | bool | `false` | Set to `true` to enable Redis Sentinel mode. Cannot be enabled simultaneously with cluster mode. When enabled, you must set `redis.deploy` to `false`. | | redis.sentinel.existingSecret | string | `""` | If you want to use an existing secret for the sentinel password, set the name of the secret here. (`redis.sentinel.password` will be ignored and picked up from this secret). | | redis.sentinel.existingSecretPasswordKey | string | `""` | The key in the existing secret that contains the sentinel password. | @@ -240,46 +265,51 @@ Open source LLM engineering platform - LLM observability, metrics, evaluations, | redis.sentinel.nodes | string | `""` | Comma-separated list of Redis Sentinel nodes in the format "host:port". Example: "sentinel-1:26379,sentinel-2:26379,sentinel-3:26379". Required when `redis.sentinel.enabled` is `true`. | | redis.sentinel.password | string | `""` | Password for Redis Sentinel authentication (optional). | | redis.sentinel.username | string | `""` | Username for Redis Sentinel authentication (optional). | -| redis.tls.caPath | string | `""` | Path to the CA certificate file for TLS verification | -| redis.tls.certPath | string | `""` | Path to the client certificate file for mutual TLS authentication | -| redis.tls.enabled | bool | `false` | Set to `true` to enable TLS/SSL encrypted connection to the Redis server | -| redis.tls.keyPath | string | `""` | Path to the client private key file for mutual TLS authentication | +| redis.service.port | int | `6379` | Port the primary Valkey Service exposes. | +| redis.tls.caPath | string | `""` | Path to the CA certificate file for TLS verification (mounted into Langfuse pods). | +| redis.tls.certPath | string | `""` | Path to the client certificate file for mutual TLS authentication. | +| redis.tls.enabled | bool | `false` | When `redis.deploy: true`, this also enables TLS termination on the Valkey sub-chart. | +| redis.tls.keyPath | string | `""` | Path to the client private key file for mutual TLS authentication. | +| s3 | object | `{"accessKeyId":{"secretKeyRef":{"key":"","name":""},"value":""},"allInOne":{"data":{"accessModes":["ReadWriteOnce"],"size":"50Gi","storageClass":"","type":"persistentVolumeClaim"},"enabled":true,"image":{"pullPolicy":"IfNotPresent","registry":"docker.io","repository":"chrislusf/seaweedfs","tag":"3.95"},"resources":{"limits":{"cpu":2,"memory":"2Gi"},"requests":{"cpu":"500m","memory":"1Gi"}},"s3":{"createBuckets":[{"name":"langfuse"}],"createBucketsHook":{"resources":{}},"enableAuth":true,"enabled":true,"existingConfigSecret":"langfuse-s3-auth","port":8333},"service":{"internalTrafficPolicy":"Cluster","type":"ClusterIP"}},"auth":{"existingSecret":"","rootPassword":"","rootPasswordSecretKey":"","rootUser":"langfuse","rootUserSecretKey":""},"batchExport":{"accessKeyId":{"secretKeyRef":{"key":"","name":""},"value":""},"bucket":"","enabled":true,"endpoint":"","forcePathStyle":null,"prefix":"","region":"","secretAccessKey":{"secretKeyRef":{"key":"","name":""},"value":""}},"bucket":"langfuse","concurrency":{"reads":50,"writes":50},"defaultBuckets":"langfuse","deploy":true,"endpoint":"","eventUpload":{"accessKeyId":{"secretKeyRef":{"key":"","name":""},"value":""},"bucket":"","endpoint":"","forcePathStyle":null,"prefix":"","region":"","secretAccessKey":{"secretKeyRef":{"key":"","name":""},"value":""}},"filer":{"enabled":false},"forcePathStyle":true,"gcs":{"credentials":{"secretKeyRef":{"key":"","name":""},"value":""}},"master":{"enabled":false},"mediaUpload":{"accessKeyId":{"secretKeyRef":{"key":"","name":""},"value":""},"bucket":"","downloadUrlExpirySeconds":3600,"enabled":true,"endpoint":"","forcePathStyle":null,"maxContentLength":1000000000,"prefix":"","region":"","secretAccessKey":{"secretKeyRef":{"key":"","name":""},"value":""}},"region":"auto","s3":{"enabled":false},"secretAccessKey":{"secretKeyRef":{"key":"","name":""},"value":""},"storageProvider":"s3","volume":{"enabled":false}}` | For production, point Langfuse at S3 / Azure Blob / GCS instead by setting `s3.deploy: false`. | | s3.accessKeyId | object | `{"secretKeyRef":{"key":"","name":""},"value":""}` | S3 accessKeyId to use for all uploads. Can be overridden per upload type. | -| s3.auth.existingSecret | string | `""` | If you want to use an existing secret for the root user password, set the name of the secret here. (`s3.auth.rootPassword` will be ignored and picked up from this secret). | -| s3.auth.rootPassword | string | `""` | Password for MinIO root user | -| s3.auth.rootPasswordSecretKey | string | `""` | Key where the Minio root user password is being stored inside the existing secret `s3.auth.existingSecret` | -| s3.auth.rootUser | string | `"minio"` | root username | -| s3.auth.rootUserSecretKey | string | `""` | Key where the Minio root user is being stored inside the existing secret `s3.auth.existingSecret` | +| s3.allInOne.data.type | string | `"persistentVolumeClaim"` | Persistence for the SeaweedFS data directory. | +| s3.allInOne.s3.createBuckets | list | `[{"name":"langfuse"}]` | Buckets to create on first start. The chart appends `s3.bucket` automatically. | +| s3.allInOne.s3.existingConfigSecret | string | `"langfuse-s3-auth"` | `fullnameOverride: langfuse` if your release name differs, or override here. | +| s3.auth.existingSecret | string | `""` | `rootUserSecretKey` (default: access-key) and `rootPasswordSecretKey` (default: secret-key). | +| s3.auth.rootPassword | string | `""` | Leave empty to have the chart generate one. | +| s3.auth.rootPasswordSecretKey | string | `""` | Key in the existing secret that contains the S3 secret access key. | +| s3.auth.rootUser | string | `"langfuse"` | Access key ID Langfuse uses to authenticate against the SeaweedFS S3 gateway. | +| s3.auth.rootUserSecretKey | string | `""` | Key in the existing secret that contains the S3 access key ID. | | s3.batchExport.accessKeyId | object | `{"secretKeyRef":{"key":"","name":""},"value":""}` | S3 accessKeyId to use for batch exports. | | s3.batchExport.bucket | string | `""` | S3 bucket to use for batch exports. | | s3.batchExport.enabled | bool | `true` | Enable batch export. | | s3.batchExport.endpoint | string | `""` | S3 endpoint to use for batch exports. | -| s3.batchExport.forcePathStyle | string | `nil` | Whether to force path style on requests. Required for MinIO. | +| s3.batchExport.forcePathStyle | string | `nil` | Whether to force path style on requests. Required for SeaweedFS / MinIO. | | s3.batchExport.prefix | string | `""` | Prefix to use for batch exports within the bucket. | | s3.batchExport.region | string | `""` | S3 region to use for batch exports. | | s3.batchExport.secretAccessKey | object | `{"secretKeyRef":{"key":"","name":""},"value":""}` | S3 secretAccessKey to use for batch exports. | -| s3.bucket | string | `""` | S3 bucket to use for all uploads. Can be overridden per upload type. | +| s3.bucket | string | `"langfuse"` | When `s3.deploy: true`, this bucket is auto-created on the SeaweedFS S3 gateway. | | s3.concurrency.reads | int | `50` | Maximum number of concurrent read operations to S3. Defaults to 50. | | s3.concurrency.writes | int | `50` | Maximum number of concurrent write operations to S3. Defaults to 50. | -| s3.defaultBuckets | string | `"langfuse"` | | -| s3.deploy | bool | `true` | Enable MinIO deployment (via Bitnami Helm Chart). If you want to use a custom BlobStorage, e.g. S3, set to false. | -| s3.endpoint | string | `""` | S3 endpoint to use for all uploads. Can be overridden per upload type. | +| s3.defaultBuckets | string | `"langfuse"` | when neither `s3.bucket` nor a per-upload `bucket` is set. | +| s3.deploy | bool | `true` | Set to false to use an external S3-compatible BlobStorage (S3 / GCS / Azure Blob / etc.). | +| s3.endpoint | string | `""` | When `s3.deploy: true`, this is set automatically to the SeaweedFS allInOne S3 Service. | | s3.eventUpload.accessKeyId | object | `{"secretKeyRef":{"key":"","name":""},"value":""}` | S3 accessKeyId to use for event uploads. | | s3.eventUpload.bucket | string | `""` | S3 bucket to use for event uploads. | | s3.eventUpload.endpoint | string | `""` | S3 endpoint to use for event uploads. | -| s3.eventUpload.forcePathStyle | string | `nil` | Whether to force path style on requests. Required for MinIO. | +| s3.eventUpload.forcePathStyle | string | `nil` | Whether to force path style on requests. Required for SeaweedFS / MinIO. | | s3.eventUpload.prefix | string | `""` | Prefix to use for event uploads within the bucket. | | s3.eventUpload.region | string | `""` | S3 region to use for event uploads. | | s3.eventUpload.secretAccessKey | object | `{"secretKeyRef":{"key":"","name":""},"value":""}` | S3 secretAccessKey to use for event uploads. | -| s3.forcePathStyle | bool | `true` | Whether to force path style on requests. Required for MinIO. Can be overridden per upload type. | +| s3.forcePathStyle | bool | `true` | Whether to force path style on requests. Required for SeaweedFS / MinIO. Can be overridden per upload type. | | s3.gcs.credentials | object | `{"secretKeyRef":{"key":"","name":""},"value":""}` | Example: Set value to the JSON service account key content, or use secretKeyRef to reference a secret | -| s3.image.repository | string | `"bitnamilegacy/minio"` | Overwrite default repository of helm chart to point to non-paid bitnami images. | +| s3.master | object | `{"enabled":false}` | ------------------------------------------------------------------------- | | s3.mediaUpload.accessKeyId | object | `{"secretKeyRef":{"key":"","name":""},"value":""}` | S3 accessKeyId to use for media uploads. | | s3.mediaUpload.bucket | string | `""` | S3 bucket to use for media uploads. | | s3.mediaUpload.downloadUrlExpirySeconds | int | `3600` | Expiry time for download URLs. Defaults to 1 hour. | | s3.mediaUpload.enabled | bool | `true` | Enable media uploads. | | s3.mediaUpload.endpoint | string | `""` | S3 endpoint to use for media uploads. | -| s3.mediaUpload.forcePathStyle | string | `nil` | Whether to force path style on requests. Required for MinIO. | +| s3.mediaUpload.forcePathStyle | string | `nil` | Whether to force path style on requests. Required for SeaweedFS / MinIO. | | s3.mediaUpload.maxContentLength | int | `1000000000` | Maximum content length for media uploads. Defaults to 1GB. | | s3.mediaUpload.prefix | string | `""` | Prefix to use for media uploads within the bucket. | | s3.mediaUpload.region | string | `""` | S3 region to use for media uploads. | diff --git a/charts/langfuse/templates/_helpers.tpl b/charts/langfuse/templates/_helpers.tpl index 3d38c353..1a81a0ef 100644 --- a/charts/langfuse/templates/_helpers.tpl +++ b/charts/langfuse/templates/_helpers.tpl @@ -79,7 +79,7 @@ Return Redis hostname {{- if .Values.redis.host }} {{- .Values.redis.host }} {{- else if .Values.redis.deploy }} -{{- printf "%s-%s-primary" (include "langfuse.fullname" .) (default "redis" .Values.redis.nameOverride) -}} +{{- printf "%s-%s" (include "langfuse.fullname" .) (default "redis" .Values.redis.nameOverride) -}} {{- end }} {{- end }} @@ -96,7 +96,7 @@ Return ClickHouse hostname (without protocol) {{- .Values.clickhouse.host -}} {{- end -}} {{- else if .Values.clickhouse.deploy }} -{{- printf "%s-clickhouse" (include "langfuse.fullname" .) -}} +{{- printf "%s-clickhouse-headless" (include "langfuse.fullname" .) -}} {{- end }} {{- end }} @@ -107,7 +107,7 @@ Return S3/MinIO endpoint -- if not set uses auto-discovery {{- if or .Values.s3.eventUpload.endpoint .Values.s3.endpoint }} {{- .Values.s3.eventUpload.endpoint | default .Values.s3.endpoint }} {{- else if .Values.s3.deploy }} -{{- printf "http://%s-%s:9000" (include "langfuse.fullname" .) (default "s3" .Values.s3.nameOverride) -}} +{{- printf "http://%s-%s-all-in-one:8333" (include "langfuse.fullname" .) (default "s3" .Values.s3.nameOverride) -}} {{- else }} {{- end }} {{- end }} @@ -201,6 +201,11 @@ Get value of a specific environment variable from additionalEnv if it exists secretKeyRef: name: {{ .Values.postgresql.auth.existingSecret }} key: {{ required "postgresql.auth.secretKeys.userPasswordKey is required when using an existing secret" .Values.postgresql.auth.secretKeys.userPasswordKey }} +{{- else if .Values.postgresql.deploy }} + valueFrom: + secretKeyRef: + name: {{ printf "%s-postgresql-auth" (include "langfuse.fullname" .) | quote }} + key: USERDB_PASSWORD {{- else }} value: {{ required "Using an existing secret or postgresql.auth.password is required" .Values.postgresql.auth.password | quote }} {{- end }} @@ -302,13 +307,18 @@ Get value of a specific environment variable from additionalEnv if it exists Compare with https://langfuse.com/self-hosting/configuration#environment-variables */}} {{- define "langfuse.redisEnv" -}} -{{- if or .Values.redis.auth.existingSecret .Values.redis.auth.password }} +{{- if or .Values.redis.auth.existingSecret .Values.redis.auth.password .Values.redis.deploy }} - name: REDIS_PASSWORD {{- if .Values.redis.auth.existingSecret }} valueFrom: secretKeyRef: name: {{ .Values.redis.auth.existingSecret }} key: {{ required "redis.auth.existingSecretPasswordKey is required when using an existing secret" .Values.redis.auth.existingSecretPasswordKey }} +{{- else if .Values.redis.deploy }} + valueFrom: + secretKeyRef: + name: {{ printf "%s-redis-auth" (include "langfuse.fullname" .) | quote }} + key: {{ .Values.redis.auth.username | quote }} {{- else }} value: {{ required "Using an existing secret or redis.auth.password is required" .Values.redis.auth.password | quote }} {{- end }} @@ -389,7 +399,7 @@ Get value of a specific environment variable from additionalEnv if it exists - name: REDIS_TLS_ENABLED value: {{ .Values.redis.tls.enabled | quote }} - name: REDIS_CONNECTION_STRING -{{- $hasPassword := or .Values.redis.auth.existingSecret .Values.redis.auth.password }} +{{- $hasPassword := or .Values.redis.auth.existingSecret .Values.redis.auth.password .Values.redis.deploy }} {{- $hasUsername := .Values.redis.auth.username }} {{- $authPart := "" }} {{- if and $hasUsername $hasPassword }} @@ -484,17 +494,15 @@ Return ClickHouse protocol (http or https) {{- else if .Values.clickhouse.auth.password }} value: {{ .Values.clickhouse.auth.password | quote }} {{- else if .Values.clickhouse.deploy }} - value: {{ required "Configuring an existing secret or clickhouse.auth.password is required" .Values.clickhouse.auth.password | quote }} + valueFrom: + secretKeyRef: + name: {{ printf "%s-clickhouse-auth" (include "langfuse.fullname" .) | quote }} + key: "password" {{- end }} {{- end }} -{{- if not .Values.clickhouse.clusterEnabled }} -{{/* User explicitly disabled cluster mode */}} -- name: CLICKHOUSE_CLUSTER_ENABLED - value: "false" -{{- else if and .Values.clickhouse.deploy ($.Values.clickhouse.replicaCount | int | eq 1) }} -{{/* Cluster enabled by default, but deploying single-replica ClickHouse */}} +{{- if or .Values.clickhouse.host .Values.clickhouse.deploy }} - name: CLICKHOUSE_CLUSTER_ENABLED - value: "false" + value: {{ .Values.clickhouse.cluster.enabled | quote }} {{- end }} {{- if or (hasKey .Values.clickhouse.migration "autoMigrate") .Values.clickhouse.deploy }} - name: LANGFUSE_AUTO_CLICKHOUSE_MIGRATION_DISABLED @@ -565,7 +573,10 @@ Return ClickHouse protocol (http or https) name: {{ .Values.s3.auth.existingSecret }} key: {{ .Values.s3.auth.rootUserSecretKey }} {{- else }} - value: {{ .Values.s3.auth.rootUser | quote }} + valueFrom: + secretKeyRef: + name: {{ printf "%s-s3-auth" (include "langfuse.fullname" .) | quote }} + key: accessKey {{- end }} {{- end }} {{- end }} @@ -581,7 +592,10 @@ Return ClickHouse protocol (http or https) name: {{ .Values.s3.auth.existingSecret }} key: {{ .Values.s3.auth.rootPasswordSecretKey }} {{- else }} - value: {{ .Values.s3.auth.rootPassword | quote }} + valueFrom: + secretKeyRef: + name: {{ printf "%s-s3-auth" (include "langfuse.fullname" .) | quote }} + key: secretKey {{- end }} {{- end }} {{- end }} @@ -622,7 +636,10 @@ Return ClickHouse protocol (http or https) name: {{ .Values.s3.auth.existingSecret }} key: {{ .Values.s3.auth.rootUserSecretKey }} {{- else }} - value: {{ .Values.s3.auth.rootUser | quote }} + valueFrom: + secretKeyRef: + name: {{ printf "%s-s3-auth" (include "langfuse.fullname" .) | quote }} + key: accessKey {{- end }} {{- end }} {{- end }} @@ -638,7 +655,10 @@ Return ClickHouse protocol (http or https) name: {{ .Values.s3.auth.existingSecret }} key: {{ .Values.s3.auth.rootPasswordSecretKey }} {{- else }} - value: {{ .Values.s3.auth.rootPassword | quote }} + valueFrom: + secretKeyRef: + name: {{ printf "%s-s3-auth" (include "langfuse.fullname" .) | quote }} + key: secretKey {{- end }} {{- end }} {{- end }} @@ -677,7 +697,10 @@ Return ClickHouse protocol (http or https) name: {{ .Values.s3.auth.existingSecret }} key: {{ .Values.s3.auth.rootUserSecretKey }} {{- else }} - value: {{ .Values.s3.auth.rootUser | quote }} + valueFrom: + secretKeyRef: + name: {{ printf "%s-s3-auth" (include "langfuse.fullname" .) | quote }} + key: accessKey {{- end }} {{- end }} {{- end }} @@ -693,7 +716,10 @@ Return ClickHouse protocol (http or https) name: {{ .Values.s3.auth.existingSecret }} key: {{ .Values.s3.auth.rootPasswordSecretKey }} {{- else }} - value: {{ .Values.s3.auth.rootPassword | quote }} + valueFrom: + secretKeyRef: + name: {{ printf "%s-s3-auth" (include "langfuse.fullname" .) | quote }} + key: secretKey {{- end }} {{- end }} {{- end }} diff --git a/charts/langfuse/templates/clickhouse/cluster.yaml b/charts/langfuse/templates/clickhouse/cluster.yaml new file mode 100644 index 00000000..4ef33aef --- /dev/null +++ b/charts/langfuse/templates/clickhouse/cluster.yaml @@ -0,0 +1,61 @@ +{{- if .Values.clickhouse.deploy -}} +apiVersion: clickhouse.com/v1alpha1 +kind: ClickHouseCluster +metadata: + name: {{ include "langfuse.fullname" . }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "langfuse.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.clickhouse.cluster.replicas }} + shards: 1 + {{- if .Values.clickhouse.keeper.enabled }} + keeperClusterRef: + name: {{ include "langfuse.fullname" . }} + {{- end }} + dataVolumeClaimSpec: + accessModes: + {{- toYaml .Values.clickhouse.cluster.storage.accessModes | nindent 6 }} + {{- with .Values.clickhouse.cluster.storage.className }} + storageClassName: {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.clickhouse.cluster.storage.size | quote }} + containerTemplate: + image: + repository: {{ .Values.clickhouse.cluster.image.repository | quote }} + tag: {{ .Values.clickhouse.cluster.image.tag | quote }} + {{- with .Values.clickhouse.cluster.resources }} + resources: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- if or .Values.clickhouse.cluster.nodeSelector .Values.clickhouse.cluster.tolerations .Values.clickhouse.cluster.affinity }} + podTemplate: + {{- with .Values.clickhouse.cluster.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.clickhouse.cluster.tolerations }} + tolerations: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.clickhouse.cluster.affinity }} + affinity: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- end }} + settings: + defaultUserPassword: + secret: + name: {{ .Values.clickhouse.auth.existingSecret | default (printf "%s-clickhouse-auth" (include "langfuse.fullname" .)) | quote }} + key: {{ .Values.clickhouse.auth.existingSecretKey | default "password" | quote }} + {{- with .Values.clickhouse.cluster.settings }} + extraConfig: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.clickhouse.cluster.profileSettings }} + extraUsersConfig: + {{- toYaml . | nindent 6 }} + {{- end }} +{{- end }} diff --git a/charts/langfuse/templates/clickhouse/keeper.yaml b/charts/langfuse/templates/clickhouse/keeper.yaml new file mode 100644 index 00000000..d4c19039 --- /dev/null +++ b/charts/langfuse/templates/clickhouse/keeper.yaml @@ -0,0 +1,43 @@ +{{- if and .Values.clickhouse.deploy .Values.clickhouse.keeper.enabled -}} +apiVersion: clickhouse.com/v1alpha1 +kind: KeeperCluster +metadata: + name: {{ include "langfuse.fullname" . }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "langfuse.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.clickhouse.keeper.replicas }} + dataVolumeClaimSpec: + accessModes: + {{- toYaml .Values.clickhouse.keeper.storage.accessModes | nindent 6 }} + {{- with .Values.clickhouse.keeper.storage.className }} + storageClassName: {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.clickhouse.keeper.storage.size | quote }} + containerTemplate: + image: + repository: {{ .Values.clickhouse.keeper.image.repository | quote }} + tag: {{ .Values.clickhouse.keeper.image.tag | quote }} + {{- with .Values.clickhouse.keeper.resources }} + resources: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- if or .Values.clickhouse.keeper.nodeSelector .Values.clickhouse.keeper.tolerations .Values.clickhouse.keeper.affinity }} + podTemplate: + {{- with .Values.clickhouse.keeper.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.clickhouse.keeper.tolerations }} + tolerations: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.clickhouse.keeper.affinity }} + affinity: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/langfuse/templates/clickhouse/secret.yaml b/charts/langfuse/templates/clickhouse/secret.yaml new file mode 100644 index 00000000..1b72e3f0 --- /dev/null +++ b/charts/langfuse/templates/clickhouse/secret.yaml @@ -0,0 +1,38 @@ +{{/* +Chart-managed ClickHouse credential Secret. + +Used by: + - the bundled clickhouse-operator-rendered ClickHouseCluster CR via + `settings.defaultUserPassword.secret` (see clickhouse/cluster.yaml). The + operator hashes this password into the user XML. + - the Langfuse helper `langfuse.clickhouseEnv` (CLICKHOUSE_PASSWORD env var) — + the helper points the env var at this Secret's `password` key. + +Keys written: + password — plaintext password for the ClickHouse user + (auto-generated if `clickhouse.auth.password` is unset) + +Skipped when the user supplies their own `clickhouse.auth.existingSecret`. +*/}} +{{- if and .Values.clickhouse.deploy (not .Values.clickhouse.auth.existingSecret) -}} +{{- $name := printf "%s-clickhouse-auth" (include "langfuse.fullname" .) -}} +{{- $password := .Values.clickhouse.auth.password -}} +{{- if not $password -}} + {{- $existing := lookup "v1" "Secret" .Release.Namespace $name -}} + {{- if and $existing (index $existing.data "password") -}} + {{- $password = index $existing.data "password" | b64dec -}} + {{- else -}} + {{- $password = randAlphaNum 40 -}} + {{- end -}} +{{- end -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $name }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "langfuse.labels" . | nindent 4 }} +type: Opaque +data: + password: {{ $password | b64enc | quote }} +{{- end }} diff --git a/charts/langfuse/templates/postgresql-secret.yaml b/charts/langfuse/templates/postgresql-secret.yaml index 50f70c9f..72299b1c 100644 --- a/charts/langfuse/templates/postgresql-secret.yaml +++ b/charts/langfuse/templates/postgresql-secret.yaml @@ -1,4 +1,55 @@ +{{/* +Chart-managed PostgreSQL credential Secret. + +Used by: + - the bundled groundhog2k/postgres sub-chart (when deploy=true) via + `userDatabase.existingSecret` and `settings.existingSecret` defaults that + point at this Secret in the parent values.yaml + - the Langfuse helper `langfuse.databaseEnv` (DATABASE_PASSWORD env var) via + `postgresql.auth.existingSecret` default + +Keys written: + POSTGRES_USER, POSTGRES_PASSWORD — superuser (groundhog2k) + POSTGRES_DB, USERDB_USER, USERDB_PASSWORD — userDatabase (groundhog2k) + postgres-direct-url — Langfuse DIRECT_URL (optional) + postgres-shadow-database-url — Langfuse SHADOW_DATABASE_URL (optional) + +Skipped when the user supplies their own `postgresql.auth.existingSecret`. +*/}} +{{- if and .Values.postgresql.deploy (not .Values.postgresql.auth.existingSecret) -}} +{{- $name := printf "%s-postgresql-auth" (include "langfuse.fullname" .) -}} +{{- $password := .Values.postgresql.auth.password -}} +{{- if not $password -}} + {{- $existing := lookup "v1" "Secret" .Release.Namespace $name -}} + {{- if and $existing (index $existing.data "POSTGRES_PASSWORD") -}} + {{- $password = index $existing.data "POSTGRES_PASSWORD" | b64dec -}} + {{- else -}} + {{- $password = randAlphaNum 40 -}} + {{- end -}} +{{- end -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $name }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "langfuse.labels" . | nindent 4 }} +type: Opaque +data: + POSTGRES_USER: {{ "postgres" | b64enc | quote }} + POSTGRES_PASSWORD: {{ $password | b64enc | quote }} + POSTGRES_DB: {{ required "postgresql.auth.database is required when deploy=true" .Values.postgresql.auth.database | toString | b64enc | quote }} + USERDB_USER: {{ required "postgresql.auth.username is required when deploy=true" .Values.postgresql.auth.username | toString | b64enc | quote }} + USERDB_PASSWORD: {{ $password | b64enc | quote }} + {{- if .Values.postgresql.directUrl }} + postgres-direct-url: {{ .Values.postgresql.directUrl | toString | b64enc | quote }} + {{- end }} + {{- if .Values.postgresql.shadowDatabaseUrl }} + postgres-shadow-database-url: {{ .Values.postgresql.shadowDatabaseUrl | toString | b64enc | quote }} + {{- end }} +{{- end }} {{- if and (not .Values.postgresql.deploy) (.Values.postgresql.auth.password) -}} +--- apiVersion: v1 kind: Secret metadata: @@ -15,4 +66,4 @@ data: {{- if .Values.postgresql.shadowDatabaseUrl }} postgres-shadow-database-url: {{ .Values.postgresql.shadowDatabaseUrl | toString | b64enc | quote }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/charts/langfuse/templates/redis-secret.yaml b/charts/langfuse/templates/redis-secret.yaml new file mode 100644 index 00000000..6098b81e --- /dev/null +++ b/charts/langfuse/templates/redis-secret.yaml @@ -0,0 +1,41 @@ +{{/* +Chart-managed Valkey/Redis credential Secret. + +Used by: + - the bundled valkey-io/valkey sub-chart (when deploy=true) via + `redis.auth.usersExistingSecret` default in the parent values.yaml. Valkey + reads `` keys from this Secret (one per ACL user) and hashes them + into the `aclfile`. + - the Langfuse helper `langfuse.redisEnv` (REDIS_PASSWORD env var) — the helper + points the env var at this Secret's `` key. + +Keys written: + — password for the ACL user Langfuse authenticates as + (key name == username, matching valkey-helm's default + passwordKey resolution). + +Skipped when the user supplies their own `redis.auth.existingSecret`. +*/}} +{{- if and .Values.redis.deploy (not .Values.redis.auth.existingSecret) -}} +{{- $name := printf "%s-redis-auth" (include "langfuse.fullname" .) -}} +{{- $username := required "redis.auth.username is required when redis.deploy=true" .Values.redis.auth.username -}} +{{- $password := .Values.redis.auth.password -}} +{{- if not $password -}} + {{- $existing := lookup "v1" "Secret" .Release.Namespace $name -}} + {{- if and $existing (index $existing.data $username) -}} + {{- $password = index $existing.data $username | b64dec -}} + {{- else -}} + {{- $password = randAlphaNum 40 -}} + {{- end -}} +{{- end -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $name }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "langfuse.labels" . | nindent 4 }} +type: Opaque +data: + {{ $username }}: {{ $password | b64enc | quote }} +{{- end }} diff --git a/charts/langfuse/templates/s3/secret.yaml b/charts/langfuse/templates/s3/secret.yaml new file mode 100644 index 00000000..12a2560e --- /dev/null +++ b/charts/langfuse/templates/s3/secret.yaml @@ -0,0 +1,47 @@ +{{/* +Chart-managed SeaweedFS S3 IAM credentials Secret. + +Used by: + - the bundled seaweedfs sub-chart (when deploy=true) via `s3.allInOne.s3.existingConfigSecret` + that points at this Secret in the parent values.yaml. SeaweedFS reads the + `seaweedfs_s3_config` JSON key. + - the Langfuse helper `langfuse.s3Env` (LANGFUSE_S3_*_ACCESS_KEY_ID / *_SECRET_ACCESS_KEY) — + the helper points env vars at this Secret's `accessKey` / `secretKey` keys. + +Keys written: + accessKey — S3 access key ID Langfuse uses + secretKey — S3 secret access key Langfuse uses (auto-generated if blank) + seaweedfs_s3_config — JSON IAM config consumed by the SeaweedFS allInOne pod + +Skipped when the user supplies their own `s3.auth.existingSecret`. +*/}} +{{- if and .Values.s3.deploy (not .Values.s3.auth.existingSecret) -}} +{{- $name := printf "%s-s3-auth" (include "langfuse.fullname" .) -}} +{{- $rootUser := required "s3.auth.rootUser is required when s3.deploy=true" .Values.s3.auth.rootUser -}} +{{- $rootPassword := .Values.s3.auth.rootPassword -}} +{{- if not $rootPassword -}} + {{- $existing := lookup "v1" "Secret" .Release.Namespace $name -}} + {{- if and $existing (index $existing.data "secretKey") -}} + {{- $rootPassword = index $existing.data "secretKey" | b64dec -}} + {{- else -}} + {{- $rootPassword = randAlphaNum 40 -}} + {{- end -}} +{{- end -}} +{{- $iamConfig := dict "identities" (list (dict + "name" $rootUser + "credentials" (list (dict "accessKey" $rootUser "secretKey" $rootPassword)) + "actions" (list "Admin" "Read" "Write" "List" "Tagging") + )) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $name }} + namespace: {{ $.Release.Namespace }} + labels: + {{- include "langfuse.labels" . | nindent 4 }} +type: Opaque +data: + accessKey: {{ $rootUser | b64enc | quote }} + secretKey: {{ $rootPassword | b64enc | quote }} + seaweedfs_s3_config: {{ $iamConfig | toJson | b64enc | quote }} +{{- end }} diff --git a/charts/langfuse/templates/validations.yaml b/charts/langfuse/templates/validations.yaml index e24ffed1..821e251a 100644 --- a/charts/langfuse/templates/validations.yaml +++ b/charts/langfuse/templates/validations.yaml @@ -16,17 +16,30 @@ {{- end -}} # Validate ClickHouse settings -{{- if ne ($.Values.clickhouse.shards | int) 1 -}} - {{- fail "Langfuse does not support ClickHouse with multiple shards. Set clickhouse.shards to 1 to continue." -}} -{{- end -}} - {{- if and (not $.Values.clickhouse.deploy) (not $.Values.clickhouse.host) (not (include "langfuse.getEnvVar" (dict "env" $.Values.langfuse.additionalEnv "name" "CLICKHOUSE_URL"))) -}} {{- fail "ClickHouse host must be configured when using an external ClickHouse instance. Set clickhouse.host to continue." -}} {{- end -}} +{{- if and $.Values.clickhouse.deploy (lt (int $.Values.clickhouse.cluster.replicas) 1) -}} + {{- fail "clickhouse.cluster.replicas must be >= 1 when clickhouse.deploy is true." -}} +{{- end -}} +{{- if and $.Values.clickhouse.deploy (gt (int $.Values.clickhouse.cluster.replicas) 1) (not $.Values.clickhouse.keeper.enabled) -}} + {{- fail "clickhouse.keeper.enabled must be true when clickhouse.cluster.replicas > 1. The upstream ClickHouse operator requires Keeper for replicated mode." -}} +{{- end -}} # Validate Redis settings -{{- if and $.Values.redis.deploy (not (has "--maxmemory-policy noeviction" $.Values.redis.primary.extraFlags)) -}} - {{- fail "Langfuse requires valkey to be deployed with the `--maxmemory-policy noeviction` flag. Set redis.primary.extraFlags to include this flag to continue." -}} +{{- $redisExtraFlags := default (list) $.Values.redis.extraFlags -}} +{{- $redisHasNoEviction := false -}} +{{- range $i, $f := $redisExtraFlags -}} + {{- if eq $f "noeviction" -}} + {{- if gt $i 0 -}} + {{- if eq (index $redisExtraFlags (sub $i 1)) "--maxmemory-policy" -}} + {{- $redisHasNoEviction = true -}} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end -}} +{{- if and $.Values.redis.deploy (not $redisHasNoEviction) -}} + {{- fail "Langfuse requires valkey to be deployed with `--maxmemory-policy noeviction`. Set redis.extraFlags to include `[\"--maxmemory-policy\", \"noeviction\"]` to continue." -}} {{- end -}} {{- if and (not $.Values.redis.deploy) (not $.Values.redis.host) (not $.Values.redis.cluster.enabled) (not $.Values.redis.sentinel.enabled) (not (include "langfuse.getEnvVar" (dict "env" $.Values.langfuse.additionalEnv "name" "REDIS_CONNECTION_STRING"))) -}} @@ -79,7 +92,7 @@ # Event Upload {{- if and $.Values.s3.deploy (not (or $.Values.s3.forcePathStyle $.Values.s3.eventUpload.forcePathStyle)) -}} - {{- fail "MinIO requires s3 to be deployed with a forcePathStyle. Set s3.forcePathStyle or s3.eventUpload.forcePathStyle to continue." -}} + {{- fail "Bundled SeaweedFS S3 gateway requires path-style addressing. Set s3.forcePathStyle or s3.eventUpload.forcePathStyle to true to continue." -}} {{- end -}} {{- if and (not $.Values.s3.deploy) (not (or $.Values.s3.bucket $.Values.s3.eventUpload.bucket)) -}} @@ -89,7 +102,7 @@ # Batch Export {{- if $.Values.s3.batchExport.enabled -}} {{- if and $.Values.s3.deploy (not (or $.Values.s3.forcePathStyle $.Values.s3.batchExport.forcePathStyle)) -}} - {{- fail "MinIO requires s3 to be deployed with a forcePathStyle. Set s3.forcePathStyle or s3.batchExport.forcePathStyle to continue." -}} + {{- fail "Bundled SeaweedFS S3 gateway requires path-style addressing. Set s3.forcePathStyle or s3.batchExport.forcePathStyle to true to continue." -}} {{- end -}} {{- if and (not $.Values.s3.deploy) (not (or $.Values.s3.bucket $.Values.s3.batchExport.bucket)) -}} @@ -100,7 +113,7 @@ # Media Upload {{- if $.Values.s3.mediaUpload.enabled -}} {{- if and $.Values.s3.deploy (not (or $.Values.s3.forcePathStyle $.Values.s3.mediaUpload.forcePathStyle)) -}} - {{- fail "MinIO requires s3 to be deployed with a forcePathStyle. Set s3.forcePathStyle or s3.mediaUpload.forcePathStyle to continue." -}} + {{- fail "Bundled SeaweedFS S3 gateway requires path-style addressing. Set s3.forcePathStyle or s3.mediaUpload.forcePathStyle to true to continue." -}} {{- end -}} {{- if and (not $.Values.s3.deploy) (not (or $.Values.s3.bucket $.Values.s3.mediaUpload.bucket)) -}} diff --git a/charts/langfuse/tests/clickhouse-cluster_test.yaml b/charts/langfuse/tests/clickhouse-cluster_test.yaml index a2f89bc6..e3d68e58 100644 --- a/charts/langfuse/tests/clickhouse-cluster_test.yaml +++ b/charts/langfuse/tests/clickhouse-cluster_test.yaml @@ -3,24 +3,22 @@ templates: - web/deployment.yaml - worker/deployment.yaml tests: - - it: should set CLICKHOUSE_CLUSTER_ENABLED to false when clusterEnabled is explicitly false with external ClickHouse + - it: should set CLICKHOUSE_CLUSTER_ENABLED to false when cluster.enabled is explicitly false with external ClickHouse values: - ../values.lint.yaml set: clickhouse.deploy: false - clickhouse.clusterEnabled: false + clickhouse.cluster.enabled: false clickhouse.host: "my-clickhouse-cloud.clickhouse.com" clickhouse.auth.username: "default" clickhouse.auth.password: "secretPassword" asserts: - # Web deployment should have CLICKHOUSE_CLUSTER_ENABLED=false - contains: path: spec.template.spec.containers[0].env content: name: CLICKHOUSE_CLUSTER_ENABLED value: "false" template: web/deployment.yaml - # Worker deployment should have CLICKHOUSE_CLUSTER_ENABLED=false - contains: path: spec.template.spec.containers[0].env content: @@ -28,111 +26,85 @@ tests: value: "false" template: worker/deployment.yaml - - it: should NOT set CLICKHOUSE_CLUSTER_ENABLED when clusterEnabled is true (default) with external ClickHouse + - it: should set CLICKHOUSE_CLUSTER_ENABLED to true when cluster.enabled is true (default) with external ClickHouse values: - ../values.lint.yaml set: clickhouse.deploy: false - clickhouse.clusterEnabled: true clickhouse.host: "my-clickhouse-cluster.clickhouse.com" clickhouse.auth.username: "default" clickhouse.auth.password: "secretPassword" asserts: - # Web deployment should NOT have CLICKHOUSE_CLUSTER_ENABLED env var - - notContains: + - contains: path: spec.template.spec.containers[0].env content: name: CLICKHOUSE_CLUSTER_ENABLED + value: "true" template: web/deployment.yaml - # Worker deployment should NOT have CLICKHOUSE_CLUSTER_ENABLED env var - - notContains: + - contains: path: spec.template.spec.containers[0].env content: name: CLICKHOUSE_CLUSTER_ENABLED + value: "true" template: worker/deployment.yaml - - it: should set CLICKHOUSE_CLUSTER_ENABLED to false when deploying single replica ClickHouse + - it: should set CLICKHOUSE_CLUSTER_ENABLED to true when deploying single-replica ClickHouse (operator-managed cluster) values: - ../values.lint.yaml set: clickhouse.deploy: true - clickhouse.replicaCount: 1 + clickhouse.cluster.replicas: 1 asserts: - # Web deployment should have CLICKHOUSE_CLUSTER_ENABLED=false - contains: path: spec.template.spec.containers[0].env content: name: CLICKHOUSE_CLUSTER_ENABLED - value: "false" + value: "true" template: web/deployment.yaml - # Worker deployment should have CLICKHOUSE_CLUSTER_ENABLED=false - contains: path: spec.template.spec.containers[0].env content: name: CLICKHOUSE_CLUSTER_ENABLED - value: "false" + value: "true" template: worker/deployment.yaml - - it: should NOT set CLICKHOUSE_CLUSTER_ENABLED when deploying multi-replica ClickHouse cluster + - it: should set CLICKHOUSE_CLUSTER_ENABLED to true when deploying multi-replica ClickHouse cluster values: - ../values.lint.yaml set: clickhouse.deploy: true - clickhouse.replicaCount: 3 + clickhouse.cluster.replicas: 3 asserts: - # Web deployment should NOT have CLICKHOUSE_CLUSTER_ENABLED env var - - notContains: + - contains: path: spec.template.spec.containers[0].env content: name: CLICKHOUSE_CLUSTER_ENABLED + value: "true" template: web/deployment.yaml - # Worker deployment should NOT have CLICKHOUSE_CLUSTER_ENABLED env var - - notContains: + - contains: path: spec.template.spec.containers[0].env content: name: CLICKHOUSE_CLUSTER_ENABLED + value: "true" template: worker/deployment.yaml - - it: should set CLICKHOUSE_CLUSTER_ENABLED to false when clusterEnabled is false even with multi-replica deployment + - it: should set CLICKHOUSE_CLUSTER_ENABLED to false when cluster.enabled is false even with multi-replica deployment values: - ../values.lint.yaml set: clickhouse.deploy: true - clickhouse.replicaCount: 3 - clickhouse.clusterEnabled: false + clickhouse.cluster.replicas: 3 + clickhouse.cluster.enabled: false asserts: - # Web deployment should have CLICKHOUSE_CLUSTER_ENABLED=false (override behavior) - contains: path: spec.template.spec.containers[0].env content: name: CLICKHOUSE_CLUSTER_ENABLED value: "false" template: web/deployment.yaml - # Worker deployment should have CLICKHOUSE_CLUSTER_ENABLED=false (override behavior) - contains: path: spec.template.spec.containers[0].env content: name: CLICKHOUSE_CLUSTER_ENABLED value: "false" template: worker/deployment.yaml - - - it: should respect default clusterEnabled=true with multi-replica deployment - values: - - ../values.lint.yaml - set: - clickhouse.deploy: true - clickhouse.replicaCount: 3 - # clusterEnabled defaults to true in values.yaml - asserts: - # Web deployment should NOT have CLICKHOUSE_CLUSTER_ENABLED env var (cluster mode enabled) - - notContains: - path: spec.template.spec.containers[0].env - content: - name: CLICKHOUSE_CLUSTER_ENABLED - template: web/deployment.yaml - # Worker deployment should NOT have CLICKHOUSE_CLUSTER_ENABLED env var (cluster mode enabled) - - notContains: - path: spec.template.spec.containers[0].env - content: - name: CLICKHOUSE_CLUSTER_ENABLED - template: worker/deployment.yaml diff --git a/charts/langfuse/tests/minimal-installation_test.yaml b/charts/langfuse/tests/minimal-installation_test.yaml index 4d6e777d..a695e7b7 100644 --- a/charts/langfuse/tests/minimal-installation_test.yaml +++ b/charts/langfuse/tests/minimal-installation_test.yaml @@ -45,7 +45,7 @@ tests: values: - ../../../examples/minimal-installation/values.yaml asserts: - # Test that web deployment uses correct secret references + # Langfuse application secrets come from the user-provided `langfuse` Secret - contains: path: spec.template.spec.containers[0].env content: @@ -64,20 +64,66 @@ tests: name: langfuse key: encryption-key template: web/deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: NEXTAUTH_SECRET + valueFrom: + secretKeyRef: + name: langfuse + key: nextauth-secret + template: web/deployment.yaml - it: should configure database connections correctly values: - ../../../examples/minimal-installation/values.yaml asserts: - # Test PostgreSQL connection configuration + # PostgreSQL password is sourced from the chart-managed `-postgresql-auth` Secret - contains: path: spec.template.spec.containers[0].env content: name: DATABASE_PASSWORD valueFrom: secretKeyRef: - name: langfuse - key: postgresql-password + name: "RELEASE-NAME-langfuse-postgresql-auth" + key: USERDB_PASSWORD + template: web/deployment.yaml + # ClickHouse password is sourced from the chart-managed `-clickhouse-auth` Secret + - contains: + path: spec.template.spec.containers[0].env + content: + name: CLICKHOUSE_PASSWORD + valueFrom: + secretKeyRef: + name: "RELEASE-NAME-langfuse-clickhouse-auth" + key: password + template: web/deployment.yaml + # Valkey/Redis password is sourced from the chart-managed `-redis-auth` Secret + - contains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: "RELEASE-NAME-langfuse-redis-auth" + key: default + template: web/deployment.yaml + # SeaweedFS S3 credentials are sourced from the chart-managed `-s3-auth` Secret + - contains: + path: spec.template.spec.containers[0].env + content: + name: LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: "RELEASE-NAME-langfuse-s3-auth" + key: accessKey + template: web/deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: "RELEASE-NAME-langfuse-s3-auth" + key: secretKey template: web/deployment.yaml - - diff --git a/charts/langfuse/tests/s3-media-upload-validation_test.yaml b/charts/langfuse/tests/s3-media-upload-validation_test.yaml index 1d7eaa18..2d928278 100644 --- a/charts/langfuse/tests/s3-media-upload-validation_test.yaml +++ b/charts/langfuse/tests/s3-media-upload-validation_test.yaml @@ -14,8 +14,8 @@ tests: - hasDocuments: count: 0 - # Test that validation fails when MinIO is deployed without forcePathStyle - - it: should fail when MinIO deployed without forcePathStyle and media upload enabled + # Test that validation fails when bundled SeaweedFS is deployed without forcePathStyle + - it: should fail when bundled S3 deployed without forcePathStyle and media upload enabled values: - ../values.lint.yaml set: @@ -27,7 +27,7 @@ tests: s3.eventUpload.forcePathStyle: true asserts: - failedTemplate: - errorMessage: "MinIO requires s3 to be deployed with a forcePathStyle. Set s3.forcePathStyle or s3.mediaUpload.forcePathStyle to continue." + errorMessage: "Bundled SeaweedFS S3 gateway requires path-style addressing. Set s3.forcePathStyle or s3.mediaUpload.forcePathStyle to true to continue." # Test that validation passes when MinIO is deployed with global forcePathStyle - it: should pass when MinIO deployed with global forcePathStyle and media upload enabled diff --git a/charts/langfuse/values.yaml b/charts/langfuse/values.yaml index 73fb0f47..8d912087 100644 --- a/charts/langfuse/values.yaml +++ b/charts/langfuse/values.yaml @@ -1,10 +1,5 @@ # Langfuse Helm Chart Configuration -global: - security: - # -- Allow insecure images to use bitnami legacy repository. Can be set to false if secure images are being used (Paid). - allowInsecureImages: true - # -- Override the name for the selector labels, defaults to the chart name nameOverride: "" @@ -439,8 +434,11 @@ langfuse: key: "" # PostgreSQL Configuration +# -- Deploys a single-instance PostgreSQL via the groundhog2k/postgres sub-chart (image `docker.io/postgres`). +# -- For HA / managed Postgres, set `postgresql.deploy: false` and configure `host` + auth fields below. postgresql: - # -- Enable PostgreSQL deployment (via Bitnami Helm Chart). If you want to use an external Postgres server (or a managed one), set this to false + # -- Enable PostgreSQL deployment via the bundled groundhog2k/postgres sub-chart. + # -- Set to false to use an external (managed) Postgres server. deploy: true # -- PostgreSQL host to connect to. If postgresql.deploy is true, this will be set automatically based on the release name. @@ -456,25 +454,27 @@ postgresql: # -- If your database user lacks the CREATE DATABASE permission, you must create a shadow database and configure the "SHADOW_DATABASE_URL". This is often the case if you use a Cloud database. Refer to the Prisma docs for detailed instructions. shadowDatabaseUrl: "" - image: - # -- Overwrite default repository of helm chart to point to non-paid bitnami images. - repository: bitnamilegacy/postgresql - # image: docker.io/bitnami/postgresql:17.3.0-debian-12-r1 - - # Authentication configuration + # Authentication configuration (Langfuse-facing — these values are read by the chart's helpers + # to build DATABASE_USERNAME / DATABASE_PASSWORD / DATABASE_NAME for the langfuse pods). auth: - # -- Username to use to connect to the postgres database deployed with Langfuse. In case `postgresql.deploy` is set to `true`, the user will be created automatically. - username: postgres - # -- Password to use to connect to the postgres database deployed with Langfuse. In case `postgresql.deploy` is set to `true`, the password will be set automatically. + # -- Username Langfuse uses to connect to PostgreSQL. When `postgresql.deploy: true`, the + # -- groundhog2k sub-chart creates this user automatically (see `userDatabase` below). + username: langfuse + # -- Password Langfuse uses to connect to PostgreSQL. When `postgresql.deploy: true`, + # -- this password is propagated to the sub-chart via this chart's auto-generated Secret. + # -- Leave empty to have the chart generate one and store it in a release-managed Secret. password: "" # -- If you want to use an existing secret for the postgres password, set the name of the secret here. (`postgresql.auth.password` will be ignored and picked up from this secret). existingSecret: "" - # -- The keys in the existing secret that contain the passwords. If using the default `postgres` user, both `userPasswordKey` and `adminPasswordKey` must be provided. + # -- The keys in the existing secret that contain the passwords. + # -- `userPasswordKey` is the password used by Langfuse; `adminPasswordKey` is only required + # -- when `postgresql.deploy: true` (used as the Postgres superuser password by the sub-chart). secretKeys: userPasswordKey: password - adminPasswordKey: password - # -- Database name to use for Langfuse. - database: postgres_langfuse + adminPasswordKey: postgres-password + # -- Database name to use for Langfuse. When `postgresql.deploy: true`, the sub-chart creates this + # -- database automatically and grants ownership to `auth.username`. + database: langfuse # -- Additional database connection arguments args: "" @@ -483,16 +483,101 @@ postgresql: # -- Whether to run automatic migrations on startup autoMigrate: true - # Subchart specific settings - architecture: standalone - primary: - service: - ports: - postgresql: 5432 + # --------------------------------------------------------------------------- + # Sub-chart settings (groundhog2k/postgres) + # Anything under this section is passed straight through to the sub-chart. + # See https://artifacthub.io/packages/helm/groundhog2k/postgres for the full schema. + # --------------------------------------------------------------------------- + image: + # -- Postgres image. Defaults to the upstream Docker Hub image (Apache 2.0 / PostgreSQL license). + repository: postgres + tag: "18" + pullPolicy: IfNotPresent + + # -- Number of replicas for the Postgres StatefulSet. Single-instance only — set to 0 to suspend. + replicaCount: 1 + + service: + type: ClusterIP + # -- Port the Postgres Service exposes. Must match `postgresql.port` (or the langfuse default 5432). + port: 5432 + + # -- PVC settings for the Postgres data volume. + storage: + # Size of the data PVC. Adjust based on expected trace + project volume. + requestedSize: 20Gi + # -- StorageClass name. Leave empty to use the cluster's default. + className: "" + # -- Keep the PVC after the release is uninstalled. + persistentVolumeClaimRetentionPolicy: + whenDeleted: Retain + whenScaled: Retain + + # -- Postgres server settings consumed by the groundhog2k chart. The chart's helpers wire + # -- the chart-managed Secret into these fields automatically when `auth.password` is set + # -- (or `existingSecret` is provided), so users typically don't edit `settings` directly. + settings: + # -- Postgres superuser password (for the built-in `postgres` role). Auto-derived from the + # -- chart-managed Secret unless `existingSecret` is supplied. + superuserPassword: {} + # -- Reference an existing Secret holding the superuser password. Defaults to the + # -- chart-managed Secret (`-postgresql-auth`) — assumes fullname "langfuse". + # -- Override (or set `fullnameOverride: langfuse`) if your release name differs. + # -- Required keys: POSTGRES_USER, POSTGRES_PASSWORD. + existingSecret: "langfuse-postgresql-auth" + + # -- Bootstrap a Langfuse-owned database + user on first start. + # -- The `name`, `user`, and `password` fields are maps in groundhog2k's schema (each is + # -- `{ secretKey: ..., value: ... }`). When `existingSecret` is set (the default below), + # -- groundhog2k reads the value from that Secret using the field's `secretKey`, falling + # -- back to a default key matching the env var name (`POSTGRES_DB`, `USERDB_USER`, + # -- `USERDB_PASSWORD`). The chart-managed Secret writes those exact keys, so empty maps + # -- here just opt into the defaults. The actual database name + user are governed by + # -- `auth.database` and `auth.username` above (those values populate the Secret). + userDatabase: + name: {} + user: {} + password: {} + # -- Reference an existing Secret holding the userDatabase credentials. Defaults to the + # -- chart-managed Secret (`-postgresql-auth`) — assumes fullname "langfuse". + # -- Override (or set `fullnameOverride: langfuse`) if your release name differs. + # -- Required keys: USERDB_USER, USERDB_PASSWORD, POSTGRES_DB. + existingSecret: "langfuse-postgresql-auth" + + # -- Resource limits/requests for the Postgres pod. Tune for your workload. + resources: {} + # limits: + # cpu: 2000m + # memory: 2Gi + # requests: + # cpu: 500m + # memory: 512Mi + + # -- Node selector / tolerations / affinity for the Postgres pod. + nodeSelector: {} + tolerations: [] + affinity: {} + + # -- Pod security context. + podSecurityContext: + fsGroup: 999 + # -- Container security context. + securityContext: + runAsUser: 999 + runAsGroup: 999 + + # -- Liveness / readiness probe customisations (see groundhog2k/postgres docs). + livenessProbe: {} + readinessProbe: {} + startupProbe: {} # Key-Value Store / Redis Configuration +# -- Deploys Valkey via the official valkey-io/valkey sub-chart (image `docker.io/valkey/valkey`). +# -- For HA, point Langfuse at an external Redis/Valkey cluster (e.g. an in-cluster Redis operator +# -- or a managed service such as ElastiCache). redis: - # -- Enable valkey deployment (via Bitnami Helm Chart). If you want to use a Redis or Valkey server already deployed, set to false. + # -- Enable Valkey deployment via the bundled valkey-io/valkey sub-chart. + # -- Set to false to use an existing Redis or Valkey deployment. deploy: true # -- Redis host to connect to. If redis.deploy is true, this will be set automatically based on the release name. @@ -500,37 +585,61 @@ redis: # -- Redis port to connect to. port: 6379 - image: - # -- Overwrite default repository of helm chart to point to non-paid bitnami images. - repository: bitnamilegacy/valkey - # image: docker.io/bitnami/valkey:8.0.2-debian-12-r2 - # Redis TLS configuration tls: - # -- Set to `true` to enable TLS/SSL encrypted connection to the Redis server + # -- Set to `true` to enable TLS/SSL encrypted connection to the Redis server. + # -- When `redis.deploy: true`, this also enables TLS termination on the Valkey sub-chart. enabled: false - # -- Path to the CA certificate file for TLS verification + # -- Path to the CA certificate file for TLS verification (mounted into Langfuse pods). caPath: "" - # -- Path to the client certificate file for mutual TLS authentication + # -- Path to the client certificate file for mutual TLS authentication. certPath: "" - # -- Path to the client private key file for mutual TLS authentication + # -- Path to the client private key file for mutual TLS authentication. keyPath: "" - # Authentication configuration + # Authentication configuration. This block is dual-purpose: + # 1. Langfuse-facing — `username`, `password`, `existingSecret`, `existingSecretPasswordKey`, + # `database` are consumed by the chart helpers to build REDIS_PASSWORD and the connection + # string for the langfuse pods. + # 2. Sub-chart pass-through — `enabled`, `usersExistingSecret`, `aclUsers`, `aclConfig` are + # consumed by the bundled valkey-io/valkey sub-chart (when `redis.deploy: true`). The + # defaults below enable ACL auth, point Valkey at the chart-managed Secret + # (`-redis-auth`, generated by templates/redis-secret.yaml), and grant the + # `default` user full permissions for the Langfuse application. auth: + # -- Enable ACL-based authentication on the bundled Valkey sub-chart. + enabled: true # -- Username for Redis authentication. Set to null to omit username from connection string entirely. - # In case `redis.deploy` is set to `true`, the user will be created automatically. + # -- When `redis.deploy: true`, an ACL user with this name is created automatically. username: "default" - # -- Password for Redis authentication. Set to null to disable authentication (for passwordless Redis like AWS ElastiCache without auth). - # Use URL-encoded passwords or avoid special characters in the password. + # -- Password for Redis authentication. Set to null to disable authentication. + # -- Use URL-encoded passwords or avoid special characters. + # -- When `redis.deploy: true`, this password is wired into the Valkey sub-chart's ACL config + # -- via the chart-managed Secret `-redis-auth`. Leave empty to have the chart + # -- generate one (idempotent across upgrades via lookup). password: "" - # -- If you want to use an existing secret for the redis password, set the name of the secret here. (`redis.auth.password` will be ignored and picked up from this secret). + # -- If you want to use an existing secret for the redis password, set the name of the secret + # -- here. When set, `redis.auth.password` is ignored and the chart-managed Secret is not + # -- created. The Langfuse helper reads the password from this Secret using + # -- `existingSecretPasswordKey`. existingSecret: "" # -- The key in the existing secret that contains the password. existingSecretPasswordKey: "" database: 0 + # -- Pass-through to valkey-helm: Secret containing one key per ACL user (key name == username). + # -- The default value assumes the release name resolves to fullname "langfuse" — set + # -- `fullnameOverride: langfuse` if your release name differs, or override here. + usersExistingSecret: "langfuse-redis-auth" + # -- Pass-through to valkey-helm: ACL users to create. Must include "default" when + # -- `enabled: true` (otherwise valkey rejects the config). `~* &* +@all` grants full access. + aclUsers: + default: + permissions: "~* &* +@all" + # -- Pass-through to valkey-helm: extra inline ACL config appended after `aclUsers`. + aclConfig: "" + # Redis Cluster configuration cluster: # -- Set to `true` to enable Redis Cluster mode. When enabled, you must set `redis.deploy` to `false` and provide cluster nodes. @@ -555,43 +664,92 @@ redis: # -- The key in the existing secret that contains the sentinel password. existingSecretPasswordKey: "" - # Subchart specific settings - architecture: standalone - primary: - # -- Extra flags for the valkey deployment. Must include `--maxmemory-policy noeviction`. - extraFlags: - - "--maxmemory-policy noeviction" + # --------------------------------------------------------------------------- + # Sub-chart settings (valkey-io/valkey) + # Anything under this section is passed straight through to the sub-chart. + # See https://github.com/valkey-io/valkey-helm for the full schema. + # --------------------------------------------------------------------------- + image: + registry: docker.io + repository: valkey/valkey + tag: "8.0" + pullPolicy: IfNotPresent + + service: + type: ClusterIP + # -- Port the primary Valkey Service exposes. + port: 6379 + + # -- Replication configuration. Default: standalone (no replicas). Set `enabled: true` and bump + # -- `replicas` for a primary/replica topology — the Langfuse helpers always target the primary. + replica: + enabled: false + replicas: 0 + persistence: + size: 8Gi + storageClass: "" + accessModes: + - ReadWriteOnce + service: + enabled: true + type: ClusterIP + port: 6379 + + # -- Persistence for the primary node. + dataStorage: + enabled: true + requestedSize: 8Gi + className: "" + accessModes: + - ReadWriteOnce + keepPvc: false + + # -- Set the maxmemory eviction policy. Langfuse requires `noeviction` to avoid losing job data. + extraFlags: + - "--maxmemory-policy" + - "noeviction" + + # -- Resource limits/requests for the Valkey pods. + resources: {} + + podSecurityContext: + fsGroup: 1000 + runAsUser: 1000 + runAsGroup: 1000 + seccompProfile: + type: RuntimeDefault + + metrics: + # -- Enable the bundled Valkey Prometheus exporter sidecar. + enabled: false # ClickHouse Configuration +# -- Deploys ClickHouse via the upstream ClickHouse/clickhouse-operator (apiVersion +# -- `clickhouse.com/v1alpha1`). The operator is alpha-quality at the time of writing — review +# -- release notes before upgrading and pin the operator chart version explicitly. clickhouse: - # -- Enable ClickHouse deployment (via Bitnami Helm Chart). If you want to use an external Clickhouse server (or a managed one), set this to false + # -- Enable a `ClickHouseCluster` + `KeeperCluster` rendered by this chart. + # -- Set to false to use an external ClickHouse server. deploy: true - # -- ClickHouse host to connect to. If clickhouse.deploy is true, this will be set automatically based on the release name. + # -- ClickHouse host to connect to. If clickhouse.deploy is true, this will be set automatically + # -- based on the cluster CR's headless Service. host: "" # -- ClickHouse HTTP port to connect to. httpPort: 8123 - # -- ClickHouse native port to connect to. + # -- ClickHouse native (TCP) port to connect to. nativePort: 9000 # -- ClickHouse database to use. database: default - image: - # -- Overwrite default repository of helm chart to point to non-paid bitnami images. - repository: bitnamilegacy/clickhouse - # image: docker.io/bitnami/clickhouse:25.2.1-debian-12-r0 - - zookeeper: - image: - # -- Overwrite default repository of helm chart to point to non-paid bitnami images. - repository: bitnamilegacy/zookeeper - # image: docker.io/bitnami/zookeeper:3.9.3-debian-12-r8 - - # Authentication configuration + # Authentication configuration (Langfuse-facing — these values are read by the chart's helpers + # to build CLICKHOUSE_USER / CLICKHOUSE_PASSWORD for the langfuse pods). auth: # -- Username for the ClickHouse user. username: default - # -- Password for the ClickHouse user. + # -- Password for the ClickHouse user. When `clickhouse.deploy: true`, the chart creates a + # -- ClickHouse Secret holding the SHA256 hash and references it from the cluster CR. + # -- Leave empty to have the chart generate one. password: "" # -- If you want to use an existing secret for the ClickHouse password, set the name of the secret here. (`clickhouse.auth.password` will be ignored and picked up from this secret). existingSecret: "" @@ -607,19 +765,69 @@ clickhouse: # -- Whether to run automatic ClickHouse migrations on startup autoMigrate: true - # -- Whether to run ClickHouse commands ON CLUSTER. Controls CLICKHOUSE_CLUSTER_ENABLED setting. - clusterEnabled: true - - # -- Subchart specific settings - shards: 1 # Fixed - Langfuse does not support sharding - # -- Number of replicas to use for the ClickHouse cluster. 1 corresponds to a single, non-HA deployment. - replicaCount: 3 - # -- The resources preset to use for the ClickHouse cluster. - resourcesPreset: 2xlarge - -# S3/MinIO Configuration + # --------------------------------------------------------------------------- + # Sub-chart settings (rendered as ClickHouseCluster + KeeperCluster CRs by this chart) + # --------------------------------------------------------------------------- + cluster: + # -- Whether to run ClickHouse commands ON CLUSTER. Controls the CLICKHOUSE_CLUSTER_ENABLED + # -- env var on web/worker. Defaults to true: the operator deploys a cluster (single- or + # -- multi-replica) and Langfuse uses ON CLUSTER DDL. Set to false only when pointing at an + # -- external non-clustered ClickHouse. + enabled: true + # -- Number of ClickHouse replicas. 1 is a valid non-HA single-pod cluster; 2+ requires Keeper enabled. + replicas: 1 + image: + repository: clickhouse/clickhouse-server + tag: "26.3" + storage: + # -- Size of each ClickHouse pod's data PVC. + size: 100Gi + # -- StorageClass name. Leave empty to use the cluster's default. + className: "" + accessModes: + - ReadWriteOnce + # -- Resource limits/requests for the ClickHouse pods. + resources: {} + # limits: + # cpu: 4 + # memory: 8Gi + # requests: + # cpu: 1 + # memory: 2Gi + # -- Pod scheduling. + nodeSelector: {} + tolerations: [] + affinity: {} + # -- Extra ClickHouse server settings (under `` config XML). + settings: {} + # -- Extra ClickHouse profile settings (mounted into `users.xml`). + profileSettings: {} + + keeper: + # -- Deploy a `KeeperCluster` CR alongside the ClickHouseCluster. Required for replicated mode. + enabled: true + # -- Keeper replica count. Must be odd (1, 3, 5). Use 3 for production. + replicas: 3 + image: + repository: clickhouse/clickhouse-keeper + tag: "26.3" + storage: + size: 10Gi + className: "" + accessModes: + - ReadWriteOnce + resources: {} + nodeSelector: {} + tolerations: [] + affinity: {} + +# S3 / Object Storage Configuration +# -- Deploys SeaweedFS in `allInOne` mode (single pod combining master/volume/filer/S3 gateway) +# -- via the official seaweedfs/seaweedfs sub-chart. Image: `chrislusf/seaweedfs` (Apache 2.0). +# -- For production, point Langfuse at S3 / Azure Blob / GCS instead by setting `s3.deploy: false`. s3: - # -- Enable MinIO deployment (via Bitnami Helm Chart). If you want to use a custom BlobStorage, e.g. S3, set to false. + # -- Enable SeaweedFS deployment via the bundled seaweedfs sub-chart. + # -- Set to false to use an external S3-compatible BlobStorage (S3 / GCS / Azure Blob / etc.). deploy: true # -- Storage provider to use. Options: s3 (default), azure, gcs @@ -628,18 +836,15 @@ s3: # -- When set to 's3', uses S3-compatible interface (default behavior) storageProvider: "s3" - image: - # -- Overwrite default repository of helm chart to point to non-paid bitnami images. - repository: bitnamilegacy/minio - # image: docker.io/bitnami/minio:2024.12.18-debian-12-r1 - # -- S3 bucket to use for all uploads. Can be overridden per upload type. - bucket: "" + # -- When `s3.deploy: true`, this bucket is auto-created on the SeaweedFS S3 gateway. + bucket: "langfuse" # -- S3 region to use for all uploads. Can be overridden per upload type. region: "auto" # -- S3 endpoint to use for all uploads. Can be overridden per upload type. + # -- When `s3.deploy: true`, this is set automatically to the SeaweedFS allInOne S3 Service. endpoint: "" - # -- Whether to force path style on requests. Required for MinIO. Can be overridden per upload type. + # -- Whether to force path style on requests. Required for SeaweedFS / MinIO. Can be overridden per upload type. forcePathStyle: true # -- S3 accessKeyId to use for all uploads. Can be overridden per upload type. accessKeyId: @@ -683,7 +888,7 @@ s3: region: "" # -- S3 endpoint to use for event uploads. endpoint: "" - # -- Whether to force path style on requests. Required for MinIO. + # -- Whether to force path style on requests. Required for SeaweedFS / MinIO. forcePathStyle: Null # -- S3 accessKeyId to use for event uploads. accessKeyId: @@ -710,7 +915,7 @@ s3: region: "" # -- S3 endpoint to use for batch exports. endpoint: "" - # -- Whether to force path style on requests. Required for MinIO. + # -- Whether to force path style on requests. Required for SeaweedFS / MinIO. forcePathStyle: Null # -- S3 accessKeyId to use for batch exports. accessKeyId: @@ -737,7 +942,7 @@ s3: region: "" # -- S3 endpoint to use for media uploads. endpoint: "" - # -- Whether to force path style on requests. Required for MinIO. + # -- Whether to force path style on requests. Required for SeaweedFS / MinIO. forcePathStyle: Null # -- S3 accessKeyId to use for media uploads. accessKeyId: @@ -756,19 +961,80 @@ s3: # -- Expiry time for download URLs. Defaults to 1 hour. downloadUrlExpirySeconds: 3600 - # MinIO subchart specific settings - defaultBuckets: langfuse + # Authentication for the bundled SeaweedFS S3 gateway. The chart wires these credentials + # into both Langfuse (as LANGFUSE_S3_*_ACCESS_KEY_ID / *_SECRET_ACCESS_KEY) and into a + # generated SeaweedFS IAM config Secret consumed by the allInOne pod. auth: - # -- root username - rootUser: minio - # -- Password for MinIO root user + # -- Access key ID Langfuse uses to authenticate against the SeaweedFS S3 gateway. + rootUser: "langfuse" + # -- Secret access key Langfuse uses to authenticate against the SeaweedFS S3 gateway. + # -- Leave empty to have the chart generate one. rootPassword: "" - # -- If you want to use an existing secret for the root user password, set the name of the secret here. (`s3.auth.rootPassword` will be ignored and picked up from this secret). + # -- Reference an existing Secret holding the access/secret key. The keys are read using + # -- `rootUserSecretKey` (default: access-key) and `rootPasswordSecretKey` (default: secret-key). existingSecret: "" - # -- Key where the Minio root user is being stored inside the existing secret `s3.auth.existingSecret` + # -- Key in the existing secret that contains the S3 access key ID. rootUserSecretKey: "" - # -- Key where the Minio root user password is being stored inside the existing secret `s3.auth.existingSecret` + # -- Key in the existing secret that contains the S3 secret access key. rootPasswordSecretKey: "" + # -- Default bucket name list for legacy compatibility. The first entry is used as fallback + # -- when neither `s3.bucket` nor a per-upload `bucket` is set. + defaultBuckets: "langfuse" + + # --------------------------------------------------------------------------- + # Sub-chart settings (seaweedfs/seaweedfs) + # In `allInOne` mode, the dedicated master/volume/filer/s3 components are disabled and + # replaced by a single combined pod. + # --------------------------------------------------------------------------- + master: + enabled: false + volume: + enabled: false + filer: + enabled: false + s3: + enabled: false + + allInOne: + enabled: true + image: + registry: docker.io + repository: chrislusf/seaweedfs + tag: "3.95" + pullPolicy: IfNotPresent + s3: + enabled: true + port: 8333 + enableAuth: true + # -- Reference a chart-managed Secret containing the SeaweedFS IAM config JSON. + # -- The chart auto-generates this Secret (`-s3-auth`) from + # -- `s3.auth.rootUser` / `rootPassword` when `s3.deploy: true`. The default value + # -- assumes the release name resolves to fullname "langfuse" — set + # -- `fullnameOverride: langfuse` if your release name differs, or override here. + existingConfigSecret: "langfuse-s3-auth" + # -- Buckets to create on first start. The chart appends `s3.bucket` automatically. + createBuckets: + - name: langfuse + createBucketsHook: + resources: {} + data: + # -- Persistence for the SeaweedFS data directory. + type: persistentVolumeClaim + size: 50Gi + storageClass: "" + accessModes: + - ReadWriteOnce + service: + type: ClusterIP + internalTrafficPolicy: Cluster + resources: + limits: + cpu: 2 + memory: 2Gi + requests: + cpu: 500m + memory: 1Gi + # Additional manifests extraManifests: [] diff --git a/examples/minimal-installation/README.md b/examples/minimal-installation/README.md index e643fb89..305deeda 100644 --- a/examples/minimal-installation/README.md +++ b/examples/minimal-installation/README.md @@ -1,93 +1,131 @@ -# Minimal Installation Example +# Minimal Installation Example (v2) -This example demonstrates a minimal installation of Langfuse in a Kubernetes cluster. It includes a basic configuration with ingress support. +This example installs Langfuse with all bundled sub-charts (PostgreSQL, ClickHouse, Valkey, SeaweedFS) on a single `helm install`. +The chart auto-generates credentials for every sub-component — you only need to provide the three Langfuse application secrets. -## Installation +> **v2 changes** +> +> - Bitnami PostgreSQL / ClickHouse / Redis / MinIO sub-charts have been replaced with `groundhog2k/postgres`, the upstream `ClickHouse/clickhouse-operator`, `valkey-io/valkey`, and `seaweedfs/seaweedfs`. +> - The chart now generates `-postgresql-auth`, `-clickhouse-auth`, `-redis-auth`, and `-s3-auth` Secrets on first install. Passwords persist across upgrades via `lookup`. +> - There is no automatic data migration from v1. To upgrade, dump v1 data, install v2 in a new namespace, and restore. +> - cert-manager and the ClickHouse operator are now **cluster-wide prereqs** (see below). They are installed once per cluster, not per release. + +## Prerequisites + +The langfuse chart creates `ClickHouseCluster`, `ClickHouseKeeperInstallation`, and cert-manager `Certificate` / `Issuer` resources, and the clickhouse-operator chart in turn creates its own `Certificate` / `Issuer`. None of these can be applied until their CRDs (and cert-manager's webhook) are already present in the cluster — otherwise the API server rejects them with `no matches for kind …`. + +Install these once per cluster, in order: -To install Langfuse using this example: +### 1. cert-manager -1. First, create the required secret: ```bash -# Edit secret.yaml and set secure values before applying -kubectl apply -f secret.yaml +helm install \ + cert-manager oci://quay.io/jetstack/charts/cert-manager \ + --version v1.20.2 \ + --namespace cert-manager \ + --create-namespace \ + --set crds.enabled=true + +kubectl wait --for=condition=Established \ + crd/certificates.cert-manager.io \ + crd/issuers.cert-manager.io \ + --timeout=120s ``` -2. Add the Helm repository: +Skip this step if cert-manager is already running in the cluster. + +### 2. ClickHouse operator + ```bash -helm repo add langfuse https://cbeneke.github.io/langfuse-k8s -helm repo update +helm install ch-operator oci://ghcr.io/clickhouse/clickhouse-operator-helm \ + --version 0.0.4 \ + --namespace clickhouse-operator --create-namespace + +kubectl wait --for=condition=Established \ + crd/clickhouseclusters.clickhouse.com \ + crd/keeperclusters.clickhouse.com \ + --timeout=120s ``` -3. Install the chart using the base values file and optional ingress configuration: -```bash -# Basic installation -helm install langfuse langfuse/langfuse -f values.yaml +## Installation -# Or with ingress enabled -helm install langfuse langfuse/langfuse -f values.yaml -f with-ingress.yaml -``` +1. Edit `secret.yaml` and replace the placeholder values with secure secrets: + + ```bash + # SALT — random 32-byte hex + openssl rand -hex 32 + # ENCRYPTION_KEY — random 32-byte hex + openssl rand -hex 32 + # NEXTAUTH_SECRET — random 32-byte base64 + openssl rand -base64 32 + ``` + +2. Apply the Secret: + + ```bash + kubectl create namespace langfuse + kubectl apply -n langfuse -f secret.yaml + ``` + +3. Install the chart: + + ```bash + helm install langfuse oci://ghcr.io/langfuse/langfuse-k8s/langfuse \ + --version 2.0.0 \ + --namespace langfuse \ + -f values.yaml + ``` + + Or with ingress enabled: -## Configuration + ```bash + helm install langfuse oci://ghcr.io/langfuse/langfuse-k8s/langfuse \ + --version 2.0.0 \ + --namespace langfuse \ + -f values.yaml -f with-ingress.yaml + ``` -The example contains three configuration files: +4. Wait for the workloads to come up. Typical readiness order: + + - `KeeperCluster` and `ClickHouseCluster` pods (managed by the operator in the `clickhouse-operator` namespace) + - PostgreSQL StatefulSet + - Valkey primary + - SeaweedFS allInOne + - `langfuse-web` and `langfuse-worker` Deployments + + ```bash + kubectl get pods -n langfuse -w + ``` + +5. Port-forward and sign in: + + ```bash + kubectl port-forward -n langfuse svc/langfuse-web 3000:3000 + open http://localhost:3000 + ``` + +## Local testing against this repo + +To install from a checkout of this repository instead of the published OCI chart, replace step 3's chart reference with the local path. +Execute the following from `./charts/langfuse`: + +```bash +helm dependency update . +helm install langfuse . \ + --namespace langfuse \ + -f ../../examples/minimal-installation/values.yaml +``` + +## Files ### `secret.yaml` -Contains all required secrets for the Langfuse installation. **Must be applied before installing the Helm chart**. Make sure to replace all placeholder values with secure values before applying. + +Contains the three Langfuse application secrets (`salt`, `encryption-key`, `nextauth-secret`). **Apply before installing the chart.** Replace placeholders with secure random values. ### `values.yaml` -The core values file that configures Langfuse to use the pre-created secret for all required credentials: -```yaml -langfuse: - salt: - secretKeyRef: - name: langfuse - key: salt - - nextauth: - secret: - secretKeyRef: - name: langfuse - key: nextauth-secret - -postgresql: - auth: - existingSecret: langfuse - secretKeys: - userPasswordKey: postgresql-password - -clickhouse: - auth: - existingSecret: langfuse - existingSecretKey: clickhouse-password - resourcesPreset: medium - -redis: - auth: - existingSecret: langfuse - existingSecretPasswordKey: redis-password - -s3: - auth: - # If existingSecret is set, both root user and root password must be supplied via the secret - existingSecret: langfuse - rootUserSecretKey: s3-user - rootPasswordSecretKey: s3-password -``` -### `with-ingress.yaml` (Optional) -Additional configuration to enable ingress: -```yaml -nextauth: - url: https://langfuse.example.com -langfuse: - ingress: - enabled: true - className: nginx - hosts: - - host: langfuse.example.com - paths: - - path: / - pathType: Prefix -``` +Wires the Langfuse application to read its three secrets from `secret.yaml`. Sub-component credentials (Postgres/ClickHouse/Valkey/SeaweedFS) are not configured here — the chart auto-generates them on first install. To pin specific passwords, set `.auth.password` or `.auth.existingSecret`. + +### `with-ingress.yaml` (optional) -Make sure to adjust the hostname in the values file to match your environment before installing. +Adds an ingress on top of the base values. Edit the host before applying. diff --git a/examples/minimal-installation/secret.yaml b/examples/minimal-installation/secret.yaml index ee5a50f6..63414ca8 100644 --- a/examples/minimal-installation/secret.yaml +++ b/examples/minimal-installation/secret.yaml @@ -4,11 +4,9 @@ type: Opaque metadata: name: langfuse stringData: - salt: your-salt - encryption-key: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef # Generate with `openssl rand -hex 32` - nextauth-secret: your-nextauth-secret - postgresql-password: your-postgresql-password - clickhouse-password: your-clickhouse-password - redis-password: your-redis-password - s3-user: your-s3-user - s3-password: your-s3-password + # Generate with `openssl rand -hex 32` + salt: "your-salt" + # Generate with `openssl rand -hex 32` + encryption-key: "0000000000000000000000000000000000000000000000000000000000000000" + # Generate with `openssl rand -base64 32` + nextauth-secret: "your-nextauth-secret" diff --git a/examples/minimal-installation/values.yaml b/examples/minimal-installation/values.yaml index 037175a0..5ac1e179 100644 --- a/examples/minimal-installation/values.yaml +++ b/examples/minimal-installation/values.yaml @@ -1,15 +1,16 @@ -# This values.yaml file demonstrates how to use the basic chart with a single, pre-created secret. -# Secrets must be set manually or via External Secrets Operator like https://external-secrets.io/latest or any other secret management tool. +# Minimal Langfuse v2 installation. The bundled sub-charts auto-provision their +# own credentials Secrets — only the three Langfuse application secrets need to +# be supplied. Apply secret.yaml first, then `helm install`. langfuse: - encryptionKey: + salt: secretKeyRef: name: langfuse - key: encryption-key + key: salt - salt: + encryptionKey: secretKeyRef: name: langfuse - key: salt + key: encryption-key nextauth: secret: @@ -17,25 +18,19 @@ langfuse: name: langfuse key: nextauth-secret +# Postgres / ClickHouse / Valkey / SeaweedFS credentials are generated by the +# chart on first install and persisted across upgrades via lookup. To override, +# set `.auth.password` (or `.auth.existingSecret`). postgresql: - auth: - existingSecret: langfuse - secretKeys: - adminPasswordKey: postgresql-password - userPasswordKey: postgresql-password + deploy: true clickhouse: - auth: - existingSecret: langfuse - existingSecretKey: clickhouse-password + deploy: true + operator: + deploy: true redis: - auth: - existingSecret: langfuse - existingSecretPasswordKey: redis-password + deploy: true s3: - auth: - existingSecret: langfuse - rootUserSecretKey: s3-user - rootPasswordSecretKey: s3-password + deploy: true