From 17601aa9131e8277ec65f48467e97b2c61d81ed4 Mon Sep 17 00:00:00 2001 From: Arthur Wu Date: Sun, 22 Jun 2025 05:02:20 -0400 Subject: [PATCH] Pod YAML: Add support for `lifecycle.stopSignal` The field allows users to specify a custom stop signal (e.g., SIGUSR1) per container. If defined, it overrides the default stop signal (SIGTERM) or that defined in the image metadata. Fixes: #25389 Signed-off-by: Arthur Wu --- docs/kubernetes_support.md | 1 + pkg/k8s.io/api/core/v1/types.go | 7 ++++ pkg/specgen/generate/kube/kube.go | 8 +++++ test/e2e/play_kube_test.go | 57 +++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+) diff --git a/docs/kubernetes_support.md b/docs/kubernetes_support.md index a9abbe4c19..2bc634ff6f 100644 --- a/docs/kubernetes_support.md +++ b/docs/kubernetes_support.md @@ -112,6 +112,7 @@ Note: **N/A** means that the option cannot be supported in a single-node Podman | resources\.requests | ✅ | | lifecycle\.postStart | no | | lifecycle\.preStop | no | +| lifecycle\.stopSignal | ✅ | | terminationMessagePath | no | | terminationMessagePolicy | no | | livenessProbe | ✅ | diff --git a/pkg/k8s.io/api/core/v1/types.go b/pkg/k8s.io/api/core/v1/types.go index e326f0ee0b..faebe8d811 100644 --- a/pkg/k8s.io/api/core/v1/types.go +++ b/pkg/k8s.io/api/core/v1/types.go @@ -1331,6 +1331,13 @@ type Lifecycle struct { // More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks // +optional PreStop *Handler `json:"preStop,omitempty"` + // StopSignal defines the signal to be sent to the container when stopping. + // This value is configured via the container's Lifecycle and overrides any + // stop signal defined in the container image. If no StopSignal is specified, + // the default signal (SIGTERM) will be used. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#defining-custom-stop-signals + // +optional + StopSignal *string `json:"stopSignal,omitempty"` } type ConditionStatus string diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 86f7694816..e23624d3d8 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -656,6 +656,14 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener s.StopTimeout = &timeout } + if lifecycle := opts.Container.Lifecycle; lifecycle != nil && lifecycle.StopSignal != nil { + stopSignal, err := util.ParseSignal(*lifecycle.StopSignal) + if err != nil { + return nil, err + } + s.StopSignal = &stopSignal + } + return s, nil } diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 29dec3d434..00c872cba1 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -1444,6 +1444,32 @@ items: restartPolicy: Never ` +var stopSignalSIGUSR1 = ` +apiVersion: v1 +kind: Pod +metadata: + name: testpod +spec: + containers: + - name: testctr + image: ` + CITEST_IMAGE + ` + lifecycle: + stopSignal: SIGUSR1 +` + +var stopSignalNosuchSignal = ` +apiVersion: v1 +kind: Pod +metadata: + name: noSuchSigTest +spec: + containers: + - name: test1 + image: ` + CITEST_IMAGE + ` + lifecycle: + stopSignal: noSuchSignal +` + var ( defaultCtrName = "testCtr" defaultCtrCmd = []string{"top"} @@ -6223,4 +6249,35 @@ spec: exec := podmanTest.PodmanExitCleanly("exec", "testPod-"+defaultCtrName, "cat", "/sys/fs/cgroup/cpuset.mems.effective") Expect(exec.OutputToString()).To(Equal("0")) }) + + It("test Lifecycle stopSignal", func() { + + // Default StopSignal SIGTERM + err = writeYaml(simplePodYaml, kubeYaml) + Expect(err).ToNot(HaveOccurred()) + + podmanTest.PodmanExitCleanly("kube", "play", kubeYaml) + inspect := podmanTest.PodmanExitCleanly("inspect", "libpod-test-") + + ctr := inspect.InspectContainerToJSON() + Expect(ctr[0].Config).To(HaveField("StopSignal", "SIGTERM")) + + // Custom StopSignal SIGUSR1 + err = writeYaml(stopSignalSIGUSR1, kubeYaml) + Expect(err).ToNot(HaveOccurred()) + + podmanTest.PodmanExitCleanly("kube", "play", kubeYaml) + inspect = podmanTest.PodmanExitCleanly("inspect", "testpod-testctr") + + ctr = inspect.InspectContainerToJSON() + Expect(ctr[0].Config).To(HaveField("StopSignal", "SIGUSR1")) + + // No such StopSignal + err = writeYaml(stopSignalNosuchSignal, kubeYaml) + Expect(err).ToNot(HaveOccurred()) + + session := podmanTest.Podman([]string{"kube", "play", kubeYaml}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitWithError(125, "invalid signal: noSuchSignal")) + }) })