This is an automated email from the ASF dual-hosted git repository.

nzw921rx pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 4f5e329374 [Fix][Zeta] Exec the JVM in seatunnel-cluster.sh so SIGTERM 
reaches it (#12710)
4f5e329374 is described below

commit 4f5e32937402c03c35d62a7a9167172e38cc707e
Author: Ricky Makhija <[email protected]>
AuthorDate: Mon Oct 26 14:18:14 2026 +0530

    [Fix][Zeta] Exec the JVM in seatunnel-cluster.sh so SIGTERM reaches it 
(#12710)
    
    Co-authored-by: David Zollo <[email protected]>
---
 .../templates/deployment-seatunnel-master.yaml     |  5 +++-
 .../templates/deployment-seatunnel-worker.yaml     |  5 +++-
 deploy/kubernetes/seatunnel/values.yaml            |  6 ++++
 docs/en/engines/zeta/tuning-guide.md               |  2 ++
 docs/en/getting-started/docker/docker.md           | 35 +++++-----------------
 .../kubernetes/hybrid-cluster-mode.md              | 18 +++++++++--
 docs/en/getting-started/kubernetes/operations.md   |  3 +-
 .../kubernetes/separated-cluster-mode.md           | 18 +++++++++--
 docs/zh/engines/zeta/tuning-guide.md               |  2 ++
 docs/zh/getting-started/docker/docker.md           | 35 +++++-----------------
 .../kubernetes/hybrid-cluster-mode.md              | 18 +++++++++--
 docs/zh/getting-started/kubernetes/operations.md   |  3 +-
 .../kubernetes/separated-cluster-mode.md           | 18 +++++++++--
 .../src/main/bin/seatunnel-cluster.sh              |  3 +-
 .../seatunnel/SeaTunnelClusterScriptTest.java      | 19 ++++++++++++
 15 files changed, 121 insertions(+), 69 deletions(-)

diff --git 
a/deploy/kubernetes/seatunnel/templates/deployment-seatunnel-master.yaml 
b/deploy/kubernetes/seatunnel/templates/deployment-seatunnel-master.yaml
index f6d904410a..f898052f69 100644
--- a/deploy/kubernetes/seatunnel/templates/deployment-seatunnel-master.yaml
+++ b/deploy/kubernetes/seatunnel/templates/deployment-seatunnel-master.yaml
@@ -40,6 +40,9 @@ spec:
        {{- include "seatunnel.master.labels" . | nindent 8 }}
     spec:
       serviceAccountName: {{ template "seatunnel.fullname" . }}
+      {{- if .Values.master.terminationGracePeriodSeconds }}
+      terminationGracePeriodSeconds: {{ 
.Values.master.terminationGracePeriodSeconds }}
+      {{- end }}
       {{- if .Values.master.affinity }}
       affinity:
         {{- toYaml .Values.master.affinity | nindent 8 }}
@@ -68,7 +71,7 @@ spec:
           {{- if .Values.master.command }}
           command: {{ .Values.master.command }}
           {{- else }}
-          command: ["/bin/sh","-c","/opt/seatunnel/bin/seatunnel-cluster.sh -r 
master"]
+          command: ["/opt/seatunnel/bin/seatunnel-cluster.sh","-r","master"]
           {{- end }}
           {{- if .Values.master.resources }}
           resources:
diff --git 
a/deploy/kubernetes/seatunnel/templates/deployment-seatunnel-worker.yaml 
b/deploy/kubernetes/seatunnel/templates/deployment-seatunnel-worker.yaml
index 612db0c702..81899fa9b5 100644
--- a/deploy/kubernetes/seatunnel/templates/deployment-seatunnel-worker.yaml
+++ b/deploy/kubernetes/seatunnel/templates/deployment-seatunnel-worker.yaml
@@ -40,6 +40,9 @@ spec:
         {{- include "seatunnel.worker.labels" . | nindent 8 }}
     spec:
       serviceAccountName: {{ template "seatunnel.fullname" . }}
+      {{- if .Values.worker.terminationGracePeriodSeconds }}
+      terminationGracePeriodSeconds: {{ 
.Values.worker.terminationGracePeriodSeconds }}
+      {{- end }}
       {{- if .Values.worker.affinity }}
       affinity:
         {{- toYaml .Values.worker.affinity | nindent 8 }}
@@ -66,7 +69,7 @@ spec:
           {{- if .Values.worker.command }}
           command: {{ .Values.worker.command }}
           {{- else }}
-          command: ["/bin/sh","-c","/opt/seatunnel/bin/seatunnel-cluster.sh -r 
worker"]
+          command: ["/opt/seatunnel/bin/seatunnel-cluster.sh","-r","worker"]
           {{- end }}
           {{- if .Values.worker.resources }}
           resources:
diff --git a/deploy/kubernetes/seatunnel/values.yaml 
b/deploy/kubernetes/seatunnel/values.yaml
index d8c7cc387b..e98a9a454f 100644
--- a/deploy/kubernetes/seatunnel/values.yaml
+++ b/deploy/kubernetes/seatunnel/values.yaml
@@ -43,6 +43,9 @@ env:
 master:
   ## The command to start master.
   command: []
+  ## Seconds Kubernetes waits after SIGTERM before SIGKILL. With 
-Dhazelcast.shutdownhook.policy=GRACEFUL,
+  ## keep hazelcast.graceful.shutdown.max.wait below this value. 0 is treated 
as unset (Kubernetes default).
+  terminationGracePeriodSeconds: 30
   ## The deployment strategy to use to replace existing pods with new ones.
   strategy:
     type: RollingUpdate
@@ -106,6 +109,9 @@ master:
 worker:
   ## The command to start worker.
   command: []
+  ## Seconds Kubernetes waits after SIGTERM before SIGKILL. With 
-Dhazelcast.shutdownhook.policy=GRACEFUL,
+  ## keep hazelcast.graceful.shutdown.max.wait below this value. 0 is treated 
as unset (Kubernetes default).
+  terminationGracePeriodSeconds: 30
   ## The deployment strategy to use to replace existing pods with new ones.
   strategy:
     type: RollingUpdate
diff --git a/docs/en/engines/zeta/tuning-guide.md 
b/docs/en/engines/zeta/tuning-guide.md
index fa07df7057..c49b2b5ee9 100644
--- a/docs/en/engines/zeta/tuning-guide.md
+++ b/docs/en/engines/zeta/tuning-guide.md
@@ -496,6 +496,8 @@ Ensure pods have enough time to flush state and leave the 
cluster:
 terminationGracePeriodSeconds: 60
 ```
 
+The JVM receives SIGTERM only when the container runs `seatunnel-cluster.sh` 
directly, not through `/bin/sh -c`. See [Health Check and 
Shutdown](../../getting-started/kubernetes/hybrid-cluster-mode.md#health-check-and-shutdown).
+
 And in `hazelcast.yaml`:
 ```yaml
 hazelcast:
diff --git a/docs/en/getting-started/docker/docker.md 
b/docs/en/getting-started/docker/docker.md
index ec660195ee..f62b4fe0a1 100644
--- a/docs/en/getting-started/docker/docker.md
+++ b/docs/en/getting-started/docker/docker.md
@@ -235,10 +235,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r master
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "master"]
     ports:
       - "5801:5801"
       - "8080:8080"
@@ -251,10 +248,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r worker
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "worker"]
     depends_on:
       - master
     networks:
@@ -266,10 +260,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r worker
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "worker"]
     depends_on:
       - master
     networks:
@@ -303,10 +294,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r master
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "master"]
     ports:
       - "5801:5801"
       - "8080:8080"
