This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 95e5e25e1a2 feat(helm): mitigate init-db Job hang under Istio sidecar
injection (closes #25798) (#41396)
95e5e25e1a2 is described below
commit 95e5e25e1a274e77a911a27029124a7c1a1d9aa3
Author: Evan Rusackas <[email protected]>
AuthorDate: Mon Jul 27 10:09:30 2026 -0700
feat(helm): mitigate init-db Job hang under Istio sidecar injection (closes
#25798) (#41396)
Co-authored-by: Devin AI
<devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
helm/superset/Chart.yaml | 2 +-
helm/superset/README.md | 6 +-
helm/superset/templates/_helpers.tpl | 8 ++
helm/superset/templates/init-job.yaml | 4 +
helm/superset/tests/test-istio.sh | 183 ++++++++++++++++++++++++++++++++++
helm/superset/values.yaml | 25 +++++
6 files changed, 226 insertions(+), 2 deletions(-)
diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml
index ccfb0c2cff0..2ea4b51b51e 100644
--- a/helm/superset/Chart.yaml
+++ b/helm/superset/Chart.yaml
@@ -29,7 +29,7 @@ maintainers:
- name: craig-rueda
email: [email protected]
url: https://github.com/craig-rueda
-version: 0.22.3 # See
[README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning)
for version details.
+version: 0.22.4 # See
[README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning)
for version details.
dependencies:
- name: postgresql
version: 16.7.27
diff --git a/helm/superset/README.md b/helm/superset/README.md
index 28ed5688b98..7c941177b41 100644
--- a/helm/superset/README.md
+++ b/helm/superset/README.md
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs:
https://github.com/norwoodj/helm-docs
# superset
-
+
Apache Superset is a modern, enterprise-ready business intelligence web
application
@@ -159,6 +159,10 @@ Alternatively, perform a fresh install. This is a one-time
migration; subsequent
| init.extraInitContainers | list | `[]` | Extra init containers appended
after init job initContainers |
| init.initContainers | list | a container waiting for postgres | List of
initContainers |
| init.initscript | string | unused; kept for backwards-compatibility only |
DEPRECATED: this field is no longer used by the chart. The init script is
rendered entirely from the internal `superset.initScript` template (which runs
`superset db upgrade`, `superset init`, admin creation, and examples). Any
customization placed here is silently ignored. See UPGRADING.md. |
+| init.istio | object |
`{"disableSidecarInjection":false,"quitEndpoint":"http://localhost:15020/quitquitquit","terminateSidecarOnExit":false}`
| Configuration for compatibility with the Istio service mesh. Without these
mitigations, when the init-db Job runs in a namespace with automatic Istio
sidecar injection enabled, the injected envoy-proxy container keeps running
after the init container has exited, preventing the Job from ever reaching the
Completed state. See https://github.com/a [...]
+| init.istio.disableSidecarInjection | bool | `false` | When true, adds the
`sidecar.istio.io/inject: "false"` label to the init job pod template, opting
it out of Istio automatic sidecar injection. This is the recommended fix when
the cluster's mesh policy allows opting out per pod. |
+| init.istio.quitEndpoint | string | `"http://localhost:15020/quitquitquit"` |
Endpoint that the trap POSTs to in order to terminate the sidecar. The default
matches the standard Istio pilot-agent admin port. |
+| init.istio.terminateSidecarOnExit | bool | `false` | When true, the rendered
`superset_init.sh` script (from the internal `superset.initScript` template)
registers an `EXIT` trap that POSTs to the Istio pilot-agent's `/quitquitquit`
endpoint after the init logic finishes, gracefully terminating an injected
envoy-proxy sidecar so the Job can complete. Enable this when sidecar injection
cannot be disabled per pod (e.g. when enforced by a cluster-wide Istio policy).
Note: requires `curl` [...]
| init.jobAnnotations."helm.sh/hook" | string | `"post-install,post-upgrade"`
| |
| init.jobAnnotations."helm.sh/hook-delete-policy" | string |
`"before-hook-creation"` | |
| init.loadExamples | bool | `false` | |
diff --git a/helm/superset/templates/_helpers.tpl
b/helm/superset/templates/_helpers.tpl
index bd993b1df0e..de82305034e 100644
--- a/helm/superset/templates/_helpers.tpl
+++ b/helm/superset/templates/_helpers.tpl
@@ -685,6 +685,14 @@ TALISMAN_CONFIG = {
{{- define "superset.initScript" -}}
#!/bin/sh
set -eu
+{{- if dig "istio" "terminateSidecarOnExit" false .Values.init }}
+# Notify the Istio pilot-agent sidecar to exit when this script completes
+# (whether successfully or via `set -e`), so that the Job can reach the
+# Completed state instead of hanging on a still-running envoy-proxy.
+# See https://github.com/apache/superset/issues/25798
+ISTIO_QUIT_ENDPOINT={{ dig "istio" "quitEndpoint"
"http://localhost:15020/quitquitquit" .Values.init | replace "'" "'\\''" |
squote }}
+trap 'rc=$?; curl -fsS -m 5 -X POST "$ISTIO_QUIT_ENDPOINT" >/dev/null 2>&1 ||
echo "WARNING: failed to notify Istio sidecar at $ISTIO_QUIT_ENDPOINT to quit;
the Job may hang if sidecar injection is active" >&2; exit $rc' EXIT
+{{- end }}
echo "Upgrading DB schema..."
superset db upgrade
echo "Initializing roles and permissions..."
diff --git a/helm/superset/templates/init-job.yaml
b/helm/superset/templates/init-job.yaml
index 72aab88c7a0..1f56150267f 100644
--- a/helm/superset/templates/init-job.yaml
+++ b/helm/superset/templates/init-job.yaml
@@ -35,6 +35,7 @@ spec:
{{- if .Values.init.podAnnotations }}
annotations: {{- toYaml .Values.init.podAnnotations | nindent 8 }}
{{- end }}
+ {{- $istioDisableInject := dig "istio" "disableSidecarInjection" false
.Values.init }}
labels:
{{- include "superset.componentSelectorLabels" (dict "component"
"init" "root" .) | nindent 8 }}
job: {{ template "superset.fullname" . }}-init-db
@@ -44,6 +45,9 @@ spec:
{{- if .Values.init.podLabels }}
{{- toYaml .Values.init.podLabels | nindent 8 }}
{{- end }}
+ {{- if $istioDisableInject }}
+ sidecar.istio.io/inject: "false"
+ {{- end }}
spec:
{{- if .Values.init.additionalPodSpec }}
{{- tpl (toYaml .Values.init.additionalPodSpec) . | nindent 6 }}
diff --git a/helm/superset/tests/test-istio.sh
b/helm/superset/tests/test-istio.sh
new file mode 100755
index 00000000000..7882533b23e
--- /dev/null
+++ b/helm/superset/tests/test-istio.sh
@@ -0,0 +1,183 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Renders the chart with `helm template` for several `init.istio.*` value
+# combinations and asserts that the relevant manifests contain (or omit)
+# the expected fields. Intended to be run from the chart directory or via
+# `bash helm/superset/tests/test-istio.sh` from the repo root.
+#
+# Covers the fix for:
+# https://github.com/apache/superset/issues/25798
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+CHART_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
+
+pass=0
+fail=0
+
+assert_contains() {
+ local label="$1"
+ local needle="$2"
+ local haystack="$3"
+ if grep -qF -- "${needle}" <<<"${haystack}"; then
+ echo " PASS: ${label}"
+ pass=$((pass + 1))
+ else
+ echo " FAIL: ${label}"
+ echo " expected to contain: ${needle}"
+ fail=$((fail + 1))
+ fi
+}
+
+assert_not_contains() {
+ local label="$1"
+ local needle="$2"
+ local haystack="$3"
+ if ! grep -qF -- "${needle}" <<<"${haystack}"; then
+ echo " PASS: ${label}"
+ pass=$((pass + 1))
+ else
+ echo " FAIL: ${label}"
+ echo " expected NOT to contain: ${needle}"
+ fail=$((fail + 1))
+ fi
+}
+
+render() {
+ helm template release "${CHART_DIR}" "$@"
+}
+
+extract_init_job() {
+ awk '
+ /^# Source: superset\/templates\/init-job\.yaml/ { capture = 1 }
+ capture && /^---$/ { capture = 0 }
+ capture { print }
+ ' <<<"$1"
+}
+
+extract_config_secret() {
+ awk '
+ /^# Source: superset\/templates\/secret-superset-config\.yaml/ {
capture = 1 }
+ capture && /^---$/ { capture = 0 }
+ capture { print }
+ ' <<<"$1"
+}
+
+echo "==> defaults: no istio mitigations applied"
+out_default="$(render)"
+init_job_default="$(extract_init_job "${out_default}")"
+config_default="$(extract_config_secret "${out_default}")"
+assert_not_contains "default does not set sidecar.istio.io/inject label" \
+ 'sidecar.istio.io/inject' "${init_job_default}"
+assert_not_contains "default initscript does not register quitquitquit trap" \
+ 'quitquitquit' "${config_default}"
+
+echo "==> init.istio.disableSidecarInjection=true"
+out_disable="$(render --set init.istio.disableSidecarInjection=true)"
+init_job_disable="$(extract_init_job "${out_disable}")"
+config_disable="$(extract_config_secret "${out_disable}")"
+assert_contains "init job sets sidecar.istio.io/inject: \"false\"" \
+ 'sidecar.istio.io/inject: "false"' "${init_job_disable}"
+assert_not_contains "disableSidecarInjection alone does not add quitquitquit
trap" \
+ 'quitquitquit' "${config_disable}"
+
+echo "==> init.istio.terminateSidecarOnExit=true"
+out_terminate="$(render --set init.istio.terminateSidecarOnExit=true)"
+init_job_terminate="$(extract_init_job "${out_terminate}")"
+config_terminate="$(extract_config_secret "${out_terminate}")"
+assert_not_contains "terminateSidecarOnExit alone does not add inject label" \
+ 'sidecar.istio.io/inject' "${init_job_terminate}"
+assert_contains "initscript registers EXIT trap that calls quitquitquit" \
+ "trap 'rc=\$?; curl -fsS -m 5 -X POST" "${config_terminate}"
+assert_contains "initscript trap targets the configured quit endpoint" \
+ 'http://localhost:15020/quitquitquit' "${config_terminate}"
+assert_contains "initscript trap binds to the EXIT signal" \
+ "' EXIT" "${config_terminate}"
+
+echo "==> init.istio.quitEndpoint override"
+out_endpoint="$(render --set init.istio.terminateSidecarOnExit=true \
+ --set init.istio.quitEndpoint=http://127.0.0.1:15020/quitquitquit)"
+config_endpoint="$(extract_config_secret "${out_endpoint}")"
+assert_contains "trap honours custom quitEndpoint" \
+ 'http://127.0.0.1:15020/quitquitquit' "${config_endpoint}"
+
+echo "==> both options combined"
+out_both="$(render --set init.istio.disableSidecarInjection=true \
+ --set init.istio.terminateSidecarOnExit=true)"
+init_job_both="$(extract_init_job "${out_both}")"
+config_both="$(extract_config_secret "${out_both}")"
+assert_contains "combined: inject label present" \
+ 'sidecar.istio.io/inject: "false"' "${init_job_both}"
+assert_contains "combined: trap present" \
+ 'quitquitquit' "${config_both}"
+
+echo "==> existing init.podLabels are preserved alongside istio label"
+out_labels="$(render --set init.istio.disableSidecarInjection=true \
+ --set init.podLabels.team=data-platform)"
+init_job_labels="$(extract_init_job "${out_labels}")"
+assert_contains "user-supplied podLabel still rendered" \
+ 'team: data-platform' "${init_job_labels}"
+assert_contains "istio inject label rendered alongside" \
+ 'sidecar.istio.io/inject: "false"' "${init_job_labels}"
+
+echo "==> init.istio explicitly overridden to null"
+out_null_istio="$(render --set init.istio=null)"
+init_job_null_istio="$(extract_init_job "${out_null_istio}")"
+config_null_istio="$(extract_config_secret "${out_null_istio}")"
+assert_not_contains "null init.istio does not set inject label" \
+ 'sidecar.istio.io/inject' "${init_job_null_istio}"
+assert_not_contains "null init.istio does not register quitquitquit trap" \
+ 'quitquitquit' "${config_null_istio}"
+
+echo "==> EXIT trap propagates the script's exit code, not the notification's"
+# The trap's own curl call is best-effort (failures are logged with
+# `|| echo ... >&2`, not swallowed with `|| true`) and must not mask a
+# failed migration. Extract the two rendered lines and actually run them,
+# with curl pointed at a closed local port so the notification itself fails,
+# to make sure the wrapped script's real exit code still comes through.
+quit_endpoint_line="$(grep -F 'ISTIO_QUIT_ENDPOINT=' <<<"${config_terminate}"
|| true)"
+trap_line="$(grep -F "trap 'rc=\$?; curl" <<<"${config_terminate}" || true)"
+if [[ -z "${quit_endpoint_line}" || -z "${trap_line}" ]]; then
+ echo " FAIL: script exit code (42) survives a failing quitquitquit
notification"
+ echo " could not locate the rendered ISTIO_QUIT_ENDPOINT/trap lines to
exercise"
+ fail=$((fail + 1))
+else
+ set +e
+ (
+ eval "${quit_endpoint_line}"
+ ISTIO_QUIT_ENDPOINT="http://127.0.0.1:1/quitquitquit"
+ eval "${trap_line}"
+ exit 42
+ )
+ trap_test_rc=$?
+ set -e
+ if [[ "${trap_test_rc}" -eq 42 ]]; then
+ echo " PASS: script exit code (42) survives a failing quitquitquit
notification"
+ pass=$((pass + 1))
+ else
+ echo " FAIL: script exit code (42) survives a failing quitquitquit
notification"
+ echo " got exit code: ${trap_test_rc}"
+ fail=$((fail + 1))
+ fi
+fi
+
+echo
+echo "passed: ${pass}, failed: ${fail}"
+if [[ ${fail} -gt 0 ]]; then
+ exit 1
+fi
diff --git a/helm/superset/values.yaml b/helm/superset/values.yaml
index ff9a6d67f51..c1cb3ec435f 100644
--- a/helm/superset/values.yaml
+++ b/helm/superset/values.yaml
@@ -1241,6 +1241,31 @@ init:
topologySpreadConstraints: []
# -- Set priorityClassName for init job pods
priorityClassName: ~
+ # -- Configuration for compatibility with the Istio service mesh.
+ # Without these mitigations, when the init-db Job runs in a namespace with
+ # automatic Istio sidecar injection enabled, the injected envoy-proxy
+ # container keeps running after the init container has exited, preventing
+ # the Job from ever reaching the Completed state.
+ # See https://github.com/apache/superset/issues/25798
+ istio:
+ # -- When true, adds the `sidecar.istio.io/inject: "false"` label to the
+ # init job pod template, opting it out of Istio automatic sidecar
+ # injection. This is the recommended fix when the cluster's mesh policy
+ # allows opting out per pod.
+ disableSidecarInjection: false
+ # -- When true, the rendered `superset_init.sh` script (from the internal
+ # `superset.initScript` template) registers an `EXIT` trap that POSTs to
+ # the Istio pilot-agent's `/quitquitquit` endpoint after the init logic
+ # finishes, gracefully terminating an injected envoy-proxy sidecar so
+ # the Job can complete. Enable this when sidecar injection cannot be
+ # disabled per pod (e.g. when enforced by a cluster-wide Istio policy).
+ # Note: requires `curl` to be available in the init container image
+ # (it is included in the default `apache/superset` image) and only
+ # takes effect when `init.command` sources the rendered `superset_init.sh`.
+ terminateSidecarOnExit: false
+ # -- Endpoint that the trap POSTs to in order to terminate the sidecar.
+ # The default matches the standard Istio pilot-agent admin port.
+ quitEndpoint: "http://localhost:15020/quitquitquit"
# -- Configuration values for the postgresql dependency.
# ref: https://github.com/bitnami/charts/tree/main/bitnami/postgresql