diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml index 5b436298a3..8a730812bc 100644 --- a/.github/workflows/images.yml +++ b/.github/workflows/images.yml @@ -404,6 +404,49 @@ jobs: chmod +x .github/test-unified-docker.sh .github/test-unified-docker.sh test-unified-image:latest + publish-helm: + runs-on: ubuntu-latest + needs: + - setup + - merge-split + if: github.event_name == 'release' && needs.merge-split.result == 'success' + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: v3.16.3 + + - name: Login to GitHub Container Registry + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io \ + --username ${{ github.actor }} \ + --password-stdin + + - name: Set chart version + working-directory: infra/helm/llmgateway + run: | + IMAGE_TAG="${{ needs.setup.outputs.image_tag }}" + CHART_VERSION="${IMAGE_TAG#v}" + sed -i "s/^version:.*/version: ${CHART_VERSION}/" Chart.yaml + sed -i "s/^appVersion:.*/appVersion: \"${IMAGE_TAG}\"/" Chart.yaml + cat Chart.yaml + + - name: Package chart + working-directory: infra/helm + run: helm package llmgateway + + - name: Push chart to GHCR + working-directory: infra/helm + run: | + OWNER="${GITHUB_REPOSITORY_OWNER,,}" + helm push llmgateway-*.tgz "oci://ghcr.io/${OWNER}/charts" + trigger-infra-update: runs-on: ubuntu-latest needs: diff --git a/apps/docs/content/self-host.mdx b/apps/docs/content/self-host.mdx index c2a0e669fa..c4c7ee235d 100644 --- a/apps/docs/content/self-host.mdx +++ b/apps/docs/content/self-host.mdx @@ -183,3 +183,15 @@ Once your LLMGateway is running: 2. **Create your first organization** and project 3. **Generate API keys** for your applications 4. **Test the gateway** by making API calls to http://localhost:4001 + +## Helm Chart + +You can also deploy LLMGateway to Kubernetes using the Helm chart, which is published as an OCI artifact on GitHub Container Registry: + +```bash +helm install llmgateway oci://ghcr.io/theopenco/charts/llmgateway +``` + +This installs the latest published version. To pin to a specific release, append `--version ` (matching a published release tag without the `v` prefix, e.g. `1.2.3`). + +See the [Helm chart README](https://github.com/theopenco/llmgateway/tree/main/infra/helm) for configuration and the [list of available versions](https://github.com/theopenco/llmgateway/pkgs/container/charts%2Fllmgateway). diff --git a/infra/helm/README.md b/infra/helm/README.md new file mode 100644 index 0000000000..02909f6bed --- /dev/null +++ b/infra/helm/README.md @@ -0,0 +1,21 @@ +# LLM Gateway Helm Chart + +This is the Helm chart for LLM Gateway. It is used to deploy LLM Gateway on a Kubernetes cluster. + +The chart is published as an OCI artifact to GitHub Container Registry on every release. + +## Installation + +```bash +helm install llmgateway oci://ghcr.io/theopenco/charts/llmgateway +``` + +This installs the latest published version. To pin to a specific release, append `--version ` (matching a published release tag without the `v` prefix, e.g. `1.2.3`). Available versions are listed at https://github.com/theopenco/llmgateway/pkgs/container/charts%2Fllmgateway. + +## Local development + +To install directly from a checkout of this repository: + +```bash +helm install llmgateway ./infra/helm/llmgateway +``` diff --git a/infra/helm/llmgateway/Chart.yaml b/infra/helm/llmgateway/Chart.yaml new file mode 100644 index 0000000000..7308de3883 --- /dev/null +++ b/infra/helm/llmgateway/Chart.yaml @@ -0,0 +1,20 @@ +apiVersion: v2 +name: llmgateway +description: A Helm chart for LLM Gateway - a full-stack LLM API gateway with multi-provider support +type: application +icon: https://llmgateway.io/brand/logo-black.svg +version: 0.1.0 +appVersion: "latest" +home: https://llmgateway.io +sources: + - https://github.com/theopenco/llmgateway +keywords: + - llm + - gateway + - ai + - openai + - anthropic + - api-gateway +maintainers: + - name: theopenco + url: https://github.com/theopenco diff --git a/infra/helm/llmgateway/templates/_helpers.tpl b/infra/helm/llmgateway/templates/_helpers.tpl new file mode 100644 index 0000000000..4bd8c9da2d --- /dev/null +++ b/infra/helm/llmgateway/templates/_helpers.tpl @@ -0,0 +1,185 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "llmgateway.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "llmgateway.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "llmgateway.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "llmgateway.labels" -}} +helm.sh/chart: {{ include "llmgateway.chart" . }} +{{ include "llmgateway.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.global.labels }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "llmgateway.selectorLabels" -}} +app.kubernetes.io/name: {{ include "llmgateway.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Component labels (call with dict "context" . "component" "api") +*/}} +{{- define "llmgateway.componentLabels" -}} +{{ include "llmgateway.labels" .context }} +app.kubernetes.io/component: {{ .component }} +{{- end }} + +{{/* +Component selector labels +*/}} +{{- define "llmgateway.componentSelectorLabels" -}} +{{ include "llmgateway.selectorLabels" .context }} +app.kubernetes.io/component: {{ .component }} +{{- end }} + +{{/* +Build image reference for a component. +Usage: {{ include "llmgateway.image" (dict "context" . "image" .Values.api.image) }} +*/}} +{{- define "llmgateway.image" -}} +{{- $registry := .image.registry | default .context.Values.global.image.registry -}} +{{- $repository := .image.repository -}} +{{- $tag := .image.tag | default .context.Values.global.image.tag | default .context.Chart.AppVersion -}} +{{- if $registry -}} +{{- printf "%s/%s:%s" $registry $repository $tag -}} +{{- else -}} +{{- printf "%s:%s" $repository $tag -}} +{{- end -}} +{{- end }} + +{{/* +Image pull secrets +*/}} +{{- define "llmgateway.imagePullSecrets" -}} +{{- with .Values.global.imagePullSecrets }} +imagePullSecrets: +{{- toYaml . | nindent 2 }} +{{- end }} +{{- end }} + +{{/* +Secret name (supports existingSecret) +*/}} +{{- define "llmgateway.secretName" -}} +{{- if .Values.existingSecret }} +{{- .Values.existingSecret }} +{{- else }} +{{- include "llmgateway.fullname" . }} +{{- end }} +{{- end }} + +{{/* +ConfigMap name +*/}} +{{- define "llmgateway.configMapName" -}} +{{- include "llmgateway.fullname" . }}-config +{{- end }} + +{{/* +PostgreSQL host +*/}} +{{- define "llmgateway.postgresql.host" -}} +{{- if .Values.postgresql.enabled }} +{{- printf "%s-postgresql" (include "llmgateway.fullname" .) }} +{{- else }} +{{- .Values.externalPostgresql.host }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL port +*/}} +{{- define "llmgateway.postgresql.port" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.postgresql.port | default 5432 }} +{{- else }} +{{- .Values.externalPostgresql.port | default 5432 }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL database +*/}} +{{- define "llmgateway.postgresql.database" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.postgresql.database | default "llmgateway" }} +{{- else }} +{{- .Values.externalPostgresql.database | default "llmgateway" }} +{{- end }} +{{- end }} + +{{/* +PostgreSQL user +*/}} +{{- define "llmgateway.postgresql.user" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.postgresql.user | default "postgres" }} +{{- else }} +{{- .Values.externalPostgresql.user | default "postgres" }} +{{- end }} +{{- end }} + +{{/* +Redis host +*/}} +{{- define "llmgateway.redis.host" -}} +{{- if .Values.redis.enabled }} +{{- printf "%s-redis" (include "llmgateway.fullname" .) }} +{{- else }} +{{- .Values.externalRedis.host }} +{{- end }} +{{- end }} + +{{/* +Redis port +*/}} +{{- define "llmgateway.redis.port" -}} +{{- if .Values.redis.enabled }} +{{- .Values.redis.port | default 6379 }} +{{- else }} +{{- .Values.externalRedis.port | default 6379 }} +{{- end }} +{{- end }} + +{{/* +Internal service URL helper. +Usage: {{ include "llmgateway.serviceUrl" (dict "context" . "name" "api") }} +*/}} +{{- define "llmgateway.serviceUrl" -}} +{{- printf "http://%s-%s:80" (include "llmgateway.fullname" .context) .name -}} +{{- end }} diff --git a/infra/helm/llmgateway/templates/admin-deployment.yaml b/infra/helm/llmgateway/templates/admin-deployment.yaml new file mode 100644 index 0000000000..917b54d461 --- /dev/null +++ b/infra/helm/llmgateway/templates/admin-deployment.yaml @@ -0,0 +1,63 @@ +{{- if .Values.admin.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "llmgateway.fullname" . }}-admin + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "admin") | nindent 4 }} +spec: + replicas: {{ .Values.admin.replicas | default 1 }} + selector: + matchLabels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "admin") | nindent 6 }} + template: + metadata: + labels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "admin") | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + spec: + {{- include "llmgateway.imagePullSecrets" . | nindent 6 }} + containers: + - name: admin + image: {{ include "llmgateway.image" (dict "context" . "image" .Values.admin.image) }} + imagePullPolicy: {{ .Values.admin.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: 80 + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "llmgateway.configMapName" . }} + - secretRef: + name: {{ include "llmgateway.secretName" . }} + {{- with .Values.admin.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + readinessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 20 + periodSeconds: 30 + timeoutSeconds: 5 + resources: + {{- toYaml .Values.admin.resources | nindent 12 }} + {{- with .Values.admin.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.admin.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/admin-service.yaml b/infra/helm/llmgateway/templates/admin-service.yaml new file mode 100644 index 0000000000..bc07ef015e --- /dev/null +++ b/infra/helm/llmgateway/templates/admin-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.admin.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "llmgateway.fullname" . }}-admin + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "admin") | nindent 4 }} +spec: + type: {{ .Values.admin.service.type | default "ClusterIP" }} + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "admin") | nindent 4 }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/api-deployment.yaml b/infra/helm/llmgateway/templates/api-deployment.yaml new file mode 100644 index 0000000000..91bcf64e72 --- /dev/null +++ b/infra/helm/llmgateway/templates/api-deployment.yaml @@ -0,0 +1,86 @@ +{{- if .Values.api.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "llmgateway.fullname" . }}-api + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "api") | nindent 4 }} +spec: + replicas: {{ .Values.api.replicas | default 1 }} + selector: + matchLabels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "api") | nindent 6 }} + template: + metadata: + labels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "api") | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + spec: + {{- include "llmgateway.imagePullSecrets" . | nindent 6 }} + initContainers: + - name: wait-for-postgresql + image: busybox:1.37 + command: + - sh + - -c + - | + until nc -z {{ include "llmgateway.postgresql.host" . }} {{ include "llmgateway.postgresql.port" . }}; do + echo "Waiting for PostgreSQL..." + sleep 2 + done + - name: wait-for-redis + image: busybox:1.37 + command: + - sh + - -c + - | + until nc -z {{ include "llmgateway.redis.host" . }} {{ include "llmgateway.redis.port" . }}; do + echo "Waiting for Redis..." + sleep 2 + done + containers: + - name: api + image: {{ include "llmgateway.image" (dict "context" . "image" .Values.api.image) }} + imagePullPolicy: {{ .Values.api.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: 80 + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "llmgateway.configMapName" . }} + - secretRef: + name: {{ include "llmgateway.secretName" . }} + {{- with .Values.api.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 3 + resources: + {{- toYaml .Values.api.resources | nindent 12 }} + {{- with .Values.api.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.api.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/api-service.yaml b/infra/helm/llmgateway/templates/api-service.yaml new file mode 100644 index 0000000000..5dee1c8276 --- /dev/null +++ b/infra/helm/llmgateway/templates/api-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.api.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "llmgateway.fullname" . }}-api + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "api") | nindent 4 }} +spec: + type: {{ .Values.api.service.type | default "ClusterIP" }} + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "api") | nindent 4 }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/code-deployment.yaml b/infra/helm/llmgateway/templates/code-deployment.yaml new file mode 100644 index 0000000000..f1fb610709 --- /dev/null +++ b/infra/helm/llmgateway/templates/code-deployment.yaml @@ -0,0 +1,63 @@ +{{- if .Values.code.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "llmgateway.fullname" . }}-code + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "code") | nindent 4 }} +spec: + replicas: {{ .Values.code.replicas | default 1 }} + selector: + matchLabels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "code") | nindent 6 }} + template: + metadata: + labels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "code") | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + spec: + {{- include "llmgateway.imagePullSecrets" . | nindent 6 }} + containers: + - name: code + image: {{ include "llmgateway.image" (dict "context" . "image" .Values.code.image) }} + imagePullPolicy: {{ .Values.code.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: 80 + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "llmgateway.configMapName" . }} + - secretRef: + name: {{ include "llmgateway.secretName" . }} + {{- with .Values.code.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + readinessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 20 + periodSeconds: 30 + timeoutSeconds: 5 + resources: + {{- toYaml .Values.code.resources | nindent 12 }} + {{- with .Values.code.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.code.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/code-service.yaml b/infra/helm/llmgateway/templates/code-service.yaml new file mode 100644 index 0000000000..17272c41a6 --- /dev/null +++ b/infra/helm/llmgateway/templates/code-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.code.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "llmgateway.fullname" . }}-code + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "code") | nindent 4 }} +spec: + type: {{ .Values.code.service.type | default "ClusterIP" }} + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "code") | nindent 4 }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/configmap.yaml b/infra/helm/llmgateway/templates/configmap.yaml new file mode 100644 index 0000000000..b710a8820b --- /dev/null +++ b/infra/helm/llmgateway/templates/configmap.yaml @@ -0,0 +1,167 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "llmgateway.configMapName" . }} + labels: + {{- include "llmgateway.labels" . | nindent 4 }} +data: + NODE_ENV: {{ .Values.global.nodeEnv | default "production" | quote }} + PORT: "80" + + # Internal service URLs (Kubernetes DNS) + API_BACKEND_URL: {{ include "llmgateway.serviceUrl" (dict "context" . "name" "api") | quote }} + REDIS_HOST: {{ include "llmgateway.redis.host" . | quote }} + REDIS_PORT: {{ include "llmgateway.redis.port" . | quote }} + + # Public-facing URLs + {{- if .Values.urls.ui }} + UI_URL: {{ .Values.urls.ui | quote }} + {{- end }} + {{- if .Values.urls.api }} + API_URL: {{ .Values.urls.api | quote }} + {{- end }} + {{- if .Values.urls.gateway }} + GATEWAY_URL: {{ .Values.urls.gateway | quote }} + {{- end }} + {{- if .Values.urls.playground }} + PLAYGROUND_URL: {{ .Values.urls.playground | quote }} + {{- end }} + {{- if .Values.urls.code }} + CODE_URL: {{ .Values.urls.code | quote }} + {{- end }} + {{- if .Values.urls.docs }} + DOCS_URL: {{ .Values.urls.docs | quote }} + {{- end }} + {{- if .Values.urls.admin }} + ADMIN_URL: {{ .Values.urls.admin | quote }} + {{- end }} + + # CORS / cookies + {{- if .Values.auth.cookieDomain }} + COOKIE_DOMAIN: {{ .Values.auth.cookieDomain | quote }} + {{- end }} + {{- if .Values.auth.originUrls }} + ORIGIN_URLS: {{ .Values.auth.originUrls | quote }} + {{- end }} + {{- if .Values.auth.hosted }} + HOSTED: {{ .Values.auth.hosted | quote }} + {{- end }} + + # Auth providers (non-secret parts) + {{- if .Values.auth.passkey.rpId }} + PASSKEY_RP_ID: {{ .Values.auth.passkey.rpId | quote }} + {{- end }} + {{- if .Values.auth.passkey.rpName }} + PASSKEY_RP_NAME: {{ .Values.auth.passkey.rpName | quote }} + {{- end }} + + # Gateway timeouts + {{- with .Values.gateway.config }} + {{- if .gatewayTimeoutMs }} + GATEWAY_TIMEOUT_MS: {{ .gatewayTimeoutMs | quote }} + {{- end }} + {{- if .aiStreamingTimeoutMs }} + AI_STREAMING_TIMEOUT_MS: {{ .aiStreamingTimeoutMs | quote }} + {{- end }} + {{- if .aiTimeoutMs }} + AI_TIMEOUT_MS: {{ .aiTimeoutMs | quote }} + {{- end }} + {{- if .keepAliveTimeoutS }} + KEEP_ALIVE_TIMEOUT_S: {{ .keepAliveTimeoutS | quote }} + {{- end }} + {{- if .shutdownGracePeriodMs }} + SHUTDOWN_GRACE_PERIOD_MS: {{ .shutdownGracePeriodMs | quote }} + {{- end }} + {{- if .healthCheckSkipDatabase }} + HEALTH_CHECK_SKIP_DATABASE: {{ .healthCheckSkipDatabase | quote }} + {{- end }} + {{- if .healthCheckTimeoutMs }} + HEALTH_CHECK_TIMEOUT_MS: {{ .healthCheckTimeoutMs | quote }} + {{- end }} + {{- if .maxStreamingBufferMb }} + MAX_STREAMING_BUFFER_MB: {{ .maxStreamingBufferMb | quote }} + {{- end }} + {{- end }} + + # API config + RUN_MIGRATIONS: {{ .Values.api.runMigrations | default true | quote }} + {{- with .Values.api.config }} + {{- if .keepAliveTimeoutS }} + KEEP_ALIVE_TIMEOUT_S: {{ .keepAliveTimeoutS | quote }} + {{- end }} + {{- if .timeoutMs }} + TIMEOUT_MS: {{ .timeoutMs | quote }} + {{- end }} + {{- end }} + + # Database pool + {{- with .Values.database }} + {{- if .poolMax }} + DATABASE_POOL_MAX: {{ .poolMax | quote }} + {{- end }} + {{- if .poolMin }} + DATABASE_POOL_MIN: {{ .poolMin | quote }} + {{- end }} + {{- if .idleTimeoutMs }} + DATABASE_IDLE_TIMEOUT_MS: {{ .idleTimeoutMs | quote }} + {{- end }} + {{- if .connectionTimeoutMs }} + DATABASE_CONNECTION_TIMEOUT_MS: {{ .connectionTimeoutMs | quote }} + {{- end }} + {{- end }} + + # Worker config + {{- with .Values.worker.config }} + {{- if .backfillDurationSeconds }} + BACKFILL_DURATION_SECONDS: {{ .backfillDurationSeconds | quote }} + {{- end }} + {{- if .enableDataRetentionCleanup }} + ENABLE_DATA_RETENTION_CLEANUP: {{ .enableDataRetentionCleanup | quote }} + {{- end }} + {{- if .emailFollowUps }} + EMAIL_FOLLOW_UPS: {{ .emailFollowUps | quote }} + {{- end }} + {{- if .logQueueBatchSize }} + LOG_QUEUE_BATCH_SIZE: {{ .logQueueBatchSize | quote }} + {{- end }} + {{- if .creditBatchSize }} + CREDIT_BATCH_SIZE: {{ .creditBatchSize | quote }} + {{- end }} + {{- if .creditBatchInterval }} + CREDIT_BATCH_INTERVAL: {{ .creditBatchInterval | quote }} + {{- end }} + {{- end }} + + # Billing + {{- if .Values.billing.billCancelledRequests }} + BILL_CANCELLED_REQUESTS: {{ .Values.billing.billCancelledRequests | quote }} + {{- end }} + {{- if .Values.billing.platformFeePercentage }} + PLATFORM_FEE_PERCENTAGE: {{ .Values.billing.platformFeePercentage | quote }} + {{- end }} + + # Observability + {{- with .Values.observability }} + {{- if .otelServiceName }} + OTEL_SERVICE_NAME: {{ .otelServiceName | quote }} + {{- end }} + {{- if .posthogHost }} + POSTHOG_HOST: {{ .posthogHost | quote }} + {{- end }} + {{- if .telemetryActive }} + TELEMETRY_ACTIVE: {{ .telemetryActive | quote }} + {{- end }} + {{- end }} + + # Content filtering + {{- with .Values.contentFilter }} + {{- if .mode }} + LLM_CONTENT_FILTER_MODE: {{ .mode | quote }} + {{- end }} + {{- if .method }} + LLM_CONTENT_FILTER_METHOD: {{ .method | quote }} + {{- end }} + {{- if .models }} + LLM_CONTENT_FILTER_MODELS: {{ .models | quote }} + {{- end }} + {{- end }} diff --git a/infra/helm/llmgateway/templates/docs-deployment.yaml b/infra/helm/llmgateway/templates/docs-deployment.yaml new file mode 100644 index 0000000000..3f60b66399 --- /dev/null +++ b/infra/helm/llmgateway/templates/docs-deployment.yaml @@ -0,0 +1,60 @@ +{{- if .Values.docs.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "llmgateway.fullname" . }}-docs + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "docs") | nindent 4 }} +spec: + replicas: {{ .Values.docs.replicas | default 1 }} + selector: + matchLabels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "docs") | nindent 6 }} + template: + metadata: + labels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "docs") | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + spec: + {{- include "llmgateway.imagePullSecrets" . | nindent 6 }} + containers: + - name: docs + image: {{ include "llmgateway.image" (dict "context" . "image" .Values.docs.image) }} + imagePullPolicy: {{ .Values.docs.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: 80 + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "llmgateway.configMapName" . }} + {{- with .Values.docs.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + readinessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 20 + periodSeconds: 30 + timeoutSeconds: 5 + resources: + {{- toYaml .Values.docs.resources | nindent 12 }} + {{- with .Values.docs.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.docs.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/docs-service.yaml b/infra/helm/llmgateway/templates/docs-service.yaml new file mode 100644 index 0000000000..1938e01ef6 --- /dev/null +++ b/infra/helm/llmgateway/templates/docs-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.docs.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "llmgateway.fullname" . }}-docs + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "docs") | nindent 4 }} +spec: + type: {{ .Values.docs.service.type | default "ClusterIP" }} + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "docs") | nindent 4 }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/gateway-deployment.yaml b/infra/helm/llmgateway/templates/gateway-deployment.yaml new file mode 100644 index 0000000000..9091caab1b --- /dev/null +++ b/infra/helm/llmgateway/templates/gateway-deployment.yaml @@ -0,0 +1,87 @@ +{{- if .Values.gateway.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "llmgateway.fullname" . }}-gateway + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "gateway") | nindent 4 }} +spec: + replicas: {{ .Values.gateway.replicas | default 1 }} + selector: + matchLabels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "gateway") | nindent 6 }} + template: + metadata: + labels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "gateway") | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + spec: + {{- include "llmgateway.imagePullSecrets" . | nindent 6 }} + initContainers: + - name: wait-for-postgresql + image: busybox:1.37 + command: + - sh + - -c + - | + until nc -z {{ include "llmgateway.postgresql.host" . }} {{ include "llmgateway.postgresql.port" . }}; do + echo "Waiting for PostgreSQL..." + sleep 2 + done + - name: wait-for-redis + image: busybox:1.37 + command: + - sh + - -c + - | + until nc -z {{ include "llmgateway.redis.host" . }} {{ include "llmgateway.redis.port" . }}; do + echo "Waiting for Redis..." + sleep 2 + done + containers: + - name: gateway + image: {{ include "llmgateway.image" (dict "context" . "image" .Values.gateway.image) }} + imagePullPolicy: {{ .Values.gateway.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: 80 + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "llmgateway.configMapName" . }} + - secretRef: + name: {{ include "llmgateway.secretName" . }} + {{- with .Values.gateway.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 15 + failureThreshold: 3 + resources: + {{- toYaml .Values.gateway.resources | nindent 12 }} + terminationGracePeriodSeconds: 120 + {{- with .Values.gateway.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.gateway.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/gateway-service.yaml b/infra/helm/llmgateway/templates/gateway-service.yaml new file mode 100644 index 0000000000..fbb9b8b820 --- /dev/null +++ b/infra/helm/llmgateway/templates/gateway-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.gateway.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "llmgateway.fullname" . }}-gateway + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "gateway") | nindent 4 }} +spec: + type: {{ .Values.gateway.service.type | default "ClusterIP" }} + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "gateway") | nindent 4 }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/ingress.yaml b/infra/helm/llmgateway/templates/ingress.yaml new file mode 100644 index 0000000000..7571ec187c --- /dev/null +++ b/infra/helm/llmgateway/templates/ingress.yaml @@ -0,0 +1,111 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "llmgateway.fullname" . }} + labels: + {{- include "llmgateway.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className | quote }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- if and .Values.ui.enabled .Values.ingress.hosts.ui }} + - host: {{ .Values.ingress.hosts.ui | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "llmgateway.fullname" . }}-ui + port: + name: http + {{- end }} + {{- if and .Values.api.enabled .Values.ingress.hosts.api }} + - host: {{ .Values.ingress.hosts.api | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "llmgateway.fullname" . }}-api + port: + name: http + {{- end }} + {{- if and .Values.gateway.enabled .Values.ingress.hosts.gateway }} + - host: {{ .Values.ingress.hosts.gateway | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "llmgateway.fullname" . }}-gateway + port: + name: http + {{- end }} + {{- if and .Values.playground.enabled .Values.ingress.hosts.playground }} + - host: {{ .Values.ingress.hosts.playground | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "llmgateway.fullname" . }}-playground + port: + name: http + {{- end }} + {{- if and .Values.code.enabled .Values.ingress.hosts.code }} + - host: {{ .Values.ingress.hosts.code | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "llmgateway.fullname" . }}-code + port: + name: http + {{- end }} + {{- if and .Values.docs.enabled .Values.ingress.hosts.docs }} + - host: {{ .Values.ingress.hosts.docs | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "llmgateway.fullname" . }}-docs + port: + name: http + {{- end }} + {{- if and .Values.admin.enabled .Values.ingress.hosts.admin }} + - host: {{ .Values.ingress.hosts.admin | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "llmgateway.fullname" . }}-admin + port: + name: http + {{- end }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/playground-deployment.yaml b/infra/helm/llmgateway/templates/playground-deployment.yaml new file mode 100644 index 0000000000..591ec01d0c --- /dev/null +++ b/infra/helm/llmgateway/templates/playground-deployment.yaml @@ -0,0 +1,63 @@ +{{- if .Values.playground.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "llmgateway.fullname" . }}-playground + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "playground") | nindent 4 }} +spec: + replicas: {{ .Values.playground.replicas | default 1 }} + selector: + matchLabels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "playground") | nindent 6 }} + template: + metadata: + labels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "playground") | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + spec: + {{- include "llmgateway.imagePullSecrets" . | nindent 6 }} + containers: + - name: playground + image: {{ include "llmgateway.image" (dict "context" . "image" .Values.playground.image) }} + imagePullPolicy: {{ .Values.playground.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: 80 + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "llmgateway.configMapName" . }} + - secretRef: + name: {{ include "llmgateway.secretName" . }} + {{- with .Values.playground.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + readinessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 20 + periodSeconds: 30 + timeoutSeconds: 5 + resources: + {{- toYaml .Values.playground.resources | nindent 12 }} + {{- with .Values.playground.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.playground.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/playground-service.yaml b/infra/helm/llmgateway/templates/playground-service.yaml new file mode 100644 index 0000000000..353939158d --- /dev/null +++ b/infra/helm/llmgateway/templates/playground-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.playground.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "llmgateway.fullname" . }}-playground + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "playground") | nindent 4 }} +spec: + type: {{ .Values.playground.service.type | default "ClusterIP" }} + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "playground") | nindent 4 }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/postgresql-service.yaml b/infra/helm/llmgateway/templates/postgresql-service.yaml new file mode 100644 index 0000000000..8f656c1546 --- /dev/null +++ b/infra/helm/llmgateway/templates/postgresql-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.postgresql.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "llmgateway.fullname" . }}-postgresql + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "postgresql") | nindent 4 }} +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: postgresql + protocol: TCP + name: postgresql + selector: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "postgresql") | nindent 4 }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/postgresql-statefulset.yaml b/infra/helm/llmgateway/templates/postgresql-statefulset.yaml new file mode 100644 index 0000000000..1611a8155f --- /dev/null +++ b/infra/helm/llmgateway/templates/postgresql-statefulset.yaml @@ -0,0 +1,79 @@ +{{- if .Values.postgresql.enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "llmgateway.fullname" . }}-postgresql + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "postgresql") | nindent 4 }} +spec: + serviceName: {{ include "llmgateway.fullname" . }}-postgresql + replicas: 1 + selector: + matchLabels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "postgresql") | nindent 6 }} + template: + metadata: + labels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "postgresql") | nindent 8 }} + spec: + {{- include "llmgateway.imagePullSecrets" . | nindent 6 }} + containers: + - name: postgresql + image: {{ .Values.postgresql.image.repository }}:{{ .Values.postgresql.image.tag }} + imagePullPolicy: {{ .Values.postgresql.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: postgresql + containerPort: 5432 + protocol: TCP + env: + - name: POSTGRES_USER + value: {{ .Values.postgresql.user | default "postgres" | quote }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "llmgateway.secretName" . }} + key: POSTGRES_PASSWORD + - name: POSTGRES_DB + value: {{ .Values.postgresql.database | default "llmgateway" | quote }} + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + readinessProbe: + exec: + command: + - pg_isready + - -U + - {{ .Values.postgresql.user | default "postgres" | quote }} + - -d + - {{ .Values.postgresql.database | default "llmgateway" | quote }} + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + exec: + command: + - pg_isready + - -U + - {{ .Values.postgresql.user | default "postgres" | quote }} + - -d + - {{ .Values.postgresql.database | default "llmgateway" | quote }} + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + resources: + {{- toYaml .Values.postgresql.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - ReadWriteOnce + {{- if .Values.postgresql.persistence.storageClass }} + storageClassName: {{ .Values.postgresql.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.postgresql.persistence.size | default "10Gi" }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/redis-service.yaml b/infra/helm/llmgateway/templates/redis-service.yaml new file mode 100644 index 0000000000..e494c40a3e --- /dev/null +++ b/infra/helm/llmgateway/templates/redis-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.redis.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "llmgateway.fullname" . }}-redis + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "redis") | nindent 4 }} +spec: + type: ClusterIP + ports: + - port: 6379 + targetPort: redis + protocol: TCP + name: redis + selector: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "redis") | nindent 4 }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/redis-statefulset.yaml b/infra/helm/llmgateway/templates/redis-statefulset.yaml new file mode 100644 index 0000000000..00200ddcaa --- /dev/null +++ b/infra/helm/llmgateway/templates/redis-statefulset.yaml @@ -0,0 +1,81 @@ +{{- if .Values.redis.enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "llmgateway.fullname" . }}-redis + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "redis") | nindent 4 }} +spec: + serviceName: {{ include "llmgateway.fullname" . }}-redis + replicas: 1 + selector: + matchLabels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "redis") | nindent 6 }} + template: + metadata: + labels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "redis") | nindent 8 }} + spec: + {{- include "llmgateway.imagePullSecrets" . | nindent 6 }} + containers: + - name: redis + image: {{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }} + imagePullPolicy: {{ .Values.redis.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: redis + containerPort: 6379 + protocol: TCP + {{- if .Values.redis.password }} + command: + - redis-server + - --requirepass + - $(REDIS_PASSWORD) + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "llmgateway.secretName" . }} + key: REDIS_PASSWORD + {{- end }} + readinessProbe: + exec: + command: + - redis-cli + {{- if .Values.redis.password }} + - -a + - $(REDIS_PASSWORD) + {{- end }} + - ping + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + exec: + command: + - redis-cli + {{- if .Values.redis.password }} + - -a + - $(REDIS_PASSWORD) + {{- end }} + - ping + initialDelaySeconds: 15 + periodSeconds: 30 + timeoutSeconds: 5 + resources: + {{- toYaml .Values.redis.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /data + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - ReadWriteOnce + {{- if .Values.redis.persistence.storageClass }} + storageClassName: {{ .Values.redis.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.redis.persistence.size | default "2Gi" }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/secret.yaml b/infra/helm/llmgateway/templates/secret.yaml new file mode 100644 index 0000000000..7392dc3254 --- /dev/null +++ b/infra/helm/llmgateway/templates/secret.yaml @@ -0,0 +1,98 @@ +{{- if not .Values.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "llmgateway.fullname" . }} + labels: + {{- include "llmgateway.labels" . | nindent 4 }} +type: Opaque +stringData: + # Database + {{- $pgUser := include "llmgateway.postgresql.user" . }} + {{- $pgHost := include "llmgateway.postgresql.host" . }} + {{- $pgPort := include "llmgateway.postgresql.port" . }} + {{- $pgDB := include "llmgateway.postgresql.database" . }} + {{- if .Values.postgresql.enabled }} + DATABASE_URL: {{ printf "postgres://%s:%s@%s:%s/%s" $pgUser .Values.postgresql.password $pgHost $pgPort $pgDB | quote }} + POSTGRES_PASSWORD: {{ .Values.postgresql.password | quote }} + {{- else }} + DATABASE_URL: {{ printf "postgres://%s:%s@%s:%s/%s" $pgUser .Values.externalPostgresql.password $pgHost $pgPort $pgDB | quote }} + {{- end }} + + # Redis + {{- if .Values.redis.enabled }} + {{- if .Values.redis.password }} + REDIS_PASSWORD: {{ .Values.redis.password | quote }} + {{- end }} + {{- else if .Values.externalRedis.password }} + REDIS_PASSWORD: {{ .Values.externalRedis.password | quote }} + {{- end }} + + # Auth + AUTH_SECRET: {{ .Values.auth.authSecret | quote }} + GATEWAY_API_KEY_HASH_SECRET: {{ .Values.auth.gatewayApiKeyHashSecret | quote }} + + # OAuth providers + {{- if .Values.auth.github.clientId }} + GITHUB_CLIENT_ID: {{ .Values.auth.github.clientId | quote }} + {{- end }} + {{- if .Values.auth.github.clientSecret }} + GITHUB_CLIENT_SECRET: {{ .Values.auth.github.clientSecret | quote }} + {{- end }} + {{- if .Values.auth.google.clientId }} + GOOGLE_CLIENT_ID: {{ .Values.auth.google.clientId | quote }} + {{- end }} + {{- if .Values.auth.google.clientSecret }} + GOOGLE_CLIENT_SECRET: {{ .Values.auth.google.clientSecret | quote }} + {{- end }} + + # LLM Provider API keys + {{- range $key, $value := .Values.llmProviders }} + {{- if $value }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} + + # Stripe + {{- with .Values.stripe }} + {{- if .secretKey }} + STRIPE_SECRET_KEY: {{ .secretKey | quote }} + {{- end }} + {{- if .webhookSecret }} + STRIPE_WEBHOOK_SECRET: {{ .webhookSecret | quote }} + {{- end }} + {{- if .proMonthlyPriceId }} + STRIPE_PRO_MONTHLY_PRICE_ID: {{ .proMonthlyPriceId | quote }} + {{- end }} + {{- if .proYearlyPriceId }} + STRIPE_PRO_YEARLY_PRICE_ID: {{ .proYearlyPriceId | quote }} + {{- end }} + {{- if .devPlanLitePriceId }} + STRIPE_DEV_PLAN_LITE_PRICE_ID: {{ .devPlanLitePriceId | quote }} + {{- end }} + {{- if .devPlanProPriceId }} + STRIPE_DEV_PLAN_PRO_PRICE_ID: {{ .devPlanProPriceId | quote }} + {{- end }} + {{- if .devPlanMaxPriceId }} + STRIPE_DEV_PLAN_MAX_PRICE_ID: {{ .devPlanMaxPriceId | quote }} + {{- end }} + {{- end }} + + # Email + {{- if .Values.email.resendApiKey }} + RESEND_API_KEY: {{ .Values.email.resendApiKey | quote }} + {{- end }} + {{- if .Values.email.resendAudienceId }} + RESEND_AUDIENCE_ID: {{ .Values.email.resendAudienceId | quote }} + {{- end }} + + # Notifications + {{- if .Values.notifications.discordWebhookUrl }} + DISCORD_NOTIFICATION_URL: {{ .Values.notifications.discordWebhookUrl | quote }} + {{- end }} + + # Analytics + {{- if .Values.observability.posthogKey }} + POSTHOG_KEY: {{ .Values.observability.posthogKey | quote }} + {{- end }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/ui-deployment.yaml b/infra/helm/llmgateway/templates/ui-deployment.yaml new file mode 100644 index 0000000000..d99a99eae3 --- /dev/null +++ b/infra/helm/llmgateway/templates/ui-deployment.yaml @@ -0,0 +1,63 @@ +{{- if .Values.ui.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "llmgateway.fullname" . }}-ui + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "ui") | nindent 4 }} +spec: + replicas: {{ .Values.ui.replicas | default 1 }} + selector: + matchLabels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "ui") | nindent 6 }} + template: + metadata: + labels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "ui") | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + spec: + {{- include "llmgateway.imagePullSecrets" . | nindent 6 }} + containers: + - name: ui + image: {{ include "llmgateway.image" (dict "context" . "image" .Values.ui.image) }} + imagePullPolicy: {{ .Values.ui.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: 80 + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "llmgateway.configMapName" . }} + - secretRef: + name: {{ include "llmgateway.secretName" . }} + {{- with .Values.ui.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + readinessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 20 + periodSeconds: 30 + timeoutSeconds: 5 + resources: + {{- toYaml .Values.ui.resources | nindent 12 }} + {{- with .Values.ui.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ui.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/ui-service.yaml b/infra/helm/llmgateway/templates/ui-service.yaml new file mode 100644 index 0000000000..d56f6ac097 --- /dev/null +++ b/infra/helm/llmgateway/templates/ui-service.yaml @@ -0,0 +1,17 @@ +{{- if .Values.ui.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "llmgateway.fullname" . }}-ui + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "ui") | nindent 4 }} +spec: + type: {{ .Values.ui.service.type | default "ClusterIP" }} + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "ui") | nindent 4 }} +{{- end }} diff --git a/infra/helm/llmgateway/templates/worker-deployment.yaml b/infra/helm/llmgateway/templates/worker-deployment.yaml new file mode 100644 index 0000000000..ca1f4d81e0 --- /dev/null +++ b/infra/helm/llmgateway/templates/worker-deployment.yaml @@ -0,0 +1,66 @@ +{{- if .Values.worker.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "llmgateway.fullname" . }}-worker + labels: + {{- include "llmgateway.componentLabels" (dict "context" . "component" "worker") | nindent 4 }} +spec: + replicas: {{ .Values.worker.replicas | default 1 }} + selector: + matchLabels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "worker") | nindent 6 }} + template: + metadata: + labels: + {{- include "llmgateway.componentSelectorLabels" (dict "context" . "component" "worker") | nindent 8 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + spec: + {{- include "llmgateway.imagePullSecrets" . | nindent 6 }} + initContainers: + - name: wait-for-postgresql + image: busybox:1.37 + command: + - sh + - -c + - | + until nc -z {{ include "llmgateway.postgresql.host" . }} {{ include "llmgateway.postgresql.port" . }}; do + echo "Waiting for PostgreSQL..." + sleep 2 + done + - name: wait-for-redis + image: busybox:1.37 + command: + - sh + - -c + - | + until nc -z {{ include "llmgateway.redis.host" . }} {{ include "llmgateway.redis.port" . }}; do + echo "Waiting for Redis..." + sleep 2 + done + containers: + - name: worker + image: {{ include "llmgateway.image" (dict "context" . "image" .Values.worker.image) }} + imagePullPolicy: {{ .Values.worker.image.pullPolicy | default "IfNotPresent" }} + envFrom: + - configMapRef: + name: {{ include "llmgateway.configMapName" . }} + - secretRef: + name: {{ include "llmgateway.secretName" . }} + {{- with .Values.worker.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.worker.resources | nindent 12 }} + {{- with .Values.worker.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.worker.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/infra/helm/llmgateway/values.yaml b/infra/helm/llmgateway/values.yaml new file mode 100644 index 0000000000..c9602200e6 --- /dev/null +++ b/infra/helm/llmgateway/values.yaml @@ -0,0 +1,450 @@ +# -- LLM Gateway Helm Chart Values +# Full documentation: https://llmgateway.io/docs + +# -- Override the chart name +nameOverride: "" +# -- Override the full release name +fullnameOverride: "" +# -- Use an existing Kubernetes Secret instead of creating one (must contain all required keys) +existingSecret: "" + +# ============================================================================= +# Global settings +# ============================================================================= +global: + # -- Default image registry for all services + image: + registry: ghcr.io + # -- Default image tag (overrides appVersion from Chart.yaml) + tag: "" + # -- Additional labels applied to all resources + labels: {} + # -- Image pull secrets for private registries + imagePullSecrets: [] + # -- Node environment + nodeEnv: production + +# ============================================================================= +# Public-facing URLs +# Set these to match your Ingress hostnames or external load balancer URLs. +# ============================================================================= +urls: + ui: "" + api: "" + gateway: "" + playground: "" + code: "" + docs: "" + admin: "" + +# ============================================================================= +# Authentication & Security +# ============================================================================= +auth: + # -- (required) Secret used for signing auth tokens (32+ characters) + authSecret: "change-me-to-a-random-32-char-secret" + # -- (required) HMAC secret for hashing API keys + gatewayApiKeyHashSecret: "change-me-to-a-random-secret" + # -- Cookie domain for session cookies + cookieDomain: "" + # -- Comma-separated list of allowed CORS origins + originUrls: "" + # -- Enable hosted mode + hosted: "" + # -- GitHub OAuth + github: + clientId: "" + clientSecret: "" + # -- Google OAuth + google: + clientId: "" + clientSecret: "" + # -- Passkey / WebAuthn configuration + passkey: + rpId: "" + rpName: "LLMGateway" + +# ============================================================================= +# Database connection pool settings +# ============================================================================= +database: + poolMax: 20 + poolMin: 2 + idleTimeoutMs: 30000 + connectionTimeoutMs: 10000 + +# ============================================================================= +# API Service +# ============================================================================= +api: + enabled: true + replicas: 1 + # -- Run database migrations on startup + runMigrations: true + image: + registry: "" + repository: theopenco/llmgateway-api + tag: "" + pullPolicy: IfNotPresent + service: + type: ClusterIP + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + config: + keepAliveTimeoutS: 60 + timeoutMs: 5000 + # -- Additional environment variables + extraEnv: [] + nodeSelector: {} + tolerations: [] + +# ============================================================================= +# Gateway Service +# ============================================================================= +gateway: + enabled: true + replicas: 1 + image: + registry: "" + repository: theopenco/llmgateway-gateway + tag: "" + pullPolicy: IfNotPresent + service: + type: ClusterIP + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + config: + gatewayTimeoutMs: 300000 + aiStreamingTimeoutMs: 240000 + aiTimeoutMs: 180000 + keepAliveTimeoutS: 620 + shutdownGracePeriodMs: 120000 + healthCheckSkipDatabase: "" + healthCheckTimeoutMs: 15000 + maxStreamingBufferMb: 50 + extraEnv: [] + nodeSelector: {} + tolerations: [] + +# ============================================================================= +# UI Dashboard (Next.js) +# ============================================================================= +ui: + enabled: true + replicas: 1 + image: + registry: "" + repository: theopenco/llmgateway-ui + tag: "" + pullPolicy: IfNotPresent + service: + type: ClusterIP + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + extraEnv: [] + nodeSelector: {} + tolerations: [] + +# ============================================================================= +# Playground (Next.js) +# ============================================================================= +playground: + enabled: true + replicas: 1 + image: + registry: "" + repository: theopenco/llmgateway-playground + tag: "" + pullPolicy: IfNotPresent + service: + type: ClusterIP + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + extraEnv: [] + nodeSelector: {} + tolerations: [] + +# ============================================================================= +# Code App (Next.js) +# ============================================================================= +code: + enabled: true + replicas: 1 + image: + registry: "" + repository: theopenco/llmgateway-code + tag: "" + pullPolicy: IfNotPresent + service: + type: ClusterIP + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + extraEnv: [] + nodeSelector: {} + tolerations: [] + +# ============================================================================= +# Documentation (Next.js) +# ============================================================================= +docs: + enabled: true + replicas: 1 + image: + registry: "" + repository: theopenco/llmgateway-docs + tag: "" + pullPolicy: IfNotPresent + service: + type: ClusterIP + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 100m + memory: 128Mi + extraEnv: [] + nodeSelector: {} + tolerations: [] + +# ============================================================================= +# Admin Dashboard (Enterprise, Next.js) +# ============================================================================= +admin: + enabled: true + replicas: 1 + image: + registry: "" + repository: theopenco/llmgateway-admin + tag: "" + pullPolicy: IfNotPresent + service: + type: ClusterIP + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + extraEnv: [] + nodeSelector: {} + tolerations: [] + +# ============================================================================= +# Background Worker +# ============================================================================= +worker: + enabled: true + replicas: 1 + image: + registry: "" + repository: theopenco/llmgateway-worker + tag: "" + pullPolicy: IfNotPresent + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + config: + backfillDurationSeconds: 300 + enableDataRetentionCleanup: false + emailFollowUps: false + logQueueBatchSize: 100 + creditBatchSize: 100 + creditBatchInterval: 5 + extraEnv: [] + nodeSelector: {} + tolerations: [] + +# ============================================================================= +# Built-in PostgreSQL +# Set enabled: false to use an external database (configure externalPostgresql). +# ============================================================================= +postgresql: + enabled: true + image: + repository: postgres + tag: "17-alpine" + pullPolicy: IfNotPresent + user: postgres + password: "changeme" + database: llmgateway + port: 5432 + persistence: + size: 10Gi + # -- Storage class (leave empty for cluster default) + storageClass: "" + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + +# -- External PostgreSQL (used when postgresql.enabled is false) +externalPostgresql: + host: "" + port: 5432 + user: postgres + password: "" + database: llmgateway + +# ============================================================================= +# Built-in Redis +# Set enabled: false to use an external Redis (configure externalRedis). +# ============================================================================= +redis: + enabled: true + image: + repository: redis + tag: "8-alpine" + pullPolicy: IfNotPresent + # -- Redis password (leave empty for no authentication) + password: "" + port: 6379 + persistence: + size: 2Gi + storageClass: "" + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + +# -- External Redis (used when redis.enabled is false) +externalRedis: + host: "" + port: 6379 + password: "" + +# ============================================================================= +# Ingress +# ============================================================================= +ingress: + enabled: false + # -- Ingress class name (e.g., nginx, traefik) + className: "" + # -- Additional Ingress annotations + annotations: {} + # nginx.ingress.kubernetes.io/proxy-body-size: "50m" + # nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + # cert-manager.io/cluster-issuer: letsencrypt-prod + # -- Per-service hostnames + hosts: + ui: "" + api: "" + gateway: "" + playground: "" + code: "" + docs: "" + admin: "" + # -- TLS configuration + tls: [] + # - secretName: llmgateway-tls + # hosts: + # - app.example.com + # - api.example.com + # - gateway.example.com + +# ============================================================================= +# LLM Provider API Keys +# Only configure the providers you want to use. Keys map directly to env vars. +# ============================================================================= +llmProviders: + # LLM_OPENAI_API_KEY: "" + # LLM_ANTHROPIC_API_KEY: "" + # LLM_GOOGLE_AI_STUDIO_API_KEY: "" + # LLM_GOOGLE_VERTEX_API_KEY: "" + # LLM_GROQ_API_KEY: "" + # LLM_X_AI_API_KEY: "" + # LLM_DEEPSEEK_API_KEY: "" + # LLM_MISTRAL_API_KEY: "" + # LLM_PERPLEXITY_API_KEY: "" + # LLM_TOGETHER_AI_API_KEY: "" + # LLM_ALIBABA_API_KEY: "" + # LLM_NEBIUS_API_KEY: "" + # LLM_CEREBRAS_API_KEY: "" + # LLM_MOONSHOT_API_KEY: "" + # LLM_NOVITA_AI_API_KEY: "" + # LLM_AWS_BEDROCK_API_KEY: "" + # LLM_AZURE_API_KEY: "" + # LLM_INFERENCE_NET_API_KEY: "" + # LLM_NANO_GPT_API_KEY: "" + +# ============================================================================= +# Stripe / Billing (optional) +# ============================================================================= +stripe: + secretKey: "" + webhookSecret: "" + proMonthlyPriceId: "" + proYearlyPriceId: "" + devPlanLitePriceId: "" + devPlanProPriceId: "" + devPlanMaxPriceId: "" + +billing: + billCancelledRequests: "" + platformFeePercentage: "" + +# ============================================================================= +# Email (optional, Resend) +# ============================================================================= +email: + resendApiKey: "" + resendAudienceId: "" + +# ============================================================================= +# Notifications (optional) +# ============================================================================= +notifications: + discordWebhookUrl: "" + +# ============================================================================= +# Observability (optional) +# ============================================================================= +observability: + otelServiceName: "" + posthogKey: "" + posthogHost: "" + telemetryActive: "" + +# ============================================================================= +# Content filtering (optional) +# ============================================================================= +contentFilter: + mode: "" + method: "" + models: ""