@@ -319,10 +307,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r worker
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "worker"]
     depends_on:
       - master
     networks:
@@ -334,10 +319,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r worker
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "worker"]
     depends_on:
       - master
     networks:
@@ -352,10 +334,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801,seatunnel_worker_3:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r worker
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "worker"]
     depends_on:
       - master
     networks:
diff --git a/docs/en/getting-started/kubernetes/hybrid-cluster-mode.md 
b/docs/en/getting-started/kubernetes/hybrid-cluster-mode.md
index 1155a10221..1e6e97224b 100644
--- a/docs/en/getting-started/kubernetes/hybrid-cluster-mode.md
+++ b/docs/en/getting-started/kubernetes/hybrid-cluster-mode.md
@@ -279,9 +279,9 @@ spec:
             name: seatunnel-engine-config
 ```
 
-## Health Check and Graceful Shutdown
+## Health Check and Shutdown
 
-For cluster mode, add `startupProbe`, health checks, and `preStop` to each 
SeaTunnel container to avoid killing slow starts and to reduce the impact of 
rolling updates or node eviction.
+For cluster mode, add `startupProbe` and health checks to each SeaTunnel 
container to avoid killing slow starts and to reduce the impact of rolling 
updates or node eviction.
 
 ```yaml
 startupProbe:
