diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000..ccc2b3d --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: libretranslate +description: A Helm chart for Kubernetes to deploy LibreTranslate API +version: 0.1.0 diff --git a/chart/README.md b/chart/README.md new file mode 100644 index 0000000..70a7bb1 --- /dev/null +++ b/chart/README.md @@ -0,0 +1,58 @@ +# LibreTranslate Helm Chart + +This Helm chart deploys a LibreTranslate instance on a Kubernetes cluster using the Helm package manager. + +## Prerequisites + +- Kubernetes 1.12+ +- Helm 3.0+ + +## Installing the Chart + +To install the chart with the release name `libretranslate`: + +```bash +helm install libretranslate ./chart --namespace libretranslate --create-namespace +``` + +This command deploys LibreTranslate on the Kubernetes cluster with the default configuration. The [values.yaml](values.yaml) file lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `libretranslate` deployment: + +```bash +helm delete libretranslate +``` + +This command removes all the Kubernetes components associated with the chart and deletes the release. + +## Configuration + +See [values.yaml](values.yaml) for the full list of parameters that can be configured. You can specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```bash +helm install libretranslate ./chart --namespace libretranslate --create-namespace --set service.port=8080 +``` + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```bash +helm install libretranslate ./chart --namespace libretranslate --create-namespace -f values.yaml +``` + +## Upgrade + +Run the following command to upgrade your LibreTranslate installation. This command will use the Helm chart in the ./chart directory, apply the custom values from values.yaml, and deploy the upgrade to the `libretranslate` namespace: + +```bash +helm upgrade --install libretranslate ./chart --namespace libretranslate -f values.yaml +``` + +> **Tip**: You can use the default [values.yaml](values.yaml) + +# References +- [https://jmrobles.medium.com/libretranslate-your-own-translation-service-on-kubernetes-b46c3e1af630](https://jmrobles.medium.com/libretranslate-your-own-translation-service-on-kubernetes-b46c3e1af630) +- [https://github.com/LibreTranslate/LibreTranslate](https://github.com/LibreTranslate/LibreTranslate) \ No newline at end of file diff --git a/chart/templates/NOTES.txt b/chart/templates/NOTES.txt new file mode 100644 index 0000000..d83122a --- /dev/null +++ b/chart/templates/NOTES.txt @@ -0,0 +1,45 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} + http{{ if .Values.ingress.tls }}s{{ end }}://{{ index .Values.ingress.hosts 0 "host" }}{{ index .Values.ingress.hosts 0 "paths" 0 "path" }} + {{- if .name }} + - http{{ if .Values.ingress.tls }}s{{ end }}://{{ .name }}{{ .path }} + {{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "libretranslate.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get svc -w {{ include "libretranslate.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "libretranslate.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "libretranslate.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:{{ .Values.service.port }} +{{- end }} + +2. Get your admin username and password: + + USER=$(kubectl get secret {{ include "libretranslate.fullname" . }}-auth -o jsonpath="{.data.username}" | base64 --decode) + PASSWORD=$(kubectl get secret {{ include "libretranslate.fullname" . }}-auth -o jsonpath="{.data.password}" | base64 --decode) + + echo "Username: $USER" + echo "Password: $PASSWORD" + +3. Manage your LibreTranslate API keys: + + If you setted `--api-keys true` to use the api keys in the StatefulSet, you can manage them with the following commands: + + # this will add an api key with a limit of 120 requests per minute + kubectl exec -it {{ include "libretranslate.fullname" . }}-0 -c {{ include "libretranslate.fullname" . }} -- /bin/bash -c "source ./venv/bin/activate && ltmanage keys add --key req-limit-120 120" + + # returns a list of all api keys + kubectl exec -it {{ include "libretranslate.fullname" . }}-0 -c {{ include "libretranslate.fullname" . }} -- /bin/bash -c "source ./venv/bin/activate && ltmanage keys" + + # remove an api key + kubectl exec -it {{ include "libretranslate.fullname" . }}-0 -c {{ include "libretranslate.fullname" . }} -- /bin/bash -c "source ./venv/bin/activate && ltmanage keys remove req-limit-120" + + *** Be aware that the api keys are stored in a sqlite database in the pod. If you scale the pod to multiple replicas, the api keys will not be synced between the pods. You can use a shared storage to sync the api keys between the pods. For example, you can use a NFS storage class with a ReadWriteMany access mode to store the api keys in a shared perspective. *** + +``` \ No newline at end of file diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl new file mode 100644 index 0000000..55c3b2f --- /dev/null +++ b/chart/templates/_helpers.tpl @@ -0,0 +1,51 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "libretranslate.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). +*/}} +{{- define "libretranslate.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 "libretranslate.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "libretranslate.labels" -}} +helm.sh/chart: {{ include "libretranslate.chart" . }} +{{ include "libretranslate.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "libretranslate.selectorLabels" -}} +app.kubernetes.io/name: {{ include "libretranslate.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} \ No newline at end of file diff --git a/chart/templates/configmap.yaml b/chart/templates/configmap.yaml new file mode 100644 index 0000000..16c1311 --- /dev/null +++ b/chart/templates/configmap.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: libretranslate-config +data: + host: {{ .Values.appConfig.host | squote }} + port: {{ .Values.appConfig.port | squote }} + charLimit: {{ .Values.appConfig.charLimit | squote }} + reqLimit: {{ .Values.appConfig.reqLimit | squote }} + reqLimitStorage: {{ .Values.appConfig.reqLimitStorage | squote }} + batchLimit: {{ .Values.appConfig.batchLimit | squote }} + gaId: {{ .Values.appConfig.gaId | squote }} + frontendLanguageSource: {{ .Values.appConfig.frontendLanguageSource | squote }} + frontendLanguageTarget: {{ .Values.appConfig.frontendLanguageTarget | squote }} + frontendTimeout: {{ .Values.appConfig.frontendTimeout | squote }} + apiKeysDbPath: {{ .Values.appConfig.apiKeysDbPath | squote }} + apiKeysRemote: {{ .Values.appConfig.apiKeysRemote | squote }} + getApiKeyLink: {{ .Values.appConfig.getApiKeyLink | squote }} + sharedStorage: {{ .Values.appConfig.sharedStorage | squote }} + loadOnly: {{ .Values.appConfig.loadOnly | squote }} + threads: {{ .Values.appConfig.threads | squote }} + metricsAuthToken: {{ .Values.appConfig.metricsAuthToken | squote }} + urlPrefix: {{ .Values.appConfig.urlPrefix | squote }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: libretranslate-appsettings +data: + debug: {{ .Values.appSettings.debug | squote }} + ssl: {{ .Values.appSettings.ssl | squote }} + apiKeys: {{ .Values.appSettings.apiKeys | squote }} + requireApiKeyOrigin: {{ .Values.appSettings.requireApiKeyOrigin | squote }} + requireApiKeySecret: {{ .Values.appSettings.requireApiKeySecret | squote }} + suggestions: {{ .Values.appSettings.suggestions | squote }} + disableFilesTranslation: {{ .Values.appSettings.disableFilesTranslation | squote }} + disableWebUi: {{ .Values.appSettings.disableWebUi | squote }} + updateModels: {{ .Values.appSettings.updateModels | squote }} + metrics: {{ .Values.appSettings.metrics | squote }} diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml new file mode 100644 index 0000000..73365c4 --- /dev/null +++ b/chart/templates/ingress.yaml @@ -0,0 +1,40 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "libretranslate.fullname" . }} + labels: + {{- include "libretranslate.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.ingress.annotations | nindent 4 }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- $fullName := include "libretranslate.fullname" $ }} + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "libretranslate.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/chart/templates/pvc.yaml b/chart/templates/pvc.yaml new file mode 100644 index 0000000..c23c850 --- /dev/null +++ b/chart/templates/pvc.yaml @@ -0,0 +1,36 @@ +{{- if and .Values.persistence.enabled (eq .Values.persistence.db.accessMode "ReadWriteMany") }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: db-volume + labels: + app.kubernetes.io/name: {{ include "libretranslate.name" . }}-pv +spec: + accessModes: + - {{ .Values.persistence.db.accessMode }} + resources: + requests: + storage: {{ .Values.persistence.db.size }} + {{- if .Values.persistence.db.storageClass }} + storageClassName: {{ .Values.persistence.db.storageClass | quote }} + {{- end }} +{{- end }} +{{- if and .Values.persistence.enabled (eq .Values.persistence.models.accessMode "ReadWriteMany") }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: models-volume + labels: + app.kubernetes.io/name: {{ include "libretranslate.name" . }}-pvc +spec: + accessModes: + - {{ .Values.persistence.models.accessMode }} + resources: + requests: + storage: {{ .Values.persistence.models.size }} + {{- if .Values.persistence.models.storageClass }} + storageClassName: {{ .Values.persistence.models.storageClass | quote }} + {{- end }} +{{- end }} diff --git a/chart/templates/secret.yaml b/chart/templates/secret.yaml new file mode 100644 index 0000000..4d50c12 --- /dev/null +++ b/chart/templates/secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "libretranslate.fullname" . }}-auth +type: Opaque +data: + auth: {{ .Values.adminUser.auth | quote }} + username: {{ .Values.adminUser.name | quote }} + password: {{ .Values.adminUser.password | quote }} diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml new file mode 100644 index 0000000..c6b238f --- /dev/null +++ b/chart/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "libretranslate.fullname" . }} + labels: + {{- include "libretranslate.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "libretranslate.selectorLabels" . | nindent 4 }} diff --git a/chart/templates/statefulset.yaml b/chart/templates/statefulset.yaml new file mode 100644 index 0000000..a787932 --- /dev/null +++ b/chart/templates/statefulset.yaml @@ -0,0 +1,344 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "libretranslate.fullname" . }} + labels: + {{- include "libretranslate.labels" . | nindent 4 }} + {{- if .Values.annotations }} + annotations: + {{- toYaml .Values.annotations | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "libretranslate.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- if .Values.podAnnotations }} + {{- toYaml .Values.podAnnotations | nindent 8 }} + {{- end }} + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + labels: + {{- include "libretranslate.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.imagePullSecrets }} + imagePullSecrets: + {{ toYaml .Values.imagePullSecrets | indent 8 }} + {{- end }} + {{- if .Values.securityContext }} + # forces the mount of the volumes to be mounted as libretranslate user and group + # and set permissions to libretranslate:libretranslate + securityContext: + {{- toYaml .Values.securityContext | nindent 8 }} + {{- end }} + {{- if .Values.persistence.enabled }} + initContainers: + - name: volume-permissions-and-pre-install-models + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + command: + - /bin/bash + - -c + - | + mkdir -p /home/libretranslate/.local/share/argos-translate/packages + chown -R 1032:1032 /home/libretranslate/.local /app/db + + if [ ! "$(ls -A /home/libretranslate/.local/share/argos-translate/packages)" ]; then + echo "Installing models... this can take quite a while depending on your internet connection." + /app/venv/bin/python /app/scripts/install_models.py --load_only_lang_codes {{ .Values.appConfig.loadOnly | quote }} + else + echo "The models directory is not empty, skipping model installation." + fi + volumeMounts: + - name: models-volume + mountPath: /home/libretranslate/.local/share/argos-translate + - name: db-volume + mountPath: {{ .Values.appConfig.apiKeysDbPathMount }} + {{- if .Values.initContainerSecurityContext }} + securityContext: + # forces the mount of the volumes to be mounted as libretranslate user and group + # and set permissions to libretranslate:libretranslate + {{- toYaml .Values.initContainerSecurityContext | nindent 12 }} + {{- end }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + resources: + {{- toYaml .Values.resources | nindent 12 }} + securityContext: + {{- if .Values.podSecurityContext }} + # forces the mount of the volumes to be mounted as libretranslate user and group + # and set permissions to libretranslate:libretranslate + {{- toYaml .Values.podSecurityContext | nindent 12 }} + {{- end }} + allowPrivilegeEscalation: false + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + env: + {{- if and (.Values.appSettings.debug) (ne .Values.appSettings.debug "") }} + - name: LT_DEBUG + valueFrom: + configMapKeyRef: + name: libretranslate-appsettings + key: debug + {{- end }} + {{- if and (.Values.appSettings.ssl) (ne .Values.appSettings.ssl "") }} + - name: LT_SSL + valueFrom: + configMapKeyRef: + name: libretranslate-appsettings + key: ssl + {{- end }} + {{- if and (.Values.appSettings.apiKeys) (ne .Values.appSettings.apiKeys "") }} + - name: LT_API_KEYS + valueFrom: + configMapKeyRef: + name: libretranslate-appsettings + key: apiKeys + {{- end }} + {{- if and (.Values.appSettings.requireApiKeyOrigin) (ne .Values.appSettings.requireApiKeyOrigin "") }} + - name: LT_REQUIRE_API_KEY_ORIGIN + valueFrom: + configMapKeyRef: + name: libretranslate-appsettings + key: requireApiKeyOrigin + {{- end }} + {{- if and (.Values.appSettings.requireApiKeySecret) (ne .Values.appSettings.requireApiKeySecret "") }} + - name: LT_REQUIRE_API_KEY_SECRET + valueFrom: + configMapKeyRef: + name: libretranslate-appsettings + key: requireApiKeySecret + {{- end }} + {{- if and (.Values.appSettings.suggestions) (ne .Values.appSettings.suggestions "") }} + - name: LT_SUGGESTIONS + valueFrom: + configMapKeyRef: + name: libretranslate-appsettings + key: suggestions + {{- end }} + {{- if and (.Values.appSettings.disableFilesTranslation) (ne .Values.appSettings.disableFilesTranslation "") }} + - name: LT_DISABLE_FILES_TRANSLATION + valueFrom: + configMapKeyRef: + name: libretranslate-appsettings + key: disableFilesTranslation + {{- end }} + {{- if and (.Values.appSettings.disableWebUi) (ne .Values.appSettings.disableWebUi "") }} + - name: LT_DISABLE_WEB_UI + valueFrom: + configMapKeyRef: + name: libretranslate-appsettings + key: disableWebUi + {{- end }} + {{- if and (.Values.appSettings.updateModels) (ne .Values.appSettings.updateModels "") }} + - name: LT_UPDATE_MODELS + valueFrom: + configMapKeyRef: + name: libretranslate-appsettings + key: updateModels + {{- end }} + {{- if and (.Values.appSettings.metrics) (ne .Values.appSettings.metrics "") }} + - name: LT_METRICS + valueFrom: + configMapKeyRef: + name: libretranslate-appsettings + key: metrics + {{- end }} + {{- if and (.Values.appConfig.host) (ne .Values.appConfig.host "") }} + - name: LT_HOST + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: host + {{- end }} + {{- if and (.Values.appSettings.port) (ne .Values.appSettings.port "") }} + - name: LT_PORT + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: port + {{- end }} + {{- if and (.Values.appConfig.charLimit) (ne .Values.appConfig.charLimit "") }} + - name: LT_CHAR_LIMIT + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: charLimit + {{- end }} + {{- if and (.Values.appConfig.reqLimit) (ne .Values.appConfig.reqLimit "") }} + - name: LT_REQ_LIMIT + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: reqLimit + {{- end }} + {{- if and (.Values.appConfig.reqLimitStorage) (ne .Values.appConfig.reqLimitStorage "") }} + - name: LT_REQ_LIMIT_STORAGE + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: reqLimitStorage + {{- end }} + {{- if and (.Values.appConfig.batchLimit) (ne .Values.appConfig.batchLimit "") }} + - name: LT_BATCH_LIMIT + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: batchLimit + {{- end }} + {{- if and (.Values.appConfig.gaId) (ne .Values.appConfig.gaId "") }} + - name: LT_GA_ID + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: gaId + {{- end }} + {{- if and (.Values.appConfig.frontendLanguageSource) (ne .Values.appConfig.frontendLanguageSource "") }} + - name: LT_FRONTEND_LANGUAGE_SOURCE + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: frontendLanguageSource + {{- end }} + {{- if and (.Values.appConfig.frontendLanguageTarget) (ne .Values.appConfig.frontendLanguageTarget "") }} + - name: LT_FRONTEND_LANGUAGE_TARGET + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: frontendLanguageTarget + {{- end }} + {{- if and (.Values.appConfig.frontendTimeout) (ne .Values.appConfig.frontendTimeout "") }} + - name: LT_FRONTEND_TIMEOUT + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: frontendTimeout + {{- end }} + {{- if and (.Values.appSettings.apiKeysDbPath) (ne .Values.appSettings.apiKeysDbPath "") }} + - name: LT_API_KEYS_DB_PATH + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: apiKeysDbPath + {{- end }} + {{- if and (.Values.appSettings.apiKeysRemote) (ne .Values.appSettings.apiKeysRemote "") }} + - name: LT_API_KEYS_REMOTE + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: apiKeysRemote + {{- end }} + {{- if and (.Values.appConfig.getApiKeyLink) (ne .Values.appConfig.getApiKeyLink "") }} + - name: LT_GET_API_KEY_LINK + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: getApiKeyLink + {{- end }} + {{- if and (.Values.appConfig.sharedStorage) (ne .Values.appConfig.sharedStorage "") }} + - name: LT_SHARED_STORAGE + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: sharedStorage + {{- end }} + {{- if and (.Values.appConfig.loadOnly) (ne .Values.appConfig.loadOnly "") }} + - name: LT_LOAD_ONLY + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: loadOnly + {{- end }} + {{- if and (.Values.appConfig.threads) (ne .Values.appConfig.threads "") }} + - name: LT_THREADS + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: threads + {{- end }} + {{- if and (.Values.appSettings.metricsAuthToken) (ne .Values.appSettings.metricsAuthToken "") }} + - name: LT_METRICS_AUTH_TOKEN + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: metricsAuthToken + {{- end }} + {{- if and (.Values.appConfig.urlPrefix) (ne .Values.appConfig.urlPrefix "") }} + - name: LT_URL_PREFIX + valueFrom: + configMapKeyRef: + name: libretranslate-config + key: urlPrefix + {{- end }} + {{- if .Values.persistence.enabled }} + volumeMounts: + - name: db-volume + mountPath: {{ .Values.appConfig.apiKeysDbPathMount }} + - name: models-volume + mountPath: /home/libretranslate/.local/share/argos-translate + {{- end }} + {{- if and .Values.persistence.enabled (or (eq .Values.persistence.models.accessMode "ReadWriteMany") (eq .Values.persistence.db.accessMode "ReadWriteMany")) }} + volumes: + {{- if eq .Values.persistence.db.accessMode "ReadWriteMany" }} + - name: db-volume + persistentVolumeClaim: + claimName: db-volume + {{- end }} + {{- if eq .Values.persistence.models.accessMode "ReadWriteMany" }} + - name: models-volume + persistentVolumeClaim: + claimName: models-volume + {{- end }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: + {{- toYaml .Values.tolerations | nindent 6 }} + {{- end }} + {{- if and .Values.persistence.enabled (or (eq .Values.persistence.models.accessMode "ReadWriteOnce") (eq .Values.persistence.db.accessMode "ReadWriteOnce")) }} + # still in beta, but this will allow us to delete the volumes when the statefulset is scaled down + persistentVolumeClaimRetentionPolicy: + whenDeleted: Retain + whenScaled: Delete + volumeClaimTemplates: + {{- if eq .Values.persistence.models.accessMode "ReadWriteOnce" }} + - metadata: + name: models-volume + labels: + {{- include "libretranslate.labels" . | nindent 8 }} + spec: + accessModes: + - {{ .Values.persistence.models.accessMode }} + resources: + requests: + storage: {{ .Values.persistence.models.size }} + {{- if .Values.persistence.models.storageClass }} + storageClassName: {{ .Values.persistence.models.storageClass | quote }} + {{- end }} + {{- end }} + {{- if eq .Values.persistence.db.accessMode "ReadWriteOnce" }} + - metadata: + name: db-volume + labels: + {{- include "libretranslate.labels" . | nindent 8 }} + spec: + accessModes: + - {{ .Values.persistence.db.accessMode }} + resources: + requests: + storage: {{ .Values.persistence.db.size }} + {{- if .Values.persistence.db.storageClass }} + storageClassName: {{ .Values.persistence.db.storageClass | quote }} + {{- end }} + {{- end }} + {{- end }} \ No newline at end of file diff --git a/chart/values.yaml b/chart/values.yaml new file mode 100644 index 0000000..cf7150b --- /dev/null +++ b/chart/values.yaml @@ -0,0 +1,164 @@ +# values.yaml + +# Number of replicas +replicaCount: 1 + +# Extra annotations for pods +podAnnotations: {} + +# Extra annotations +annotations: {} + +# Extra tolerations for pods +tolerations: [] + +# Chart name override +nameOverride: "" + +# Full name of the deployment to override the default one +fullnameOverride: "" + +# Image settings +image: + repository: libretranslate/libretranslate + pullPolicy: Always + tag: "latest" +# Using a Private Registry +# If you want to use a custom image from a private registry, you'll need to create an image pull secret with the registry's credentials: +# kubectl create secret docker-registry my-registry-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL +# imagePullSecrets: +# - name: my-registry-secret +imagePullSecrets: [] + +# Service settings +service: + type: ClusterIP + port: 5000 + +# Ingress settings +ingress: + enabled: false + className: "" # set this to the name of the ingress controller class to use like nginx + annotations: + # cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/proxy-body-size: 10m + # Check for the adminUser key below. + # This will enable basic auth for the whole site. + # nginx.ingress.kubernetes.io/auth-type: basic + # nginx.ingress.kubernetes.io/auth-secret: libretranslate-auth + # nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required' + hosts: + - host: translate.example.com + paths: + - path: / + pathType: Prefix + # tls: + # - secretName: libretranslate-secret-tls + # hosts: + # - translate.example.com + +# Security Context +securityContext: + fsGroup: 1032 + +# Security Context for init container +initContainerSecurityContext: + runAsUser: 0 + runAsGroup: 0 + +# Pod Security Context +podSecurityContext: + runAsUser: 1032 + runAsGroup: 1032 + +# Persistent settings +# if you don't want to download a copy of the models per pod, you can use a shared storage like an nfs storage class +# and set it to ReadWriteMany. this way if you scale the pods later on, they will all use the same models and won't +# duplicate the space and download requests. +persistence: + enabled: false + db: + storageClass: "" + accessMode: "ReadWriteOnce" + size: "1Gi" + models: + storageClass: "" + accessMode: "ReadWriteOnce" + size: "10Gi" # as of August 2023, the models are about 6.6GB in size for all languages + + +# Resource limits +resources: + limits: + cpu: 1 + memory: 2Gi + requests: + cpu: 500m + memory: 1Gi + +# Readiness probe for kubernetes +readinessProbe: + exec: + command: + - /app/venv/bin/python + - /app/scripts/healthcheck.py + initialDelaySeconds: 10 + periodSeconds: 5 + +# Liveness probe for kubernetes +livenessProbe: + exec: + command: + - /app/venv/bin/python + - scripts/healthcheck.py + initialDelaySeconds: 10 + periodSeconds: 5 + +# Auth secret for basic authentication +# generate base64-user-password-pair with: +# htpasswd -nb | base64 +# +# e.g.: +# +# htpasswd -nb admin mySecretPassword | base64 +# This is used by the nginx ingress controller to enable basic auth for the whole site. +# It will create a secret with the name libretranslate-auth. +adminUser: + name: "YWRtaW4K" # copy the username in base64 as a reference + auth: "YWRtaW46JGFwcjEkYlpydmYvUFYkSHBHSlhqZU1EN0ZON2kyYndsMVRNMQoK" # copy the output from the htpasswd command here as a reference + password: "bXlTZWNyZXRQYXNzd29yZAo=" # copy the password as base64 for the admin user here as a reference + +# Settings / Flags +appSettings: + debug: "false" # Enable debug environment (Default: Disabled) + ssl: "false" # Enable SSL (Default: Disabled) + apiKeys: "false" # Enable API keys database for per-client rate limits when --req-limit is reached (Default: Don't use API keys) + requireApiKeyOrigin: "" # Require use of an API key for programmatic access to the API, unless the request origin matches this domain (Default: No restrictions on domain origin) + requireApiKeySecret: "" # Require use of an API key for programmatic access to the API, unless the client also sends a secret match (Default: No secrets required) + suggestions: "false" # Allow user suggestions (Default: Disabled) + disableFilesTranslation: "false" # Disable files translation (Default: File translation allowed) + disableWebUi: "false" # Disable web ui (Default: Web Ui enabled) + updateModels: "false" # Update language models at startup (Default: Only on if no models found) + metrics: "false" # Enable the /metrics endpoint for exporting Prometheus usage metrics (Default: Disabled) + +# Configuration Parameters +appConfig: + host: "0.0.0.0" # Set host to bind the server to (Default: 127.0.0.1) + port: "5000" # Set port to bind the server to (Default: 5000) + charLimit: "null" # Set character limit (Default: No limit) + reqLimit: "null" # Set maximum number of requests per minute per client (outside of limits set by api keys) (Default: No limit) + reqLimitStorage: "memory://" # Storage URI to use for request limit data storage. See Flask Limiter (Default: memory://) + batchLimit: "null" # Set maximum number of texts to translate in a batch request (Default: No limit) + gaId: "" # Enable Google Analytics on the API client page by providing an ID (Default: Empty (no tracking)) + frontendLanguageSource: "auto" # Set frontend default language - source (Default: auto) + frontendLanguageTarget: "locale" # Set frontend default language - target (Default: locale (match site's locale)) + frontendTimeout: "500" # Set frontend translation timeout (Default: 500) + apiKeysDbPath: "/app/db/api_keys.db" # Use a specific path inside the container for the local database. Can be absolute or relative (Default: /app/db/api_keys.db) + apiKeysDbPathMount: "/app/db" # Use a specific path inside the container for the local database. Must be the same as apiKeysDbPath (Default: /app/db) + apiKeysRemote: "" # Use this remote endpoint to query for valid API keys instead of using the local database (Default: Empty (use local db instead)) + getApiKeyLink: "" # Show a link in the UI where to direct users to get an API key (Default: Empty (no link shown on web ui)) + sharedStorage: "memory://" # Shared storage URI to use for multi-process data sharing (e.g. when using gunicorn) (Default: memory://) + loadOnly: "" # Set available languages (Default: Empty (use all from argostranslate)) + threads: "4" # Set number of threads (Default: 4) + metricsAuthToken: "" # Protect the /metrics endpoint by allowing only clients that have a valid Authorization Bearer token (Default: Empty (no auth required)) + urlPrefix: "" # Add prefix to URL: example.com:5000/url-prefix/ (Default: /)