From 1556320abd104512ee3a4bd9b41607db09e5910a Mon Sep 17 00:00:00 2001 From: akshat5302 Date: Thu, 23 Jul 2026 15:47:49 +0530 Subject: [PATCH 1/6] feat(plane-enterprise): expose gunicorn rotation and celery broker env vars Add GUNICORN_MAX_REQUESTS, GUNICORN_MAX_REQUESTS_JITTER, CELERY_TASK_PUBLISH_RETRY, and CELERY_BROKER_POOL_LIMIT to the app-vars ConfigMap so operators can tune worker rotation and broker pool behaviour without rebuilding the image. Defaults set to Kubernetes-friendly values (rotation disabled, publish retry enabled, pool bounded to 10) to eliminate the silent task-dispatch failures reported in issue #261. Co-Authored-By: Claude Sonnet 4.6 --- charts/plane-enterprise/Chart.yaml | 2 +- charts/plane-enterprise/README.md | 4 ++++ charts/plane-enterprise/questions.yml | 20 +++++++++++++++++++ .../templates/config-secrets/app-env.yaml | 4 ++++ charts/plane-enterprise/values.yaml | 11 ++++++++++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/charts/plane-enterprise/Chart.yaml b/charts/plane-enterprise/Chart.yaml index 32f83301..db7b1e63 100644 --- a/charts/plane-enterprise/Chart.yaml +++ b/charts/plane-enterprise/Chart.yaml @@ -5,7 +5,7 @@ description: Meet Plane. An Enterprise software development tool to manage issue type: application -version: 3.0.0 +version: 3.0.1 appVersion: "3.0.0" home: https://plane.so/ diff --git a/charts/plane-enterprise/README.md b/charts/plane-enterprise/README.md index 1fa17fe0..c8211e42 100644 --- a/charts/plane-enterprise/README.md +++ b/charts/plane-enterprise/README.md @@ -498,6 +498,10 @@ securityContext: | env.web_url | | | (optional) Custom Web URL for the application. If not set, it will be auto-generated based on the license domain and SSL settings | | env.webhook_allowed_ips | | | (optional) Comma-separated list of IPs/CIDRs that webhooks are allowed to target. Leave empty to allow all. | | env.webhook_allowed_hosts | | | (optional) Comma-separated list of hostnames that webhooks are allowed to target. Leave empty to allow all. | +| env.gunicorn_max_requests | 0 | | Maximum requests a gunicorn worker handles before restart. Default `0` disables rotation — recommended for Kubernetes where pod lifecycle is managed by the orchestrator. Set to a positive integer (e.g. `1200`) to re-enable periodic rotation. | +| env.gunicorn_max_requests_jitter | 0 | | Random jitter added to `GUNICORN_MAX_REQUESTS` to stagger worker restarts across replicas. Set to `0` when rotation is disabled. | +| env.celery_task_publish_retry | true | | When `true`, Celery retries task publishing on transient AMQP failures instead of silently dropping tasks. Prevents stuck export/import records caused by brief broker reconnect windows. | +| env.celery_broker_pool_limit | 10 | | Bounds the Celery broker connection pool. Prevents stale connections from accumulating without bound; tune relative to worker concurrency. | | services.api.assign_cluster_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.api.nodeSelector | {} | | This key allows you to set the node selector for the deployment of `api`. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | | services.api.tolerations | [] | | This key allows you to set the tolerations for the deployment of `api`. This is useful when you want to run the deployment on nodes with specific taints in your Kubernetes cluster. | diff --git a/charts/plane-enterprise/questions.yml b/charts/plane-enterprise/questions.yml index 12241b87..ca390113 100644 --- a/charts/plane-enterprise/questions.yml +++ b/charts/plane-enterprise/questions.yml @@ -550,6 +550,26 @@ questions: label: "Webhook Allowed Hosts" type: string default: "" + - variable: env.gunicorn_max_requests + label: "Gunicorn Max Requests" + description: "Maximum number of requests a gunicorn worker handles before being restarted. Set to 0 to disable rotation (recommended for Kubernetes)." + type: int + default: 0 + - variable: env.gunicorn_max_requests_jitter + label: "Gunicorn Max Requests Jitter" + description: "Random jitter added to GUNICORN_MAX_REQUESTS to stagger worker restarts. Set to 0 when rotation is disabled." + type: int + default: 0 + - variable: env.celery_task_publish_retry + label: "Celery Task Publish Retry" + description: "Retry task publishing on transient AMQP failures instead of silently dropping tasks." + type: boolean + default: true + - variable: env.celery_broker_pool_limit + label: "Celery Broker Pool Limit" + description: "Maximum number of connections in the Celery broker connection pool. Limits stale connection accumulation." + type: int + default: 10 - variable: services.external_api.enabled label: "Enable External API" type: boolean diff --git a/charts/plane-enterprise/templates/config-secrets/app-env.yaml b/charts/plane-enterprise/templates/config-secrets/app-env.yaml index 269e639a..f0cfe4d8 100644 --- a/charts/plane-enterprise/templates/config-secrets/app-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/app-env.yaml @@ -76,6 +76,10 @@ data: DEBUG: "0" DOCKERIZED: "1" GUNICORN_WORKERS: "1" + GUNICORN_MAX_REQUESTS: {{ .Values.env.gunicorn_max_requests | default 0 | quote }} + GUNICORN_MAX_REQUESTS_JITTER: {{ .Values.env.gunicorn_max_requests_jitter | default 0 | quote }} + CELERY_TASK_PUBLISH_RETRY: {{ .Values.env.celery_task_publish_retry | default true | ternary "True" "False" | quote }} + CELERY_BROKER_POOL_LIMIT: {{ .Values.env.celery_broker_pool_limit | default 10 | quote }} {{- if .Values.env.web_url}} WEB_URL: {{ .Values.env.web_url | default "" | quote }} {{- else if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }} diff --git a/charts/plane-enterprise/values.yaml b/charts/plane-enterprise/values.yaml index 8394e9af..a1012d55 100644 --- a/charts/plane-enterprise/values.yaml +++ b/charts/plane-enterprise/values.yaml @@ -598,6 +598,17 @@ env: secret_key: "60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5" api_key_rate_limit: "60/minute" + # Gunicorn worker rotation settings. Set to 0 to disable rotation (recommended for Kubernetes, + # where pod lifecycle is managed by the orchestrator rather than the worker process itself). + gunicorn_max_requests: 0 + gunicorn_max_requests_jitter: 0 + + # Celery broker settings. + # celery_task_publish_retry: retry task publishing on transient AMQP failures instead of silently dropping. + # celery_broker_pool_limit: bound the connection pool to prevent stale connections accumulating. + celery_task_publish_retry: true + celery_broker_pool_limit: 10 + sentry_dsn: '' sentry_environment: '' From 30ac77ab807bef0f43b192b82557f5d911410613 Mon Sep 17 00:00:00 2001 From: akshat5302 Date: Thu, 23 Jul 2026 15:50:07 +0530 Subject: [PATCH 2/6] feat(plane-enterprise): expose gunicorn/celery envs and add PLANE_INTERNAL_API_HOST to pi-api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add to app-vars ConfigMap (configurable via values.yaml): - GUNICORN_MAX_REQUESTS (default 0 — rotation disabled, recommended for K8s) - GUNICORN_MAX_REQUESTS_JITTER (default 0) - CELERY_TASK_PUBLISH_RETRY (default True — prevent silent task drops on AMQP reconnect) - CELERY_BROKER_POOL_LIMIT (default 10 — bound connection pool to avoid stale accumulation) Add to pi-api-vars ConfigMap (hardcoded internal cluster URL): - PLANE_INTERNAL_API_HOST — points directly to the in-cluster API service Fixes the silent CSV export failures reported in issue #261 where gunicorn worker rotation caused stale AMQP connections to silently discard published tasks. Co-Authored-By: Claude Sonnet 4.6 --- charts/plane-enterprise/templates/config-secrets/pi-api-env.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/charts/plane-enterprise/templates/config-secrets/pi-api-env.yaml b/charts/plane-enterprise/templates/config-secrets/pi-api-env.yaml index 6305673e..a5f8297d 100644 --- a/charts/plane-enterprise/templates/config-secrets/pi-api-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/pi-api-env.yaml @@ -109,6 +109,7 @@ data: FEATURE_FLAG_SERVER_BASE_URL: "http://{{ .Release.Name }}-monitor.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:8080" PI_BASE_PATH: "/pi" + PLANE_INTERNAL_API_HOST: "http://{{ .Release.Name }}-api.{{ .Release.Namespace }}.svc.{{ .Values.env.default_cluster_domain | default "cluster.local" }}:8000" {{- if eq .Values.env.pi_envs.cors_allowed_origins "*"}} From dfa566f74af7ccd6a2248d2721b51911e9e67f77 Mon Sep 17 00:00:00 2001 From: akshat5302 Date: Thu, 23 Jul 2026 15:53:47 +0530 Subject: [PATCH 3/6] fix(plane-enterprise): use ternary directly for CELERY_TASK_PUBLISH_RETRY Align with existing boolean pattern (ternary without default) so that setting celery_task_publish_retry: false in values.yaml is respected. Co-Authored-By: Claude Sonnet 4.6 --- charts/plane-enterprise/templates/config-secrets/app-env.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/plane-enterprise/templates/config-secrets/app-env.yaml b/charts/plane-enterprise/templates/config-secrets/app-env.yaml index f0cfe4d8..c1af44f5 100644 --- a/charts/plane-enterprise/templates/config-secrets/app-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/app-env.yaml @@ -78,7 +78,7 @@ data: GUNICORN_WORKERS: "1" GUNICORN_MAX_REQUESTS: {{ .Values.env.gunicorn_max_requests | default 0 | quote }} GUNICORN_MAX_REQUESTS_JITTER: {{ .Values.env.gunicorn_max_requests_jitter | default 0 | quote }} - CELERY_TASK_PUBLISH_RETRY: {{ .Values.env.celery_task_publish_retry | default true | ternary "True" "False" | quote }} + CELERY_TASK_PUBLISH_RETRY: {{ .Values.env.celery_task_publish_retry | ternary "True" "False" | quote }} CELERY_BROKER_POOL_LIMIT: {{ .Values.env.celery_broker_pool_limit | default 10 | quote }} {{- if .Values.env.web_url}} WEB_URL: {{ .Values.env.web_url | default "" | quote }} From 8f619382f4abbc71f5879ec49f9f1db7dba54849 Mon Sep 17 00:00:00 2001 From: akshat5302 Date: Thu, 23 Jul 2026 17:23:52 +0530 Subject: [PATCH 4/6] feat(plane-enterprise): update gunicorn and celery environment variables for improved configuration --- charts/plane-enterprise/README.md | 4 ++-- charts/plane-enterprise/questions.yml | 6 +++--- .../plane-enterprise/templates/config-secrets/app-env.yaml | 4 ++-- charts/plane-enterprise/values.yaml | 7 +++---- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/charts/plane-enterprise/README.md b/charts/plane-enterprise/README.md index c8211e42..3c90d31c 100644 --- a/charts/plane-enterprise/README.md +++ b/charts/plane-enterprise/README.md @@ -498,8 +498,8 @@ securityContext: | env.web_url | | | (optional) Custom Web URL for the application. If not set, it will be auto-generated based on the license domain and SSL settings | | env.webhook_allowed_ips | | | (optional) Comma-separated list of IPs/CIDRs that webhooks are allowed to target. Leave empty to allow all. | | env.webhook_allowed_hosts | | | (optional) Comma-separated list of hostnames that webhooks are allowed to target. Leave empty to allow all. | -| env.gunicorn_max_requests | 0 | | Maximum requests a gunicorn worker handles before restart. Default `0` disables rotation — recommended for Kubernetes where pod lifecycle is managed by the orchestrator. Set to a positive integer (e.g. `1200`) to re-enable periodic rotation. | -| env.gunicorn_max_requests_jitter | 0 | | Random jitter added to `GUNICORN_MAX_REQUESTS` to stagger worker restarts across replicas. Set to `0` when rotation is disabled. | +| env.gunicorn_max_requests | 1000 | | Maximum requests a gunicorn worker handles before restart. Set to `0` to disable rotation. | +| env.gunicorn_max_requests_jitter | 150 | | Random jitter added to `GUNICORN_MAX_REQUESTS` to stagger worker restarts across replicas. Set to `0` when rotation is disabled. | | env.celery_task_publish_retry | true | | When `true`, Celery retries task publishing on transient AMQP failures instead of silently dropping tasks. Prevents stuck export/import records caused by brief broker reconnect windows. | | env.celery_broker_pool_limit | 10 | | Bounds the Celery broker connection pool. Prevents stale connections from accumulating without bound; tune relative to worker concurrency. | | services.api.assign_cluster_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | diff --git a/charts/plane-enterprise/questions.yml b/charts/plane-enterprise/questions.yml index ca390113..05a87bb9 100644 --- a/charts/plane-enterprise/questions.yml +++ b/charts/plane-enterprise/questions.yml @@ -552,14 +552,14 @@ questions: default: "" - variable: env.gunicorn_max_requests label: "Gunicorn Max Requests" - description: "Maximum number of requests a gunicorn worker handles before being restarted. Set to 0 to disable rotation (recommended for Kubernetes)." + description: "Maximum number of requests a gunicorn worker handles before being restarted. Set to 0 to disable rotation." type: int - default: 0 + default: 1000 - variable: env.gunicorn_max_requests_jitter label: "Gunicorn Max Requests Jitter" description: "Random jitter added to GUNICORN_MAX_REQUESTS to stagger worker restarts. Set to 0 when rotation is disabled." type: int - default: 0 + default: 150 - variable: env.celery_task_publish_retry label: "Celery Task Publish Retry" description: "Retry task publishing on transient AMQP failures instead of silently dropping tasks." diff --git a/charts/plane-enterprise/templates/config-secrets/app-env.yaml b/charts/plane-enterprise/templates/config-secrets/app-env.yaml index c1af44f5..a88762e2 100644 --- a/charts/plane-enterprise/templates/config-secrets/app-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/app-env.yaml @@ -76,8 +76,8 @@ data: DEBUG: "0" DOCKERIZED: "1" GUNICORN_WORKERS: "1" - GUNICORN_MAX_REQUESTS: {{ .Values.env.gunicorn_max_requests | default 0 | quote }} - GUNICORN_MAX_REQUESTS_JITTER: {{ .Values.env.gunicorn_max_requests_jitter | default 0 | quote }} + GUNICORN_MAX_REQUESTS: {{ .Values.env.gunicorn_max_requests | default 1000 | quote }} + GUNICORN_MAX_REQUESTS_JITTER: {{ .Values.env.gunicorn_max_requests_jitter | default 150 | quote }} CELERY_TASK_PUBLISH_RETRY: {{ .Values.env.celery_task_publish_retry | ternary "True" "False" | quote }} CELERY_BROKER_POOL_LIMIT: {{ .Values.env.celery_broker_pool_limit | default 10 | quote }} {{- if .Values.env.web_url}} diff --git a/charts/plane-enterprise/values.yaml b/charts/plane-enterprise/values.yaml index a1012d55..ca113d40 100644 --- a/charts/plane-enterprise/values.yaml +++ b/charts/plane-enterprise/values.yaml @@ -598,10 +598,9 @@ env: secret_key: "60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5" api_key_rate_limit: "60/minute" - # Gunicorn worker rotation settings. Set to 0 to disable rotation (recommended for Kubernetes, - # where pod lifecycle is managed by the orchestrator rather than the worker process itself). - gunicorn_max_requests: 0 - gunicorn_max_requests_jitter: 0 + # Gunicorn worker rotation settings. + gunicorn_max_requests: 1000 + gunicorn_max_requests_jitter: 150 # Celery broker settings. # celery_task_publish_retry: retry task publishing on transient AMQP failures instead of silently dropping. From f7a672ecf22738645d476b05c777478fd8401b3e Mon Sep 17 00:00:00 2001 From: akshat5302 Date: Fri, 24 Jul 2026 13:42:00 +0530 Subject: [PATCH 5/6] feat(plane-enterprise): quote gunicorn and celery environment variable values for consistency --- .../plane-enterprise/templates/config-secrets/app-env.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/plane-enterprise/templates/config-secrets/app-env.yaml b/charts/plane-enterprise/templates/config-secrets/app-env.yaml index a88762e2..5abd6da4 100644 --- a/charts/plane-enterprise/templates/config-secrets/app-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/app-env.yaml @@ -76,10 +76,10 @@ data: DEBUG: "0" DOCKERIZED: "1" GUNICORN_WORKERS: "1" - GUNICORN_MAX_REQUESTS: {{ .Values.env.gunicorn_max_requests | default 1000 | quote }} - GUNICORN_MAX_REQUESTS_JITTER: {{ .Values.env.gunicorn_max_requests_jitter | default 150 | quote }} + GUNICORN_MAX_REQUESTS: {{ .Values.env.gunicorn_max_requests | quote }} + GUNICORN_MAX_REQUESTS_JITTER: {{ .Values.env.gunicorn_max_requests_jitter | quote }} CELERY_TASK_PUBLISH_RETRY: {{ .Values.env.celery_task_publish_retry | ternary "True" "False" | quote }} - CELERY_BROKER_POOL_LIMIT: {{ .Values.env.celery_broker_pool_limit | default 10 | quote }} + CELERY_BROKER_POOL_LIMIT: {{ .Values.env.celery_broker_pool_limit | quote }} {{- if .Values.env.web_url}} WEB_URL: {{ .Values.env.web_url | default "" | quote }} {{- else if or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) }} From 6e78498ccfc4d7bcb501cabde83ba3113a292fb2 Mon Sep 17 00:00:00 2001 From: akshat5302 Date: Thu, 30 Jul 2026 16:33:45 +0530 Subject: [PATCH 6/6] fix: use 1/0 instead of True/False for CELERY_TASK_PUBLISH_RETRY Co-Authored-By: Claude Sonnet 4.6 --- charts/plane-enterprise/README.md | 2 +- charts/plane-enterprise/templates/config-secrets/app-env.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/plane-enterprise/README.md b/charts/plane-enterprise/README.md index 3c90d31c..864f550c 100644 --- a/charts/plane-enterprise/README.md +++ b/charts/plane-enterprise/README.md @@ -500,7 +500,7 @@ securityContext: | env.webhook_allowed_hosts | | | (optional) Comma-separated list of hostnames that webhooks are allowed to target. Leave empty to allow all. | | env.gunicorn_max_requests | 1000 | | Maximum requests a gunicorn worker handles before restart. Set to `0` to disable rotation. | | env.gunicorn_max_requests_jitter | 150 | | Random jitter added to `GUNICORN_MAX_REQUESTS` to stagger worker restarts across replicas. Set to `0` when rotation is disabled. | -| env.celery_task_publish_retry | true | | When `true`, Celery retries task publishing on transient AMQP failures instead of silently dropping tasks. Prevents stuck export/import records caused by brief broker reconnect windows. | +| env.celery_task_publish_retry | true | | When `true` (rendered as `1`), Celery retries task publishing on transient AMQP failures instead of silently dropping tasks. Prevents stuck export/import records caused by brief broker reconnect windows. | | env.celery_broker_pool_limit | 10 | | Bounds the Celery broker connection pool. Prevents stale connections from accumulating without bound; tune relative to worker concurrency. | | services.api.assign_cluster_ip | false | | Set it to `true` if you want to assign `ClusterIP` to the service | | services.api.nodeSelector | {} | | This key allows you to set the node selector for the deployment of `api`. This is useful when you want to run the deployment on specific nodes in your Kubernetes cluster. | diff --git a/charts/plane-enterprise/templates/config-secrets/app-env.yaml b/charts/plane-enterprise/templates/config-secrets/app-env.yaml index 5abd6da4..dd5b8d02 100644 --- a/charts/plane-enterprise/templates/config-secrets/app-env.yaml +++ b/charts/plane-enterprise/templates/config-secrets/app-env.yaml @@ -78,7 +78,7 @@ data: GUNICORN_WORKERS: "1" GUNICORN_MAX_REQUESTS: {{ .Values.env.gunicorn_max_requests | quote }} GUNICORN_MAX_REQUESTS_JITTER: {{ .Values.env.gunicorn_max_requests_jitter | quote }} - CELERY_TASK_PUBLISH_RETRY: {{ .Values.env.celery_task_publish_retry | ternary "True" "False" | quote }} + CELERY_TASK_PUBLISH_RETRY: {{ .Values.env.celery_task_publish_retry | ternary "1" "0" | quote }} CELERY_BROKER_POOL_LIMIT: {{ .Values.env.celery_broker_pool_limit | quote }} {{- if .Values.env.web_url}} WEB_URL: {{ .Values.env.web_url | default "" | quote }}