@@ -303,6 +303,19 @@ livenessProbe:
   periodSeconds: 30
   timeoutSeconds: 5
   failureThreshold: 3
+```
+
+On pod deletion Kubernetes sends SIGTERM to PID 1 and sends SIGKILL after 
`terminationGracePeriodSeconds`. The StatefulSet above already sets the two 
things a clean exit needs:
+
+- `command` runs `seatunnel-cluster.sh` directly, not through `/bin/sh -c`. 
The script `exec`s the JVM, so the JVM is PID 1 and receives the SIGTERM. A 
`/bin/sh -c` wrapper as PID 1 ignores SIGTERM, and the pod is killed only after 
the grace period.
+- `terminationGracePeriodSeconds` gives the JVM time to run its shutdown hook 
before SIGKILL.
+
+On SIGTERM the Hazelcast shutdown hook runs with 
`hazelcast.shutdownhook.policy`, the node stops its running tasks and leaves 
the cluster within seconds, and those tasks recover from their last checkpoint. 
This is a fast, clean exit from the cluster, not a graceful stop of running 
jobs. `GRACEFUL` only lets Hazelcast migrate partition data before the member 
leaves; if you use it, keep `hazelcast.graceful.shutdown.max.wait` below 
`terminationGracePeriodSeconds`.
+
+:::tip Optional preStop
+A `preStop` hook is only needed when the container cannot run the script 
directly, for example when `command` must stay `/bin/sh -c`. 
`stop-seatunnel-cluster.sh` sends SIGTERM to the `SeaTunnelServer` process 
itself, and the hook then waits for the process to exit:
+
+```yaml
 lifecycle:
   preStop:
     exec:
@@ -315,6 +328,7 @@ lifecycle:
             sleep 1
           done
 ```
+:::
 
 Apply the manifests:
 
diff --git a/docs/en/getting-started/kubernetes/operations.md 
b/docs/en/getting-started/kubernetes/operations.md
index aefdd24a80..914863457c 100644
--- a/docs/en/getting-started/kubernetes/operations.md
+++ b/docs/en/getting-started/kubernetes/operations.md
@@ -72,8 +72,9 @@ Do not scale down multiple Workers at once. Decrease replicas 
one by one and obs
 
 When image or configuration changes, StatefulSet updates Pods in order. 
Recommended practices:
 
-- Configure `preStop` to call `stop-seatunnel-cluster.sh`.
+- Run `seatunnel-cluster.sh` directly in the container `command`, not through 
`/bin/sh -c`, so the JVM receives SIGTERM.
 - Set a long enough `terminationGracePeriodSeconds`.
+- `preStop` with `stop-seatunnel-cluster.sh` is optional, only needed when the 
command must stay `/bin/sh -c`. See [Health Check and 
Shutdown](hybrid-cluster-mode.md#health-check-and-shutdown).
 - Check Master replicas and Worker slot capacity before updates.
 - Avoid updating Master and Worker at the same time during peak hours.
 - If configuration files are mounted through `subPath`, ConfigMap updates are 
not propagated into the running container automatically. Run a rolling restart 
or use a tool such as Reloader to trigger restarts.
diff --git a/docs/en/getting-started/kubernetes/separated-cluster-mode.md 
b/docs/en/getting-started/kubernetes/separated-cluster-mode.md
index bfe0609e60..f249928c50 100644
--- a/docs/en/getting-started/kubernetes/separated-cluster-mode.md
+++ b/docs/en/getting-started/kubernetes/separated-cluster-mode.md
@@ -442,9 +442,9 @@ spec:
 
 Custom plugins are not included in the base StatefulSet. If extra plugins are 
required, see [Plugin Loading](configuration.md#plugin-loading) and manage them 
separately with a custom image, initContainer overlay, PersistentVolume, or 
object storage.
 
-## Health Check and Graceful Shutdown
+## Health Check and Shutdown
 
-Add the following container fragment to both Master and Worker. `startupProbe` 
avoids killing slow starts. `preStop` calls the SeaTunnel stop script first, 
then waits for the `SeaTunnelServer` process to exit. Together with 
`terminationGracePeriodSeconds`, this reduces the impact of rolling updates and 
node eviction on running jobs.
+Add the following probes to both Master and Worker. `startupProbe` avoids 
killing slow starts.
 
 ```yaml
 startupProbe:
