From 78ee14d40f9512dd9cc42bb216ef5baa2a7bf8e9 Mon Sep 17 00:00:00 2001 From: Adrien Poupa Date: Mon, 27 Apr 2026 16:32:52 -0400 Subject: [PATCH 1/6] feat(helm/router): expose HPA behavior and startup probe Add two optional, opt-in keys to the router chart: - autoscaling.behavior: passthrough to HPA spec.behavior, allowing users to configure scaleUp/scaleDown stabilization windows and policies. Useful when the router does meaningful startup work (e.g. persisted-operations manifest cache warmup) which would otherwise spike CPU/memory and cause the HPA to over-provision on deploys. - probes.startup: passthrough to the container's startupProbe. Lets operators give the router additional startup grace without loosening liveness/readiness defaults. Both default to {} and render nothing when unset, so existing installations are unaffected. --- helm/cosmo/charts/router/README.md | 2 ++ helm/cosmo/charts/router/templates/deployment.yaml | 4 ++++ helm/cosmo/charts/router/templates/hpa.yaml | 4 ++++ helm/cosmo/charts/router/values.yaml | 2 ++ 4 files changed, 12 insertions(+) diff --git a/helm/cosmo/charts/router/README.md b/helm/cosmo/charts/router/README.md index 3240a24266..916abf8c92 100644 --- a/helm/cosmo/charts/router/README.md +++ b/helm/cosmo/charts/router/README.md @@ -15,6 +15,7 @@ This is the official Helm Chart for the WunderGraph Cosmo Router. | autoscaling.maxReplicas | int | `100` | | | autoscaling.minReplicas | int | `1` | | | autoscaling.targetCPUUtilizationPercentage | int | `80` | | +| autoscaling.behavior | object | `{}` | Configure the [HPA scaling behavior](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#configurable-scaling-behavior) | | commonConfiguration | string | `"version: \"1\"\nlog_level: \"info\""` | You can use this to provide the router configuration via yaml. Values here have precedence over the configurations section. For a full list of available configuration options, see https://cosmo-docs.wundergraph.com/router/configuration This value is processed with the helm `tpl` function allowing referencing of variables and inclusion of templates | | commonConfigurationPath | string | `""` | Path to a configuration file to embed. If set, this takes precedence over commonConfiguration. The file path is relative to the chart directory and will be processed with the helm `tpl` function. Example: "configs/router-config.yaml" | | commonLabels | object | `{}` | Add labels to all deployed resources | @@ -68,6 +69,7 @@ This is the official Helm Chart for the WunderGraph Cosmo Router. | priorityClassName | string | `""` | Set to existing PriorityClass name to control pod preemption by the scheduler | | probes.liveness | object | `{"httpGet":{"path":"/health/live","port":"http"},"initialDelaySeconds":10}` | Configure liveness probe | | probes.readiness | object | `{"httpGet":{"path":"/health/ready","port":"http"},"initialDelaySeconds":5}` | Configure readiness probe | +| probes.startup | object | `{}` | Configure startup probe | | replicaCount | int | `1` | | | resources | object | `{}` | | | securityContext | object | `{}` | | diff --git a/helm/cosmo/charts/router/templates/deployment.yaml b/helm/cosmo/charts/router/templates/deployment.yaml index 403db2e672..7d63e7027b 100644 --- a/helm/cosmo/charts/router/templates/deployment.yaml +++ b/helm/cosmo/charts/router/templates/deployment.yaml @@ -211,6 +211,10 @@ spec: readinessProbe: {{- toYaml . | nindent 12 }} {{- end }} + {{- with .Values.probes.startup }} + startupProbe: + {{- toYaml . | nindent 12 }} + {{- end }} volumeMounts: {{- if .Values.existingConfigmap }} - name: router-config diff --git a/helm/cosmo/charts/router/templates/hpa.yaml b/helm/cosmo/charts/router/templates/hpa.yaml index abc18ef0ef..c9a7434b94 100644 --- a/helm/cosmo/charts/router/templates/hpa.yaml +++ b/helm/cosmo/charts/router/templates/hpa.yaml @@ -29,4 +29,8 @@ spec: type: Utilization averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} {{- end }} + {{- with .Values.autoscaling.behavior }} + behavior: + {{- toYaml . | nindent 4 }} + {{- end }} {{- end }} diff --git a/helm/cosmo/charts/router/values.yaml b/helm/cosmo/charts/router/values.yaml index 010d045f2b..4987599d09 100644 --- a/helm/cosmo/charts/router/values.yaml +++ b/helm/cosmo/charts/router/values.yaml @@ -143,6 +143,7 @@ autoscaling: maxReplicas: 100 targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 + behavior: {} nodeSelector: {} @@ -172,6 +173,7 @@ probes: path: /health/live port: http initialDelaySeconds: 10 + startup: {} global: helmTests: false From bccc3c6583f98eb36a1e9ed6fbd2f6100e469321 Mon Sep 17 00:00:00 2001 From: Adrien Poupa Date: Mon, 27 Apr 2026 17:08:53 -0400 Subject: [PATCH 2/6] chore(helm): fix Helm CI --- helm/cosmo/charts/router/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/cosmo/charts/router/README.md b/helm/cosmo/charts/router/README.md index 916abf8c92..7e7b5fb03e 100644 --- a/helm/cosmo/charts/router/README.md +++ b/helm/cosmo/charts/router/README.md @@ -11,11 +11,11 @@ This is the official Helm Chart for the WunderGraph Cosmo Router. | additionalLabels | object | `{}` | Add labels to deployment (metadata.labels) | | additionalPodLabels | object | `{}` | Add labels to deployment pod template (spec.template.metadata.labels) | | affinity | object | `{}` | | +| autoscaling.behavior | object | `{}` | | | autoscaling.enabled | bool | `false` | | | autoscaling.maxReplicas | int | `100` | | | autoscaling.minReplicas | int | `1` | | | autoscaling.targetCPUUtilizationPercentage | int | `80` | | -| autoscaling.behavior | object | `{}` | Configure the [HPA scaling behavior](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#configurable-scaling-behavior) | | commonConfiguration | string | `"version: \"1\"\nlog_level: \"info\""` | You can use this to provide the router configuration via yaml. Values here have precedence over the configurations section. For a full list of available configuration options, see https://cosmo-docs.wundergraph.com/router/configuration This value is processed with the helm `tpl` function allowing referencing of variables and inclusion of templates | | commonConfigurationPath | string | `""` | Path to a configuration file to embed. If set, this takes precedence over commonConfiguration. The file path is relative to the chart directory and will be processed with the helm `tpl` function. Example: "configs/router-config.yaml" | | commonLabels | object | `{}` | Add labels to all deployed resources | @@ -69,7 +69,7 @@ This is the official Helm Chart for the WunderGraph Cosmo Router. | priorityClassName | string | `""` | Set to existing PriorityClass name to control pod preemption by the scheduler | | probes.liveness | object | `{"httpGet":{"path":"/health/live","port":"http"},"initialDelaySeconds":10}` | Configure liveness probe | | probes.readiness | object | `{"httpGet":{"path":"/health/ready","port":"http"},"initialDelaySeconds":5}` | Configure readiness probe | -| probes.startup | object | `{}` | Configure startup probe | +| probes.startup | object | `{}` | | | replicaCount | int | `1` | | | resources | object | `{}` | | | securityContext | object | `{}` | | From 5992dc29b546fa89c9fe553ff161a55caaabdf51 Mon Sep 17 00:00:00 2001 From: Adrien Poupa Date: Mon, 27 Apr 2026 17:24:26 -0400 Subject: [PATCH 3/6] chore(helm): add HPA scaling behaviour example --- helm/cosmo/charts/router/values.yaml | 75 +++++++++++++++++----------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/helm/cosmo/charts/router/values.yaml b/helm/cosmo/charts/router/values.yaml index 4987599d09..bc87154027 100644 --- a/helm/cosmo/charts/router/values.yaml +++ b/helm/cosmo/charts/router/values.yaml @@ -25,10 +25,10 @@ deploymentStrategy: {} imagePullSecrets: [] # -- String to partially override common.names.fullname template (will maintain the release name) -nameOverride: "" +nameOverride: '' # -- String to fully override common.names.fullname template -fullnameOverride: "" +fullnameOverride: '' # -- Allows to set additional environment / runtime variables on the container. Useful for global application non-specific settings. extraEnvVars: [] @@ -46,10 +46,10 @@ extraVolumes: [] extraVolumeMounts: [] # -- Name of existing ConfigMap containing extra env vars -extraEnvVarsCM: "" +extraEnvVarsCM: '' # -- Name of existing Secret containing extra env vars -extraEnvVarsSecret: "" +extraEnvVarsSecret: '' serviceAccount: # -- Specifies whether a service account should be created @@ -58,7 +58,7 @@ serviceAccount: annotations: {} # -- The name of the service account to use. # If not set and create is true, a name is generated using the fullname template - name: "" + name: '' serviceAnnotations: {} @@ -80,16 +80,16 @@ service: port: 3002 ingress: -# enabled: true -# className: "" -# annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" + # enabled: true + # className: "" + # annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" hosts: -# - host: router.wundergraph.local -# paths: -# - path: / -# pathType: ImplementationSpecific + # - host: router.wundergraph.local + # paths: + # - path: / + # pathType: ImplementationSpecific tls: [] # - secretName: chart-example-tls # hosts: @@ -144,6 +144,25 @@ autoscaling: targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 behavior: {} + # Configures the HPA scaling behavior. When unset, Kubernetes applies its defaults + # (300s scale-down stabilization window, no scale-up window). + # Example of a custom scale up and scale down behavior: + # scaleUp: + # stabilizationWindowSeconds: 180 + # selectPolicy: Min + # policies: + # - type: Pods + # value: 2 + # periodSeconds: 60 + # - type: Percent + # value: 50 + # periodSeconds: 60 + # scaleDown: + # stabilizationWindowSeconds: 120 + # policies: + # - type: Percent + # value: 50 + # periodSeconds: 60 nodeSelector: {} @@ -155,7 +174,7 @@ affinity: {} podDisruptionBudget: {} # -- Set to existing PriorityClass name to control pod preemption by the scheduler -priorityClassName: "" +priorityClassName: '' # -- Sets the [termination grace period](https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#hook-handler-execution) for Deployment pods terminationGracePeriodSeconds: 30 @@ -190,10 +209,10 @@ global: # -- The name of the configmap to use for the router configuration. The key "config.yaml" is required in the configmap. # If this is set, the commonConfiguration section is ignored. -existingConfigmap: "" +existingConfigmap: '' # -- Existing secret in the same namespace containing the graphApiToken. The secret key has to match with current secret. -existingSecret: "" +existingSecret: '' # Use this section to pass the graphApiToken or to configure simple settings. # -- You can use this to provide the router configuration via yaml. Values here have precedence over the configurations section. @@ -206,30 +225,30 @@ commonConfiguration: |- # -- Path to a configuration file to embed. If set, this takes precedence over commonConfiguration. # The file path is relative to the chart directory and will be processed with the helm `tpl` function. # Example: "configs/router-config.yaml" -commonConfigurationPath: "" +commonConfigurationPath: '' # Use this section to pass the graphApiToken or to configure simple settings. configuration: # -- The router token is used to authenticate the router against the controlplane (required) - graphApiToken: "replace-me" + graphApiToken: 'replace-me' # -- The execution config file to statically configure the router. If set, polling of the config is disabled. # If your config exceeds 1MB (Kubernetes limit), you have to mount it as a file and set the path in routerConfigPath instead - executionConfig: "" + executionConfig: '' # -- The log level of the router. Default to info if not set. - logLevel: "info" + logLevel: 'info' # -- The URL of the Cosmo Controlplane. Should be internal to the cluster. Default to cloud if not set. - controlplaneUrl: "" + controlplaneUrl: '' # -- The URL of the Cosmo GraphQL OTEL Collector. Should be internal to the cluster. Default to cloud if not set. - otelCollectorUrl: "" + otelCollectorUrl: '' # -- The URL of the Cosmo GraphQL Metrics Collector. Should be internal to the cluster. Default to cloud if not set. - graphqlMetricsCollectorUrl: "" + graphqlMetricsCollectorUrl: '' # -- Set to true to enable the development mode. This allows for Advanced Request Tracing (ART) in the GraphQL Playground devMode: false #-- The URL of the Cosmo CDN. Should be internal to the cluster. Default to cloud if not set. - cdnUrl: "" + cdnUrl: '' # -- The path to the router execution config file. Before, you have to mount the file as a volume and set the path here. # A possible to solution could be to use an init container to download the file from a CDN. If set, polling of the config is disabled. - routerConfigPath: "" + routerConfigPath: '' # -- The path to the router config file. This does not refer to the execution config. # See: https://cosmo-docs.wundergraph.com/router/configuration#config-file @@ -241,7 +260,7 @@ configuration: # -- The port where metrics are exposed. Default is port 8088. port: 8088 # -- The HTTP path where metrics are exposed. Default is "/metrics". - path: "/metrics" + path: '/metrics' # Use this section to configure the router's HTTP(S) proxy settings. # When set the proxy is enabled. @@ -251,7 +270,7 @@ configuration: httpProxy: '' # -- NO_PROXY is a comma-separated list of hosts or domains for which the proxy should not be used. noProxy: '' - + # Use this section to disable/enable and configure the MCP server. mcp: # -- Enables MCP server support. Default is false. From 4b80ba11647e83c87f6244e9ce7fb5f2f1ec472e Mon Sep 17 00:00:00 2001 From: Adrien Poupa Date: Mon, 27 Apr 2026 17:27:44 -0400 Subject: [PATCH 4/6] chore(helm): fix formatting --- helm/cosmo/charts/router/values.yaml | 58 ++++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/helm/cosmo/charts/router/values.yaml b/helm/cosmo/charts/router/values.yaml index bc87154027..8bd39e2096 100644 --- a/helm/cosmo/charts/router/values.yaml +++ b/helm/cosmo/charts/router/values.yaml @@ -25,10 +25,10 @@ deploymentStrategy: {} imagePullSecrets: [] # -- String to partially override common.names.fullname template (will maintain the release name) -nameOverride: '' +nameOverride: "" # -- String to fully override common.names.fullname template -fullnameOverride: '' +fullnameOverride: "" # -- Allows to set additional environment / runtime variables on the container. Useful for global application non-specific settings. extraEnvVars: [] @@ -46,10 +46,10 @@ extraVolumes: [] extraVolumeMounts: [] # -- Name of existing ConfigMap containing extra env vars -extraEnvVarsCM: '' +extraEnvVarsCM: "" # -- Name of existing Secret containing extra env vars -extraEnvVarsSecret: '' +extraEnvVarsSecret: "" serviceAccount: # -- Specifies whether a service account should be created @@ -58,7 +58,7 @@ serviceAccount: annotations: {} # -- The name of the service account to use. # If not set and create is true, a name is generated using the fullname template - name: '' + name: "" serviceAnnotations: {} @@ -80,16 +80,16 @@ service: port: 3002 ingress: - # enabled: true - # className: "" - # annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" +# enabled: true +# className: "" +# annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" hosts: - # - host: router.wundergraph.local - # paths: - # - path: / - # pathType: ImplementationSpecific +# - host: router.wundergraph.local +# paths: +# - path: / +# pathType: ImplementationSpecific tls: [] # - secretName: chart-example-tls # hosts: @@ -143,7 +143,7 @@ autoscaling: maxReplicas: 100 targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 - behavior: {} + behavior: # Configures the HPA scaling behavior. When unset, Kubernetes applies its defaults # (300s scale-down stabilization window, no scale-up window). # Example of a custom scale up and scale down behavior: @@ -174,7 +174,7 @@ affinity: {} podDisruptionBudget: {} # -- Set to existing PriorityClass name to control pod preemption by the scheduler -priorityClassName: '' +priorityClassName: "" # -- Sets the [termination grace period](https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#hook-handler-execution) for Deployment pods terminationGracePeriodSeconds: 30 @@ -209,10 +209,10 @@ global: # -- The name of the configmap to use for the router configuration. The key "config.yaml" is required in the configmap. # If this is set, the commonConfiguration section is ignored. -existingConfigmap: '' +existingConfigmap: "" # -- Existing secret in the same namespace containing the graphApiToken. The secret key has to match with current secret. -existingSecret: '' +existingSecret: "" # Use this section to pass the graphApiToken or to configure simple settings. # -- You can use this to provide the router configuration via yaml. Values here have precedence over the configurations section. @@ -225,30 +225,30 @@ commonConfiguration: |- # -- Path to a configuration file to embed. If set, this takes precedence over commonConfiguration. # The file path is relative to the chart directory and will be processed with the helm `tpl` function. # Example: "configs/router-config.yaml" -commonConfigurationPath: '' +commonConfigurationPath: "" # Use this section to pass the graphApiToken or to configure simple settings. configuration: # -- The router token is used to authenticate the router against the controlplane (required) - graphApiToken: 'replace-me' + graphApiToken: "replace-me" # -- The execution config file to statically configure the router. If set, polling of the config is disabled. # If your config exceeds 1MB (Kubernetes limit), you have to mount it as a file and set the path in routerConfigPath instead - executionConfig: '' + executionConfig: "" # -- The log level of the router. Default to info if not set. - logLevel: 'info' + logLevel: "info" # -- The URL of the Cosmo Controlplane. Should be internal to the cluster. Default to cloud if not set. - controlplaneUrl: '' + controlplaneUrl: "" # -- The URL of the Cosmo GraphQL OTEL Collector. Should be internal to the cluster. Default to cloud if not set. - otelCollectorUrl: '' + otelCollectorUrl: "" # -- The URL of the Cosmo GraphQL Metrics Collector. Should be internal to the cluster. Default to cloud if not set. - graphqlMetricsCollectorUrl: '' + graphqlMetricsCollectorUrl: "" # -- Set to true to enable the development mode. This allows for Advanced Request Tracing (ART) in the GraphQL Playground devMode: false #-- The URL of the Cosmo CDN. Should be internal to the cluster. Default to cloud if not set. - cdnUrl: '' + cdnUrl: "" # -- The path to the router execution config file. Before, you have to mount the file as a volume and set the path here. # A possible to solution could be to use an init container to download the file from a CDN. If set, polling of the config is disabled. - routerConfigPath: '' + routerConfigPath: "" # -- The path to the router config file. This does not refer to the execution config. # See: https://cosmo-docs.wundergraph.com/router/configuration#config-file @@ -260,7 +260,7 @@ configuration: # -- The port where metrics are exposed. Default is port 8088. port: 8088 # -- The HTTP path where metrics are exposed. Default is "/metrics". - path: '/metrics' + path: "/metrics" # Use this section to configure the router's HTTP(S) proxy settings. # When set the proxy is enabled. @@ -270,7 +270,7 @@ configuration: httpProxy: '' # -- NO_PROXY is a comma-separated list of hosts or domains for which the proxy should not be used. noProxy: '' - + # Use this section to disable/enable and configure the MCP server. mcp: # -- Enables MCP server support. Default is false. From ba8a43abbe2491d55495a6baa36e8adc7371d630 Mon Sep 17 00:00:00 2001 From: Adrien Poupa Date: Mon, 27 Apr 2026 17:28:50 -0400 Subject: [PATCH 5/6] chore(helm): add startup probe comment --- helm/cosmo/charts/router/values.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/helm/cosmo/charts/router/values.yaml b/helm/cosmo/charts/router/values.yaml index 8bd39e2096..b52b123c6a 100644 --- a/helm/cosmo/charts/router/values.yaml +++ b/helm/cosmo/charts/router/values.yaml @@ -192,6 +192,7 @@ probes: path: /health/live port: http initialDelaySeconds: 10 + # -- Configure startup probe startup: {} global: From 22a247913d4e4b978764a9fd62822c3cfe435f6b Mon Sep 17 00:00:00 2001 From: Adrien Poupa Date: Mon, 27 Apr 2026 17:33:02 -0400 Subject: [PATCH 6/6] chore(helm): add startup probe comment --- helm/cosmo/charts/router/README.md | 2 +- helm/cosmo/charts/router/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/helm/cosmo/charts/router/README.md b/helm/cosmo/charts/router/README.md index 7e7b5fb03e..7c22bdde4c 100644 --- a/helm/cosmo/charts/router/README.md +++ b/helm/cosmo/charts/router/README.md @@ -69,7 +69,7 @@ This is the official Helm Chart for the WunderGraph Cosmo Router. | priorityClassName | string | `""` | Set to existing PriorityClass name to control pod preemption by the scheduler | | probes.liveness | object | `{"httpGet":{"path":"/health/live","port":"http"},"initialDelaySeconds":10}` | Configure liveness probe | | probes.readiness | object | `{"httpGet":{"path":"/health/ready","port":"http"},"initialDelaySeconds":5}` | Configure readiness probe | -| probes.startup | object | `{}` | | +| probes.startup | object | `{}` | Configure startup probe | | replicaCount | int | `1` | | | resources | object | `{}` | | | securityContext | object | `{}` | | diff --git a/helm/cosmo/charts/router/values.yaml b/helm/cosmo/charts/router/values.yaml index b52b123c6a..56942de69c 100644 --- a/helm/cosmo/charts/router/values.yaml +++ b/helm/cosmo/charts/router/values.yaml @@ -143,7 +143,7 @@ autoscaling: maxReplicas: 100 targetCPUUtilizationPercentage: 80 # targetMemoryUtilizationPercentage: 80 - behavior: + behavior: {} # Configures the HPA scaling behavior. When unset, Kubernetes applies its defaults # (300s scale-down stabilization window, no scale-up window). # Example of a custom scale up and scale down behavior: