diff --git a/helm/litellm/templates/_helpers.tpl b/helm/litellm/templates/_helpers.tpl index a0205c0a3a2..bffd627393a 100644 --- a/helm/litellm/templates/_helpers.tpl +++ b/helm/litellm/templates/_helpers.tpl @@ -138,6 +138,59 @@ is false the chart uses the provided name, or the namespace `default` SA. {{- end -}} {{- end -}} +{{/* +ServiceAccount name for the migrations Job. + +The Job is a pre-install / pre-upgrade hook, so it is created before the +chart's ordinary resources. A ServiceAccount the chart creates is one of +those ordinary resources, which makes borrowing the backend name a cycle: +the hook pod is rejected because the account does not exist yet. So when +`serviceAccounts.backend.create` is true the Job falls back to the namespace +`default` account unless the operator names one that already exists. With +`create` false the backend name is either an operator-supplied existing +account or `default`, both of which are safe for the hook, so the Job keeps +sharing it. + +`migrationJob.serviceAccountName` always wins when set, which is how a Job +that needs credentials of its own (IRSA / Workload Identity for IAM database +auth) gets them. +*/}} +{{- define "litellm.migrations.serviceAccountName" -}} +{{- if .Values.migrationJob.serviceAccountName -}} +{{ .Values.migrationJob.serviceAccountName }} +{{- else if .Values.serviceAccounts.backend.create -}} +default +{{- else -}} +{{ include "litellm.backend.serviceAccountName" . }} +{{- end -}} +{{- end -}} + +{{/* +Extra pod labels for a component's Deployment, validated against its selector. + +Invoke with a dict: + (dict "podLabels" .Values.gateway.podLabels "componentName" "gateway") + +The three selector keys are also emitted on the pod template, so a podLabels +entry reusing one renders a duplicate YAML key whose later value wins. That +leaves the pod template no longer matching the (immutable) selector and the +apiserver rejects the Deployment. Fail at template time naming the key +instead, so the operator gets the reason here rather than an opaque +`selector does not match template labels` from the apiserver. + +The migrations Job takes podLabels unvalidated: a Job's selector is generated +by the controller rather than declared, so nothing there can collide. +*/}} +{{- define "litellm.podLabels" -}} +{{- $componentName := .componentName -}} +{{- range $key, $value := .podLabels }} +{{- if has $key (list "app.kubernetes.io/name" "app.kubernetes.io/instance" "app.kubernetes.io/component") }} +{{- fail (printf "%s.podLabels cannot set %s: it is part of the Deployment's immutable selector" $componentName $key) }} +{{- end }} +{{- end }} +{{- toYaml .podLabels }} +{{- end -}} + {{/* Master-key + database + redis env block — shared by gateway, backend, and the migrations Job. diff --git a/helm/litellm/templates/backend/deployment.yaml b/helm/litellm/templates/backend/deployment.yaml index 892b84ff7d5..c5d799a0faf 100644 --- a/helm/litellm/templates/backend/deployment.yaml +++ b/helm/litellm/templates/backend/deployment.yaml @@ -23,9 +23,16 @@ spec: {{- end }} labels: {{- include "litellm.backend.selectorLabels" . | nindent 8 }} + {{- with .Values.backend.podLabels }} + {{- include "litellm.podLabels" (dict "podLabels" . "componentName" "backend") | nindent 8 }} + {{- end }} spec: serviceAccountName: {{ include "litellm.backend.serviceAccountName" . }} automountServiceAccountToken: {{ .Values.serviceAccounts.backend.automount }} + {{- with .Values.backend.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} @@ -34,6 +41,10 @@ spec: - name: backend image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + {{- with .Values.backend.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} ports: - name: http containerPort: 4001 @@ -70,8 +81,15 @@ spec: readinessProbe: {{- toYaml . | nindent 12 }} {{- end }} + {{- with .Values.backend.lifecycle }} + lifecycle: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.backend.resources | nindent 12 }} + {{- with .Values.backend.extraContainers }} + {{- tpl (toYaml .) $ | nindent 8 }} + {{- end }} {{- if or .Values.gateway.config.create .Values.backend.volumes .Values.billingMetrics.enabled }} volumes: {{- if .Values.gateway.config.create }} @@ -102,4 +120,8 @@ spec: topologySpreadConstraints: {{- toYaml . | nindent 8 }} {{- end }} + {{- $gracePeriod := .Values.backend.terminationGracePeriodSeconds }} + {{- if not (or (kindIs "invalid" $gracePeriod) (eq (printf "%v" $gracePeriod) "")) }} + terminationGracePeriodSeconds: {{ $gracePeriod }} + {{- end }} {{- end }} diff --git a/helm/litellm/templates/gateway/deployment.yaml b/helm/litellm/templates/gateway/deployment.yaml index b2e22612905..7d16134a53d 100644 --- a/helm/litellm/templates/gateway/deployment.yaml +++ b/helm/litellm/templates/gateway/deployment.yaml @@ -21,9 +21,16 @@ spec: {{- end }} labels: {{- include "litellm.gateway.selectorLabels" . | nindent 8 }} + {{- with .Values.gateway.podLabels }} + {{- include "litellm.podLabels" (dict "podLabels" . "componentName" "gateway") | nindent 8 }} + {{- end }} spec: serviceAccountName: {{ include "litellm.gateway.serviceAccountName" . }} automountServiceAccountToken: {{ .Values.serviceAccounts.gateway.automount }} + {{- with .Values.gateway.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} @@ -32,6 +39,10 @@ spec: - name: gateway image: "{{ .Values.gateway.image.repository }}:{{ .Values.gateway.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.gateway.image.pullPolicy }} + {{- with .Values.gateway.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} ports: - name: http containerPort: 4000 @@ -72,8 +83,15 @@ spec: readinessProbe: {{- toYaml . | nindent 12 }} {{- end }} + {{- with .Values.gateway.lifecycle }} + lifecycle: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.gateway.resources | nindent 12 }} + {{- with .Values.gateway.extraContainers }} + {{- tpl (toYaml .) $ | nindent 8 }} + {{- end }} {{- if or .Values.gateway.config.create .Values.gateway.volumes .Values.billingMetrics.enabled }} volumes: {{- if .Values.gateway.config.create }} @@ -104,4 +122,8 @@ spec: topologySpreadConstraints: {{- toYaml . | nindent 8 }} {{- end }} + {{- $gracePeriod := .Values.gateway.terminationGracePeriodSeconds }} + {{- if not (or (kindIs "invalid" $gracePeriod) (eq (printf "%v" $gracePeriod) "")) }} + terminationGracePeriodSeconds: {{ $gracePeriod }} + {{- end }} {{- end }} diff --git a/helm/litellm/templates/migrations-job.yaml b/helm/litellm/templates/migrations-job.yaml index 92671388546..2debe8a1e10 100644 --- a/helm/litellm/templates/migrations-job.yaml +++ b/helm/litellm/templates/migrations-job.yaml @@ -23,12 +23,21 @@ spec: ttlSecondsAfterFinished: {{ .Values.migrationJob.ttlSecondsAfterFinished }} template: metadata: + {{- /* The Job's selector is generated by the controller rather than + declared, so podLabels may override a chart label here. Merge + instead of appending so an override replaces the key rather than + rendering it twice. */}} + {{- $chartLabels := merge (dict "app.kubernetes.io/component" "migrations") (fromYaml (include "litellm.commonLabels" .)) }} labels: - {{- include "litellm.commonLabels" . | nindent 8 }} - app.kubernetes.io/component: migrations + {{- toYaml (merge (deepCopy .Values.migrationJob.podLabels) $chartLabels) | nindent 8 }} spec: restartPolicy: Never - serviceAccountName: {{ include "litellm.backend.serviceAccountName" . }} + serviceAccountName: {{ include "litellm.migrations.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.migrationJob.automountServiceAccountToken }} + {{- with .Values.migrationJob.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} @@ -37,10 +46,22 @@ spec: - name: prisma-migrations image: "{{ .Values.migrationJob.image.repository }}:{{ .Values.migrationJob.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.migrationJob.image.pullPolicy }} + {{- with .Values.migrationJob.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} env: {{- include "litellm.serverEnv" (dict "root" $ "component" .Values.migrationJob) | nindent 12 }} + {{- with .Values.migrationJob.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} {{- with .Values.migrationJob.resources }} resources: {{- toYaml . | nindent 12 }} {{- end }} + {{- with .Values.migrationJob.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} diff --git a/helm/litellm/templates/ui/deployment.yaml b/helm/litellm/templates/ui/deployment.yaml index cd1f8c08fd4..b4129dbc8ac 100644 --- a/helm/litellm/templates/ui/deployment.yaml +++ b/helm/litellm/templates/ui/deployment.yaml @@ -18,9 +18,16 @@ spec: {{- end }} labels: {{- include "litellm.ui.selectorLabels" . | nindent 8 }} + {{- with .Values.ui.podLabels }} + {{- include "litellm.podLabels" (dict "podLabels" . "componentName" "ui") | nindent 8 }} + {{- end }} spec: serviceAccountName: {{ include "litellm.ui.serviceAccountName" . }} automountServiceAccountToken: {{ .Values.serviceAccounts.ui.automount }} + {{- with .Values.ui.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} @@ -29,6 +36,10 @@ spec: - name: ui image: "{{ .Values.ui.image.repository }}:{{ .Values.ui.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.ui.image.pullPolicy }} + {{- with .Values.ui.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} ports: - name: http containerPort: 3000 @@ -58,8 +69,15 @@ spec: readinessProbe: {{- toYaml . | nindent 12 }} {{- end }} + {{- with .Values.ui.lifecycle }} + lifecycle: + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.ui.resources | nindent 12 }} + {{- with .Values.ui.extraContainers }} + {{- tpl (toYaml .) $ | nindent 8 }} + {{- end }} {{- with .Values.ui.volumes }} volumes: {{- toYaml . | nindent 8 }} @@ -80,4 +98,8 @@ spec: topologySpreadConstraints: {{- toYaml . | nindent 8 }} {{- end }} + {{- $gracePeriod := .Values.ui.terminationGracePeriodSeconds }} + {{- if not (or (kindIs "invalid" $gracePeriod) (eq (printf "%v" $gracePeriod) "")) }} + terminationGracePeriodSeconds: {{ $gracePeriod }} + {{- end }} {{- end }} diff --git a/helm/litellm/tests/migration_job_tests.yaml b/helm/litellm/tests/migration_job_tests.yaml new file mode 100644 index 00000000000..12e525c5a8c --- /dev/null +++ b/helm/litellm/tests/migration_job_tests.yaml @@ -0,0 +1,169 @@ +suite: test migrations Job ServiceAccount resolution and pod hardening +templates: + - migrations-job.yaml +values: + - ./values/required.yaml +tests: + - it: borrows the namespace default account when no ServiceAccount is configured + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: default + + - it: falls back to the namespace default account when the chart creates the backend ServiceAccount + set: + serviceAccounts.backend.create: true + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: default + - notEqual: + path: spec.template.spec.serviceAccountName + value: RELEASE-NAME-litellm-backend + + - it: keeps sharing an existing backend ServiceAccount the chart does not create + set: + serviceAccounts.backend.create: false + serviceAccounts.backend.name: existing-backend-sa + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: existing-backend-sa + + - it: prefers an explicit migration ServiceAccount over the created backend one + set: + serviceAccounts.backend.create: true + migrationJob.serviceAccountName: migrations-sa + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: migrations-sa + + - it: prefers an explicit migration ServiceAccount over an existing backend one + set: + serviceAccounts.backend.create: false + serviceAccounts.backend.name: existing-backend-sa + migrationJob.serviceAccountName: migrations-sa + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: migrations-sa + + - it: mounts no ServiceAccount token by default + asserts: + - equal: + path: spec.template.spec.automountServiceAccountToken + value: false + + - it: mounts a ServiceAccount token when the operator asks for one + set: + migrationJob.automountServiceAccountToken: true + asserts: + - equal: + path: spec.template.spec.automountServiceAccountToken + value: true + + - it: keeps the token off the Job when the backend disables automounting + set: + serviceAccounts.backend.create: true + serviceAccounts.backend.automount: false + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: default + - equal: + path: spec.template.spec.automountServiceAccountToken + value: false + + - it: renders no hardening fields by default + asserts: + - isNull: + path: spec.template.spec.securityContext + - isNull: + path: spec.template.spec.containers[0].securityContext + - isNull: + path: spec.template.spec.volumes + - isNull: + path: spec.template.spec.containers[0].volumeMounts + - equal: + path: spec.template.metadata.labels + value: + app.kubernetes.io/name: litellm + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + helm.sh/chart: litellm-0.1.0 + app.kubernetes.io/component: migrations + + - it: renders pod-level and container-level securityContext in their own scopes + set: + migrationJob.podSecurityContext: + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + migrationJob.securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + asserts: + - equal: + path: spec.template.spec.securityContext + value: + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + - equal: + path: spec.template.spec.containers[0].securityContext + value: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + + - it: renders volumes on the pod and volumeMounts on the migration container + set: + migrationJob.volumes: + - name: tmp + emptyDir: + sizeLimit: 64Mi + migrationJob.volumeMounts: + - name: tmp + mountPath: /tmp + asserts: + - equal: + path: spec.template.spec.volumes + value: + - name: tmp + emptyDir: + sizeLimit: 64Mi + - equal: + path: spec.template.spec.containers[0].volumeMounts + value: + - name: tmp + mountPath: /tmp + + - it: merges podLabels with the chart labels on the Job pod + set: + migrationJob.podLabels: + egress-policy: restricted + asserts: + - equal: + path: spec.template.metadata.labels['egress-policy'] + value: restricted + - equal: + path: spec.template.metadata.labels['app.kubernetes.io/component'] + value: migrations + + - it: accepts a podLabel that reuses a chart label, since the Job selector is controller-generated + set: + migrationJob.podLabels: + app.kubernetes.io/component: batch-migrations + asserts: + - notFailedTemplate: {} + - equal: + path: spec.template.metadata.labels['app.kubernetes.io/component'] + value: batch-migrations diff --git a/helm/litellm/tests/pod_hardening_tests.yaml b/helm/litellm/tests/pod_hardening_tests.yaml new file mode 100644 index 00000000000..18e836670c0 --- /dev/null +++ b/helm/litellm/tests/pod_hardening_tests.yaml @@ -0,0 +1,298 @@ +suite: test pod hardening knobs on the component deployments +templates: + - gateway/deployment.yaml + - gateway/configmap.yaml + - backend/deployment.yaml + - ui/deployment.yaml +values: + - ./values/required.yaml +tests: + - it: gateway renders no hardening fields by default + template: gateway/deployment.yaml + asserts: + - isNull: + path: spec.template.spec.securityContext + - isNull: + path: spec.template.spec.containers[0].securityContext + - isNull: + path: spec.template.spec.containers[0].lifecycle + - isNull: + path: spec.template.spec.terminationGracePeriodSeconds + - lengthEqual: + path: spec.template.spec.containers + count: 1 + - equal: + path: spec.template.metadata.labels + value: + app.kubernetes.io/name: litellm + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/component: gateway + + - it: gateway renders pod-level and container-level securityContext in their own scopes + template: gateway/deployment.yaml + set: + gateway.podSecurityContext: + runAsNonRoot: true + runAsUser: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + gateway.securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + asserts: + - equal: + path: spec.template.spec.securityContext + value: + runAsNonRoot: true + runAsUser: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + - equal: + path: spec.template.spec.containers[0].securityContext + value: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + + - it: gateway merges podLabels with the selector labels + template: gateway/deployment.yaml + set: + gateway.podLabels: + egress-policy: restricted + team: platform + asserts: + - equal: + path: spec.template.metadata.labels + value: + app.kubernetes.io/name: litellm + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/component: gateway + egress-policy: restricted + team: platform + - equal: + path: spec.selector.matchLabels + value: + app.kubernetes.io/name: litellm + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/component: gateway + + - it: gateway rejects a podLabel that collides with the selector + template: gateway/deployment.yaml + set: + gateway.podLabels: + app.kubernetes.io/component: not-gateway + asserts: + - failedTemplate: + errorMessage: "gateway.podLabels cannot set app.kubernetes.io/component: it is part of the Deployment's immutable selector" + + - it: backend rejects a podLabel that collides with the selector + template: backend/deployment.yaml + set: + backend.podLabels: + app.kubernetes.io/name: not-litellm + asserts: + - failedTemplate: + errorMessage: "backend.podLabels cannot set app.kubernetes.io/name: it is part of the Deployment's immutable selector" + + - it: ui rejects a podLabel that collides with the selector + template: ui/deployment.yaml + set: + ui.podLabels: + app.kubernetes.io/instance: not-the-release + asserts: + - failedTemplate: + errorMessage: "ui.podLabels cannot set app.kubernetes.io/instance: it is part of the Deployment's immutable selector" + + - it: gateway renders lifecycle hooks on the container + template: gateway/deployment.yaml + set: + gateway.lifecycle: + preStop: + httpGet: + path: /health/drain + port: 4000 + asserts: + - equal: + path: spec.template.spec.containers[0].lifecycle + value: + preStop: + httpGet: + path: /health/drain + port: 4000 + + - it: gateway renders terminationGracePeriodSeconds on the pod spec + template: gateway/deployment.yaml + set: + gateway.terminationGracePeriodSeconds: 90 + asserts: + - equal: + path: spec.template.spec.terminationGracePeriodSeconds + value: 90 + + - it: gateway honors an explicit terminationGracePeriodSeconds of zero + template: gateway/deployment.yaml + set: + gateway.terminationGracePeriodSeconds: 0 + asserts: + - equal: + path: spec.template.spec.terminationGracePeriodSeconds + value: 0 + + - it: gateway appends extraContainers after the gateway container + template: gateway/deployment.yaml + set: + gateway.extraContainers: + - name: auth-sidecar + image: registry.example.com/auth-proxy:1.2.3 + args: + - --upstream + - http://127.0.0.1:4000 + asserts: + - lengthEqual: + path: spec.template.spec.containers + count: 2 + - equal: + path: spec.template.spec.containers[0].name + value: gateway + - equal: + path: spec.template.spec.containers[1] + value: + name: auth-sidecar + image: registry.example.com/auth-proxy:1.2.3 + args: + - --upstream + - http://127.0.0.1:4000 + + - it: gateway templates chart context inside extraContainers + template: gateway/deployment.yaml + set: + gateway.extraContainers: + - name: auth-sidecar + image: registry.example.com/auth-proxy:1.2.3 + env: + - name: RELEASE + value: "{{ .Release.Name }}" + asserts: + - equal: + path: spec.template.spec.containers[1].env[0].value + value: RELEASE-NAME + + - it: backend renders every hardening knob in the right scope + template: backend/deployment.yaml + set: + backend.podLabels: + egress-policy: restricted + backend.podSecurityContext: + runAsNonRoot: true + backend.securityContext: + readOnlyRootFilesystem: true + backend.lifecycle: + preStop: + exec: + command: + - sleep + - "5" + backend.terminationGracePeriodSeconds: 60 + backend.extraContainers: + - name: auth-sidecar + image: registry.example.com/auth-proxy:1.2.3 + asserts: + - equal: + path: spec.template.metadata.labels['egress-policy'] + value: restricted + - equal: + path: spec.template.spec.securityContext + value: + runAsNonRoot: true + - equal: + path: spec.template.spec.containers[0].securityContext + value: + readOnlyRootFilesystem: true + - equal: + path: spec.template.spec.containers[0].lifecycle + value: + preStop: + exec: + command: + - sleep + - "5" + - equal: + path: spec.template.spec.terminationGracePeriodSeconds + value: 60 + - equal: + path: spec.template.spec.containers[1].name + value: auth-sidecar + + - it: ui renders every hardening knob in the right scope + template: ui/deployment.yaml + set: + ui.podLabels: + egress-policy: restricted + ui.podSecurityContext: + runAsNonRoot: true + fsGroup: 101 + ui.securityContext: + readOnlyRootFilesystem: true + ui.lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - nginx -s quit + ui.terminationGracePeriodSeconds: 30 + ui.extraContainers: + - name: auth-sidecar + image: registry.example.com/auth-proxy:1.2.3 + asserts: + - equal: + path: spec.template.metadata.labels['egress-policy'] + value: restricted + - equal: + path: spec.template.spec.securityContext + value: + runAsNonRoot: true + fsGroup: 101 + - equal: + path: spec.template.spec.containers[0].securityContext + value: + readOnlyRootFilesystem: true + - equal: + path: spec.template.spec.containers[0].lifecycle + value: + preStop: + exec: + command: + - /bin/sh + - -c + - nginx -s quit + - equal: + path: spec.template.spec.terminationGracePeriodSeconds + value: 30 + - equal: + path: spec.template.spec.containers[1].name + value: auth-sidecar + + - it: backend and ui render no hardening fields by default + templates: + - backend/deployment.yaml + - ui/deployment.yaml + asserts: + - isNull: + path: spec.template.spec.securityContext + - isNull: + path: spec.template.spec.containers[0].securityContext + - isNull: + path: spec.template.spec.containers[0].lifecycle + - isNull: + path: spec.template.spec.terminationGracePeriodSeconds + - lengthEqual: + path: spec.template.spec.containers + count: 1 diff --git a/helm/litellm/values.yaml b/helm/litellm/values.yaml index 461935b2f50..48e55a6805b 100644 --- a/helm/litellm/values.yaml +++ b/helm/litellm/values.yaml @@ -57,6 +57,42 @@ migrationJob: backoffLimit: 4 ttlSecondsAfterFinished: 120 resources: {} + # ServiceAccount for the Job pod only. + # + # The Job is a pre-install / pre-upgrade hook, so it runs before the chart's + # ordinary resources exist. With `serviceAccounts.backend.create: true` the + # backend ServiceAccount is one of those ordinary resources, so a Job that + # borrowed its name would reference an account that does not exist yet and + # the first install would fail with a forbidden pod creation. The name set + # here always wins; when it is empty the Job falls back to `default` if the + # chart creates the backend ServiceAccount, and to the backend + # ServiceAccount name otherwise (that name is either an existing account you + # supplied or `default`). + # + # Point this at a pre-existing ServiceAccount when the Job needs credentials + # of its own, e.g. the IRSA / Workload Identity annotations that + # `database.writer.useIAMAuth` relies on. That is also the upgrade path to + # watch: a release already running with `serviceAccounts.backend.create: + # true` used to hand the Job the created backend account on every upgrade, + # and now hands it `default` unless you name an account here. + serviceAccountName: "" + # The Job runs `prisma migrate deploy` against Postgres and never calls the + # K8s API, so it defaults to no projected ServiceAccount token, the same + # reasoning the ui SA above uses. Flip to true if your Job genuinely needs + # one; IAM database auth does not, since EKS Pod Identity injects its own + # projected token volume and GKE Workload Identity goes through the + # metadata server, neither of which is the default token mount. + automountServiceAccountToken: false + # Standard k8s pod-level and container-level securityContext for the Job + # pod. Same shape as gateway.podSecurityContext / gateway.securityContext. + podSecurityContext: {} + securityContext: {} + # Extra pod labels on the Job pod, merged into the chart's common labels. + podLabels: {} + # Additional volumes on the Job pod and volumeMounts on its container, e.g. + # the writable scratch space a read-only root filesystem needs. + volumes: [] + volumeMounts: [] image: repository: ghcr.io/berriai/litellm-migrations tag: "" # defaults to .Chart.AppVersion @@ -200,6 +236,37 @@ gateway: minAvailable: "" maxUnavailable: "" podAnnotations: {} + # Extra pod labels, merged into the chart's selector labels. Do not + # re-declare `app.kubernetes.io/name` / `instance` / `component` here: they + # form the Deployment's immutable selector. + podLabels: {} + # Pod-level securityContext, applied to every container in the pod + # (runAsNonRoot, runAsUser, fsGroup, seccompProfile, ...). Empty by default + # so the cluster's own defaults keep applying to existing installs; clusters + # enforcing a restricted Pod Security Standard usually want at least + # `runAsNonRoot: true` and `seccompProfile.type: RuntimeDefault`. + podSecurityContext: {} + # Container-level securityContext for the gateway container. Empty by + # default for the same reason. Example: + # allowPrivilegeEscalation: false + # readOnlyRootFilesystem: true + # capabilities: + # drop: + # - ALL + # `readOnlyRootFilesystem: true` needs writable scratch space; supply it + # through `volumes` / `volumeMounts` above rather than expecting the chart + # to guess the paths your workload writes to. + securityContext: {} + # Extra sidecar containers appended to the gateway pod, e.g. an auth or + # egress proxy. Rendered through `tpl`, so entries may reference chart + # values and release metadata. + extraContainers: [] + # Container lifecycle hooks (postStart / preStop) for the gateway container. + lifecycle: {} + # Grace period the kubelet allows between SIGTERM and SIGKILL. Leave empty + # to inherit the Kubernetes default of 30s. Set it a few seconds above the + # proxy's GRACEFUL_SHUTDOWN_TIMEOUT when you use a draining preStop hook. + terminationGracePeriodSeconds: "" nodeSelector: {} tolerations: [] affinity: {} @@ -257,6 +324,13 @@ backend: minAvailable: "" maxUnavailable: "" podAnnotations: {} + # Same shape as the gateway blocks of the same name. + podLabels: {} + podSecurityContext: {} + securityContext: {} + extraContainers: [] + lifecycle: {} + terminationGracePeriodSeconds: "" nodeSelector: {} tolerations: [] affinity: {} @@ -310,6 +384,16 @@ ui: minAvailable: "" maxUnavailable: "" podAnnotations: {} + # Same shape as the gateway blocks of the same name. The nginx runtime + # writes its pid, cache, and proxy temp files under the image's root + # filesystem, so `securityContext.readOnlyRootFilesystem: true` here needs + # emptyDir volumes mounted over those paths. + podLabels: {} + podSecurityContext: {} + securityContext: {} + extraContainers: [] + lifecycle: {} + terminationGracePeriodSeconds: "" nodeSelector: {} tolerations: [] affinity: {}