@@ -466,6 +466,19 @@ livenessProbe:
   periodSeconds: 30
   timeoutSeconds: 5
   failureThreshold: 3
+```
+
+On pod deletion Kubernetes sends SIGTERM to PID 1 and sends SIGKILL after 
`terminationGracePeriodSeconds`. The Master and Worker StatefulSets above 
already set the two things a clean exit needs:
+
+- `command` runs `seatunnel-cluster.sh` directly, not through `/bin/sh -c`. 
The script `exec`s the JVM, so the JVM is PID 1 and receives the SIGTERM. A 
`/bin/sh -c` wrapper as PID 1 ignores SIGTERM, and the pod is killed only after 
the grace period.
+- `terminationGracePeriodSeconds` gives the JVM time to run its shutdown hook 
before SIGKILL.
+
+On SIGTERM the Hazelcast shutdown hook runs with 
`hazelcast.shutdownhook.policy`, the node stops its running tasks and leaves 
the cluster within seconds, and those tasks recover from their last checkpoint. 
This is a fast, clean exit from the cluster, not a graceful stop of running 
jobs. `GRACEFUL` only lets Hazelcast migrate partition data before the member 
leaves; if you use it, keep `hazelcast.graceful.shutdown.max.wait` below 
`terminationGracePeriodSeconds`.
+
+:::tip Optional preStop
+A `preStop` hook is only needed when the container cannot run the script 
directly, for example when `command` must stay `/bin/sh -c`. 
`stop-seatunnel-cluster.sh` sends SIGTERM to the `SeaTunnelServer` process 
itself, and the hook then waits for the process to exit:
+
+```yaml
 lifecycle:
   preStop:
     exec:
@@ -478,6 +491,7 @@ lifecycle:
             sleep 1
           done
 ```
+:::
 
 ## Apply Manifests
 
diff --git a/docs/zh/engines/zeta/tuning-guide.md 
b/docs/zh/engines/zeta/tuning-guide.md
index 40505bcbbe..5e2eb104a7 100644
--- a/docs/zh/engines/zeta/tuning-guide.md
+++ b/docs/zh/engines/zeta/tuning-guide.md
@@ -495,6 +495,8 @@ readinessProbe:
 terminationGracePeriodSeconds: 60
 ```
 
