diff --git a/charts/document-engine/CHANGELOG.md b/charts/document-engine/CHANGELOG.md index 7305ea9..6cab411 100644 --- a/charts/document-engine/CHANGELOG.md +++ b/charts/document-engine/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog - [Changelog](#changelog) + - [7.2.0 (2025-11-01)](#720-2025-11-01) + - [Added](#added) - [7.1.4 (2025-10-27)](#714-2025-10-27) - [Changed](#changed) - [7.1.3 (2025-10-23)](#713-2025-10-23) @@ -14,32 +16,32 @@ - [7.0.1 (2025-10-13)](#701-2025-10-13) - [Changed](#changed-3) - [7.0.0 (2025-10-09)](#700-2025-10-09) - - [Added](#added) + - [Added](#added-1) - [Changed](#changed-4) - [6.3.1 (2025-10-08)](#631-2025-10-08) - [Changed](#changed-5) - [6.3.0 (2025-10-07)](#630-2025-10-07) - [Changed](#changed-6) - [6.2.0 (2025-10-07)](#620-2025-10-07) - - [Added](#added-1) + - [Added](#added-2) - [6.1.0 (2025-10-05)](#610-2025-10-05) - [Changed](#changed-7) - [6.0.0 (2025-10-01)](#600-2025-10-01) - - [Added](#added-2) + - [Added](#added-3) - [Changed](#changed-8) - [5.4.1 (2025-09-28)](#541-2025-09-28) - - [Added](#added-3) + - [Added](#added-4) - [5.4.0 (2025-09-23)](#540-2025-09-23) - [Changed](#changed-9) - [Fixed](#fixed-2) - [5.3.0 (2025-09-21)](#530-2025-09-21) - - [Added](#added-4) + - [Added](#added-5) - [5.2.0 (2025-08-25)](#520-2025-08-25) - [Changed](#changed-10) - [5.1.3 (2025-07-24)](#513-2025-07-24) - [Changed](#changed-11) - [5.1.2 (2025-07-16)](#512-2025-07-16) - - [Added](#added-5) + - [Added](#added-6) - [5.1.1 (2025-07-08)](#511-2025-07-08) - [Changed](#changed-12) - [5.1.0 (2025-07-05)](#510-2025-07-05) @@ -47,17 +49,17 @@ - [5.0.1 (2025-06-27)](#501-2025-06-27) - [Fixed](#fixed-3) - [5.0.0 (2025-06-27)](#500-2025-06-27) - - [Added](#added-6) + - [Added](#added-7) - [Changed](#changed-14) - [4.0.1 (2025-06-24)](#401-2025-06-24) - [Fixed](#fixed-4) - [4.0.0 (2025-06-24)](#400-2025-06-24) - - [Added](#added-7) + - [Added](#added-8) - [Changed](#changed-15) - [3.10.1 (2025-06-18)](#3101-2025-06-18) - [Changed](#changed-16) - [3.10.0 (2025-06-10)](#3100-2025-06-10) - - [Added](#added-8) + - [Added](#added-9) - [3.9.1 (2025-06-09)](#391-2025-06-09) - [Changed](#changed-17) - [3.9.0 (2025-05-29)](#390-2025-05-29) @@ -71,7 +73,7 @@ - [3.8.8 (2025-05-12)](#388-2025-05-12) - [Changed](#changed-21) - [3.8.7 (2025-05-12)](#387-2025-05-12) - - [Added](#added-9) + - [Added](#added-10) - [3.8.6 (2025-04-09)](#386-2025-04-09) - [Changed](#changed-22) - [3.8.5 (2025-04-03)](#385-2025-04-03) @@ -86,7 +88,7 @@ - [Fixed](#fixed-10) - [Changed](#changed-23) - [3.8.0 (2025-04-03)](#380-2025-04-03) - - [Added](#added-10) + - [Added](#added-11) - [3.7.1 (2025-03-26)](#371-2025-03-26) - [Changed](#changed-24) - [3.7.0 (2025-03-20)](#370-2025-03-20) @@ -104,7 +106,7 @@ - [3.3.2 (2025-01-15)](#332-2025-01-15) - [Changed](#changed-31) - [3.3.1 (2025-01-10)](#331-2025-01-10) - - [Added](#added-11) + - [Added](#added-12) - [3.2.12 (2024-12-05)](#3212-2024-12-05) - [Changed](#changed-32) - [3.2.11 (2024-11-21)](#3211-2024-11-21) @@ -114,10 +116,10 @@ - [3.2.9 (2024-11-15)](#329-2024-11-15) - [Changed](#changed-35) - [3.2.7 (2024-11-15)](#327-2024-11-15) - - [Added](#added-12) + - [Added](#added-13) - [Changed](#changed-36) - [3.2.6 (2024-10-29)](#326-2024-10-29) - - [Added](#added-13) + - [Added](#added-14) - [Changed](#changed-37) - [3.2.5 (2024-10-24)](#325-2024-10-24) - [Changed](#changed-38) @@ -136,27 +138,27 @@ - [3.1.1 (2024-08-23)](#311-2024-08-23) - [Fixed](#fixed-13) - [3.1.0 (2024-08-22)](#310-2024-08-22) - - [Added](#added-14) + - [Added](#added-15) - [3.0.6 (2024-08-22)](#306-2024-08-22) - [Changed](#changed-43) - [3.0.5 (2024-08-21)](#305-2024-08-21) - [Fixed](#fixed-14) - [3.0.4 (2024-08-21)](#304-2024-08-21) - [Changed](#changed-44) - - [Added](#added-15) + - [Added](#added-16) - [2.9.3 (2024-08-16)](#293-2024-08-16) - [Fixed](#fixed-15) - [2.9.2 (2024-08-13)](#292-2024-08-13) - [Changed](#changed-45) - [2.9.1 (2024-08-10)](#291-2024-08-10) - - [Added](#added-16) + - [Added](#added-17) - [Changed](#changed-46) - [2.9.0 (2024-08-01)](#290-2024-08-01) - - [Added](#added-17) + - [Added](#added-18) - [Changed](#changed-47) - [Fixed](#fixed-16) - [2.8.0](#280) - - [Added](#added-18) + - [Added](#added-19) - [Changed](#changed-48) - [Fixed](#fixed-17) - [2.7.3](#273) @@ -167,21 +169,27 @@ - [2.7.0](#270) - [Changed](#changed-50) - [2.6.2](#262) - - [Added](#added-19) + - [Added](#added-20) - [Changed](#changed-51) - [2.6.0](#260) - - [Added](#added-20) - - [2.4.0](#240) - [Added](#added-21) - - [2.3.0](#230) + - [2.4.0](#240) - [Added](#added-22) - - [2.2.0](#220) + - [2.3.0](#230) - [Added](#added-23) + - [2.2.0](#220) + - [Added](#added-24) - [2.1.0](#210) - [Changed](#changed-52) - [2.0.0](#200) - [Changed](#changed-53) +## 7.2.0 (2025-11-01) + +### Added + +* Added Envoy as an optional sidecar for better load distrubution using consistent hashing. + ## 7.1.4 (2025-10-27) ### Changed diff --git a/charts/document-engine/Chart.yaml b/charts/document-engine/Chart.yaml index 5acd31f..a809097 100644 --- a/charts/document-engine/Chart.yaml +++ b/charts/document-engine/Chart.yaml @@ -4,7 +4,7 @@ type: application description: Document Engine is a backend software for processing documents and powering automation workflows. home: https://www.nutrient.io/sdk/document-engine icon: https://cdn.prod.website-files.com/65fdb7696055f07a05048833/66e58e33c3880ff24aa34027_nutrient-logo.png -version: 7.1.4 +version: 7.2.0 appVersion: "1.12.2" keywords: diff --git a/charts/document-engine/README.md b/charts/document-engine/README.md index 64dbc7f..e873ea2 100644 --- a/charts/document-engine/README.md +++ b/charts/document-engine/README.md @@ -1,6 +1,6 @@ # Document Engine Helm chart -![Version: 7.1.4](https://img.shields.io/badge/Version-7.1.4-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.12.2](https://img.shields.io/badge/AppVersion-1.12.2-informational?style=flat-square) +![Version: 7.2.0](https://img.shields.io/badge/Version-7.2.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.12.2](https://img.shields.io/badge/AppVersion-1.12.2-informational?style=flat-square) Document Engine is a backend software for processing documents and powering automation workflows. @@ -422,6 +422,17 @@ Note: | Key | Description | Default | |-----|-------------|---------| +| [`envoySidecar`](./values.yaml#L896) | Envoy sidecar for consistent hashing by document ID | [...](./values.yaml#L896) | +| [`envoySidecar.adminPort`](./values.yaml#L912) | Admin port for Envoy | `9901` | +| [`envoySidecar.enabled`](./values.yaml#L899) | Enable Envoy sidecar for consistent hashing | `false` | +| [`envoySidecar.healthCheck`](./values.yaml#L916) | Health check configuration for upstream cluster | [...](./values.yaml#L916) | +| [`envoySidecar.healthCheck.healthyThreshold`](./values.yaml#L928) | Healthy threshold | `2` | +| [`envoySidecar.healthCheck.interval`](./values.yaml#L922) | Health check interval | `"10s"` | +| [`envoySidecar.healthCheck.timeout`](./values.yaml#L919) | Health check timeout | `"5s"` | +| [`envoySidecar.healthCheck.unhealthyThreshold`](./values.yaml#L925) | Unhealthy threshold | `2` | +| [`envoySidecar.image`](./values.yaml#L903) | Envoy sidecar image configuration | [...](./values.yaml#L903) | +| [`envoySidecar.port`](./values.yaml#L909) | Port where Envoy sidecar listens | `8080` | +| [`envoySidecar.resources`](./values.yaml#L932) | Resource limits for Envoy sidecar | [...](./values.yaml#L932) | | [`extraIngresses`](./values.yaml#L880) | Additional ingresses, e.g. for the dashboard | [...](./values.yaml#L880) | | [`ingress`](./values.yaml#L845) | Ingress | [...](./values.yaml#L845) | | [`ingress.annotations`](./values.yaml#L854) | Ingress annotations | `{}` | @@ -429,13 +440,13 @@ Note: | [`ingress.enabled`](./values.yaml#L848) | Enable ingress | `false` | | [`ingress.hosts`](./values.yaml#L857) | Hosts | `[]` | | [`ingress.tls`](./values.yaml#L871) | Ingress TLS section | `[]` | -| [`networkPolicy`](./values.yaml#L897) | [Network policy](https://kubernetes.io/docs/concepts/services-networking/network-policies/) | [...](./values.yaml#L897) | -| [`networkPolicy.allowExternal`](./values.yaml#L905) | Allow access from anywhere | `true` | -| [`networkPolicy.allowExternalEgress`](./values.yaml#L929) | Allow the pod to access any range of port and all destinations. | `true` | -| [`networkPolicy.enabled`](./values.yaml#L900) | Enable network policy | `true` | -| [`networkPolicy.extraEgress`](./values.yaml#L932) | Extra egress rules | `[]` | -| [`networkPolicy.extraIngress`](./values.yaml#L908) | Additional ingress rules | `[]` | -| [`networkPolicy.ingressMatchSelectorLabels`](./values.yaml#L923) | Allow traffic from other namespaces | `[]` | +| [`networkPolicy`](./values.yaml#L944) | [Network policy](https://kubernetes.io/docs/concepts/services-networking/network-policies/) | [...](./values.yaml#L944) | +| [`networkPolicy.allowExternal`](./values.yaml#L952) | Allow access from anywhere | `true` | +| [`networkPolicy.allowExternalEgress`](./values.yaml#L976) | Allow the pod to access any range of port and all destinations. | `true` | +| [`networkPolicy.enabled`](./values.yaml#L947) | Enable network policy | `true` | +| [`networkPolicy.extraEgress`](./values.yaml#L979) | Extra egress rules | `[]` | +| [`networkPolicy.extraIngress`](./values.yaml#L955) | Additional ingress rules | `[]` | +| [`networkPolicy.ingressMatchSelectorLabels`](./values.yaml#L970) | Allow traffic from other namespaces | `[]` | | [`service`](./values.yaml#L825) | Service | [...](./values.yaml#L825) | | [`service.annotations`](./values.yaml#L834) | Service annotations | `{}` | | [`service.internalTrafficPolicy`](./values.yaml#L837) | Service internal traffic policy | `"Cluster"` | @@ -481,42 +492,42 @@ Note: | Key | Description | Default | |-----|-------------|---------| -| [`lifecycle`](./values.yaml#L992) | [Lifecycle](https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/) | `map[]` | -| [`livenessProbe`](./values.yaml#L962) | [Liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | [...](./values.yaml#L962) | -| [`readinessProbe`](./values.yaml#L975) | [Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | [...](./values.yaml#L975) | -| [`startupProbe`](./values.yaml#L949) | [Startup probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | [...](./values.yaml#L949) | -| [`terminationGracePeriodSeconds`](./values.yaml#L988) | [Termination grace period](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/). Should be greater than the longest expected request processing time (`config.requestTimeoutSeconds`). | `65` | +| [`lifecycle`](./values.yaml#L1039) | [Lifecycle](https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/) | `map[]` | +| [`livenessProbe`](./values.yaml#L1009) | [Liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | [...](./values.yaml#L1009) | +| [`readinessProbe`](./values.yaml#L1022) | [Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | [...](./values.yaml#L1022) | +| [`startupProbe`](./values.yaml#L996) | [Startup probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | [...](./values.yaml#L996) | +| [`terminationGracePeriodSeconds`](./values.yaml#L1035) | [Termination grace period](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/). Should be greater than the longest expected request processing time (`config.requestTimeoutSeconds`). | `65` | ### Scheduling | Key | Description | Default | |-----|-------------|---------| -| [`affinity`](./values.yaml#L1047) | Node affinity | `{}` | -| [`autoscaling`](./values.yaml#L1000) | [Autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) | [...](./values.yaml#L1000) | -| [`nodeSelector`](./values.yaml#L1044) | [Node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/) | `{}` | -| [`podDisruptionBudget`](./values.yaml#L1037) | [Pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) | [...](./values.yaml#L1037) | -| [`priorityClassName`](./values.yaml#L1056) | [Priority classs](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/) | `""` | -| [`replicaCount`](./values.yaml#L1025) | Number of replicas | `1` | -| [`resources`](./values.yaml#L1022) | [Resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | `{}` | -| [`schedulerName`](./values.yaml#L1059) | [Scheduler](https://kubernetes.io/docs/concepts/scheduling-eviction/kube-scheduler/) | `""` | -| [`tolerations`](./values.yaml#L1050) | [Node tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | `[]` | -| [`topologySpreadConstraints`](./values.yaml#L1053) | [Topology spread constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/) | `[]` | -| [`updateStrategy`](./values.yaml#L1028) | [Update strategy](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy) | `{"rollingUpdate":{},"type":"RollingUpdate"}` | +| [`affinity`](./values.yaml#L1094) | Node affinity | `{}` | +| [`autoscaling`](./values.yaml#L1047) | [Autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) | [...](./values.yaml#L1047) | +| [`nodeSelector`](./values.yaml#L1091) | [Node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/) | `{}` | +| [`podDisruptionBudget`](./values.yaml#L1084) | [Pod disruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) | [...](./values.yaml#L1084) | +| [`priorityClassName`](./values.yaml#L1103) | [Priority classs](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/) | `""` | +| [`replicaCount`](./values.yaml#L1072) | Number of replicas | `1` | +| [`resources`](./values.yaml#L1069) | [Resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) | `{}` | +| [`schedulerName`](./values.yaml#L1106) | [Scheduler](https://kubernetes.io/docs/concepts/scheduling-eviction/kube-scheduler/) | `""` | +| [`tolerations`](./values.yaml#L1097) | [Node tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | `[]` | +| [`topologySpreadConstraints`](./values.yaml#L1100) | [Topology spread constraints](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/) | `[]` | +| [`updateStrategy`](./values.yaml#L1075) | [Update strategy](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy) | `{"rollingUpdate":{},"type":"RollingUpdate"}` | ### Storage resource definitions | Key | Description | Default | |-----|-------------|---------| -| [`cloudNativePG`](./values.yaml#L1064) | [CloudNativePG](https://cloudnative-pg.io/) resources | [...](./values.yaml#L1064) | -| [`cloudNativePG.clusterAnnotations`](./values.yaml#L1099) | Cluster annotations | `{}` | -| [`cloudNativePG.clusterLabels`](./values.yaml#L1096) | Cluster labels | `{}` | -| [`cloudNativePG.clusterName`](./values.yaml#L1076) | CloudNativePG custom Cluster name | `"{{ .Release.Name }}-postgres"` | -| [`cloudNativePG.clusterSpec`](./values.yaml#L1080) | CloudNativePG [cluster spec](https://cloudnative-pg.io/documentation/current/cloudnative-pg.v1/#postgresql-cnpg-io-v1-ClusterSpec) | [...](./values.yaml#L1080) | -| [`cloudNativePG.enabled`](./values.yaml#L1067) | Enable CloudNativePG resources | `false` | -| [`cloudNativePG.networkPolicy`](./values.yaml#L1108) | Network policy to allow access to the cluster | `{"enabled":true}` | -| [`cloudNativePG.operatorNamespace`](./values.yaml#L1070) | CloudNativePG operator namespace | `"cnpg-system"` | -| [`cloudNativePG.operatorReleaseName`](./values.yaml#L1073) | CloudNativePG operator release name | `"cloudnative-pg"` | -| [`cloudNativePG.superuserSecret`](./values.yaml#L1102) | Superuser secret to use with the cluster | `{"create":true,"password":"despair","username":"postgres"}` | +| [`cloudNativePG`](./values.yaml#L1111) | [CloudNativePG](https://cloudnative-pg.io/) resources | [...](./values.yaml#L1111) | +| [`cloudNativePG.clusterAnnotations`](./values.yaml#L1146) | Cluster annotations | `{}` | +| [`cloudNativePG.clusterLabels`](./values.yaml#L1143) | Cluster labels | `{}` | +| [`cloudNativePG.clusterName`](./values.yaml#L1123) | CloudNativePG custom Cluster name | `"{{ .Release.Name }}-postgres"` | +| [`cloudNativePG.clusterSpec`](./values.yaml#L1127) | CloudNativePG [cluster spec](https://cloudnative-pg.io/documentation/current/cloudnative-pg.v1/#postgresql-cnpg-io-v1-ClusterSpec) | [...](./values.yaml#L1127) | +| [`cloudNativePG.enabled`](./values.yaml#L1114) | Enable CloudNativePG resources | `false` | +| [`cloudNativePG.networkPolicy`](./values.yaml#L1155) | Network policy to allow access to the cluster | `{"enabled":true}` | +| [`cloudNativePG.operatorNamespace`](./values.yaml#L1117) | CloudNativePG operator namespace | `"cnpg-system"` | +| [`cloudNativePG.operatorReleaseName`](./values.yaml#L1120) | CloudNativePG operator release name | `"cloudnative-pg"` | +| [`cloudNativePG.superuserSecret`](./values.yaml#L1149) | Superuser secret to use with the cluster | `{"create":true,"password":"despair","username":"postgres"}` | ### Other Values @@ -526,7 +537,7 @@ Note: | [`config.hoard.binaryCopyThreshold`](./values.yaml#L139) | `HOARD_BINARY_COPY_THRESHOLD` — internal parameter, do not change unless explicitly recommended by Nutrient support. | `2` | | [`config.http2SharedRendering.checkinTimeoutMilliseconds`](./values.yaml#L150) | `HTTP2_SHARED_RENDERING_PROCESS_CHECKIN_TIMEOUT` — document processing daemon checkin timeout. Do not change unless explicitly recommended by Nutrient support. | `0` | | [`config.http2SharedRendering.checkoutTimeoutMilliseconds`](./values.yaml#L153) | `HTTP2_SHARED_RENDERING_PROCESS_CHECKOUT_TIMEOUT` — document processing daemon checkout timeout. Do not change unless explicitly recommended by Nutrient support. | `5000` | -| [`revisionHistoryLimit`](./values.yaml#L1032) | [Revision history limit](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#clean-up-policy) | `10` | +| [`revisionHistoryLimit`](./values.yaml#L1079) | [Revision history limit](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#clean-up-policy) | `10` | ## Contribution diff --git a/charts/document-engine/ci/05-envoy-sidecar-values.yaml b/charts/document-engine/ci/05-envoy-sidecar-values.yaml new file mode 100644 index 0000000..1007d8d --- /dev/null +++ b/charts/document-engine/ci/05-envoy-sidecar-values.yaml @@ -0,0 +1,16 @@ +# Minimal test for Envoy sidecar consistent hashing +envoySidecar: + enabled: true + +replicaCount: 2 + +database: + enabled: true + migrationJob: + enabled: false + +assetStorage: + backendType: built-in + +cloudNativePG: + enabled: true diff --git a/charts/document-engine/ci/README.md b/charts/document-engine/ci/README.md index 1734fc0..0394da4 100644 --- a/charts/document-engine/ci/README.md +++ b/charts/document-engine/ci/README.md @@ -16,5 +16,11 @@ * `04-cnpg-azure-values.yaml` * PostgreSQL * Azurite to test Azure Blob storage +* `05-envoy-sidecar-values.yaml` + * Envoy sidecar for consistent hashing by document ID + * Multiple replicas (2) to test hash distribution + * Envoy admin endpoint exposed for metrics + * Network policy enabled to test Envoy port access + * Headless service for pod discovery * `10-env-variables-values.yaml` * Very blunt environment variables setting, an easy migration from Docker compose diff --git a/charts/document-engine/templates/deployment.yaml b/charts/document-engine/templates/deployment.yaml index c68842c..2e970c6 100644 --- a/charts/document-engine/templates/deployment.yaml +++ b/charts/document-engine/templates/deployment.yaml @@ -203,13 +203,41 @@ spec: lifecycle: {{- toYaml . | nindent 12 }} {{- end }} + {{- if .Values.envoySidecar.enabled }} + - name: envoy-sidecar + image: "{{ .Values.envoySidecar.image.repository }}:{{ .Values.envoySidecar.image.tag }}" + imagePullPolicy: {{ .Values.envoySidecar.image.pullPolicy }} + ports: + - name: envoy + containerPort: {{ .Values.envoySidecar.port }} + protocol: TCP + - name: envoy-admin + containerPort: {{ .Values.envoySidecar.adminPort }} + protocol: TCP + resources: + {{- toYaml .Values.envoySidecar.resources | nindent 12 }} + volumeMounts: + - name: envoy-config + mountPath: /etc/envoy + readOnly: true + command: + - envoy + - -c + - /etc/envoy/envoy.yaml + {{- end }} {{- if .Values.sidecars }} {{ toYaml .Values.sidecars | nindent 8 }} {{- end }} {{- if or .Values.extraVolumeMounts - .Values.certificateTrust.digitalSignatures - .Values.certificateTrust.customCertificates }} + .Values.certificateTrust.digitalSignatures + .Values.certificateTrust.customCertificates + .Values.envoySidecar.enabled }} volumes: + {{- if .Values.envoySidecar.enabled }} + - name: envoy-config + configMap: + name: {{ include "document-engine.fullname" . }}-envoy-sidecar + {{- end }} {{- with .Values.extraVolumes }} {{ toYaml . | nindent 8 }} {{- end }} diff --git a/charts/document-engine/templates/envoy-sidecar-configmap.yaml b/charts/document-engine/templates/envoy-sidecar-configmap.yaml new file mode 100644 index 0000000..31258b2 --- /dev/null +++ b/charts/document-engine/templates/envoy-sidecar-configmap.yaml @@ -0,0 +1,109 @@ +{{- if .Values.envoySidecar.enabled -}} +{{- $fullName := include "document-engine.fullname" . -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ $fullName }}-envoy-sidecar + labels: + {{- include "document-engine.labels" . | nindent 4 }} +data: + envoy.yaml: | + admin: + address: + socket_address: + address: 0.0.0.0 + port_value: {{ .Values.envoySidecar.adminPort }} + + static_resources: + listeners: + - name: listener_0 + address: + socket_address: + address: 0.0.0.0 + port_value: {{ .Values.envoySidecar.port }} + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + access_log: + - name: envoy.access_loggers.stdout + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog + route_config: + name: local_route + virtual_hosts: + - name: backend + domains: ["*"] + response_headers_to_add: + - header: + key: "x-hash-by" + value: "%REQ(X-Hash-By)%" + - header: + key: "x-envoy-upstream-remote-address" + value: "%UPSTREAM_REMOTE_ADDRESS%" + routes: + - match: + prefix: "/" + route: + cluster: document_engine_cluster + hash_policy: + - header: + header_name: "X-Hash-By" + http_filters: + # Extract document ID from URI and set as header + - name: envoy.filters.http.lua + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + inline_code: | + function envoy_on_request(request_handle) + local path = request_handle:headers():get(":path") + local document_id = nil + + -- Try different URI patterns + document_id = string.match(path, "^/api/documents/([a-zA-Z0-9._~-]+)") + if not document_id then + document_id = string.match(path, "^/i/d/([a-zA-Z0-9._~-]+)") + end + if not document_id then + document_id = string.match(path, "^/documents/([a-zA-Z0-9._~-]+)") + end + if not document_id then + document_id = string.match(path, "^/dashboard/api/documents/([a-zA-Z0-9._~-]+)") + end + + -- Only set header if document_id found, otherwise use round-robin + if document_id then + request_handle:headers():add("X-Hash-By", document_id) + end + end + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + + clusters: + - name: document_engine_cluster + connect_timeout: 5s + type: STRICT_DNS + dns_lookup_family: V4_ONLY + lb_policy: RING_HASH + ring_hash_lb_config: + minimum_ring_size: 1024 + load_assignment: + cluster_name: document_engine_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: {{ $fullName }}-headless.{{ .Release.Namespace }}.svc.cluster.local + port_value: {{ .Values.service.port }} + health_checks: + - timeout: {{ .Values.envoySidecar.healthCheck.timeout }} + interval: {{ .Values.envoySidecar.healthCheck.interval }} + unhealthy_threshold: {{ .Values.envoySidecar.healthCheck.unhealthyThreshold }} + healthy_threshold: {{ .Values.envoySidecar.healthCheck.healthyThreshold }} + http_health_check: + path: /healthcheck +{{- end }} diff --git a/charts/document-engine/templates/monitoring/servicemonitor.yaml b/charts/document-engine/templates/monitoring/servicemonitor.yaml index 91616fb..747b5c0 100644 --- a/charts/document-engine/templates/monitoring/servicemonitor.yaml +++ b/charts/document-engine/templates/monitoring/servicemonitor.yaml @@ -38,6 +38,16 @@ spec: {{- with .Values.observability.metrics.serviceMonitor.honorLabels }} honorLabels: {{ . }} {{- end }} + {{- if $.Values.envoySidecar.enabled }} + - port: envoy-admin + path: /stats/prometheus + {{- with .Values.observability.metrics.serviceMonitor.interval }} + interval: {{ . }} + {{- end }} + {{- with .Values.observability.metrics.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ . }} + {{- end }} + {{- end }} namespaceSelector: matchNames: - {{ .Release.Namespace | quote }} diff --git a/charts/document-engine/templates/networkpolicy.yaml b/charts/document-engine/templates/networkpolicy.yaml index aceb517..638205c 100644 --- a/charts/document-engine/templates/networkpolicy.yaml +++ b/charts/document-engine/templates/networkpolicy.yaml @@ -63,6 +63,11 @@ spec: ingress: - ports: - port: {{ .Values.config.port }} + {{- if .Values.envoySidecar.enabled }} + # Envoy sidecar ports for consistent hashing + - port: {{ .Values.envoySidecar.port }} + - port: {{ .Values.envoySidecar.adminPort }} + {{- end }} {{- if .Values.observability.metrics.prometheusEndpoint.enabled }} - port: {{ .Values.observability.metrics.prometheusEndpoint.port }} {{- end }} diff --git a/charts/document-engine/templates/service-headless.yaml b/charts/document-engine/templates/service-headless.yaml new file mode 100644 index 0000000..70ecd87 --- /dev/null +++ b/charts/document-engine/templates/service-headless.yaml @@ -0,0 +1,17 @@ +{{- if .Values.envoySidecar.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "document-engine.fullname" . }}-headless + labels: + {{- include "document-engine.labels" . | nindent 4 }} +spec: + clusterIP: None + selector: + {{- include "document-engine.selectorLabels" . | nindent 4 }} + ports: + - port: {{ .Values.service.port }} + targetPort: api + protocol: TCP + name: api +{{- end }} diff --git a/charts/document-engine/templates/service.yaml b/charts/document-engine/templates/service.yaml index 109a05b..328c407 100644 --- a/charts/document-engine/templates/service.yaml +++ b/charts/document-engine/templates/service.yaml @@ -18,7 +18,11 @@ spec: {{- end }} ports: - port: {{ .Values.service.port }} + {{- if .Values.envoySidecar.enabled }} + targetPort: envoy + {{- else }} targetPort: api + {{- end }} protocol: TCP name: api {{- with .Values.observability.metrics.prometheusEndpoint }} @@ -29,5 +33,11 @@ spec: name: metrics {{- end }} {{- end }} + {{- if .Values.envoySidecar.enabled }} + - port: {{ .Values.envoySidecar.adminPort }} + targetPort: envoy-admin + protocol: TCP + name: envoy-admin + {{- end }} selector: {{- include "document-engine.selectorLabels" . | nindent 4 }} diff --git a/charts/document-engine/templates/tests/test-envoy-hashing.yaml b/charts/document-engine/templates/tests/test-envoy-hashing.yaml new file mode 100644 index 0000000..917367f --- /dev/null +++ b/charts/document-engine/templates/tests/test-envoy-hashing.yaml @@ -0,0 +1,93 @@ +{{- if .Values.envoySidecar.enabled }} +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "document-engine.fullname" . }}-test-envoy-hashing" + labels: + {{- include "document-engine.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: test + image: curlimages/curl:latest + command: + - /bin/sh + - -c + - | + set -e + + SERVICE="{{ include "document-engine.fullname" . }}:{{ .Values.service.port }}" + + echo "Testing Envoy consistent hashing..." + echo "Service: $SERVICE" + echo "" + + # Test 1: Check service is reachable + echo "Test 1: Service reachability" + if curl -s -o /dev/null -w "%{http_code}" "http://$SERVICE/healthcheck" | grep -q "200"; then + echo "✓ Service is reachable" + else + echo "✗ Service is not reachable" + exit 1 + fi + echo "" + + # Test 2: Check Envoy headers are present + echo "Test 2: Envoy headers present" + if curl -s -I "http://$SERVICE/healthcheck" | grep -i "x-envoy-upstream-remote-address" > /dev/null; then + echo "✓ Envoy headers present" + else + echo "✗ Envoy headers missing (sidecar may not be working)" + exit 1 + fi + echo "" + + # Test 3: Consistent hashing - same document ID goes to same backend + echo "Test 3: Consistent hashing for doc-test-1" + FIRST_BACKEND="" + for i in $(seq 1 5); do + BACKEND=$(curl -s -I "http://$SERVICE/api/documents/doc-test-1" | grep -i "x-envoy-upstream-remote-address" | awk '{print $2}' | tr -d '\r') + echo " Request $i backend: $BACKEND" + + if [ -z "$FIRST_BACKEND" ]; then + FIRST_BACKEND="$BACKEND" + elif [ "$BACKEND" != "$FIRST_BACKEND" ]; then + echo "✗ Inconsistent routing for same document ID" + exit 1 + fi + sleep 0.5 + done + + echo "✓ Same document ID routed to same backend (5/5 requests)" + echo "" + + # Test 4: Different document IDs (test distribution if multiple replicas) + {{- if gt (int .Values.replicaCount) 1 }} + echo "Test 4: Distribution across backends ({{ .Values.replicaCount }} replicas)" + + # Collect backends for multiple document IDs + BACKENDS="" + for i in $(seq 1 20); do + BACKEND=$(curl -s -I "http://$SERVICE/api/documents/doc-test-$i" | grep -i "x-envoy-upstream-remote-address" | awk '{print $2}' | tr -d '\r') + BACKENDS="$BACKENDS $BACKEND" + done + + # Count unique backends + UNIQUE=$(echo "$BACKENDS" | tr ' ' '\n' | sort -u | grep -v '^$' | wc -l) + + echo " Unique backends used: $UNIQUE" + + if [ "$UNIQUE" -gt 1 ]; then + echo "✓ Requests distributed across multiple backends" + else + echo "⚠ All requests went to single backend (may be expected with few document IDs)" + fi + {{- else }} + echo "Test 4: Skipped (single replica)" + {{- end }} + echo "" + + echo "All tests passed! ✓" + restartPolicy: Never +{{- end }} diff --git a/charts/document-engine/values.schema.json b/charts/document-engine/values.schema.json index 8c1e9ca..effa972 100644 --- a/charts/document-engine/values.schema.json +++ b/charts/document-engine/values.schema.json @@ -821,6 +821,78 @@ } } }, + "envoySidecar": { + "type": "object", + "properties": { + "adminPort": { + "type": "integer" + }, + "enabled": { + "type": "boolean" + }, + "healthCheck": { + "type": "object", + "properties": { + "healthyThreshold": { + "type": "integer" + }, + "interval": { + "type": "string" + }, + "timeout": { + "type": "string" + }, + "unhealthyThreshold": { + "type": "integer" + } + } + }, + "image": { + "type": "object", + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "port": { + "type": "integer" + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + } + } + }, "extraEnvFrom": { "type": "array" }, diff --git a/charts/document-engine/values.yaml b/charts/document-engine/values.yaml index d5c5408..bec202d 100644 --- a/charts/document-engine/values.yaml +++ b/charts/document-engine/values.yaml @@ -890,6 +890,53 @@ extraIngresses: {} # pathType: Prefix # tls: [] +# -- (object) Envoy sidecar for consistent hashing by document ID +# @section -- C. Networking +# @notationType -- reference +envoySidecar: + # -- Enable Envoy sidecar for consistent hashing + # @section -- C. Networking + enabled: false + # -- (object) Envoy sidecar image configuration + # @section -- C. Networking + # @notationType -- reference + image: + repository: envoyproxy/envoy + tag: v1.36-latest + pullPolicy: IfNotPresent + # -- Port where Envoy sidecar listens + # @section -- C. Networking + port: 8080 + # -- Admin port for Envoy + # @section -- C. Networking + adminPort: 9901 + # -- (object) Health check configuration for upstream cluster + # @section -- C. Networking + # @notationType -- reference + healthCheck: + # -- Health check timeout + # @section -- C. Networking + timeout: 5s + # -- Health check interval + # @section -- C. Networking + interval: 10s + # -- Unhealthy threshold + # @section -- C. Networking + unhealthyThreshold: 2 + # -- Healthy threshold + # @section -- C. Networking + healthyThreshold: 2 + # -- Resource limits for Envoy sidecar + # @section -- C. Networking + # @notationType -- reference + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 200m + memory: 256Mi + # https://editor.networkpolicy.io/ # -- (object) [Network policy](https://kubernetes.io/docs/concepts/services-networking/network-policies/) # @section -- C. Networking