Skip to content

Commit a03affa

Browse files
committed
Terminate restartable init containers ignoring not-started containers
This ensures that the restartable init containers receive a termination signal even if there are any not-started restartable init containers, by ignoring the not-running containers.
1 parent 4a214f6 commit a03affa

File tree

2 files changed

+111
-2
lines changed

2 files changed

+111
-2
lines changed

pkg/kubelet/kuberuntime/kuberuntime_termination_order.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ func newTerminationOrdering(pod *v1.Pod, runningContainerNames []string) *termin
5656
to.terminated[c.Name] = channel
5757
mainContainerChannels = append(mainContainerChannels, channel)
5858

59-
// if its not a running container, pre-close the channel so nothing waits on it
59+
// if it's not a running container, pre-close the channel so nothing
60+
// waits on it
6061
if _, isRunning := runningContainers[c.Name]; !isRunning {
6162
close(channel)
6263
}
@@ -67,7 +68,14 @@ func newTerminationOrdering(pod *v1.Pod, runningContainerNames []string) *termin
6768
// get the init containers in reverse order
6869
ic := pod.Spec.InitContainers[len(pod.Spec.InitContainers)-i-1]
6970

70-
to.terminated[ic.Name] = make(chan struct{})
71+
channel := make(chan struct{})
72+
to.terminated[ic.Name] = channel
73+
74+
// if it's not a running container, pre-close the channel so nothing
75+
// waits on it
76+
if _, isRunning := runningContainers[ic.Name]; !isRunning {
77+
close(channel)
78+
}
7179

7280
if types.IsRestartableInitContainer(&ic) {
7381
// sidecars need to wait for all main containers to exit

test/e2e_node/container_lifecycle_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3115,6 +3115,107 @@ var _ = SIGDescribe(nodefeature.SidecarContainers, "Containers Lifecycle", func(
31153115
// should delete quickly and not try to start/wait on any sidecars since they never started
31163116
gomega.Expect(deleteTime).To(gomega.BeNumerically("<", grace+buffer), fmt.Sprintf("should delete in < %d seconds, took %f", grace+buffer, deleteTime))
31173117
})
3118+
3119+
f.It("should terminate restartable init containers gracefully if there is a non-started restartable init container", func(ctx context.Context) {
3120+
init1 := "init-1"
3121+
restartableInit2 := "restartable-init-2"
3122+
restartableInit3 := "restartable-init-3"
3123+
regular1 := "regular-1"
3124+
3125+
podTerminationGracePeriodSeconds := int64(180)
3126+
containerTerminationSeconds := 1
3127+
3128+
pod := &v1.Pod{
3129+
ObjectMeta: metav1.ObjectMeta{
3130+
Name: "terminate-restartable-init-gracefully",
3131+
},
3132+
Spec: v1.PodSpec{
3133+
TerminationGracePeriodSeconds: &podTerminationGracePeriodSeconds,
3134+
RestartPolicy: v1.RestartPolicyNever,
3135+
InitContainers: []v1.Container{
3136+
{
3137+
Name: init1,
3138+
Image: busyboxImage,
3139+
Command: ExecCommand(init1, execCommand{
3140+
Delay: 1,
3141+
TerminationSeconds: 5,
3142+
ExitCode: 0,
3143+
}),
3144+
},
3145+
{
3146+
Name: restartableInit2,
3147+
Image: busyboxImage,
3148+
Command: ExecCommand(restartableInit2, execCommand{
3149+
Delay: 600,
3150+
TerminationSeconds: containerTerminationSeconds,
3151+
ExitCode: 0,
3152+
}),
3153+
StartupProbe: &v1.Probe{
3154+
FailureThreshold: 600,
3155+
ProbeHandler: v1.ProbeHandler{
3156+
Exec: &v1.ExecAction{
3157+
Command: []string{"false"},
3158+
},
3159+
},
3160+
},
3161+
RestartPolicy: &containerRestartPolicyAlways,
3162+
},
3163+
{
3164+
Name: restartableInit3,
3165+
Image: busyboxImage,
3166+
Command: ExecCommand(restartableInit3, execCommand{
3167+
Delay: 600,
3168+
TerminationSeconds: 1,
3169+
ExitCode: 0,
3170+
}),
3171+
RestartPolicy: &containerRestartPolicyAlways,
3172+
},
3173+
},
3174+
Containers: []v1.Container{
3175+
{
3176+
Name: regular1,
3177+
Image: busyboxImage,
3178+
Command: ExecCommand(regular1, execCommand{
3179+
Delay: 600,
3180+
TerminationSeconds: 1,
3181+
ExitCode: 0,
3182+
}),
3183+
},
3184+
},
3185+
},
3186+
}
3187+
3188+
preparePod(pod)
3189+
3190+
client := e2epod.NewPodClient(f)
3191+
pod = client.Create(ctx, pod)
3192+
3193+
err := e2epod.WaitForPodCondition(ctx, f.ClientSet, pod.Namespace, pod.Name, "the second init container is running but not started", 2*time.Minute, func(pod *v1.Pod) (bool, error) {
3194+
if pod.Status.Phase != v1.PodPending {
3195+
return false, fmt.Errorf("pod should be in pending phase")
3196+
}
3197+
if len(pod.Status.InitContainerStatuses) != 3 {
3198+
return false, fmt.Errorf("pod should have the same number of statuses as init containers")
3199+
}
3200+
containerStatus := pod.Status.InitContainerStatuses[1]
3201+
return containerStatus.State.Running != nil &&
3202+
(containerStatus.Started == nil || *containerStatus.Started == false), nil
3203+
})
3204+
framework.ExpectNoError(err)
3205+
3206+
ginkgo.By("Deleting the pod")
3207+
err = client.Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &podTerminationGracePeriodSeconds})
3208+
framework.ExpectNoError(err)
3209+
3210+
ginkgo.By("Waiting for the pod to terminate gracefully before its terminationGracePeriodSeconds")
3211+
err = e2epod.WaitForPodNotFoundInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace,
3212+
// The duration should be less than the pod's
3213+
// terminationGracePeriodSeconds while adding a buffer(60s) to the
3214+
// container termination seconds(1s) to account for the time it
3215+
// takes to delete the pod.
3216+
time.Duration(containerTerminationSeconds+60)*time.Second)
3217+
framework.ExpectNoError(err, "the pod should be deleted before its terminationGracePeriodSeconds if the restartalbe init containers get termination signal correctly")
3218+
})
31183219
})
31193220

31203221
var _ = SIGDescribe(nodefeature.SidecarContainers, framework.WithSerial(), "Containers Lifecycle", func() {

0 commit comments

Comments
 (0)