From 1556320abd104512ee3a4bd9b41607db09e5910a Mon Sep 17 00:00:00 2001 From: akshat5302 Date: Thu, 23 Jul 2026 15:47:49 +0530 Subject: [PATCH 1/4] 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 32f8330..db7b1e6 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 1fa17fe..c8211e4 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 12241b8..ca39011 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 269e639..f0cfe4d 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 8394e9a..a1012d5 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/4] 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 6305673..a5f8297 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/4] 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 f0cfe4d..c1af44f 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/4] 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 c8211e4..3c90d31 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 ca39011..05a87bb 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 c1af44f..a88762e 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 a1012d5..ca113d4 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.