+只有当容器直接运行 `seatunnel-cluster.sh`(不通过 `/bin/sh -c` 包装)时,JVM 才能收到 SIGTERM。参见 
[健康检查和停止](../../getting-started/kubernetes/hybrid-cluster-mode.md#健康检查和停止)。
+
 并在 `hazelcast.yaml` 中:
 ```yaml
 hazelcast:
diff --git a/docs/zh/getting-started/docker/docker.md 
b/docs/zh/getting-started/docker/docker.md
index d0893e77e5..d335044887 100644
--- a/docs/zh/getting-started/docker/docker.md
+++ b/docs/zh/getting-started/docker/docker.md
@@ -233,10 +233,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r master
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "master"]
     ports:
       - "5801:5801"
       - "8080:8080"
@@ -249,10 +246,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r worker
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "worker"]
     depends_on:
       - master
     networks:
@@ -264,10 +258,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r worker
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "worker"]
     depends_on:
       - master
     networks:
@@ -296,10 +287,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r master
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "master"]
     ports:
       - "5801:5801"
       - "8080:8080"
@@ -312,10 +300,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r worker
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "worker"]
     depends_on:
       - master
     networks:
@@ -327,10 +312,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r worker
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "worker"]
     depends_on:
       - master
     networks:
@@ -345,10 +327,7 @@ services:
     restart: unless-stopped
     environment:
       - 
ST_DOCKER_MEMBER_LIST=seatunnel_master:5801,seatunnel_worker_1:5801,seatunnel_worker_2:5801,seatunnel_worker_3:5801
-    entrypoint: >
-      /bin/sh -c "
-      /opt/seatunnel/bin/seatunnel-cluster.sh -r worker
-      "
+    entrypoint: ["/opt/seatunnel/bin/seatunnel-cluster.sh", "-r", "worker"]
     depends_on:
       - master
     networks:
diff --git a/docs/zh/getting-started/kubernetes/hybrid-cluster-mode.md 
b/docs/zh/getting-started/kubernetes/hybrid-cluster-mode.md
index bb82b6a39d..c9d6fdab97 100644
--- a/docs/zh/getting-started/kubernetes/hybrid-cluster-mode.md
+++ b/docs/zh/getting-started/kubernetes/hybrid-cluster-mode.md
@@ -279,9 +279,9 @@ spec:
             name: seatunnel-engine-config
 ```
 
-## 健康检查和优雅停止
+## 健康检查和停止
 
-集群模式建议为每个 SeaTunnel 容器添加 `startupProbe`、健康检查和 
`preStop`,避免启动期误杀,并减少滚动更新或节点驱逐对集群的影响。
+集群模式建议为每个 SeaTunnel 容器添加 `startupProbe` 和健康检查,避免启动期误杀,并减少滚动更新或节点驱逐对集群的影响。
 
 ```yaml
 startupProbe:
@@ -303,6 +303,19 @@ livenessProbe:
   periodSeconds: 30
   timeoutSeconds: 5
   failureThreshold: 3
+```
+
+删除 Pod 时,Kubernetes 向 PID 1 发送 SIGTERM,并在 `terminationGracePeriodSeconds` 后发送 
SIGKILL。上面的 StatefulSet 已经包含干净退出所需的两项配置:
+
+- `command` 直接运行 `seatunnel-cluster.sh`,不通过 `/bin/sh -c` 包装。脚本会 `exec` 启动 
JVM,使 JVM 成为 PID 1 并收到 SIGTERM。若 PID 1 是 `/bin/sh -c`,SIGTERM 会被忽略,Pod 
要等到宽限期结束后才被强制终止。
+- `terminationGracePeriodSeconds` 让 JVM 在 SIGKILL 之前有时间执行关闭钩子。
+
+收到 SIGTERM 后,Hazelcast 会按 `hazelcast.shutdownhook.policy` 
执行关闭钩子,节点停止其上运行的任务并在数秒内离开集群,这些任务从最近的 checkpoint 
恢复。这是快速、干净地退出集群,而不是优雅地停止运行中的作业。`GRACEFUL` 只会让 Hazelcast 在成员离开前迁移分区数据;若使用 
`GRACEFUL`,请确保 `hazelcast.graceful.shutdown.max.wait` 小于 
`terminationGracePeriodSeconds`。
+
+:::tip 可选的 preStop
+只有当容器无法直接运行脚本时(例如 `command` 必须保留 `/bin/sh -c`)才需要 
`preStop`。`stop-seatunnel-cluster.sh` 会直接向 `SeaTunnelServer` 进程发送 
SIGTERM,随后钩子等待该进程退出:
+
+```yaml
 lifecycle:
   preStop:
     exec:
@@ -315,6 +328,7 @@ lifecycle:
             sleep 1
           done
 ```
+:::
 
 应用配置:
 
diff --git a/docs/zh/getting-started/kubernetes/operations.md 
b/docs/zh/getting-started/kubernetes/operations.md
index f65adde83c..83f630b242 100644
--- a/docs/zh/getting-started/kubernetes/operations.md
+++ b/docs/zh/getting-started/kubernetes/operations.md
@@ -72,8 +72,9 @@ kubectl get pods -l app=seatunnel,component=worker
 
 更新镜像或配置时,StatefulSet 会按顺序滚动更新 Pod。建议:
 
-- 配置 `preStop` 调用 `stop-seatunnel-cluster.sh`。
+- 容器 `command` 直接运行 `seatunnel-cluster.sh`,不要通过 `/bin/sh -c` 包装,使 JVM 能收到 
SIGTERM。
 - 设置足够长的 `terminationGracePeriodSeconds`。
+- 通过 `preStop` 调用 `stop-seatunnel-cluster.sh` 是可选的,仅在 `command` 必须保留 `/bin/sh 
-c` 时需要。参见 [健康检查和停止](hybrid-cluster-mode.md#健康检查和停止)。
 - 更新前确认 Master 副本数和 Worker slot 余量。
 - 避免在高峰期同时更新 Master 和 Worker。
 - 如果配置文件通过 `subPath` 挂载,ConfigMap 更新不会自动同步到容器内,需要执行滚动重启或通过 Reloader 等工具触发重启。
diff --git a/docs/zh/getting-started/kubernetes/separated-cluster-mode.md 
b/docs/zh/getting-started/kubernetes/separated-cluster-mode.md
index 889c6d2fb4..cbefbd1e68 100644
--- a/docs/zh/getting-started/kubernetes/separated-cluster-mode.md
+++ b/docs/zh/getting-started/kubernetes/separated-cluster-mode.md
@@ -442,9 +442,9 @@ spec:
 
 自定义插件不包含在基础 StatefulSet 中。需要额外插件时,请参考 
[插件加载](configuration.md#插件加载),通过自定义镜像、initContainer overlay、PersistentVolume 
或对象存储单独管理插件。
 
-## 健康检查和优雅停止
+## 健康检查和停止
 
-Master 和 Worker 都建议添加以下容器片段。`startupProbe` 可以避免启动期误杀;`preStop` 会先调用 SeaTunnel 
停止脚本,再等待 `SeaTunnelServer` 进程退出,配合 `terminationGracePeriodSeconds` 
可以降低滚动更新和节点驱逐对运行任务的影响。
+Master 和 Worker 都建议添加以下探针。`startupProbe` 可以避免启动期误杀。
 
 ```yaml
 startupProbe:
@@ -466,6 +466,19 @@ livenessProbe:
   periodSeconds: 30
   timeoutSeconds: 5
   failureThreshold: 3
+```
+
+删除 Pod 时,Kubernetes 向 PID 1 发送 SIGTERM,并在 `terminationGracePeriodSeconds` 后发送 
SIGKILL。上面的 Master 和 Worker StatefulSet 已经包含干净退出所需的两项配置:
+
+- `command` 直接运行 `seatunnel-cluster.sh`,不通过 `/bin/sh -c` 包装。脚本会 `exec` 启动 
JVM,使 JVM 成为 PID 1 并收到 SIGTERM。若 PID 1 是 `/bin/sh -c`,SIGTERM 会被忽略,Pod 
要等到宽限期结束后才被强制终止。
+- `terminationGracePeriodSeconds` 让 JVM 在 SIGKILL 之前有时间执行关闭钩子。
+
+收到 SIGTERM 后,Hazelcast 会按 `hazelcast.shutdownhook.policy` 
执行关闭钩子,节点停止其上运行的任务并在数秒内离开集群,这些任务从最近的 checkpoint 
恢复。这是快速、干净地退出集群,而不是优雅地停止运行中的作业。`GRACEFUL` 只会让 Hazelcast 在成员离开前迁移分区数据;若使用 
`GRACEFUL`,请确保 `hazelcast.graceful.shutdown.max.wait` 小于 
`terminationGracePeriodSeconds`。
+
+:::tip 可选的 preStop
+只有当容器无法直接运行脚本时(例如 `command` 必须保留 `/bin/sh -c`)才需要 
`preStop`。`stop-seatunnel-cluster.sh` 会直接向 `SeaTunnelServer` 进程发送 
SIGTERM,随后钩子等待该进程退出:
+
+```yaml
 lifecycle:
   preStop:
     exec:
@@ -478,6 +491,7 @@ lifecycle:
             sleep 1
           done
 ```
+:::
 
 ## 应用配置
 
diff --git a/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.sh 
b/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.sh
index a18431c518..1a51667f4a 100755
--- a/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.sh
+++ b/seatunnel-core/seatunnel-starter/src/main/bin/seatunnel-cluster.sh
@@ -229,5 +229,6 @@ if [[ $DAEMON == true && $HELP == false ]]; then
   touch $OUT
   nohup java ${JAVA_OPTS} -cp ${CLASS_PATH} ${APP_MAIN} ${args} > "$OUT" 
200<&- 2>&1 < /dev/null &
   else
-  java ${JAVA_OPTS} -cp ${CLASS_PATH} ${APP_MAIN} ${args}
+  # exec so the JVM is PID 1 in containers and receives SIGTERM directly
+  exec java ${JAVA_OPTS} -cp ${CLASS_PATH} ${APP_MAIN} ${args}
 fi
diff --git 
a/seatunnel-core/seatunnel-starter/src/test/java/org/apache/seatunnel/core/starter/seatunnel/SeaTunnelClusterScriptTest.java
 
b/seatunnel-core/seatunnel-starter/src/test/java/org/apache/seatunnel/core/starter/seatunnel/SeaTunnelClusterScriptTest.java
index 4f6ecddfb9..ddefc0e872 100644
--- 
a/seatunnel-core/seatunnel-starter/src/test/java/org/apache/seatunnel/core/starter/seatunnel/SeaTunnelClusterScriptTest.java
+++ 
b/seatunnel-core/seatunnel-starter/src/test/java/org/apache/seatunnel/core/starter/seatunnel/SeaTunnelClusterScriptTest.java
@@ -140,6 +140,24 @@ public class SeaTunnelClusterScriptTest {
                 "SEATUNNEL_HOME should use the externally configured SeaTunnel 
home");
     }
 
+    /**
+     * Verifies that the foreground start replaces the shell with the JVM, so 
a SIGTERM sent to the
+     * started process reaches the JVM instead of a shell that ignores it.
+     */
+    @Test
+    public void testClusterScriptExecsServerJvmInForeground() throws Exception 
{
+        Path appDirectory = createMinimalDistribution();
+        runClusterScript(appDirectory, null);
+
+        List<String> javaParentPid =
+                Files.readAllLines(
+                        temporaryDirectory.resolve("java-args.txt.ppid"), 
StandardCharsets.UTF_8);
+        Assertions.assertEquals(
+                String.valueOf(ProcessHandle.current().pid()),
+                javaParentPid.get(0).trim(),
+                "The JVM should replace the script process (exec), not run as 
its child");
+    }
+
     /**
      * Verifies that the Windows cluster script keeps custom SeaTunnel home 
values and forwards the
      * effective value before later JVM option appends.
@@ -250,6 +268,7 @@ public class SeaTunnelClusterScriptTest {
         Files.write(
                 fakeJava,
                 ("#!/bin/sh\n"
+                                + "echo \"$PPID\" > \"$CAPTURE_FILE.ppid\"\n"
                                 + "for arg in \"$@\"; do\n"
                                 + "  printf '%s\\n' \"$arg\"\n"
                                 + "done > \"$CAPTURE_FILE\"\n")

Reply via email to