chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
{{- /*
NOTES.txt — printed after every `helm install` or `helm upgrade`.
Keep this short and action-oriented. Users land here and decide what to do next.
*/ -}}
Thank you for installing {{ .Chart.Name }} ({{ .Chart.Name }}-{{ .Chart.Version }}, app {{ .Chart.AppVersion }}).
Your release is named {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
1. Watch the rollout finish:
kubectl --namespace {{ .Release.Namespace }} rollout status deployment/{{ include "sim.fullname" . }}-app
{{- if .Values.realtime.enabled }}
kubectl --namespace {{ .Release.Namespace }} rollout status deployment/{{ include "sim.fullname" . }}-realtime
{{- end }}
{{- if .Values.copilot.enabled }}
kubectl --namespace {{ .Release.Namespace }} rollout status deployment/{{ include "sim.fullname" . }}-copilot
{{- end }}
{{- if .Values.pii.enabled }}
kubectl --namespace {{ .Release.Namespace }} rollout status deployment/{{ include "sim.fullname" . }}-pii
{{- end }}
2. Reach the application:
{{- if and .Values.ingress.enabled .Values.ingress.app.host }}
Sim is exposed at:
{{- if .Values.ingress.tls.enabled }}
https://{{ .Values.ingress.app.host }}
{{- else }}
http://{{ .Values.ingress.app.host }}
{{- end }}
If DNS is not yet wired up, point your hostname at the ingress controller's
external IP/LoadBalancer.
{{- else }}
Ingress is disabled. Use port-forward to reach the app locally:
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "sim.fullname" . }}-app {{ .Values.app.service.port | default 3000 }}:{{ .Values.app.service.port | default 3000 }}
Then open http://localhost:{{ .Values.app.service.port | default 3000 }}
To expose Sim on the public internet, enable `ingress.enabled=true` and set
`ingress.app.host`. See examples/values-production.yaml.
{{- end }}
3. Required secrets:
Sim requires three application secrets and a Postgres password to start.
{{- if .Values.externalSecrets.enabled }}
Using External Secrets Operator. Verify the ExternalSecret has synced:
kubectl --namespace {{ .Release.Namespace }} get externalsecret
kubectl --namespace {{ .Release.Namespace }} get secret {{ include "sim.fullname" . }}-app-secrets
{{- else if .Values.app.secrets.existingSecret.enabled }}
Using existing Kubernetes Secret: {{ .Values.app.secrets.existingSecret.name }}
{{- else if or (eq (default "" .Values.app.env.BETTER_AUTH_SECRET) "") (eq (default "" .Values.app.env.ENCRYPTION_KEY) "") (eq (default "" .Values.app.env.INTERNAL_API_SECRET) "") }}
WARNING: one or more required secrets is empty. The pods will fail to start
until these are set. Generate values with:
openssl rand -hex 32 # BETTER_AUTH_SECRET, ENCRYPTION_KEY, INTERNAL_API_SECRET, CRON_SECRET
openssl rand -hex 32 # API_ENCRYPTION_KEY (must be 64 hex chars)
Then pass them on `helm upgrade --set app.env.BETTER_AUTH_SECRET=...` or
provision an existing Kubernetes Secret and set
`app.secrets.existingSecret.enabled=true`.
{{- else }}
Secrets are set inline. For production, prefer pre-existing Kubernetes
Secrets or External Secrets Operator — see
examples/values-existing-secret.yaml and examples/values-external-secrets.yaml.
{{- end }}
4. Useful commands:
# Tail app logs
kubectl --namespace {{ .Release.Namespace }} logs -f deployment/{{ include "sim.fullname" . }}-app
# Show the rendered values
helm get values {{ .Release.Name }} --namespace {{ .Release.Namespace }}
# Upgrade after changing values
helm upgrade {{ .Release.Name }} ./helm/sim --namespace {{ .Release.Namespace }} -f your-values.yaml
5. Upgrade notes (read before upgrading from a chart version released before this one):
* externalSecrets.apiVersion default is "v1beta1" (was "v1"). v1beta1 is
supported by every ESO release from v0.7+ through current. If you're on
ESO v0.17+ and want the graduated v1 API, set externalSecrets.apiVersion: "v1".
* networkPolicy.egress remains a list of custom egress rules (unchanged).
Cloud-metadata CIDR blocking is now configured via networkPolicy.egressExceptCidrs
(defaults to AWS/GCP/Azure IMDS + ECS task metadata).
6. Where to go next:
* Production checklist: helm/sim/README.md (search "Production checklist")
* Troubleshooting: helm/sim/README.md (search "Troubleshooting")
* Docs: https://docs.sim.ai
* Issues: https://github.com/simstudioai/sim/issues
+683
View File
@@ -0,0 +1,683 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "sim.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "sim.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 "sim.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "sim.labels" -}}
helm.sh/chart: {{ include "sim.chart" . }}
{{ include "sim.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- with .Values.global.commonLabels }}
{{ toYaml . }}
{{- end }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "sim.selectorLabels" -}}
app.kubernetes.io/name: {{ include "sim.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
App specific labels
*/}}
{{- define "sim.app.labels" -}}
{{ include "sim.labels" . }}
app.kubernetes.io/component: app
{{- end }}
{{/*
App selector labels
*/}}
{{- define "sim.app.selectorLabels" -}}
{{ include "sim.selectorLabels" . }}
app.kubernetes.io/component: app
{{- end }}
{{/*
Realtime specific labels
*/}}
{{- define "sim.realtime.labels" -}}
{{ include "sim.labels" . }}
app.kubernetes.io/component: realtime
{{- end }}
{{/*
Realtime selector labels
*/}}
{{- define "sim.realtime.selectorLabels" -}}
{{ include "sim.selectorLabels" . }}
app.kubernetes.io/component: realtime
{{- end }}
{{/*
PostgreSQL specific labels
*/}}
{{- define "sim.postgresql.labels" -}}
{{ include "sim.labels" . }}
app.kubernetes.io/component: postgresql
{{- end }}
{{/*
PostgreSQL selector labels
*/}}
{{- define "sim.postgresql.selectorLabels" -}}
{{ include "sim.selectorLabels" . }}
app.kubernetes.io/component: postgresql
{{- end }}
{{/*
Ollama specific labels
*/}}
{{- define "sim.ollama.labels" -}}
{{ include "sim.labels" . }}
app.kubernetes.io/component: ollama
{{- end }}
{{/*
Ollama selector labels
*/}}
{{- define "sim.ollama.selectorLabels" -}}
{{ include "sim.selectorLabels" . }}
app.kubernetes.io/component: ollama
{{- end }}
{{/*
PII (Presidio) specific labels
*/}}
{{- define "sim.pii.labels" -}}
{{ include "sim.labels" . }}
app.kubernetes.io/component: pii
{{- end }}
{{/*
PII (Presidio) selector labels
*/}}
{{- define "sim.pii.selectorLabels" -}}
{{ include "sim.selectorLabels" . }}
app.kubernetes.io/component: pii
{{- end }}
{{/*
Migrations specific labels
*/}}
{{- define "sim.migrations.labels" -}}
{{ include "sim.labels" . }}
app.kubernetes.io/component: migrations
{{- end }}
{{/*
Copilot specific labels
*/}}
{{- define "sim.copilot.labels" -}}
{{ include "sim.labels" . }}
app.kubernetes.io/component: copilot
{{- end }}
{{/*
Copilot selector labels
*/}}
{{- define "sim.copilot.selectorLabels" -}}
{{ include "sim.selectorLabels" . }}
app.kubernetes.io/component: copilot
{{- end }}
{{/*
Copilot PostgreSQL specific labels
*/}}
{{- define "sim.copilotPostgresql.labels" -}}
{{ include "sim.labels" . }}
app.kubernetes.io/component: copilot-postgresql
{{- end }}
{{/*
Copilot PostgreSQL selector labels
*/}}
{{- define "sim.copilotPostgresql.selectorLabels" -}}
{{ include "sim.selectorLabels" . }}
app.kubernetes.io/component: copilot-postgresql
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "sim.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "sim.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Create image name with optional registry and digest pinning.
Accepts a context dict with:
imageRoot the image object (repository, optional tag, optional digest, pullPolicy)
global .Values.global (for imageRegistry and useRegistryForAllImages)
chartAppVersion .Chart.AppVersion (used as default tag when imageRoot.tag is empty)
Resolution order:
1. If imageRoot.digest is set, render "<registry?/>repo@<digest>"
2. Else render "<registry?/>repo:<tag>" where tag defaults to chartAppVersion
Usage: {{ include "sim.image" (dict "imageRoot" .Values.app.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
*/}}
{{- define "sim.image" -}}
{{- $imageRoot := .imageRoot -}}
{{- $global := .global -}}
{{- $repository := $imageRoot.repository -}}
{{- $digest := $imageRoot.digest | default "" -}}
{{- $tag := $imageRoot.tag | default "" | toString -}}
{{- if and (eq $tag "") (eq $digest "") -}}
{{- $tag = .chartAppVersion | default "" | toString -}}
{{- end -}}
{{- $registry := "" -}}
{{- if and $global $global.imageRegistry -}}
{{- if or (hasPrefix "simstudioai/" $repository) $global.useRegistryForAllImages -}}
{{- $registry = $global.imageRegistry -}}
{{- end -}}
{{- end -}}
{{- $repoPath := $repository -}}
{{- if $registry -}}
{{- $repoPath = printf "%s/%s" $registry $repository -}}
{{- end -}}
{{- if ne $digest "" -}}
{{- printf "%s@%s" $repoPath $digest }}
{{- else -}}
{{- $resolvedTag := required (printf "sim.image: no tag or digest resolvable for %q (set imageRoot.tag, imageRoot.digest, or Chart.AppVersion)" $repository) $tag -}}
{{- printf "%s:%s" $repoPath $resolvedTag }}
{{- end -}}
{{- end }}
{{/*
Database URL for internal PostgreSQL
*/}}
{{- define "sim.databaseUrl" -}}
{{- if .Values.postgresql.enabled }}
{{- $host := printf "%s-postgresql" (include "sim.fullname" .) }}
{{- $port := .Values.postgresql.service.port }}
{{- $username := .Values.postgresql.auth.username }}
{{- $database := .Values.postgresql.auth.database }}
{{- $sslMode := ternary "require" "disable" .Values.postgresql.tls.enabled }}
{{- printf "postgresql://%s:$(POSTGRES_PASSWORD)@%s:%v/%s?sslmode=%s" $username $host $port $database $sslMode }}
{{- else if .Values.externalDatabase.enabled }}
{{- $host := .Values.externalDatabase.host }}
{{- $port := .Values.externalDatabase.port }}
{{- $username := .Values.externalDatabase.username }}
{{- $database := .Values.externalDatabase.database }}
{{- $sslMode := .Values.externalDatabase.sslMode }}
{{- printf "postgresql://%s:$(EXTERNAL_DB_PASSWORD)@%s:%v/%s?sslmode=%s" $username $host $port $database $sslMode }}
{{- end }}
{{- end }}
{{/*
Validate required secrets and reject default placeholder values
Skip validation when using existing secrets or External Secrets Operator
*/}}
{{- define "sim.validateSecrets" -}}
{{- $useExistingAppSecret := and .Values.app.secrets .Values.app.secrets.existingSecret .Values.app.secrets.existingSecret.enabled }}
{{- $useExternalSecrets := and .Values.externalSecrets .Values.externalSecrets.enabled }}
{{- $useExistingPostgresSecret := and .Values.postgresql.auth.existingSecret .Values.postgresql.auth.existingSecret.enabled }}
{{- $useExistingExternalDbSecret := and .Values.externalDatabase.existingSecret .Values.externalDatabase.existingSecret.enabled }}
{{- /* App secrets validation - skip if using existing secret or ESO */ -}}
{{- if not (or $useExistingAppSecret $useExternalSecrets) }}
{{- if and .Values.app.enabled (not .Values.app.env.BETTER_AUTH_SECRET) }}
{{- fail "app.env.BETTER_AUTH_SECRET is required for production deployment" }}
{{- end }}
{{- if and .Values.app.enabled (eq .Values.app.env.BETTER_AUTH_SECRET "CHANGE-ME-32-CHAR-SECRET-FOR-PRODUCTION-USE") }}
{{- fail "app.env.BETTER_AUTH_SECRET must not use the default placeholder value. Generate a secure secret with: openssl rand -hex 32" }}
{{- end }}
{{- if and .Values.app.enabled (not .Values.app.env.ENCRYPTION_KEY) }}
{{- fail "app.env.ENCRYPTION_KEY is required for production deployment" }}
{{- end }}
{{- if and .Values.app.enabled (eq .Values.app.env.ENCRYPTION_KEY "CHANGE-ME-32-CHAR-ENCRYPTION-KEY-FOR-PROD") }}
{{- fail "app.env.ENCRYPTION_KEY must not use the default placeholder value. Generate a secure key with: openssl rand -hex 32" }}
{{- end }}
{{- if and .Values.app.enabled (not .Values.app.env.INTERNAL_API_SECRET) }}
{{- fail "app.env.INTERNAL_API_SECRET is required for production deployment (shared auth between sim-app and sim-realtime pods). Generate one with: openssl rand -hex 32" }}
{{- end }}
{{- if and .Values.realtime.enabled (eq .Values.realtime.env.BETTER_AUTH_SECRET "CHANGE-ME-32-CHAR-SECRET-FOR-PRODUCTION-USE") }}
{{- fail "realtime.env.BETTER_AUTH_SECRET must not use the default placeholder value. Generate a secure secret with: openssl rand -hex 32" }}
{{- end }}
{{- /* CRON_SECRET is required when cronjobs are enabled pods reference it via secretKeyRef and would fail to start if the key is missing from the Secret. */ -}}
{{- if and .Values.cronjobs.enabled (not .Values.app.env.CRON_SECRET) }}
{{- fail "app.env.CRON_SECRET is required when cronjobs.enabled=true (every cron pod authenticates with this token). Generate one with: openssl rand -hex 32, or set cronjobs.enabled=false." }}
{{- end }}
{{- end }}
{{- /* PostgreSQL password validation - skip if using existing secret or ESO */ -}}
{{- if not (or $useExistingPostgresSecret $useExternalSecrets) }}
{{- if and .Values.postgresql.enabled (not .Values.postgresql.auth.password) }}
{{- fail "postgresql.auth.password is required when using internal PostgreSQL" }}
{{- end }}
{{- if and .Values.postgresql.enabled (eq .Values.postgresql.auth.password "CHANGE-ME-SECURE-PASSWORD") }}
{{- fail "postgresql.auth.password must not use the default placeholder value. Set a secure password for production" }}
{{- end }}
{{- if and .Values.postgresql.enabled .Values.postgresql.auth.password (not (regexMatch "^[a-zA-Z0-9._-]+$" .Values.postgresql.auth.password)) }}
{{- fail "postgresql.auth.password must only contain alphanumeric characters, hyphens, underscores, or periods to ensure DATABASE_URL compatibility. Generate with: openssl rand -base64 16 | tr -d '/+='" }}
{{- end }}
{{- end }}
{{- /* External database password validation - skip if using existing secret or ESO */ -}}
{{- if not (or $useExistingExternalDbSecret $useExternalSecrets) }}
{{- if and .Values.externalDatabase.enabled (not .Values.externalDatabase.password) }}
{{- fail "externalDatabase.password is required when using external database" }}
{{- end }}
{{- if and .Values.externalDatabase.enabled .Values.externalDatabase.password (not (regexMatch "^[a-zA-Z0-9._-]+$" .Values.externalDatabase.password)) }}
{{- fail "externalDatabase.password must only contain alphanumeric characters, hyphens, underscores, or periods to ensure DATABASE_URL compatibility." }}
{{- end }}
{{- end }}
{{- /* ESO coverage validation - every key set in app.env / realtime.env must be mapped in externalSecrets.remoteRefs.app */ -}}
{{- include "sim.validateExternalSecretCoverage" . }}
{{- end }}
{{/*
Validate that every key set in app.env / realtime.env is also mapped in
externalSecrets.remoteRefs.app when ESO is enabled. When ESO is on, the
chart-managed Secret is not rendered anything not mapped via ESO would
be silently missing at runtime.
Chart-computed keys (DATABASE_URL, SOCKET_SERVER_URL, OLLAMA_URL, PII_URL)
are exempt because they're inlined on the container, not sourced from the
Secret.
Fail-fast is only safe for ESO because we can introspect remoteRefs at
template time. For existingSecret we cannot read the user's pre-created
Secret, so coverage there is documented (values.yaml + README) rather
than enforced.
*/}}
{{- define "sim.validateExternalSecretCoverage" -}}
{{- if and .Values.externalSecrets .Values.externalSecrets.enabled -}}
{{- $remoteRefs := default (dict) (default (dict) .Values.externalSecrets.remoteRefs).app -}}
{{- $chartComputed := list "DATABASE_URL" "SOCKET_SERVER_URL" "OLLAMA_URL" "PII_URL" -}}
{{- $appEnv := default (dict) .Values.app.env -}}
{{/*
Required-key coverage: these are non-optional at runtime. With ESO enabled
the chart-managed Secret is not rendered, so a missing key surfaces as a
runtime CrashLoopBackOff with cryptic env errors. Fail at template time
if the key is neither set in app.env nor mapped via remoteRefs.app.
*/}}
{{- if .Values.app.enabled -}}
{{- $required := list "BETTER_AUTH_SECRET" "ENCRYPTION_KEY" "INTERNAL_API_SECRET" -}}
{{- if .Values.cronjobs.enabled -}}
{{- $required = append $required "CRON_SECRET" -}}
{{- end -}}
{{- range $key := $required -}}
{{- $inEnv := index $appEnv $key -}}
{{- $mapped := index $remoteRefs $key -}}
{{- if and (or (not $inEnv) (eq (toString $inEnv) "") (eq (toString $inEnv) "<nil>")) (not $mapped) -}}
{{- fail (printf "Required key '%s' is missing: externalSecrets.enabled=true but the key is neither set in app.env nor mapped in externalSecrets.remoteRefs.app. Map it via externalSecrets.remoteRefs.app.%s='path/in/store' so it is synced into the app Secret." $key $key) }}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if .Values.app.enabled -}}
{{- range $key, $value := default (dict) .Values.app.env -}}
{{- if not (has $key $chartComputed) -}}
{{- if and (ne (toString $value) "") (ne (toString $value) "<nil>") -}}
{{- $mapped := index $remoteRefs $key -}}
{{- if not $mapped -}}
{{- fail (printf "Key '%s' is set in app.env but externalSecrets.enabled=true and externalSecrets.remoteRefs.app.%s is not configured. When ESO is enabled the chart-managed app Secret is not rendered, so the container would start with no value. Either map it via externalSecrets.remoteRefs.app.%s='path/in/store' or remove it from app.env." $key $key $key) }}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if .Values.realtime.enabled -}}
{{- range $key, $value := default (dict) .Values.realtime.env -}}
{{- if not (has $key $chartComputed) -}}
{{- if and (ne (toString $value) "") (ne (toString $value) "<nil>") -}}
{{- $mapped := index $remoteRefs $key -}}
{{- if not $mapped -}}
{{- fail (printf "Key '%s' is set in realtime.env but externalSecrets.enabled=true and externalSecrets.remoteRefs.app.%s is not configured. Either map it via externalSecrets.remoteRefs.app.%s='path/in/store' or remove it from realtime.env." $key $key $key) }}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end }}
{{/*
Get the app secrets name
Returns the name of the secret containing app credentials (auth, encryption keys)
*/}}
{{- define "sim.appSecretName" -}}
{{- if and .Values.app.secrets .Values.app.secrets.existingSecret .Values.app.secrets.existingSecret.enabled -}}
{{- .Values.app.secrets.existingSecret.name -}}
{{- else -}}
{{- printf "%s-app-secrets" (include "sim.fullname" .) -}}
{{- end -}}
{{- end }}
{{/*
Get the PostgreSQL secret name
Returns the name of the secret containing PostgreSQL password
*/}}
{{- define "sim.postgresqlSecretName" -}}
{{- if and .Values.postgresql.auth.existingSecret .Values.postgresql.auth.existingSecret.enabled -}}
{{- .Values.postgresql.auth.existingSecret.name -}}
{{- else -}}
{{- printf "%s-postgresql-secret" (include "sim.fullname" .) -}}
{{- end -}}
{{- end }}
{{/*
Get the PostgreSQL password key name
Returns the key name in the secret that contains the password
*/}}
{{- define "sim.postgresqlPasswordKey" -}}
{{- if and .Values.postgresql.auth.existingSecret .Values.postgresql.auth.existingSecret.enabled -}}
{{- .Values.postgresql.auth.existingSecret.passwordKey | default "POSTGRES_PASSWORD" -}}
{{- else -}}
{{- print "POSTGRES_PASSWORD" -}}
{{- end -}}
{{- end }}
{{/*
Get the external database secret name
Returns the name of the secret containing external database password
*/}}
{{- define "sim.externalDbSecretName" -}}
{{- if and .Values.externalDatabase.existingSecret .Values.externalDatabase.existingSecret.enabled -}}
{{- .Values.externalDatabase.existingSecret.name -}}
{{- else -}}
{{- printf "%s-external-db-secret" (include "sim.fullname" .) -}}
{{- end -}}
{{- end }}
{{/*
Get the external database password key name
Returns the key name in the secret that contains the password
*/}}
{{- define "sim.externalDbPasswordKey" -}}
{{- if and .Values.externalDatabase.existingSecret .Values.externalDatabase.existingSecret.enabled -}}
{{- .Values.externalDatabase.existingSecret.passwordKey | default "EXTERNAL_DB_PASSWORD" -}}
{{- else -}}
{{- print "EXTERNAL_DB_PASSWORD" -}}
{{- end -}}
{{- end }}
{{/*
Check if app secrets should be created by the chart
Returns true if we should create the app secrets (not using existing or ESO)
*/}}
{{- define "sim.createAppSecrets" -}}
{{- $useExistingAppSecret := and .Values.app.secrets .Values.app.secrets.existingSecret .Values.app.secrets.existingSecret.enabled }}
{{- $useExternalSecrets := and .Values.externalSecrets .Values.externalSecrets.enabled }}
{{- if not (or $useExistingAppSecret $useExternalSecrets) -}}
true
{{- end -}}
{{- end }}
{{/*
Check if PostgreSQL secret should be created by the chart
Returns true if we should create the PostgreSQL secret (not using existing or ESO)
*/}}
{{- define "sim.createPostgresqlSecret" -}}
{{- $useExistingSecret := and .Values.postgresql.auth.existingSecret .Values.postgresql.auth.existingSecret.enabled }}
{{- $useExternalSecrets := and .Values.externalSecrets .Values.externalSecrets.enabled }}
{{- if not (or $useExistingSecret $useExternalSecrets) -}}
true
{{- end -}}
{{- end }}
{{/*
Check if external database secret should be created by the chart
Returns true if we should create the external database secret (not using existing or ESO)
*/}}
{{- define "sim.createExternalDbSecret" -}}
{{- $useExistingSecret := and .Values.externalDatabase.existingSecret .Values.externalDatabase.existingSecret.enabled }}
{{- $useExternalSecrets := and .Values.externalSecrets .Values.externalSecrets.enabled }}
{{- if not (or $useExistingSecret $useExternalSecrets) -}}
true
{{- end -}}
{{- end }}
{{/*
Ollama URL
*/}}
{{- define "sim.ollamaUrl" -}}
{{- if .Values.ollama.enabled }}
{{- $serviceName := printf "%s-ollama" (include "sim.fullname" .) }}
{{- $port := .Values.ollama.service.port }}
{{- printf "http://%s:%v" $serviceName $port }}
{{- else }}
{{- .Values.app.env.OLLAMA_URL | default "http://localhost:11434" }}
{{- end }}
{{- end }}
{{/*
PII (Presidio) service URL
*/}}
{{- define "sim.piiUrl" -}}
{{- if .Values.pii.enabled }}
{{- $serviceName := printf "%s-pii" (include "sim.fullname" .) }}
{{- $port := .Values.pii.service.port }}
{{- printf "http://%s:%v" $serviceName $port }}
{{- else }}
{{- .Values.app.env.PII_URL | default "http://localhost:5001" }}
{{- end }}
{{- end }}
{{/*
Socket Server URL (internal)
*/}}
{{- define "sim.socketServerUrl" -}}
{{- if .Values.realtime.enabled }}
{{- $serviceName := printf "%s-realtime" (include "sim.fullname" .) }}
{{- $port := .Values.realtime.service.port }}
{{- printf "http://%s:%v" $serviceName $port }}
{{- else }}
{{- .Values.app.env.SOCKET_SERVER_URL | default "http://localhost:3002" }}
{{- end }}
{{- end }}
{{/*
Resource limits and requests
*/}}
{{- define "sim.resources" -}}
{{- if .resources }}
resources:
{{- if .resources.limits }}
limits:
{{- toYaml .resources.limits | nindent 4 }}
{{- end }}
{{- if .resources.requests }}
requests:
{{- toYaml .resources.requests | nindent 4 }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Pod-level security context with Pod Security Standards "restricted" defaults.
User-supplied `.podSecurityContext` values override defaults (user wins).
Usage: {{ include "sim.podSecurityContext" .Values.app | nindent 6 }}
*/}}
{{- define "sim.podSecurityContext" -}}
{{- $defaults := dict "runAsNonRoot" true "runAsUser" 1001 "runAsGroup" 1001 "fsGroup" 1001 "seccompProfile" (dict "type" "RuntimeDefault") -}}
{{- $user := default (dict) .podSecurityContext -}}
{{- $merged := mergeOverwrite (deepCopy $defaults) $user -}}
securityContext:
{{- toYaml $merged | nindent 2 }}
{{- end }}
{{/*
Container-level security context with Pod Security Standards "restricted" defaults.
User-supplied `.securityContext` values override defaults (user wins).
`readOnlyRootFilesystem` is intentionally NOT defaulted set it per-workload in values
when the container can tolerate a read-only root (Next.js writes to `.next/cache`,
Postgres writes to `/var/lib/postgresql/data`, so they're left writable by default).
Usage: {{ include "sim.containerSecurityContext" .Values.app | nindent 10 }}
*/}}
{{- define "sim.containerSecurityContext" -}}
{{- $defaults := dict "runAsNonRoot" true "allowPrivilegeEscalation" false "capabilities" (dict "drop" (list "ALL")) "seccompProfile" (dict "type" "RuntimeDefault") -}}
{{- $user := default (dict) .securityContext -}}
{{- $merged := mergeOverwrite (deepCopy $defaults) $user -}}
securityContext:
{{- toYaml $merged | nindent 2 }}
{{- end }}
{{/*
Backwards-compatible alias for container security context.
*/}}
{{- define "sim.securityContext" -}}
{{- include "sim.containerSecurityContext" . }}
{{- end }}
{{/*
Node selector
*/}}
{{- define "sim.nodeSelector" -}}
{{- if .nodeSelector }}
nodeSelector:
{{- toYaml .nodeSelector | nindent 2 }}
{{- end }}
{{- end }}
{{/*
Tolerations
*/}}
{{- define "sim.tolerations" -}}
{{- if .tolerations }}
tolerations:
{{- toYaml .tolerations | nindent 2 }}
{{- end }}
{{- end }}
{{/*
Affinity
*/}}
{{- define "sim.affinity" -}}
{{- if .affinity }}
affinity:
{{- toYaml .affinity | nindent 2 }}
{{- end }}
{{- end }}
{{/*
Topology spread constraints — spreads pods across failure domains.
Pass the per-component spec (.Values.app, .Values.realtime, ...). Users supply
the full constraint list including labelSelector; pattern mirrors affinity.
Usage: {{ include "sim.topologySpreadConstraints" .Values.app | nindent 6 }}
*/}}
{{- define "sim.topologySpreadConstraints" -}}
{{- if .topologySpreadConstraints }}
topologySpreadConstraints:
{{- toYaml .topologySpreadConstraints | nindent 2 }}
{{- end }}
{{- end }}
{{/*
Copilot environment secret name
*/}}
{{- define "sim.copilot.envSecretName" -}}
{{- if and .Values.copilot.server.secret.name (ne .Values.copilot.server.secret.name "") -}}
{{- .Values.copilot.server.secret.name -}}
{{- else -}}
{{- printf "%s-copilot-env" (include "sim.fullname" .) -}}
{{- end -}}
{{- end }}
{{/*
Copilot database secret name
*/}}
{{- define "sim.copilot.databaseSecretName" -}}
{{- if .Values.copilot.postgresql.enabled -}}
{{- printf "%s-copilot-postgresql-secret" (include "sim.fullname" .) -}}
{{- else if and .Values.copilot.database.existingSecretName (ne .Values.copilot.database.existingSecretName "") -}}
{{- .Values.copilot.database.existingSecretName -}}
{{- else -}}
{{- printf "%s-copilot-database-secret" (include "sim.fullname" .) -}}
{{- end -}}
{{- end }}
{{/*
Copilot database secret key
*/}}
{{- define "sim.copilot.databaseSecretKey" -}}
{{- default "DATABASE_URL" .Values.copilot.database.secretKey -}}
{{- end }}
{{/*
Validate Copilot configuration
*/}}
{{- define "sim.copilot.validate" -}}
{{- if .Values.copilot.enabled -}}
{{- if and (not .Values.copilot.server.secret.create) (or (not .Values.copilot.server.secret.name) (eq .Values.copilot.server.secret.name "")) -}}
{{- fail "copilot.server.secret.name must be provided when copilot.server.secret.create=false" -}}
{{- end -}}
{{- if .Values.copilot.server.secret.create -}}
{{- $env := .Values.copilot.server.env -}}
{{- $useExternalSecrets := and .Values.externalSecrets .Values.externalSecrets.enabled -}}
{{- $remoteRefs := default (dict) (default (dict) .Values.externalSecrets.remoteRefs).copilot -}}
{{- $required := list "AGENT_API_DB_ENCRYPTION_KEY" "INTERNAL_API_SECRET" "LICENSE_KEY" "SIM_BASE_URL" "SIM_AGENT_API_KEY" "REDIS_URL" -}}
{{- range $key := $required -}}
{{- $inEnv := and $env (index $env $key) (ne (index $env $key) "") -}}
{{- $mapped := index $remoteRefs $key -}}
{{- if not (or $inEnv (and $useExternalSecrets $mapped)) -}}
{{- if $useExternalSecrets -}}
{{- fail (printf "Required key '%s' is missing: externalSecrets.enabled=true but the key is neither set in copilot.server.env nor mapped in externalSecrets.remoteRefs.copilot. Map it via externalSecrets.remoteRefs.copilot.%s='path/in/store' so it is synced into the copilot Secret." $key $key) -}}
{{- else -}}
{{- fail (printf "copilot.server.env.%s is required when copilot is enabled" $key) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if $useExternalSecrets -}}
{{- range $key, $value := $env -}}
{{- if and (ne (toString $value) "") (ne (toString $value) "<nil>") -}}
{{- if not (index $remoteRefs $key) -}}
{{- fail (printf "Key '%s' is set in copilot.server.env but externalSecrets.enabled=true and externalSecrets.remoteRefs.copilot.%s is not configured. When ESO is enabled the chart-managed copilot Secret is not rendered, so the container would start with no value. Either map it via externalSecrets.remoteRefs.copilot.%s='path/in/store' or remove it from copilot.server.env." $key $key $key) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- $hasOpenAI := or (and $env (ne (default "" (index $env "OPENAI_API_KEY_1")) "")) (and $useExternalSecrets (index $remoteRefs "OPENAI_API_KEY_1")) -}}
{{- $hasAnthropic := or (and $env (ne (default "" (index $env "ANTHROPIC_API_KEY_1")) "")) (and $useExternalSecrets (index $remoteRefs "ANTHROPIC_API_KEY_1")) -}}
{{- if not (or $hasOpenAI $hasAnthropic) -}}
{{- fail "Set at least one of copilot.server.env.OPENAI_API_KEY_1 or copilot.server.env.ANTHROPIC_API_KEY_1 (or map one via externalSecrets.remoteRefs.copilot when externalSecrets.enabled=true)" -}}
{{- end -}}
{{- end -}}
{{- if .Values.copilot.postgresql.enabled -}}
{{- if or (not .Values.copilot.postgresql.auth.password) (eq .Values.copilot.postgresql.auth.password "") -}}
{{- fail "copilot.postgresql.auth.password is required when copilot.postgresql.enabled=true" -}}
{{- end -}}
{{- else -}}
{{- if and (or (not .Values.copilot.database.existingSecretName) (eq .Values.copilot.database.existingSecretName "")) (or (not .Values.copilot.database.url) (eq .Values.copilot.database.url "")) -}}
{{- fail "Provide copilot.database.existingSecretName or copilot.database.url when copilot.postgresql.enabled=false" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end }}
@@ -0,0 +1,84 @@
{{- if .Values.certManager.enabled }}
{{- /*
cert-manager Issuer Bootstrap Pattern
PREREQUISITE: cert-manager must be installed in your cluster before enabling this.
The root CA Certificate is created in the namespace specified by certManager.rootCA.namespace
(defaults to "cert-manager"). Ensure this namespace exists and cert-manager is running there.
Install cert-manager: https://cert-manager.io/docs/installation/
This implements the recommended pattern from cert-manager documentation:
1. A self-signed ClusterIssuer (for bootstrapping the root CA only)
2. A root CA Certificate (self-signed, used to sign other certificates)
3. A CA ClusterIssuer (uses the root CA to sign certificates)
Reference: https://cert-manager.io/docs/configuration/selfsigned/
*/ -}}
---
# 1. Self-Signed ClusterIssuer (Bootstrap Only)
# This issuer is used ONLY to create the root CA certificate.
# It should NOT be used directly for application certificates.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: {{ .Values.certManager.selfSignedIssuer.name }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: cert-manager
spec:
selfSigned: {}
---
# 2. Root CA Certificate
# This certificate is signed by the self-signed issuer and becomes the root of trust.
# The secret created here will be used by the CA issuer to sign certificates.
# NOTE: This must be created in the cert-manager namespace (or the namespace specified
# in certManager.rootCA.namespace). Ensure cert-manager is installed there first.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ .Values.certManager.rootCA.certificateName }}
namespace: {{ .Values.certManager.rootCA.namespace | default "cert-manager" }} # Must match cert-manager's cluster-resource-namespace
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: cert-manager
spec:
isCA: true
commonName: {{ .Values.certManager.rootCA.commonName }}
secretName: {{ .Values.certManager.rootCA.secretName }}
duration: {{ .Values.certManager.rootCA.duration | default "87600h" }}
renewBefore: {{ .Values.certManager.rootCA.renewBefore | default "2160h" }}
privateKey:
algorithm: {{ .Values.certManager.rootCA.privateKey.algorithm | default "RSA" }}
size: {{ .Values.certManager.rootCA.privateKey.size | default 4096 }}
subject:
organizations:
{{- if .Values.certManager.rootCA.subject.organizations }}
{{- toYaml .Values.certManager.rootCA.subject.organizations | nindent 6 }}
{{- else }}
- {{ .Release.Name }}
{{- end }}
issuerRef:
name: {{ .Values.certManager.selfSignedIssuer.name }}
kind: ClusterIssuer
group: cert-manager.io
---
# 3. CA ClusterIssuer
# This is the issuer that should be used by applications to obtain certificates.
# It signs certificates using the root CA created above.
# NOTE: This issuer may briefly show "not ready" on first install while cert-manager
# processes the Certificate above and creates the secret. It will auto-reconcile.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: {{ .Values.certManager.caIssuer.name }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: cert-manager
spec:
ca:
secretName: {{ .Values.certManager.rootCA.secretName }}
{{- end }}
@@ -0,0 +1,35 @@
{{- if and .Values.postgresql.enabled .Values.postgresql.tls.enabled }}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: {{ include "sim.fullname" . }}-postgresql-tls-certificate
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.postgresql.labels" . | nindent 4 }}
spec:
secretName: {{ .Values.postgresql.tls.certificatesSecret }}
duration: {{ .Values.postgresql.tls.duration | default "87600h" }} # Default: 10 years
renewBefore: {{ .Values.postgresql.tls.renewBefore | default "2160h" }} # Default: 90 days before expiry
isCA: false
privateKey:
algorithm: {{ .Values.postgresql.tls.privateKey.algorithm | default "RSA" }}
size: {{ .Values.postgresql.tls.privateKey.size | default 4096 }}
{{- if .Values.postgresql.tls.rotationPolicy }}
rotationPolicy: {{ .Values.postgresql.tls.rotationPolicy }}
{{- end }}
usages:
- server auth
- client auth
dnsNames:
- {{ include "sim.fullname" . }}-postgresql
- {{ include "sim.fullname" . }}-postgresql.{{ .Release.Namespace }}.svc.cluster.local
{{- with .Values.postgresql.tls.additionalDnsNames }}
{{- toYaml . | nindent 2 }}
{{- end }}
issuerRef:
name: {{ .Values.postgresql.tls.issuerRef.name }}
kind: {{ .Values.postgresql.tls.issuerRef.kind | default "ClusterIssuer" }}
{{- if .Values.postgresql.tls.issuerRef.group }}
group: {{ .Values.postgresql.tls.issuerRef.group }}
{{- end }}
{{- end }}
@@ -0,0 +1,25 @@
{{- if and .Values.branding.enabled (or .Values.branding.files .Values.branding.binaryFiles) }}
---
# Branding ConfigMap
# Mounts custom branding assets (logos, CSS, etc.) into the application
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "sim.fullname" . }}-branding
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: branding
{{- if .Values.branding.files }}
data:
{{- range $key, $value := .Values.branding.files }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- if .Values.branding.binaryFiles }}
binaryData:
{{- range $key, $value := .Values.branding.binaryFiles }}
{{ $key }}: {{ $value }}
{{- end }}
{{- end }}
{{- end }}
+98
View File
@@ -0,0 +1,98 @@
{{- if .Values.cronjobs.enabled }}
{{- range $jobKey, $jobConfig := .Values.cronjobs.jobs }}
{{- if $jobConfig.enabled }}
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ include "sim.fullname" $ }}-{{ $jobConfig.name }}
namespace: {{ $.Release.Namespace }}
labels:
{{- include "sim.labels" $ | nindent 4 }}
app.kubernetes.io/component: cronjob-{{ $jobConfig.name }}
spec:
schedule: {{ $jobConfig.schedule | quote }}
concurrencyPolicy: {{ $jobConfig.concurrencyPolicy | default "Forbid" }}
successfulJobsHistoryLimit: {{ $jobConfig.successfulJobsHistoryLimit | default 3 }}
failedJobsHistoryLimit: {{ $jobConfig.failedJobsHistoryLimit | default 1 }}
{{- with $.Values.cronjobs.startingDeadlineSeconds }}
startingDeadlineSeconds: {{ . }}
{{- end }}
jobTemplate:
spec:
ttlSecondsAfterFinished: {{ $.Values.cronjobs.ttlSecondsAfterFinished | default 600 }}
{{- with $.Values.cronjobs.activeDeadlineSeconds }}
activeDeadlineSeconds: {{ . }}
{{- end }}
template:
metadata:
labels:
{{- include "sim.selectorLabels" $ | nindent 12 }}
app.kubernetes.io/component: cronjob-{{ $jobConfig.name }}
# Stable group label so NetworkPolicy can allow all cron pods → app
# without enumerating each job's component value.
simstudio.ai/component-group: cronjob
spec:
serviceAccountName: {{ include "sim.serviceAccountName" $ }}
automountServiceAccountToken: false
restartPolicy: {{ $.Values.cronjobs.restartPolicy | default "OnFailure" }}
{{- include "sim.podSecurityContext" $.Values.cronjobs | nindent 10 }}
containers:
- name: {{ $jobConfig.name }}
image: {{ include "sim.image" (dict "imageRoot" $.Values.cronjobs.image "global" $.Values.global "chartAppVersion" $.Chart.AppVersion) }}
imagePullPolicy: {{ $.Values.cronjobs.image.pullPolicy }}
{{- include "sim.containerSecurityContext" $.Values.cronjobs | nindent 12 }}
env:
- name: CRON_SECRET
valueFrom:
secretKeyRef:
name: {{ include "sim.appSecretName" $ }}
key: CRON_SECRET
command:
- /bin/sh
- -c
args:
- |
echo "Starting cron job: {{ $jobConfig.name }}"
echo "Making HTTP request to {{ $jobConfig.path }}"
# Determine the service URL (use internal service regardless of ingress)
SERVICE_URL="http://{{ include "sim.fullname" $ }}-app:{{ $.Values.app.service.port }}"
# Make the HTTP request with timeout and retry logic
for i in $(seq 1 3); do
echo "Attempt $i/3"
if curl -f -s -S --max-time 60 --retry 2 --retry-delay 5 \
-H "Content-Type: application/json" \
-H "User-Agent: Kubernetes-CronJob/{{ $jobConfig.name }}" \
-H "Authorization: Bearer ${CRON_SECRET}" \
"$SERVICE_URL{{ $jobConfig.path }}"; then
echo "Success: HTTP request completed"
exit 0
fi
echo "Attempt $i failed, retrying..."
sleep 10
done
echo "Error: All attempts failed"
exit 1
resources:
{{- toYaml $.Values.cronjobs.resources | nindent 14 }}
{{- with $.Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $.Values.app.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $.Values.affinity }}
affinity:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with $.Values.tolerations }}
tolerations:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
+196
View File
@@ -0,0 +1,196 @@
{{- if .Values.app.enabled }}
{{- include "sim.validateSecrets" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "sim.fullname" . }}-app
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.app.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "sim.app.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/secret: {{ include (print $.Template.BasePath "/secrets-app.yaml") . | sha256sum }}
{{- if .Values.branding.enabled }}
checksum/config: {{ include (print $.Template.BasePath "/configmap-branding.yaml") . | sha256sum }}
{{- end }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "sim.app.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "sim.serviceAccountName" . }}
automountServiceAccountToken: false
{{- include "sim.podSecurityContext" .Values.app | nindent 6 }}
{{- include "sim.nodeSelector" .Values.app | nindent 6 }}
{{- include "sim.tolerations" .Values | nindent 6 }}
{{- include "sim.affinity" .Values | nindent 6 }}
{{- include "sim.topologySpreadConstraints" .Values.app | nindent 6 }}
{{- if .Values.migrations.enabled }}
initContainers:
- name: migrations
image: {{ include "sim.image" (dict "imageRoot" .Values.migrations.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
imagePullPolicy: {{ .Values.migrations.image.pullPolicy }}
command: ["/bin/sh", "-c"]
args:
- |
cd /app/packages/db
export DATABASE_URL="{{ include "sim.databaseUrl" . }}"
bun run db:migrate
envFrom:
{{- if .Values.postgresql.enabled }}
- secretRef:
name: {{ include "sim.postgresqlSecretName" . }}
{{- else if .Values.externalDatabase.enabled }}
- secretRef:
name: {{ include "sim.externalDbSecretName" . }}
{{- end }}
{{- include "sim.resources" .Values.migrations | nindent 10 }}
{{- include "sim.containerSecurityContext" .Values.migrations | nindent 10 }}
{{- end }}
containers:
- name: app
image: {{ include "sim.image" (dict "imageRoot" .Values.app.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
imagePullPolicy: {{ .Values.app.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.app.service.targetPort }}
protocol: TCP
{{- /*
All keys from .Values.app.env are mounted via envFrom (see below).
Chart-computed values and operational tunables (.Values.app.envDefaults)
are inlined here so they don't leak into the chart-managed Secret and
don't need to be mapped when externalSecrets.enabled=true.
*/}}
env:
- name: DATABASE_URL
value: {{ include "sim.databaseUrl" . | quote }}
- name: SOCKET_SERVER_URL
value: {{ include "sim.socketServerUrl" . | quote }}
- name: OLLAMA_URL
value: {{ include "sim.ollamaUrl" . | quote }}
- name: PII_URL
value: {{ include "sim.piiUrl" . | quote }}
{{- /*
Skip envDefaults keys that the user has explicitly overridden in app.env
with a non-empty value. K8s `env` takes precedence over `envFrom`, so an
inline default would otherwise mask the user's Secret-bound value.
Also skip envDefaults entirely in existingSecret mode: the pre-existing
Secret is the source of truth, and inlining localhost URL defaults would
silently shadow keys (NEXT_PUBLIC_APP_URL, BETTER_AUTH_URL, etc.) the
user stored in their Secret but left empty in app.env.
*/}}
{{- $appEnv := .Values.app.env | default dict }}
{{- $useExistingSecret := and .Values.app.secrets.existingSecret.enabled (not .Values.externalSecrets.enabled) }}
{{- if not $useExistingSecret }}
{{- range $key, $value := .Values.app.envDefaults | default dict }}
{{- $override := index $appEnv $key }}
{{- if and (ne (toString $value) "") (ne (toString $value) "<nil>") (or (not $override) (eq (toString $override) "") (eq (toString $override) "<nil>")) }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- /*
In existingSecret mode the chart-managed Secret is not rendered, so
non-empty values in app.env would otherwise go nowhere — inline them
here so the user's overrides actually reach the pod. Skipped in ESO
mode (ESO is the source of truth; inlining would mask the synced value)
and in inline mode (values flow through the chart-managed Secret).
*/}}
{{- if and .Values.app.secrets.existingSecret.enabled (not .Values.externalSecrets.enabled) }}
{{- $chartComputed := list "DATABASE_URL" "SOCKET_SERVER_URL" "OLLAMA_URL" "PII_URL" }}
{{- range $key, $value := $appEnv }}
{{- if and (ne (toString $value) "") (ne (toString $value) "<nil>") (not (has $key $chartComputed)) }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.telemetry.enabled }}
{{- $nodeEnv := default (default "production" (index (.Values.app.envDefaults | default dict) "NODE_ENV")) (index (.Values.app.env | default dict) "NODE_ENV") }}
# OpenTelemetry configuration
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://{{ include "sim.fullname" . }}-otel-collector:4318"
- name: OTEL_SERVICE_NAME
value: sim-app
- name: OTEL_SERVICE_VERSION
value: {{ .Chart.AppVersion | quote }}
- name: OTEL_RESOURCE_ATTRIBUTES
value: "service.name=sim-app,service.version={{ .Chart.AppVersion }},deployment.environment={{ $nodeEnv }}"
{{- end }}
{{- with .Values.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
# App secrets (authentication, encryption keys)
- secretRef:
name: {{ include "sim.appSecretName" . }}
# Database secrets
{{- if .Values.postgresql.enabled }}
- secretRef:
name: {{ include "sim.postgresqlSecretName" . }}
{{- else if .Values.externalDatabase.enabled }}
- secretRef:
name: {{ include "sim.externalDbSecretName" . }}
{{- end }}
{{- if .Values.app.startupProbe }}
startupProbe:
{{- toYaml .Values.app.startupProbe | nindent 12 }}
{{- end }}
{{- if .Values.app.livenessProbe }}
livenessProbe:
{{- toYaml .Values.app.livenessProbe | nindent 12 }}
{{- end }}
{{- if .Values.app.readinessProbe }}
readinessProbe:
{{- toYaml .Values.app.readinessProbe | nindent 12 }}
{{- end }}
{{- include "sim.resources" .Values.app | nindent 10 }}
{{- include "sim.containerSecurityContext" .Values.app | nindent 10 }}
{{- $hasBranding := and .Values.branding.enabled (or .Values.branding.files .Values.branding.binaryFiles) }}
{{- if or $hasBranding .Values.extraVolumeMounts .Values.app.extraVolumeMounts }}
volumeMounts:
{{- if $hasBranding }}
- name: branding
mountPath: {{ .Values.branding.mountPath | default "/app/public/branding" }}
readOnly: true
{{- end }}
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.app.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- $hasBranding := and .Values.branding.enabled (or .Values.branding.files .Values.branding.binaryFiles) }}
{{- if or $hasBranding .Values.extraVolumes .Values.app.extraVolumes }}
volumes:
{{- if $hasBranding }}
- name: branding
configMap:
name: {{ include "sim.fullname" . }}-branding
{{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.app.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
+155
View File
@@ -0,0 +1,155 @@
{{- if .Values.copilot.enabled }}
{{- include "sim.copilot.validate" . }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "sim.fullname" . }}-copilot
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.copilot.labels" . | nindent 4 }}
spec:
type: {{ .Values.copilot.server.service.type }}
ports:
- port: {{ .Values.copilot.server.service.port }}
targetPort: {{ .Values.copilot.server.service.targetPort }}
protocol: TCP
name: http
selector:
{{- include "sim.copilot.selectorLabels" . | nindent 4 }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "sim.fullname" . }}-copilot
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.copilot.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.copilot.server.replicaCount }}
selector:
matchLabels:
{{- include "sim.copilot.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- /*
Hash both the inline Secret and the ExternalSecret template — whichever mode is
active, only one renders non-empty content, but concatenating both means a mode
switch or a remoteRefs.copilot mapping change still changes the checksum and
forces a rollout. This can't see the live value ESO syncs from the external
store (Helm only has the ExternalSecret manifest at render time, not the
store's payload) — that's an inherent ESO limitation, not something a Helm
checksum can close; ESO's own reconciliation handles picking up rotated values.
*/}}
checksum/secret: {{ printf "%s%s" (include (print $.Template.BasePath "/secrets-copilot.yaml") .) (include (print $.Template.BasePath "/external-secret-copilot.yaml") .) | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "sim.copilot.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "sim.serviceAccountName" . }}
automountServiceAccountToken: false
{{- include "sim.podSecurityContext" .Values.copilot.server | nindent 6 }}
{{- with .Values.copilot.server.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: copilot
image: {{ include "sim.image" (dict "imageRoot" .Values.copilot.server.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
imagePullPolicy: {{ .Values.copilot.server.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.copilot.server.service.targetPort }}
protocol: TCP
envFrom:
- secretRef:
name: {{ include "sim.copilot.envSecretName" . }}
- secretRef:
name: {{ include "sim.copilot.databaseSecretName" . }}
{{- with .Values.copilot.server.extraEnvFrom }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- /*
copilot.server.env flows through envFrom (the Secret/ExternalSecret) above.
copilot.server.envDefaults are inlined here so they don't leak into the
chart-managed Secret and don't need to be mapped when
externalSecrets.enabled=true — same rationale as app.envDefaults. Skipped
entirely in existingSecret mode: the pre-created Secret is the source of
truth, and inlining defaults would silently shadow keys (PORT, LOG_LEVEL,
etc.) the user stored there via envFrom, since explicit env wins over
envFrom. Keyed purely on copilot.server.secret.create, NOT the global
externalSecrets.enabled flag — envFrom always points at the user-provided
Secret name whenever secret.create=false, regardless of whether ESO is used
for other components, so the global flag is irrelevant here.
*/}}
{{- $useExistingSecret := not .Values.copilot.server.secret.create }}
{{- if or (not $useExistingSecret) .Values.copilot.server.extraEnv }}
env:
{{- $copilotEnv := .Values.copilot.server.env | default dict }}
{{- if not $useExistingSecret }}
{{- range $key, $value := .Values.copilot.server.envDefaults | default dict }}
{{- $override := index $copilotEnv $key }}
{{- if and (ne (toString $value) "") (ne (toString $value) "<nil>") (or (not $override) (eq (toString $override) "") (eq (toString $override) "<nil>")) }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- with .Values.copilot.server.extraEnv }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- if .Values.copilot.server.startupProbe }}
startupProbe:
{{- toYaml .Values.copilot.server.startupProbe | nindent 12 }}
{{- end }}
{{- if .Values.copilot.server.livenessProbe }}
livenessProbe:
{{- toYaml .Values.copilot.server.livenessProbe | nindent 12 }}
{{- end }}
{{- if .Values.copilot.server.readinessProbe }}
readinessProbe:
{{- toYaml .Values.copilot.server.readinessProbe | nindent 12 }}
{{- end }}
{{- with .Values.copilot.server.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- include "sim.containerSecurityContext" .Values.copilot.server | nindent 10 }}
{{- if or .Values.extraVolumeMounts .Values.copilot.server.extraVolumeMounts }}
volumeMounts:
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.copilot.server.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- if or .Values.extraVolumes .Values.copilot.server.extraVolumes }}
volumes:
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.copilot.server.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
+125
View File
@@ -0,0 +1,125 @@
{{- if .Values.ollama.enabled }}
---
# PersistentVolumeClaim for Ollama data
{{- if .Values.ollama.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "sim.fullname" . }}-ollama-data
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.ollama.labels" . | nindent 4 }}
spec:
{{- if .Values.ollama.persistence.storageClass }}
{{- if (eq "-" .Values.ollama.persistence.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: {{ .Values.ollama.persistence.storageClass | quote }}
{{- end }}
{{- else if .Values.global.storageClass }}
storageClassName: {{ .Values.global.storageClass | quote }}
{{- end }}
accessModes:
{{- range .Values.ollama.persistence.accessModes }}
- {{ . | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.ollama.persistence.size | quote }}
{{- end }}
---
# Deployment for Ollama
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "sim.fullname" . }}-ollama
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.ollama.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.ollama.replicaCount }}
selector:
matchLabels:
{{- include "sim.ollama.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "sim.ollama.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "sim.serviceAccountName" . }}
automountServiceAccountToken: false
{{- include "sim.podSecurityContext" .Values.ollama | nindent 6 }}
{{- include "sim.nodeSelector" .Values.ollama | nindent 6 }}
{{- include "sim.tolerations" .Values.ollama | nindent 6 }}
{{- include "sim.affinity" .Values | nindent 6 }}
containers:
- name: ollama
image: {{ include "sim.image" (dict "imageRoot" .Values.ollama.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
imagePullPolicy: {{ .Values.ollama.image.pullPolicy }}
command: ["ollama", "serve"]
ports:
- name: http
containerPort: {{ .Values.ollama.service.targetPort }}
protocol: TCP
env:
{{- range $key, $value := .Values.ollama.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- with .Values.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.ollama.startupProbe }}
startupProbe:
{{- toYaml .Values.ollama.startupProbe | nindent 12 }}
{{- end }}
{{- if .Values.ollama.livenessProbe }}
livenessProbe:
{{- toYaml .Values.ollama.livenessProbe | nindent 12 }}
{{- end }}
{{- if .Values.ollama.readinessProbe }}
readinessProbe:
{{- toYaml .Values.ollama.readinessProbe | nindent 12 }}
{{- end }}
{{- include "sim.resources" .Values.ollama | nindent 10 }}
{{- include "sim.containerSecurityContext" .Values.ollama | nindent 10 }}
{{- if or .Values.ollama.persistence.enabled .Values.extraVolumeMounts .Values.ollama.extraVolumeMounts }}
volumeMounts:
{{- if .Values.ollama.persistence.enabled }}
- name: ollama-data
mountPath: /data
{{- end }}
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.ollama.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- if or .Values.ollama.persistence.enabled .Values.extraVolumes .Values.ollama.extraVolumes }}
volumes:
{{- if .Values.ollama.persistence.enabled }}
- name: ollama-data
persistentVolumeClaim:
claimName: {{ include "sim.fullname" . }}-ollama-data
{{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.ollama.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
+93
View File
@@ -0,0 +1,93 @@
{{- if .Values.pii.enabled }}
---
# Deployment for the Presidio PII redaction service (analyzer + anonymizer combined)
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "sim.fullname" . }}-pii
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.pii.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.pii.replicaCount }}
selector:
matchLabels:
{{- include "sim.pii.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "sim.pii.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "sim.serviceAccountName" . }}
automountServiceAccountToken: false
{{- include "sim.podSecurityContext" .Values.pii | nindent 6 }}
{{- include "sim.nodeSelector" .Values.pii | nindent 6 }}
{{- include "sim.tolerations" .Values | nindent 6 }}
{{- include "sim.affinity" .Values | nindent 6 }}
{{- include "sim.topologySpreadConstraints" .Values.pii | nindent 6 }}
containers:
- name: pii
image: {{ include "sim.image" (dict "imageRoot" .Values.pii.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
imagePullPolicy: {{ .Values.pii.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.pii.service.targetPort }}
protocol: TCP
env:
- name: PII_ENGINE
value: {{ .Values.pii.engine | quote }}
{{- if .Values.pii.device }}
- name: PII_DEVICE
value: {{ .Values.pii.device | quote }}
{{- end }}
{{- range $key, $value := omit (.Values.pii.env | default dict) "PII_ENGINE" "PII_DEVICE" }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- with .Values.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.pii.startupProbe }}
startupProbe:
{{- toYaml .Values.pii.startupProbe | nindent 12 }}
{{- end }}
{{- if .Values.pii.livenessProbe }}
livenessProbe:
{{- toYaml .Values.pii.livenessProbe | nindent 12 }}
{{- end }}
{{- if .Values.pii.readinessProbe }}
readinessProbe:
{{- toYaml .Values.pii.readinessProbe | nindent 12 }}
{{- end }}
{{- include "sim.resources" .Values.pii | nindent 10 }}
{{- include "sim.containerSecurityContext" .Values.pii | nindent 10 }}
{{- if or .Values.extraVolumeMounts .Values.pii.extraVolumeMounts }}
volumeMounts:
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.pii.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- if or .Values.extraVolumes .Values.pii.extraVolumes }}
volumes:
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.pii.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
+176
View File
@@ -0,0 +1,176 @@
{{- if .Values.realtime.enabled }}
{{- include "sim.validateSecrets" . }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "sim.fullname" . }}-realtime
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.realtime.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.realtime.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "sim.realtime.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/secret: {{ include (print $.Template.BasePath "/secrets-app.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "sim.realtime.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "sim.serviceAccountName" . }}
automountServiceAccountToken: false
{{- include "sim.podSecurityContext" .Values.realtime | nindent 6 }}
{{- include "sim.nodeSelector" .Values.realtime | nindent 6 }}
{{- include "sim.tolerations" .Values | nindent 6 }}
{{- include "sim.affinity" .Values | nindent 6 }}
{{- include "sim.topologySpreadConstraints" .Values.realtime | nindent 6 }}
containers:
- name: realtime
image: {{ include "sim.image" (dict "imageRoot" .Values.realtime.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
imagePullPolicy: {{ .Values.realtime.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.realtime.service.targetPort }}
protocol: TCP
{{- /*
All keys from .Values.realtime.env are mounted via envFrom (see below).
Chart-computed values and operational tunables (.Values.realtime.envDefaults)
are inlined here so they don't leak into the chart-managed Secret and don't
need to be mapped when externalSecrets.enabled=true.
*/}}
env:
- name: DATABASE_URL
value: {{ include "sim.databaseUrl" . | quote }}
{{- if .Values.telemetry.enabled }}
{{- $nodeEnv := default (default "production" (index (.Values.realtime.envDefaults | default dict) "NODE_ENV")) (index (.Values.realtime.env | default dict) "NODE_ENV") }}
# OpenTelemetry configuration
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://{{ include "sim.fullname" . }}-otel-collector:4318"
- name: OTEL_SERVICE_NAME
value: sim-realtime
- name: OTEL_SERVICE_VERSION
value: {{ .Chart.AppVersion | quote }}
- name: OTEL_RESOURCE_ATTRIBUTES
value: "service.name=sim-realtime,service.version={{ .Chart.AppVersion }},deployment.environment={{ $nodeEnv }}"
{{- end }}
{{- /*
Inline realtime.envDefaults, skipping keys explicitly set in realtime.env
OR app.env (K8s `env` overrides `envFrom`, so an inline default would
otherwise mask a Secret-bound value). The chart-managed Secret is shared
between app and realtime via envFrom, so a key set in app.env (e.g.
NEXT_PUBLIC_APP_URL=https://prod.example.com) lands on the realtime pod
too — inlining the localhost envDefault here would mask it.
*/}}
{{- $rtEnv := .Values.realtime.env | default dict }}
{{- $appEnv := .Values.app.env | default dict }}
{{- $useExistingSecret := and .Values.app.secrets.existingSecret.enabled (not .Values.externalSecrets.enabled) }}
{{- /*
Skip envDefaults entirely in existingSecret mode — same rationale as
deployment-app.yaml: the pre-existing Secret is the source of truth,
and inlining localhost defaults would shadow URL keys (BETTER_AUTH_URL,
ALLOWED_ORIGINS, NEXT_PUBLIC_APP_URL, etc.) stored in the Secret.
*/}}
{{- if not $useExistingSecret }}
{{- range $key, $value := .Values.realtime.envDefaults | default dict }}
{{- $rtOverride := index $rtEnv $key }}
{{- $appOverride := index $appEnv $key }}
{{- $hasRt := and $rtOverride (ne (toString $rtOverride) "") (ne (toString $rtOverride) "<nil>") }}
{{- $hasApp := and $appOverride (ne (toString $appOverride) "") (ne (toString $appOverride) "<nil>") }}
{{- if and (ne (toString $value) "") (ne (toString $value) "<nil>") (not $hasRt) (not $hasApp) }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- /*
In existingSecret mode the chart-managed Secret is not rendered, so
non-empty values in realtime.env / app.env would otherwise go nowhere
— inline them here so the user's overrides actually reach the pod.
realtime.env wins over app.env on collision (realtime-specific intent).
Skipped in ESO mode and inline mode for the same reasons as the app
deployment.
*/}}
{{- if and .Values.app.secrets.existingSecret.enabled (not .Values.externalSecrets.enabled) }}
{{- $chartComputed := list "DATABASE_URL" "SOCKET_SERVER_URL" "OLLAMA_URL" "PII_URL" }}
{{- /*
Build the effective realtime env from app.env as the base, then
overlay non-empty realtime.env values. Sprig's `merge` keeps the
first source on collision and treats "" as a real value, which
would shadow non-empty app.env entries — so do the overlay
manually with an explicit empty-string check.
*/}}
{{- $effective := deepCopy $appEnv }}
{{- range $key, $value := $rtEnv }}
{{- if and (ne (toString $value) "") (ne (toString $value) "<nil>") }}
{{- $_ := set $effective $key $value }}
{{- end }}
{{- end }}
{{- range $key, $value := $effective }}
{{- if and (ne (toString $value) "") (ne (toString $value) "<nil>") (not (has $key $chartComputed)) }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- with .Values.extraEnvVars }}
{{- toYaml . | nindent 12 }}
{{- end }}
envFrom:
# App secrets (authentication keys shared with main app)
- secretRef:
name: {{ include "sim.appSecretName" . }}
# Database secrets
{{- if .Values.postgresql.enabled }}
- secretRef:
name: {{ include "sim.postgresqlSecretName" . }}
{{- else if .Values.externalDatabase.enabled }}
- secretRef:
name: {{ include "sim.externalDbSecretName" . }}
{{- end }}
{{- if .Values.realtime.startupProbe }}
startupProbe:
{{- toYaml .Values.realtime.startupProbe | nindent 12 }}
{{- end }}
{{- if .Values.realtime.livenessProbe }}
livenessProbe:
{{- toYaml .Values.realtime.livenessProbe | nindent 12 }}
{{- end }}
{{- if .Values.realtime.readinessProbe }}
readinessProbe:
{{- toYaml .Values.realtime.readinessProbe | nindent 12 }}
{{- end }}
{{- include "sim.resources" .Values.realtime | nindent 10 }}
{{- include "sim.containerSecurityContext" .Values.realtime | nindent 10 }}
{{- if or .Values.extraVolumeMounts .Values.realtime.extraVolumeMounts }}
volumeMounts:
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.realtime.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- if or .Values.extraVolumes .Values.realtime.extraVolumes }}
volumes:
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.realtime.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,14 @@
{{- if and .Values.externalDatabase.enabled (include "sim.createExternalDbSecret" .) }}
---
# Secret for external database credentials
apiVersion: v1
kind: Secret
metadata:
name: {{ include "sim.fullname" . }}-external-db-secret
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
type: Opaque
data:
EXTERNAL_DB_PASSWORD: {{ .Values.externalDatabase.password | b64enc }}
{{- end }}
@@ -0,0 +1,41 @@
{{- if and .Values.externalSecrets.enabled .Values.app.enabled }}
# ExternalSecret for app credentials (syncs from external secret managers)
#
# The data list is generated by iterating every non-empty entry in
# externalSecrets.remoteRefs.app. This lets users sync arbitrary sensitive
# keys (OPENAI_API_KEY, AWS_SECRET_ACCESS_KEY, ...) — not just the legacy
# six (BETTER_AUTH_SECRET, ENCRYPTION_KEY, INTERNAL_API_SECRET, CRON_SECRET,
# API_ENCRYPTION_KEY, REDIS_URL). Each entry's value may be either:
# - a string: treated as the remoteRef.key (legacy form)
# - a map: passed through as the remoteRef block (e.g. {key, property,
# version, decodingStrategy, conversionStrategy, metadataPolicy})
apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1beta1" }}
kind: ExternalSecret
metadata:
name: {{ include "sim.fullname" . }}-app-secrets
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.app.labels" . | nindent 4 }}
spec:
refreshInterval: {{ .Values.externalSecrets.refreshInterval | quote }}
secretStoreRef:
name: {{ required "externalSecrets.secretStoreRef.name is required when externalSecrets.enabled=true" .Values.externalSecrets.secretStoreRef.name }}
kind: {{ .Values.externalSecrets.secretStoreRef.kind | default "ClusterSecretStore" }}
target:
name: {{ include "sim.fullname" . }}-app-secrets
creationPolicy: Owner
data:
{{- range $secretKey, $ref := .Values.externalSecrets.remoteRefs.app }}
{{- if $ref }}
{{- if kindIs "string" $ref }}
- secretKey: {{ $secretKey }}
remoteRef:
key: {{ $ref }}
{{- else if kindIs "map" $ref }}
- secretKey: {{ $secretKey }}
remoteRef:
{{- toYaml $ref | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,38 @@
{{- if and .Values.externalSecrets.enabled .Values.copilot.enabled .Values.copilot.server.secret.create }}
# ExternalSecret for copilot server credentials (syncs from external secret managers)
#
# Mirrors external-secret-app.yaml: the data list is generated by iterating every
# non-empty entry in externalSecrets.remoteRefs.copilot. sim.copilot.validate fails
# template rendering if a required key (or any non-empty copilot.server.env key) is
# missing a matching remoteRefs.copilot entry, so a misconfiguration surfaces at
# `helm template` time instead of a container starting with an empty value.
apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1beta1" }}
kind: ExternalSecret
metadata:
name: {{ include "sim.copilot.envSecretName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.copilot.labels" . | nindent 4 }}
spec:
refreshInterval: {{ .Values.externalSecrets.refreshInterval | quote }}
secretStoreRef:
name: {{ required "externalSecrets.secretStoreRef.name is required when externalSecrets.enabled=true" .Values.externalSecrets.secretStoreRef.name }}
kind: {{ .Values.externalSecrets.secretStoreRef.kind | default "ClusterSecretStore" }}
target:
name: {{ include "sim.copilot.envSecretName" . }}
creationPolicy: Owner
data:
{{- range $secretKey, $ref := .Values.externalSecrets.remoteRefs.copilot }}
{{- if $ref }}
{{- if kindIs "string" $ref }}
- secretKey: {{ $secretKey }}
remoteRef:
key: {{ $ref }}
{{- else if kindIs "map" $ref }}
- secretKey: {{ $secretKey }}
remoteRef:
{{- toYaml $ref | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,23 @@
{{- if and .Values.externalSecrets.enabled .Values.externalDatabase.enabled .Values.externalSecrets.remoteRefs.externalDatabase.password }}
# ExternalSecret for external database password (syncs from external secret managers)
apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1beta1" }}
kind: ExternalSecret
metadata:
name: {{ include "sim.fullname" . }}-external-db-secret
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: external-database
spec:
refreshInterval: {{ .Values.externalSecrets.refreshInterval | quote }}
secretStoreRef:
name: {{ required "externalSecrets.secretStoreRef.name is required when externalSecrets.enabled=true" .Values.externalSecrets.secretStoreRef.name }}
kind: {{ .Values.externalSecrets.secretStoreRef.kind | default "ClusterSecretStore" }}
target:
name: {{ include "sim.fullname" . }}-external-db-secret
creationPolicy: Owner
data:
- secretKey: EXTERNAL_DB_PASSWORD
remoteRef:
key: {{ .Values.externalSecrets.remoteRefs.externalDatabase.password }}
{{- end }}
@@ -0,0 +1,22 @@
{{- if and .Values.externalSecrets.enabled .Values.postgresql.enabled .Values.externalSecrets.remoteRefs.postgresql.password }}
# ExternalSecret for PostgreSQL password (syncs from external secret managers)
apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1beta1" }}
kind: ExternalSecret
metadata:
name: {{ include "sim.fullname" . }}-postgresql-secret
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.postgresql.labels" . | nindent 4 }}
spec:
refreshInterval: {{ .Values.externalSecrets.refreshInterval | quote }}
secretStoreRef:
name: {{ required "externalSecrets.secretStoreRef.name is required when externalSecrets.enabled=true" .Values.externalSecrets.secretStoreRef.name }}
kind: {{ .Values.externalSecrets.secretStoreRef.kind | default "ClusterSecretStore" }}
target:
name: {{ include "sim.fullname" . }}-postgresql-secret
creationPolicy: Owner
data:
- secretKey: POSTGRES_PASSWORD
remoteRef:
key: {{ .Values.externalSecrets.remoteRefs.postgresql.password }}
{{- end }}
+115
View File
@@ -0,0 +1,115 @@
{{- if and .Values.ollama.enabled .Values.ollama.gpu.enabled }}
---
# 1. ConfigMap for NVIDIA Device Plugin Configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "sim.fullname" . }}-nvidia-device-plugin-config
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: nvidia-device-plugin
data:
config.yaml: |
version: v1
flags:
{{- if eq .Values.ollama.gpu.strategy "mig" }}
migStrategy: "single"
{{- else }}
migStrategy: "none"
{{- end }}
failOnInitError: false
plugin:
passDeviceSpecs: true
deviceListStrategy: envvar
{{- if eq .Values.ollama.gpu.strategy "time-slicing" }}
sharing:
timeSlicing:
resources:
- name: nvidia.com/gpu
replicas: {{ .Values.ollama.gpu.timeSlicingReplicas | default 5 }}
{{- end }}
---
# 2. NVIDIA Device Plugin DaemonSet for GPU support
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: {{ include "sim.fullname" . }}-nvidia-device-plugin
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: nvidia-device-plugin
spec:
selector:
matchLabels:
{{- include "sim.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: nvidia-device-plugin
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
{{- include "sim.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: nvidia-device-plugin
spec:
tolerations:
# Allow scheduling on GPU nodes
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
- key: sku
operator: Equal
value: gpu
effect: NoSchedule
nodeSelector:
# Only schedule on nodes with NVIDIA GPUs
accelerator: nvidia
priorityClassName: system-node-critical
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
- name: dev
hostPath:
path: /dev
- name: sys
hostPath:
path: /sys
# Volume to mount the ConfigMap
- name: nvidia-device-plugin-config
configMap:
name: {{ include "sim.fullname" . }}-nvidia-device-plugin-config
containers:
- name: nvidia-device-plugin
image: nvcr.io/nvidia/k8s-device-plugin:v0.18.2
imagePullPolicy: Always
args:
- "--config-file=/etc/device-plugin/config.yaml"
{{- if eq .Values.ollama.gpu.strategy "mig" }}
env:
- name: NVIDIA_MIG_MONITOR_DEVICES
value: all
{{- end }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
- name: dev
mountPath: /dev
- name: sys
mountPath: /sys
readOnly: true
- name: nvidia-device-plugin-config
mountPath: /etc/device-plugin/
readOnly: true
resources:
requests:
cpu: 50m
memory: 20Mi
limits:
cpu: 50m
memory: 50Mi
{{- end }}
+85
View File
@@ -0,0 +1,85 @@
{{- if .Values.autoscaling.enabled }}
---
# HorizontalPodAutoscaler for main application
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "sim.fullname" . }}-app
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.app.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "sim.fullname" . }}-app
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- with .Values.autoscaling.customMetrics }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- if .Values.autoscaling.behavior }}
behavior:
{{- toYaml .Values.autoscaling.behavior | nindent 4 }}
{{- end }}
{{- end }}
{{- if and .Values.autoscaling.enabled .Values.realtime.enabled }}
---
# HorizontalPodAutoscaler for realtime service
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "sim.fullname" . }}-realtime
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.realtime.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "sim.fullname" . }}-realtime
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- with .Values.autoscaling.customMetrics }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- if .Values.autoscaling.behavior }}
behavior:
{{- toYaml .Values.autoscaling.behavior | nindent 4 }}
{{- end }}
{{- end }}
+129
View File
@@ -0,0 +1,129 @@
{{- if .Values.ingressInternal.enabled }}
{{- $appActive := .Values.app.enabled -}}
{{- $realtimeActive := .Values.realtime.enabled -}}
{{- $hasCopilotIngress := and .Values.copilot.enabled .Values.ingressInternal.copilot -}}
{{- $realtimeHasOwnRule := and $realtimeActive (or (not $appActive) (ne .Values.ingressInternal.realtime.host .Values.ingressInternal.app.host)) -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "sim.fullname" . }}-ingress-internal
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
{{- with .Values.ingressInternal.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingressInternal.className }}
ingressClassName: {{ .Values.ingressInternal.className }}
{{- end }}
{{- if .Values.ingressInternal.tls.enabled }}
tls:
- hosts:
{{- if $appActive }}
- {{ .Values.ingressInternal.app.host | quote }}
{{- end }}
{{- if $realtimeHasOwnRule }}
- {{ .Values.ingressInternal.realtime.host | quote }}
{{- end }}
{{- if $hasCopilotIngress }}
{{- $copilotHostCovered := false }}
{{- if and $appActive (eq .Values.ingressInternal.copilot.host .Values.ingressInternal.app.host) }}
{{- $copilotHostCovered = true }}
{{- end }}
{{- if and $realtimeHasOwnRule (eq .Values.ingressInternal.copilot.host .Values.ingressInternal.realtime.host) }}
{{- $copilotHostCovered = true }}
{{- end }}
{{- if not $copilotHostCovered }}
- {{ .Values.ingressInternal.copilot.host | quote }}
{{- end }}
{{- end }}
secretName: {{ .Values.ingressInternal.tls.secretName }}
{{- end }}
rules:
{{- if $appActive }}
- host: {{ .Values.ingressInternal.app.host | quote }}
http:
paths:
{{- if and $realtimeActive (eq .Values.ingressInternal.realtime.host .Values.ingressInternal.app.host) }}
{{- range .Values.ingressInternal.realtime.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-realtime
port:
number: {{ $.Values.realtime.service.port }}
{{- end }}
{{- end }}
{{- if and $hasCopilotIngress (eq .Values.ingressInternal.copilot.host .Values.ingressInternal.app.host) }}
{{- range .Values.ingressInternal.copilot.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-copilot
port:
number: {{ $.Values.copilot.server.service.port }}
{{- end }}
{{- end }}
{{- range .Values.ingressInternal.app.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-app
port:
number: {{ $.Values.app.service.port }}
{{- end }}
{{- end }}
{{- if $realtimeHasOwnRule }}
- host: {{ .Values.ingressInternal.realtime.host | quote }}
http:
paths:
{{- range .Values.ingressInternal.realtime.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-realtime
port:
number: {{ $.Values.realtime.service.port }}
{{- end }}
{{- if and $hasCopilotIngress (eq .Values.ingressInternal.copilot.host .Values.ingressInternal.realtime.host) }}
{{- range .Values.ingressInternal.copilot.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-copilot
port:
number: {{ $.Values.copilot.server.service.port }}
{{- end }}
{{- end }}
{{- end }}
{{- if $hasCopilotIngress }}
{{- $copilotServed := false }}
{{- if and $appActive (eq .Values.ingressInternal.copilot.host .Values.ingressInternal.app.host) }}
{{- $copilotServed = true }}
{{- end }}
{{- if and $realtimeHasOwnRule (eq .Values.ingressInternal.copilot.host .Values.ingressInternal.realtime.host) }}
{{- $copilotServed = true }}
{{- end }}
{{- if not $copilotServed }}
- host: {{ .Values.ingressInternal.copilot.host | quote }}
http:
paths:
{{- range .Values.ingressInternal.copilot.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-copilot
port:
number: {{ $.Values.copilot.server.service.port }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
+129
View File
@@ -0,0 +1,129 @@
{{- if .Values.ingress.enabled }}
{{- $appActive := .Values.app.enabled -}}
{{- $realtimeActive := .Values.realtime.enabled -}}
{{- $hasCopilotIngress := and .Values.copilot.enabled .Values.ingress.copilot -}}
{{- $realtimeHasOwnRule := and $realtimeActive (or (not $appActive) (ne .Values.ingress.realtime.host .Values.ingress.app.host)) -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "sim.fullname" . }}-ingress
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls.enabled }}
tls:
- hosts:
{{- if $appActive }}
- {{ .Values.ingress.app.host | quote }}
{{- end }}
{{- if $realtimeHasOwnRule }}
- {{ .Values.ingress.realtime.host | quote }}
{{- end }}
{{- if $hasCopilotIngress }}
{{- $copilotHostCovered := false }}
{{- if and $appActive (eq .Values.ingress.copilot.host .Values.ingress.app.host) }}
{{- $copilotHostCovered = true }}
{{- end }}
{{- if and $realtimeHasOwnRule (eq .Values.ingress.copilot.host .Values.ingress.realtime.host) }}
{{- $copilotHostCovered = true }}
{{- end }}
{{- if not $copilotHostCovered }}
- {{ .Values.ingress.copilot.host | quote }}
{{- end }}
{{- end }}
secretName: {{ .Values.ingress.tls.secretName }}
{{- end }}
rules:
{{- if $appActive }}
- host: {{ .Values.ingress.app.host | quote }}
http:
paths:
{{- if and $realtimeActive (eq .Values.ingress.realtime.host .Values.ingress.app.host) }}
{{- range .Values.ingress.realtime.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-realtime
port:
number: {{ $.Values.realtime.service.port }}
{{- end }}
{{- end }}
{{- if and $hasCopilotIngress (eq .Values.ingress.copilot.host .Values.ingress.app.host) }}
{{- range .Values.ingress.copilot.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-copilot
port:
number: {{ $.Values.copilot.server.service.port }}
{{- end }}
{{- end }}
{{- range .Values.ingress.app.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-app
port:
number: {{ $.Values.app.service.port }}
{{- end }}
{{- end }}
{{- if $realtimeHasOwnRule }}
- host: {{ .Values.ingress.realtime.host | quote }}
http:
paths:
{{- range .Values.ingress.realtime.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-realtime
port:
number: {{ $.Values.realtime.service.port }}
{{- end }}
{{- if and $hasCopilotIngress (eq .Values.ingress.copilot.host .Values.ingress.realtime.host) }}
{{- range .Values.ingress.copilot.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-copilot
port:
number: {{ $.Values.copilot.server.service.port }}
{{- end }}
{{- end }}
{{- end }}
{{- if $hasCopilotIngress }}
{{- $copilotServed := false }}
{{- if and $appActive (eq .Values.ingress.copilot.host .Values.ingress.app.host) }}
{{- $copilotServed = true }}
{{- end }}
{{- if and $realtimeHasOwnRule (eq .Values.ingress.copilot.host .Values.ingress.realtime.host) }}
{{- $copilotServed = true }}
{{- end }}
{{- if not $copilotServed }}
- host: {{ .Values.ingress.copilot.host | quote }}
http:
paths:
{{- range .Values.ingress.copilot.paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "sim.fullname" $ }}-copilot
port:
number: {{ $.Values.copilot.server.service.port }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,76 @@
{{- if and .Values.copilot.enabled .Values.copilot.migrations.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "sim.fullname" . }}-copilot-migrations
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: copilot-migrations
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
backoffLimit: {{ .Values.copilot.migrations.backoffLimit }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "sim.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: copilot-migrations
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "sim.serviceAccountName" . }}
automountServiceAccountToken: false
restartPolicy: {{ .Values.copilot.migrations.restartPolicy }}
{{- include "sim.podSecurityContext" .Values.copilot.migrations | nindent 6 }}
{{- with .Values.copilot.server.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.copilot.postgresql.enabled }}
initContainers:
- name: wait-for-postgres
image: {{ include "sim.image" (dict "imageRoot" .Values.copilot.postgresql.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
command:
- /bin/sh
- -c
- |
until pg_isready -h {{ include "sim.fullname" . }}-copilot-postgresql -p {{ .Values.copilot.postgresql.service.port }} -U {{ .Values.copilot.postgresql.auth.username }}; do
echo "Waiting for Copilot PostgreSQL to be ready..."
sleep 2
done
echo "Copilot PostgreSQL is ready!"
envFrom:
- secretRef:
name: {{ include "sim.fullname" . }}-copilot-postgresql-secret
{{- include "sim.containerSecurityContext" .Values.copilot.migrations | nindent 10 }}
{{- end }}
containers:
- name: migrations
image: {{ include "sim.image" (dict "imageRoot" .Values.copilot.migrations.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
imagePullPolicy: {{ .Values.copilot.migrations.image.pullPolicy }}
command: ["/usr/local/bin/migrate"]
envFrom:
- secretRef:
name: {{ include "sim.copilot.envSecretName" . }}
- secretRef:
name: {{ include "sim.copilot.databaseSecretName" . }}
{{- with .Values.copilot.server.extraEnvFrom }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.copilot.server.extraEnv }}
env:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.copilot.migrations.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- include "sim.containerSecurityContext" .Values.copilot.migrations | nindent 10 }}
{{- end }}
+437
View File
@@ -0,0 +1,437 @@
{{- if .Values.networkPolicy.enabled }}
---
# Network Policy for main application
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "sim.fullname" . }}-app
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.app.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "sim.app.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
# Allow ingress from realtime service
{{- if .Values.realtime.enabled }}
- from:
- podSelector:
matchLabels:
{{- include "sim.realtime.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.app.service.targetPort }}
{{- end }}
# Allow ingress from cron pods (every cron job curls /api/schedules/execute,
# webhook polls, etc. against the app service)
{{- if .Values.cronjobs.enabled }}
- from:
- podSelector:
matchLabels:
{{- include "sim.selectorLabels" . | nindent 10 }}
simstudio.ai/component-group: cronjob
ports:
- protocol: TCP
port: {{ .Values.app.service.targetPort }}
{{- end }}
# Allow ingress from ingress controller (configurable peers; defaults to any)
{{- if .Values.ingress.enabled }}
- from:
{{- toYaml (default (list (dict)) .Values.networkPolicy.ingressFrom) | nindent 6 }}
ports:
- protocol: TCP
port: {{ .Values.app.service.targetPort }}
{{- end }}
# Allow custom ingress rules
{{- with .Values.networkPolicy.ingress }}
{{- toYaml . | nindent 2 }}
{{- end }}
egress:
# Allow egress to PostgreSQL
{{- if .Values.postgresql.enabled }}
- to:
- podSelector:
matchLabels:
{{- include "sim.postgresql.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.postgresql.service.targetPort }}
{{- end }}
# Allow egress to realtime service
{{- if .Values.realtime.enabled }}
- to:
- podSelector:
matchLabels:
{{- include "sim.realtime.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.realtime.service.targetPort }}
{{- end }}
# Allow egress to Ollama
{{- if .Values.ollama.enabled }}
- to:
- podSelector:
matchLabels:
{{- include "sim.ollama.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.ollama.service.targetPort }}
{{- end }}
# Allow egress to the PII (Presidio) service
{{- if .Values.pii.enabled }}
- to:
- podSelector:
matchLabels:
{{- include "sim.pii.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.pii.service.targetPort }}
{{- end }}
# Allow egress to OpenTelemetry collector (OTLP gRPC + HTTP)
{{- if .Values.telemetry.enabled }}
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: {{ include "sim.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: telemetry
ports:
- protocol: TCP
port: 4317
- protocol: TCP
port: 4318
{{- end }}
# Allow DNS resolution
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow HTTPS egress for external APIs (excludes cloud metadata endpoints)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
{{- range (default (list "169.254.169.254/32" "169.254.170.2/32") .Values.networkPolicy.egressExceptCidrs) }}
- {{ . | quote }}
{{- end }}
ports:
- protocol: TCP
port: 443
# Allow custom egress rules
{{- with .Values.networkPolicy.egress }}
{{- toYaml . | nindent 2 }}
{{- end }}
{{- if .Values.realtime.enabled }}
---
# Network Policy for realtime service
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "sim.fullname" . }}-realtime
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.realtime.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "sim.realtime.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
# Allow ingress from main application
- from:
- podSelector:
matchLabels:
{{- include "sim.app.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.realtime.service.targetPort }}
# Allow ingress from ingress controller (configurable peers; defaults to any)
{{- if .Values.ingress.enabled }}
- from:
{{- toYaml (default (list (dict)) .Values.networkPolicy.ingressFrom) | nindent 6 }}
ports:
- protocol: TCP
port: {{ .Values.realtime.service.targetPort }}
{{- end }}
egress:
# Allow egress to PostgreSQL
{{- if .Values.postgresql.enabled }}
- to:
- podSelector:
matchLabels:
{{- include "sim.postgresql.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.postgresql.service.targetPort }}
{{- end }}
# Allow egress to OpenTelemetry collector (OTLP gRPC + HTTP)
{{- if .Values.telemetry.enabled }}
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: {{ include "sim.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: telemetry
ports:
- protocol: TCP
port: 4317
- protocol: TCP
port: 4318
{{- end }}
# Allow DNS resolution
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow HTTPS egress for external APIs (excludes cloud metadata endpoints)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
{{- range (default (list "169.254.169.254/32" "169.254.170.2/32") .Values.networkPolicy.egressExceptCidrs) }}
- {{ . | quote }}
{{- end }}
ports:
- protocol: TCP
port: 443
# Allow custom egress rules
{{- with .Values.networkPolicy.egress }}
{{- toYaml . | nindent 2 }}
{{- end }}
{{- end }}
{{- if .Values.postgresql.enabled }}
---
# Network Policy for PostgreSQL
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "sim.fullname" . }}-postgresql
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.postgresql.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "sim.postgresql.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
# Allow ingress from main application
- from:
- podSelector:
matchLabels:
{{- include "sim.app.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.postgresql.service.targetPort }}
# Allow ingress from realtime service
{{- if .Values.realtime.enabled }}
- from:
- podSelector:
matchLabels:
{{- include "sim.realtime.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.postgresql.service.targetPort }}
{{- end }}
# Allow ingress from migrations job
{{- if .Values.migrations.enabled }}
- from:
- podSelector:
matchLabels:
{{- include "sim.migrations.labels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.postgresql.service.targetPort }}
{{- end }}
egress:
# Allow minimal egress (for health checks, etc.)
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
{{- end }}
{{- if .Values.ollama.enabled }}
---
# Network Policy for Ollama
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "sim.fullname" . }}-ollama
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.ollama.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "sim.ollama.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
# Allow ingress from main application
- from:
- podSelector:
matchLabels:
{{- include "sim.app.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.ollama.service.targetPort }}
egress:
# Allow DNS resolution
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow HTTPS egress for model downloads (excludes cloud metadata endpoints)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
{{- range (default (list "169.254.169.254/32" "169.254.170.2/32") .Values.networkPolicy.egressExceptCidrs) }}
- {{ . | quote }}
{{- end }}
ports:
- protocol: TCP
port: 443
{{- end }}
{{- if .Values.pii.enabled }}
---
# Network Policy for the PII (Presidio) service
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "sim.fullname" . }}-pii
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.pii.labels" . | nindent 4 }}
spec:
podSelector:
matchLabels:
{{- include "sim.pii.selectorLabels" . | nindent 6 }}
policyTypes:
- Ingress
- Egress
ingress:
# Allow ingress from main application
- from:
- podSelector:
matchLabels:
{{- include "sim.app.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: {{ .Values.pii.service.targetPort }}
egress:
# Allow DNS resolution. Models are baked into the image, so no external egress.
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
{{- end }}
{{- if .Values.telemetry.enabled }}
---
# Network Policy for OpenTelemetry Collector
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "sim.fullname" . }}-otel-collector
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: telemetry
spec:
podSelector:
matchLabels:
{{- include "sim.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: telemetry
policyTypes:
- Ingress
- Egress
ingress:
# OTLP from app
- from:
- podSelector:
matchLabels:
{{- include "sim.app.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: 4317
- protocol: TCP
port: 4318
# OTLP from realtime
{{- if .Values.realtime.enabled }}
- from:
- podSelector:
matchLabels:
{{- include "sim.realtime.selectorLabels" . | nindent 10 }}
ports:
- protocol: TCP
port: 4317
- protocol: TCP
port: 4318
{{- end }}
# OTLP from copilot
{{- if .Values.copilot.enabled }}
- from:
- podSelector:
matchLabels:
{{- include "sim.selectorLabels" . | nindent 10 }}
app.kubernetes.io/component: copilot
ports:
- protocol: TCP
port: 4317
- protocol: TCP
port: 4318
{{- end }}
egress:
# DNS
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# HTTPS for forwarding to external observability backends (Datadog, Honeycomb, etc.)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
{{- range (default (list "169.254.169.254/32" "169.254.170.2/32") .Values.networkPolicy.egressExceptCidrs) }}
- {{ . | quote }}
{{- end }}
ports:
- protocol: TCP
port: 443
{{- end }}
{{- /*
Copilot + copilot-postgresql intentionally do NOT ship dedicated NetworkPolicies.
Copilot requires REDIS_URL (external Redis on a non-443 port), and the chart
cannot know the user's Redis host/port at render time — a default egress rule
would silently block Redis on most installs. Users running networkPolicy.enabled=true
with copilot enabled should add their own NPs (or extend networkPolicy.egress
with the appropriate egress rules).
*/}}
{{- end }}
@@ -0,0 +1,94 @@
{{- /*
Tri-state activation for the app PDB:
- enabled: true → force on
- enabled: false → force off (explicit opt-out, even on HA)
- enabled: null → auto-enable when the workload is HA — either a static
replicaCount > 1, or HPA enabled with minReplicas > 1
(HPA owns spec.replicas so replicaCount alone is not
authoritative under autoscaling).
*/ -}}
{{- $pdbEnabled := .Values.podDisruptionBudget.enabled -}}
{{- $hpaMin := 0 -}}
{{- if .Values.autoscaling.enabled -}}
{{- $hpaMin = int (default 1 .Values.autoscaling.minReplicas) -}}
{{- end -}}
{{- $pdbAuto := and (kindIs "invalid" $pdbEnabled) (or (gt (int .Values.app.replicaCount) 1) (gt $hpaMin 1)) -}}
{{- if and .Values.app.enabled (or (eq $pdbEnabled true) $pdbAuto) }}
{{- with .Values.podDisruptionBudget }}
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "sim.fullname" $ }}-app-pdb
namespace: {{ $.Release.Namespace }}
labels:
{{- include "sim.app.labels" $ | nindent 4 }}
spec:
{{- if .minAvailable }}
minAvailable: {{ .minAvailable }}
{{- else if .maxUnavailable }}
maxUnavailable: {{ .maxUnavailable }}
{{- else }}
minAvailable: 1
{{- end }}
{{- if .unhealthyPodEvictionPolicy }}
unhealthyPodEvictionPolicy: {{ .unhealthyPodEvictionPolicy }}
{{- end }}
selector:
matchLabels:
{{- include "sim.app.selectorLabels" $ | nindent 6 }}
{{- end }}
{{- end }}
{{- $rtPdbAuto := and (kindIs "invalid" .Values.podDisruptionBudget.enabled) (or (gt (int .Values.realtime.replicaCount) 1) (gt $hpaMin 1)) -}}
{{- if and .Values.realtime.enabled (or (eq .Values.podDisruptionBudget.enabled true) $rtPdbAuto) }}
{{- with .Values.podDisruptionBudget }}
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "sim.fullname" $ }}-realtime-pdb
namespace: {{ $.Release.Namespace }}
labels:
{{- include "sim.realtime.labels" $ | nindent 4 }}
spec:
{{- if .minAvailable }}
minAvailable: {{ .minAvailable }}
{{- else if .maxUnavailable }}
maxUnavailable: {{ .maxUnavailable }}
{{- else }}
minAvailable: 1
{{- end }}
{{- if .unhealthyPodEvictionPolicy }}
unhealthyPodEvictionPolicy: {{ .unhealthyPodEvictionPolicy }}
{{- end }}
selector:
matchLabels:
{{- include "sim.realtime.selectorLabels" $ | nindent 6 }}
{{- end }}
{{- end }}
{{- if and .Values.copilot.enabled .Values.copilot.server.podDisruptionBudget.enabled }}
{{- with .Values.copilot.server.podDisruptionBudget }}
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "sim.fullname" $ }}-copilot-pdb
namespace: {{ $.Release.Namespace }}
labels:
{{- include "sim.copilot.labels" $ | nindent 4 }}
spec:
{{- if .minAvailable }}
minAvailable: {{ .minAvailable }}
{{- else if .maxUnavailable }}
maxUnavailable: {{ .maxUnavailable }}
{{- else }}
maxUnavailable: 1
{{- end }}
{{- if .unhealthyPodEvictionPolicy }}
unhealthyPodEvictionPolicy: {{ .unhealthyPodEvictionPolicy }}
{{- end }}
selector:
matchLabels:
{{- include "sim.copilot.selectorLabels" $ | nindent 6 }}
{{- end }}
{{- end }}
+51
View File
@@ -0,0 +1,51 @@
{{- if and .Values.app.enabled (include "sim.createAppSecrets" .) }}
{{/*
Secret for app + realtime env. Every key in .Values.app.env and
.Values.realtime.env is written here and mounted via envFrom on the
respective Deployments. Chart-computed values (DATABASE_URL,
SOCKET_SERVER_URL, OLLAMA_URL, PII_URL) are omitted — they're injected as
inline env on the container so they reflect chart-time resolution.
Treating all env values as secret-grade avoids maintaining a sensitivity
classifier and prevents accidental leaks when new provider keys are added.
*/}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "sim.fullname" . }}-app-secrets
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.app.labels" . | nindent 4 }}
type: Opaque
stringData:
{{- $chartComputed := list "DATABASE_URL" "SOCKET_SERVER_URL" "OLLAMA_URL" "PII_URL" }}
{{- /*
Intent: app.env is authoritative for shared keys (both pods envFrom this
Secret, so the app container must not be silently overwritten by a
realtime-side value), BUT empty strings must never win.
Sprig `merge` treats "" as a real value, so a default-empty app.env entry
would shadow a non-empty realtime.env entry. Build the effective dict
manually: start from realtime.env, then overlay non-empty app.env values
on top — this gives app.env-wins-on-collision without empty-string
shadowing. Mirrors the pattern used in deployment-realtime.yaml.
*/}}
{{- $appEnv := .Values.app.env | default dict }}
{{- $rtEnv := .Values.realtime.env | default dict }}
{{- $effective := dict }}
{{- range $key, $value := $rtEnv }}
{{- if and (ne (toString $value) "") (ne (toString $value) "<nil>") }}
{{- $_ := set $effective $key $value }}
{{- end }}
{{- end }}
{{- range $key, $value := $appEnv }}
{{- if and (ne (toString $value) "") (ne (toString $value) "<nil>") }}
{{- $_ := set $effective $key $value }}
{{- end }}
{{- end }}
{{- range $key, $value := $effective }}
{{- if not (has $key $chartComputed) }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- end }}
+35
View File
@@ -0,0 +1,35 @@
{{- if and .Values.copilot.enabled .Values.copilot.server.secret.create (not (and .Values.externalSecrets .Values.externalSecrets.enabled)) }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "sim.copilot.envSecretName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.copilot.labels" . | nindent 4 }}
{{- with .Values.copilot.server.secret.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
type: Opaque
stringData:
{{- range $key, $value := .Values.copilot.server.env }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- end }}
{{- if and .Values.copilot.enabled (not .Values.copilot.postgresql.enabled) (or (not .Values.copilot.database.existingSecretName) (eq .Values.copilot.database.existingSecretName "")) (ne .Values.copilot.database.url "") }}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ include "sim.copilot.databaseSecretName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.copilot.labels" . | nindent 4 }}
{{- with .Values.copilot.server.secret.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
type: Opaque
stringData:
{{ include "sim.copilot.databaseSecretKey" . }}: {{ required "copilot.database.url is required when using an external database" .Values.copilot.database.url | quote }}
{{- end }}
+14
View File
@@ -0,0 +1,14 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "sim.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: false
{{- end }}
+79
View File
@@ -0,0 +1,79 @@
{{- if .Values.monitoring.serviceMonitor.enabled }}
---
# ServiceMonitor for main application
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "sim.fullname" . }}-app
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.app.labels" . | nindent 4 }}
{{- with .Values.monitoring.serviceMonitor.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.monitoring.serviceMonitor.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
{{- include "sim.app.selectorLabels" . | nindent 6 }}
endpoints:
- port: http
path: {{ .Values.monitoring.serviceMonitor.path }}
interval: {{ .Values.monitoring.serviceMonitor.interval }}
scrapeTimeout: {{ .Values.monitoring.serviceMonitor.scrapeTimeout }}
{{- with .Values.monitoring.serviceMonitor.metricRelabelings }}
metricRelabelings:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .Values.monitoring.serviceMonitor.relabelings }}
relabelings:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- if .Values.monitoring.serviceMonitor.targetLabels }}
targetLabels:
{{- toYaml .Values.monitoring.serviceMonitor.targetLabels | nindent 4 }}
{{- end }}
{{- end }}
{{- if and .Values.monitoring.serviceMonitor.enabled .Values.realtime.enabled }}
---
# ServiceMonitor for realtime service
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "sim.fullname" . }}-realtime
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.realtime.labels" . | nindent 4 }}
{{- with .Values.monitoring.serviceMonitor.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.monitoring.serviceMonitor.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
{{- include "sim.realtime.selectorLabels" . | nindent 6 }}
endpoints:
- port: http
path: {{ .Values.monitoring.serviceMonitor.path }}
interval: {{ .Values.monitoring.serviceMonitor.interval }}
scrapeTimeout: {{ .Values.monitoring.serviceMonitor.scrapeTimeout }}
{{- with .Values.monitoring.serviceMonitor.metricRelabelings }}
metricRelabelings:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with .Values.monitoring.serviceMonitor.relabelings }}
relabelings:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- if .Values.monitoring.serviceMonitor.targetLabels }}
targetLabels:
{{- toYaml .Values.monitoring.serviceMonitor.targetLabels | nindent 4 }}
{{- end }}
{{- end }}
+123
View File
@@ -0,0 +1,123 @@
{{- if .Values.app.enabled }}
---
# Service for main application
apiVersion: v1
kind: Service
metadata:
name: {{ include "sim.fullname" . }}-app
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.app.labels" . | nindent 4 }}
spec:
type: {{ .Values.app.service.type }}
ports:
- port: {{ .Values.app.service.port }}
targetPort: {{ .Values.app.service.targetPort }}
protocol: TCP
name: http
selector:
{{- include "sim.app.selectorLabels" . | nindent 4 }}
{{- end }}
{{- if .Values.realtime.enabled }}
---
# Service for realtime server
apiVersion: v1
kind: Service
metadata:
name: {{ include "sim.fullname" . }}-realtime
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.realtime.labels" . | nindent 4 }}
spec:
type: {{ .Values.realtime.service.type }}
ports:
- port: {{ .Values.realtime.service.port }}
targetPort: {{ .Values.realtime.service.targetPort }}
protocol: TCP
name: http
selector:
{{- include "sim.realtime.selectorLabels" . | nindent 4 }}
{{- end }}
{{- if .Values.postgresql.enabled }}
---
# Service for PostgreSQL (ClusterIP — used by client workloads)
apiVersion: v1
kind: Service
metadata:
name: {{ include "sim.fullname" . }}-postgresql
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.postgresql.labels" . | nindent 4 }}
spec:
type: {{ .Values.postgresql.service.type }}
ports:
- port: {{ .Values.postgresql.service.port }}
targetPort: {{ .Values.postgresql.service.targetPort }}
protocol: TCP
name: postgresql
selector:
{{- include "sim.postgresql.selectorLabels" . | nindent 4 }}
---
# Headless Service for PostgreSQL StatefulSet (stable per-pod DNS)
apiVersion: v1
kind: Service
metadata:
name: {{ include "sim.fullname" . }}-postgresql-headless
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.postgresql.labels" . | nindent 4 }}
spec:
clusterIP: None
publishNotReadyAddresses: true
ports:
- port: {{ .Values.postgresql.service.port }}
targetPort: {{ .Values.postgresql.service.targetPort }}
protocol: TCP
name: postgresql
selector:
{{- include "sim.postgresql.selectorLabels" . | nindent 4 }}
{{- end }}
{{- if .Values.ollama.enabled }}
---
# Service for Ollama
apiVersion: v1
kind: Service
metadata:
name: {{ include "sim.fullname" . }}-ollama
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.ollama.labels" . | nindent 4 }}
spec:
type: {{ .Values.ollama.service.type }}
ports:
- port: {{ .Values.ollama.service.port }}
targetPort: {{ .Values.ollama.service.targetPort }}
protocol: TCP
name: http
selector:
{{- include "sim.ollama.selectorLabels" . | nindent 4 }}
{{- end }}
{{- if .Values.pii.enabled }}
---
# Service fronting the Presidio PII redaction deployment
apiVersion: v1
kind: Service
metadata:
name: {{ include "sim.fullname" . }}-pii
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.pii.labels" . | nindent 4 }}
spec:
type: {{ .Values.pii.service.type }}
ports:
- port: {{ .Values.pii.service.port }}
targetPort: {{ .Values.pii.service.targetPort }}
protocol: TCP
name: http
selector:
{{- include "sim.pii.selectorLabels" . | nindent 4 }}
{{- end }}
+48
View File
@@ -0,0 +1,48 @@
{{- if .Values.sharedStorage.enabled }}
{{- range .Values.sharedStorage.volumes }}
---
# Shared Storage PVC for {{ .name }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "sim.fullname" $ }}-{{ .name }}
namespace: {{ $.Release.Namespace }}
labels:
{{- include "sim.labels" $ | nindent 4 }}
sim.ai/volume-type: shared-storage
sim.ai/volume-name: {{ .name }}
{{- with .annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .storageClass }}
{{- if (eq "-" .storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: {{ .storageClass | quote }}
{{- end }}
{{- else if $.Values.sharedStorage.storageClass }}
storageClassName: {{ $.Values.sharedStorage.storageClass | quote }}
{{- else if $.Values.global.storageClass }}
storageClassName: {{ $.Values.global.storageClass | quote }}
{{- end }}
accessModes:
{{- if .accessModes }}
{{- range .accessModes }}
- {{ . | quote }}
{{- end }}
{{- else }}
{{- range $.Values.sharedStorage.defaultAccessModes }}
- {{ . | quote }}
{{- end }}
{{- end }}
resources:
requests:
storage: {{ .size | quote }}
{{- if .selector }}
selector:
{{- toYaml .selector | nindent 4 }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,154 @@
{{- if and .Values.copilot.enabled .Values.copilot.postgresql.enabled }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "sim.fullname" . }}-copilot-postgresql-secret
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.copilotPostgresql.labels" . | nindent 4 }}
type: Opaque
stringData:
POSTGRES_USER: {{ .Values.copilot.postgresql.auth.username | quote }}
POSTGRES_PASSWORD: {{ required "copilot.postgresql.auth.password is required when copilot is enabled" .Values.copilot.postgresql.auth.password | quote }}
POSTGRES_DB: {{ .Values.copilot.postgresql.auth.database | quote }}
DATABASE_URL: "postgresql://{{ .Values.copilot.postgresql.auth.username }}:{{ .Values.copilot.postgresql.auth.password }}@{{ include "sim.fullname" . }}-copilot-postgresql:{{ .Values.copilot.postgresql.service.port }}/{{ .Values.copilot.postgresql.auth.database }}"
---
# ClusterIP Service used by client workloads
apiVersion: v1
kind: Service
metadata:
name: {{ include "sim.fullname" . }}-copilot-postgresql
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.copilotPostgresql.labels" . | nindent 4 }}
spec:
type: {{ .Values.copilot.postgresql.service.type }}
ports:
- port: {{ .Values.copilot.postgresql.service.port }}
targetPort: {{ .Values.copilot.postgresql.service.targetPort }}
protocol: TCP
name: postgresql
selector:
{{- include "sim.copilotPostgresql.selectorLabels" . | nindent 4 }}
---
# Headless Service for the StatefulSet (stable per-pod DNS)
apiVersion: v1
kind: Service
metadata:
name: {{ include "sim.fullname" . }}-copilot-postgresql-headless
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.copilotPostgresql.labels" . | nindent 4 }}
spec:
clusterIP: None
publishNotReadyAddresses: true
ports:
- port: {{ .Values.copilot.postgresql.service.port }}
targetPort: {{ .Values.copilot.postgresql.service.targetPort }}
protocol: TCP
name: postgresql
selector:
{{- include "sim.copilotPostgresql.selectorLabels" . | nindent 4 }}
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "sim.fullname" . }}-copilot-postgresql
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.copilotPostgresql.labels" . | nindent 4 }}
spec:
# Must remain {{ include "sim.fullname" . }}-copilot-postgresql (not the
# -headless name) — spec.serviceName is immutable on a StatefulSet, and
# the prior chart shipped with this value. Same rationale as the main
# postgresql STS; see statefulset-postgresql.yaml for details.
serviceName: {{ include "sim.fullname" . }}-copilot-postgresql
replicas: 1
podManagementPolicy: OrderedReady
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
{{- include "sim.copilotPostgresql.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "sim.copilotPostgresql.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "sim.serviceAccountName" . }}
automountServiceAccountToken: false
{{- with .Values.copilot.postgresql.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- include "sim.podSecurityContext" .Values.copilot.postgresql | nindent 6 }}
containers:
- name: postgresql
image: {{ include "sim.image" (dict "imageRoot" .Values.copilot.postgresql.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
imagePullPolicy: {{ .Values.copilot.postgresql.image.pullPolicy }}
ports:
- name: postgresql
containerPort: {{ .Values.copilot.postgresql.service.targetPort }}
protocol: TCP
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
envFrom:
- secretRef:
name: {{ include "sim.fullname" . }}-copilot-postgresql-secret
{{- if .Values.copilot.postgresql.startupProbe }}
startupProbe:
{{- toYaml .Values.copilot.postgresql.startupProbe | nindent 12 }}
{{- end }}
{{- if .Values.copilot.postgresql.livenessProbe }}
livenessProbe:
{{- toYaml .Values.copilot.postgresql.livenessProbe | nindent 12 }}
{{- end }}
{{- if .Values.copilot.postgresql.readinessProbe }}
readinessProbe:
{{- toYaml .Values.copilot.postgresql.readinessProbe | nindent 12 }}
{{- end }}
{{- with .Values.copilot.postgresql.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- include "sim.containerSecurityContext" .Values.copilot.postgresql | nindent 10 }}
{{- if .Values.copilot.postgresql.persistence.enabled }}
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
subPath: pgdata
{{- end }}
{{- if .Values.copilot.postgresql.persistence.enabled }}
volumeClaimTemplates:
- metadata:
name: data
labels:
{{- include "sim.copilotPostgresql.labels" . | nindent 10 }}
spec:
accessModes:
{{- range .Values.copilot.postgresql.persistence.accessModes }}
- {{ . | quote }}
{{- end }}
{{- if .Values.copilot.postgresql.persistence.storageClass }}
{{- if (eq "-" .Values.copilot.postgresql.persistence.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: {{ .Values.copilot.postgresql.persistence.storageClass | quote }}
{{- end }}
{{- else if .Values.global.storageClass }}
storageClassName: {{ .Values.global.storageClass | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.copilot.postgresql.persistence.size | quote }}
{{- end }}
{{- end }}
@@ -0,0 +1,210 @@
{{- if .Values.postgresql.enabled }}
---
# ConfigMap for PostgreSQL configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "sim.fullname" . }}-postgresql-config
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.postgresql.labels" . | nindent 4 }}
data:
postgresql.conf: |
hba_file = '/etc/postgresql/pg_hba.conf'
listen_addresses = '0.0.0.0'
max_connections = {{ .Values.postgresql.config.maxConnections }}
tcp_keepalives_idle = 60
tcp_keepalives_interval = 5
tcp_keepalives_count = 3
authentication_timeout = 1min
password_encryption = scram-sha-256
{{- if .Values.postgresql.tls.enabled }}
ssl = on
ssl_cert_file = '/etc/postgresql/tls/tls.crt'
ssl_key_file = '/etc/postgresql/tls/tls.key'
{{- else }}
ssl = off
{{- end }}
shared_buffers = {{ .Values.postgresql.config.sharedBuffers }}
dynamic_shared_memory_type = posix
max_wal_size = {{ .Values.postgresql.config.maxWalSize }}
min_wal_size = {{ .Values.postgresql.config.minWalSize }}
log_timezone = 'Etc/UTC'
idle_in_transaction_session_timeout = 50000000
datestyle = 'iso, mdy'
timezone = 'Etc/UTC'
lc_messages = 'en_US.utf8'
lc_monetary = 'en_US.utf8'
lc_numeric = 'en_US.utf8'
lc_time = 'en_US.utf8'
default_text_search_config = 'pg_catalog.english'
pg_hba.conf: |
# Secure authentication for all connections
local all all scram-sha-256
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
host all all all scram-sha-256
# Replication connections also require authentication
local replication all scram-sha-256
host replication all 127.0.0.1/32 scram-sha-256
host replication all ::1/128 scram-sha-256
---
# ConfigMap for PostgreSQL environment variables
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "sim.fullname" . }}-postgresql-env
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.postgresql.labels" . | nindent 4 }}
data:
POSTGRES_DB: {{ .Values.postgresql.auth.database | quote }}
POSTGRES_USER: {{ .Values.postgresql.auth.username | quote }}
PGDATA: "/var/lib/postgresql/data/pgdata"
{{- if (include "sim.createPostgresqlSecret" .) }}
---
# Secret for PostgreSQL password
apiVersion: v1
kind: Secret
metadata:
name: {{ include "sim.fullname" . }}-postgresql-secret
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.postgresql.labels" . | nindent 4 }}
type: Opaque
data:
POSTGRES_PASSWORD: {{ .Values.postgresql.auth.password | b64enc }}
{{- end }}
---
# StatefulSet for PostgreSQL
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "sim.fullname" . }}-postgresql
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.postgresql.labels" . | nindent 4 }}
spec:
# Must remain {{ include "sim.fullname" . }}-postgresql (not the -headless
# name) — spec.serviceName is immutable on a StatefulSet, and the prior
# chart shipped with this value. Changing it would break `helm upgrade` for
# every existing install with `Forbidden: updates to statefulset spec ...`.
# The headless Service in services.yaml is added alongside, not as a swap.
serviceName: {{ include "sim.fullname" . }}-postgresql
replicas: 1
minReadySeconds: 10
podManagementPolicy: OrderedReady
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
{{- include "sim.postgresql.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "sim.postgresql.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "sim.serviceAccountName" . }}
automountServiceAccountToken: false
{{- include "sim.podSecurityContext" .Values.postgresql | nindent 6 }}
{{- include "sim.nodeSelector" .Values.postgresql | nindent 6 }}
{{- include "sim.tolerations" .Values | nindent 6 }}
{{- include "sim.affinity" .Values | nindent 6 }}
containers:
- name: postgresql
image: {{ include "sim.image" (dict "imageRoot" .Values.postgresql.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
imagePullPolicy: {{ .Values.postgresql.image.pullPolicy }}
args: ["-c", "config_file=/etc/postgresql/postgresql.conf"]
ports:
- name: postgresql
containerPort: {{ .Values.postgresql.service.targetPort }}
protocol: TCP
envFrom:
- configMapRef:
name: {{ include "sim.fullname" . }}-postgresql-env
- secretRef:
name: {{ include "sim.postgresqlSecretName" . }}
{{- if .Values.postgresql.startupProbe }}
startupProbe:
{{- toYaml .Values.postgresql.startupProbe | nindent 12 }}
{{- end }}
{{- if .Values.postgresql.livenessProbe }}
livenessProbe:
{{- toYaml .Values.postgresql.livenessProbe | nindent 12 }}
{{- end }}
{{- if .Values.postgresql.readinessProbe }}
readinessProbe:
{{- toYaml .Values.postgresql.readinessProbe | nindent 12 }}
{{- end }}
{{- include "sim.resources" .Values.postgresql | nindent 10 }}
{{- include "sim.containerSecurityContext" .Values.postgresql | nindent 10 }}
volumeMounts:
{{- if .Values.postgresql.persistence.enabled }}
- name: postgresql-data
mountPath: /var/lib/postgresql/data
subPath: pgdata
{{- end }}
- name: postgresql-config
mountPath: "/etc/postgresql"
{{- if .Values.postgresql.tls.enabled }}
- name: postgresql-tls
mountPath: "/etc/postgresql/tls"
readOnly: true
{{- end }}
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
volumes:
- name: postgresql-config
configMap:
name: {{ include "sim.fullname" . }}-postgresql-config
{{- if .Values.postgresql.tls.enabled }}
- name: postgresql-tls
secret:
secretName: {{ .Values.postgresql.tls.certificatesSecret }}
defaultMode: 0600
{{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.postgresql.persistence.enabled }}
volumeClaimTemplates:
- metadata:
name: postgresql-data
labels:
{{- include "sim.postgresql.labels" . | nindent 10 }}
spec:
{{- if .Values.postgresql.persistence.storageClass }}
{{- if (eq "-" .Values.postgresql.persistence.storageClass) }}
storageClassName: ""
{{- else }}
storageClassName: {{ .Values.postgresql.persistence.storageClass | quote }}
{{- end }}
{{- else if .Values.global.storageClass }}
storageClassName: {{ .Values.global.storageClass | quote }}
{{- end }}
accessModes:
{{- range .Values.postgresql.persistence.accessModes }}
- {{ . | quote }}
{{- end }}
resources:
requests:
storage: {{ .Values.postgresql.persistence.size | quote }}
{{- end }}
{{- end }}
+223
View File
@@ -0,0 +1,223 @@
{{- if .Values.telemetry.enabled }}
---
# OpenTelemetry Collector Configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "sim.fullname" . }}-otel-config
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: telemetry
data:
otel-config.yaml: |
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
prometheus:
config:
scrape_configs:
- job_name: 'sim-app'
static_configs:
- targets: ['{{ include "sim.fullname" . }}-app:{{ .Values.app.service.port }}']
- job_name: 'sim-realtime'
static_configs:
- targets: ['{{ include "sim.fullname" . }}-realtime:{{ .Values.realtime.service.port }}']
processors:
batch:
timeout: 1s
send_batch_size: 1024
memory_limiter:
limit_mib: 512
exporters:
{{- if .Values.telemetry.jaeger.enabled }}
jaeger:
endpoint: {{ .Values.telemetry.jaeger.endpoint }}
tls:
insecure: {{ not .Values.telemetry.jaeger.tls.enabled }}
{{- end }}
{{- if .Values.telemetry.prometheus.enabled }}
prometheusremotewrite:
endpoint: {{ .Values.telemetry.prometheus.endpoint }}
headers:
Authorization: {{ .Values.telemetry.prometheus.auth | quote }}
{{- end }}
{{- if .Values.telemetry.otlp.enabled }}
otlp:
endpoint: {{ .Values.telemetry.otlp.endpoint }}
tls:
insecure: {{ not .Values.telemetry.otlp.tls.enabled }}
{{- end }}
logging:
loglevel: info
extensions:
health_check:
endpoint: 0.0.0.0:13133
pprof:
endpoint: 0.0.0.0:1777
zpages:
endpoint: 0.0.0.0:55679
service:
extensions: [health_check, pprof, zpages]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters:
- logging
{{- if .Values.telemetry.jaeger.enabled }}
- jaeger
{{- end }}
{{- if .Values.telemetry.otlp.enabled }}
- otlp
{{- end }}
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, batch]
exporters:
- logging
{{- if .Values.telemetry.prometheus.enabled }}
- prometheusremotewrite
{{- end }}
{{- if .Values.telemetry.otlp.enabled }}
- otlp
{{- end }}
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters:
- logging
{{- if .Values.telemetry.otlp.enabled }}
- otlp
{{- end }}
---
# OpenTelemetry Collector Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "sim.fullname" . }}-otel-collector
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: telemetry
spec:
replicas: {{ .Values.telemetry.replicaCount }}
selector:
matchLabels:
{{- include "sim.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: telemetry
template:
metadata:
labels:
{{- include "sim.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: telemetry
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "sim.serviceAccountName" . }}
automountServiceAccountToken: false
{{- include "sim.podSecurityContext" .Values.telemetry | nindent 6 }}
containers:
- name: otel-collector
image: {{ include "sim.image" (dict "imageRoot" .Values.telemetry.image "global" .Values.global "chartAppVersion" .Chart.AppVersion) }}
imagePullPolicy: {{ .Values.telemetry.image.pullPolicy }}
command:
- /otelcol-contrib
- --config=/etc/otel-collector-config/otel-config.yaml
ports:
- name: otlp-grpc
containerPort: 4317
protocol: TCP
- name: otlp-http
containerPort: 4318
protocol: TCP
- name: health
containerPort: 13133
protocol: TCP
- name: pprof
containerPort: 1777
protocol: TCP
- name: zpages
containerPort: 55679
protocol: TCP
env:
- name: GOGC
value: "80"
volumeMounts:
- name: otel-config
mountPath: /etc/otel-collector-config
readOnly: true
livenessProbe:
httpGet:
path: /
port: health
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: health
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
{{- toYaml .Values.telemetry.resources | nindent 12 }}
{{- include "sim.containerSecurityContext" .Values.telemetry | nindent 10 }}
volumes:
- name: otel-config
configMap:
name: {{ include "sim.fullname" . }}-otel-config
{{- with .Values.telemetry.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.telemetry.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.telemetry.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
---
# OpenTelemetry Collector Service
apiVersion: v1
kind: Service
metadata:
name: {{ include "sim.fullname" . }}-otel-collector
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: telemetry
spec:
type: {{ .Values.telemetry.service.type }}
ports:
- name: otlp-grpc
port: 4317
targetPort: otlp-grpc
protocol: TCP
- name: otlp-http
port: 4318
targetPort: otlp-http
protocol: TCP
- name: health
port: 13133
targetPort: health
protocol: TCP
selector:
{{- include "sim.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: telemetry
{{- end }}
@@ -0,0 +1,54 @@
{{- if .Values.tests.enabled }}
apiVersion: v1
kind: Pod
metadata:
name: {{ include "sim.fullname" . }}-test-connection
namespace: {{ .Release.Namespace }}
labels:
{{- include "sim.labels" . | nindent 4 }}
app.kubernetes.io/component: test
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
{{- with .Values.tests.image.pullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 4 }}
{{- end }}
securityContext:
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
containers:
- name: probe
image: "{{ .Values.tests.image.repository }}:{{ .Values.tests.image.tag }}"
imagePullPolicy: {{ .Values.tests.image.pullPolicy }}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65534
capabilities:
drop:
- ALL
command:
- sh
- -c
- |
set -eu
APP_URL="http://{{ include "sim.fullname" . }}-app:{{ .Values.app.service.port }}/"
echo "Probing $APP_URL"
wget -q -T {{ .Values.tests.timeoutSeconds }} --tries=1 --spider "$APP_URL"
{{- if .Values.realtime.enabled }}
RT_URL="http://{{ include "sim.fullname" . }}-realtime:{{ .Values.realtime.service.port }}/health"
echo "Probing $RT_URL"
wget -q -T {{ .Values.tests.timeoutSeconds }} --tries=1 --spider "$RT_URL"
{{- end }}
echo "OK"
resources:
{{- toYaml .Values.tests.resources | nindent 8 }}
{{- end }}