Merge pull request #119168 from gjkim42/sidecar-allow-probes-and-lifecycle-hooks

Allow all probes and lifecycle for restartable init containers
This commit is contained in:
Kubernetes Prow Robot 2023-07-17 18:11:07 -07:00 committed by GitHub
commit d17f3ba2cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1814 additions and 99 deletions

View File

@ -2859,6 +2859,45 @@ func validatePodResourceClaimSource(claimSource core.ClaimSource, fldPath *field
return allErrs
}
func validateLivenessProbe(probe *core.Probe, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if probe == nil {
return allErrs
}
allErrs = append(allErrs, validateProbe(probe, fldPath)...)
if probe.SuccessThreshold != 1 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1"))
}
return allErrs
}
func validateReadinessProbe(probe *core.Probe, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if probe == nil {
return allErrs
}
allErrs = append(allErrs, validateProbe(probe, fldPath)...)
if probe.TerminationGracePeriodSeconds != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("terminationGracePeriodSeconds"), probe.TerminationGracePeriodSeconds, "must not be set for readinessProbes"))
}
return allErrs
}
func validateStartupProbe(probe *core.Probe, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if probe == nil {
return allErrs
}
allErrs = append(allErrs, validateProbe(probe, fldPath)...)
if probe.SuccessThreshold != 1 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("successThreshold"), probe.SuccessThreshold, "must be 1"))
}
return allErrs
}
func validateProbe(probe *core.Probe, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
@ -3245,36 +3284,26 @@ func validateInitContainers(containers []core.Container, regularContainers []cor
switch {
case restartAlways:
// TODO: Allow restartable init containers to have a lifecycle hook.
if ctr.Lifecycle != nil {
allErrs = append(allErrs, field.Forbidden(idxPath.Child("lifecycle"), "may not be set for init containers"))
}
// TODO: Allow restartable init containers to have a liveness probe.
if ctr.LivenessProbe != nil {
allErrs = append(allErrs, field.Forbidden(idxPath.Child("livenessProbe"), "may not be set for init containers"))
}
// TODO: Allow restartable init containers to have a readiness probe.
if ctr.ReadinessProbe != nil {
allErrs = append(allErrs, field.Forbidden(idxPath.Child("readinessProbe"), "may not be set for init containers"))
}
allErrs = append(allErrs, validateProbe(ctr.StartupProbe, idxPath.Child("startupProbe"))...)
if ctr.StartupProbe != nil && ctr.StartupProbe.SuccessThreshold != 1 {
allErrs = append(allErrs, field.Invalid(idxPath.Child("startupProbe", "successThreshold"), ctr.StartupProbe.SuccessThreshold, "must be 1"))
allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, idxPath.Child("lifecycle"))...)
}
allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, idxPath.Child("livenessProbe"))...)
allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, idxPath.Child("readinessProbe"))...)
allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, idxPath.Child("startupProbe"))...)
default:
// These fields are disallowed for init containers.
if ctr.Lifecycle != nil {
allErrs = append(allErrs, field.Forbidden(idxPath.Child("lifecycle"), "may not be set for init containers"))
allErrs = append(allErrs, field.Forbidden(idxPath.Child("lifecycle"), "may not be set for init containers without restartPolicy=Always"))
}
if ctr.LivenessProbe != nil {
allErrs = append(allErrs, field.Forbidden(idxPath.Child("livenessProbe"), "may not be set for init containers"))
allErrs = append(allErrs, field.Forbidden(idxPath.Child("livenessProbe"), "may not be set for init containers without restartPolicy=Always"))
}
if ctr.ReadinessProbe != nil {
allErrs = append(allErrs, field.Forbidden(idxPath.Child("readinessProbe"), "may not be set for init containers"))
allErrs = append(allErrs, field.Forbidden(idxPath.Child("readinessProbe"), "may not be set for init containers without restartPolicy=Always"))
}
if ctr.StartupProbe != nil {
allErrs = append(allErrs, field.Forbidden(idxPath.Child("startupProbe"), "may not be set for init containers"))
allErrs = append(allErrs, field.Forbidden(idxPath.Child("startupProbe"), "may not be set for init containers without restartPolicy=Always"))
}
}
@ -3383,23 +3412,16 @@ func validateContainers(containers []core.Container, volumes map[string]core.Vol
allNames.Insert(ctr.Name)
}
// These fields are only allowed for regular containers, so only check supported values here.
// Init and ephemeral container validation will return field.Forbidden() for these paths.
// These fields are allowed for regular containers and restartable init
// containers.
// Regular init container and ephemeral container validation will return
// field.Forbidden() for these paths.
if ctr.Lifecycle != nil {
allErrs = append(allErrs, validateLifecycle(ctr.Lifecycle, path.Child("lifecycle"))...)
}
allErrs = append(allErrs, validateProbe(ctr.LivenessProbe, path.Child("livenessProbe"))...)
if ctr.LivenessProbe != nil && ctr.LivenessProbe.SuccessThreshold != 1 {
allErrs = append(allErrs, field.Invalid(path.Child("livenessProbe", "successThreshold"), ctr.LivenessProbe.SuccessThreshold, "must be 1"))
}
allErrs = append(allErrs, validateProbe(ctr.ReadinessProbe, path.Child("readinessProbe"))...)
if ctr.ReadinessProbe != nil && ctr.ReadinessProbe.TerminationGracePeriodSeconds != nil {
allErrs = append(allErrs, field.Invalid(path.Child("readinessProbe", "terminationGracePeriodSeconds"), ctr.ReadinessProbe.TerminationGracePeriodSeconds, "must not be set for readinessProbes"))
}
allErrs = append(allErrs, validateProbe(ctr.StartupProbe, path.Child("startupProbe"))...)
if ctr.StartupProbe != nil && ctr.StartupProbe.SuccessThreshold != 1 {
allErrs = append(allErrs, field.Invalid(path.Child("startupProbe", "successThreshold"), ctr.StartupProbe.SuccessThreshold, "must be 1"))
}
allErrs = append(allErrs, validateLivenessProbe(ctr.LivenessProbe, path.Child("livenessProbe"))...)
allErrs = append(allErrs, validateReadinessProbe(ctr.ReadinessProbe, path.Child("readinessProbe"))...)
allErrs = append(allErrs, validateStartupProbe(ctr.StartupProbe, path.Child("startupProbe"))...)
// These fields are disallowed for regular containers
if ctr.RestartPolicy != nil {

View File

@ -8162,14 +8162,43 @@ func TestValidateInitContainers(t *testing.T) {
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
}, {
Name: "container-3-restart-always-with-startup-probe",
Name: "container-3-restart-always-with-lifecycle-hook-and-probes",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
RestartPolicy: &containerRestartPolicyAlways,
Lifecycle: &core.Lifecycle{
PostStart: &core.LifecycleHandler{
Exec: &core.ExecAction{
Command: []string{"echo", "post start"},
},
},
PreStop: &core.LifecycleHandler{
Exec: &core.ExecAction{
Command: []string{"echo", "pre stop"},
},
},
},
LivenessProbe: &core.Probe{
ProbeHandler: core.ProbeHandler{
TCPSocket: &core.TCPSocketAction{
Port: intstr.FromInt32(80),
},
},
SuccessThreshold: 1,
},
ReadinessProbe: &core.Probe{
ProbeHandler: core.ProbeHandler{
TCPSocket: &core.TCPSocketAction{
Port: intstr.FromInt32(80),
},
},
},
StartupProbe: &core.Probe{
ProbeHandler: core.ProbeHandler{
TCPSocket: &core.TCPSocketAction{Port: intstr.FromInt(80)},
TCPSocket: &core.TCPSocketAction{
Port: intstr.FromInt(80),
},
},
SuccessThreshold: 1,
},
@ -8390,6 +8419,165 @@ func TestValidateInitContainers(t *testing.T) {
},
}},
field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].startupProbe.successThreshold", BadValue: int32(2)}},
}, {
"invalid readiness probe, terminationGracePeriodSeconds set.",
line(),
[]core.Container{{
Name: "life-123",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
RestartPolicy: &containerRestartPolicyAlways,
ReadinessProbe: &core.Probe{
ProbeHandler: core.ProbeHandler{
TCPSocket: &core.TCPSocketAction{
Port: intstr.FromInt32(80),
},
},
TerminationGracePeriodSeconds: utilpointer.Int64(10),
},
}},
field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].readinessProbe.terminationGracePeriodSeconds", BadValue: utilpointer.Int64(10)}},
}, {
"invalid liveness probe, successThreshold != 1",
line(),
[]core.Container{{
Name: "live-123",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
RestartPolicy: &containerRestartPolicyAlways,
LivenessProbe: &core.Probe{
ProbeHandler: core.ProbeHandler{
TCPSocket: &core.TCPSocketAction{
Port: intstr.FromInt32(80),
},
},
SuccessThreshold: 2,
},
}},
field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].livenessProbe.successThreshold", BadValue: int32(2)}},
}, {
"invalid lifecycle, no exec command.",
line(),
[]core.Container{{
Name: "life-123",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
RestartPolicy: &containerRestartPolicyAlways,
Lifecycle: &core.Lifecycle{
PreStop: &core.LifecycleHandler{
Exec: &core.ExecAction{},
},
},
}},
field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.exec.command", BadValue: ""}},
}, {
"invalid lifecycle, no http path.",
line(),
[]core.Container{{
Name: "life-123",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
RestartPolicy: &containerRestartPolicyAlways,
Lifecycle: &core.Lifecycle{
PreStop: &core.LifecycleHandler{
HTTPGet: &core.HTTPGetAction{
Port: intstr.FromInt32(80),
Scheme: "HTTP",
},
},
},
}},
field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop.httpGet.path", BadValue: ""}},
}, {
"invalid lifecycle, no http port.",
line(),
[]core.Container{{
Name: "life-123",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
RestartPolicy: &containerRestartPolicyAlways,
Lifecycle: &core.Lifecycle{
PreStop: &core.LifecycleHandler{
HTTPGet: &core.HTTPGetAction{
Path: "/",
Scheme: "HTTP",
},
},
},
}},
field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.httpGet.port", BadValue: 0}},
}, {
"invalid lifecycle, no http scheme.",
line(),
[]core.Container{{
Name: "life-123",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
RestartPolicy: &containerRestartPolicyAlways,
Lifecycle: &core.Lifecycle{
PreStop: &core.LifecycleHandler{
HTTPGet: &core.HTTPGetAction{
Path: "/",
Port: intstr.FromInt32(80),
},
},
},
}},
field.ErrorList{{Type: field.ErrorTypeNotSupported, Field: "initContainers[0].lifecycle.preStop.httpGet.scheme", BadValue: core.URIScheme("")}},
}, {
"invalid lifecycle, no tcp socket port.",
line(),
[]core.Container{{
Name: "life-123",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
RestartPolicy: &containerRestartPolicyAlways,
Lifecycle: &core.Lifecycle{
PreStop: &core.LifecycleHandler{
TCPSocket: &core.TCPSocketAction{},
},
},
}},
field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}},
}, {
"invalid lifecycle, zero tcp socket port.",
line(),
[]core.Container{{
Name: "life-123",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
RestartPolicy: &containerRestartPolicyAlways,
Lifecycle: &core.Lifecycle{
PreStop: &core.LifecycleHandler{
TCPSocket: &core.TCPSocketAction{
Port: intstr.FromInt32(0),
},
},
},
}},
field.ErrorList{{Type: field.ErrorTypeInvalid, Field: "initContainers[0].lifecycle.preStop.tcpSocket.port", BadValue: 0}},
}, {
"invalid lifecycle, no action.",
line(),
[]core.Container{{
Name: "life-123",
Image: "image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: "File",
RestartPolicy: &containerRestartPolicyAlways,
Lifecycle: &core.Lifecycle{
PreStop: &core.LifecycleHandler{},
},
}},
field.ErrorList{{Type: field.ErrorTypeRequired, Field: "initContainers[0].lifecycle.preStop", BadValue: ""}},
},
}
for _, tc := range errorCases {

View File

@ -1738,9 +1738,10 @@ func (kl *Kubelet) generateAPIPodStatus(pod *v1.Pod, podStatus *kubecontainer.Po
if utilfeature.DefaultFeatureGate.Enabled(features.PodReadyToStartContainersCondition) {
s.Conditions = append(s.Conditions, status.GeneratePodReadyToStartContainersCondition(pod, podStatus))
}
s.Conditions = append(s.Conditions, status.GeneratePodInitializedCondition(&pod.Spec, append(s.InitContainerStatuses, s.ContainerStatuses...), s.Phase))
s.Conditions = append(s.Conditions, status.GeneratePodReadyCondition(&pod.Spec, s.Conditions, s.ContainerStatuses, s.Phase))
s.Conditions = append(s.Conditions, status.GenerateContainersReadyCondition(&pod.Spec, s.ContainerStatuses, s.Phase))
allContainerStatuses := append(s.InitContainerStatuses, s.ContainerStatuses...)
s.Conditions = append(s.Conditions, status.GeneratePodInitializedCondition(&pod.Spec, allContainerStatuses, s.Phase))
s.Conditions = append(s.Conditions, status.GeneratePodReadyCondition(&pod.Spec, s.Conditions, allContainerStatuses, s.Phase))
s.Conditions = append(s.Conditions, status.GenerateContainersReadyCondition(&pod.Spec, allContainerStatuses, s.Phase))
s.Conditions = append(s.Conditions, v1.PodCondition{
Type: v1.PodScheduled,
Status: v1.ConditionTrue,

View File

@ -877,7 +877,7 @@ func hasAnyRegularContainerCreated(pod *v1.Pod, podStatus *kubecontainer.PodStat
// The actions include:
// - Start the first init container that has not been started.
// - Restart all restartable init containers that have started but are not running.
// - Kill the restartable init containers that have failed the startup probe.
// - Kill the restartable init containers that are not alive or started.
func (m *kubeGenericRuntimeManager) computeInitContainerActions(pod *v1.Pod, podStatus *kubecontainer.PodStatus, changes *podActions) bool {
if len(pod.Spec.InitContainers) == 0 {
return true
@ -960,6 +960,27 @@ func (m *kubeGenericRuntimeManager) computeInitContainerActions(pod *v1.Pod, pod
// this init container is initialized for the first time, start the next one
changes.InitContainersToStart = append(changes.InitContainersToStart, i+1)
}
// A restartable init container does not have to take into account its
// liveness probe when it determines to start the next init container.
if container.LivenessProbe != nil {
liveness, found := m.livenessManager.Get(status.ID)
if !found {
// If the liveness probe has not been run, wait for it.
break
}
if liveness == proberesults.Failure {
// If the restartable init container failed the liveness probe,
// restart it.
changes.ContainersToKill[status.ID] = containerToKillInfo{
name: container.Name,
container: container,
message: fmt.Sprintf("Init container %s failed liveness probe", container.Name),
reason: reasonLivenessProbe,
}
changes.InitContainersToStart = append(changes.InitContainersToStart, i)
}
}
} else { // init container
// nothing do to but wait for it to finish
break

View File

@ -341,7 +341,7 @@ func TestToKubeContainerStatusWithResources(t *testing.T) {
}
}
func TestLifeCycleHook(t *testing.T) {
func testLifeCycleHook(t *testing.T, testPod *v1.Pod, testContainer *v1.Container) {
// Setup
fakeRuntime, _, m, _ := createTestRuntimeManager()
@ -352,23 +352,6 @@ func TestLifeCycleHook(t *testing.T) {
ID: "foo",
}
testPod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "default",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
Image: "busybox",
ImagePullPolicy: v1.PullIfNotPresent,
Command: []string{"testCommand"},
WorkingDir: "testWorkingDir",
},
},
},
}
cmdPostStart := &v1.Lifecycle{
PostStart: &v1.LifecycleHandler{
Exec: &v1.ExecAction{
@ -418,7 +401,7 @@ func TestLifeCycleHook(t *testing.T) {
// Configured and works as expected
t.Run("PreStop-CMDExec", func(t *testing.T) {
ctx := context.Background()
testPod.Spec.Containers[0].Lifecycle = cmdLifeCycle
testContainer.Lifecycle = cmdLifeCycle
m.killContainer(ctx, testPod, cID, "foo", "testKill", "", &gracePeriod)
if fakeRunner.Cmd[0] != cmdLifeCycle.PreStop.Exec.Command[0] {
t.Errorf("CMD Prestop hook was not invoked")
@ -432,7 +415,7 @@ func TestLifeCycleHook(t *testing.T) {
defer func() { fakeHTTP.req = nil }()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ConsistentHTTPGetHandlers, false)()
httpLifeCycle.PreStop.HTTPGet.Port = intstr.IntOrString{}
testPod.Spec.Containers[0].Lifecycle = httpLifeCycle
testContainer.Lifecycle = httpLifeCycle
m.killContainer(ctx, testPod, cID, "foo", "testKill", "", &gracePeriod)
if fakeHTTP.req == nil || !strings.Contains(fakeHTTP.req.URL.String(), httpLifeCycle.PreStop.HTTPGet.Host) {
@ -443,7 +426,7 @@ func TestLifeCycleHook(t *testing.T) {
ctx := context.Background()
defer func() { fakeHTTP.req = nil }()
httpLifeCycle.PreStop.HTTPGet.Port = intstr.FromInt32(80)
testPod.Spec.Containers[0].Lifecycle = httpLifeCycle
testContainer.Lifecycle = httpLifeCycle
m.killContainer(ctx, testPod, cID, "foo", "testKill", "", &gracePeriod)
if fakeHTTP.req == nil || !strings.Contains(fakeHTTP.req.URL.String(), httpLifeCycle.PreStop.HTTPGet.Host) {
@ -473,8 +456,7 @@ func TestLifeCycleHook(t *testing.T) {
// Fake all the things you need before trying to create a container
fakeSandBox, _ := makeAndSetFakePod(t, m, fakeRuntime, testPod)
fakeSandBoxConfig, _ := m.generatePodSandboxConfig(testPod, 0)
testPod.Spec.Containers[0].Lifecycle = cmdPostStart
testContainer := &testPod.Spec.Containers[0]
testContainer.Lifecycle = cmdPostStart
fakePodStatus := &kubecontainer.PodStatus{
ContainerStatuses: []*kubecontainer.Status{
{
@ -500,6 +482,51 @@ func TestLifeCycleHook(t *testing.T) {
})
}
func TestLifeCycleHook(t *testing.T) {
testPod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "default",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "foo",
Image: "busybox",
ImagePullPolicy: v1.PullIfNotPresent,
Command: []string{"testCommand"},
WorkingDir: "testWorkingDir",
},
},
},
}
testLifeCycleHook(t, testPod, &testPod.Spec.Containers[0])
}
func TestLifeCycleHookForRestartableInitContainer(t *testing.T) {
testPod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
Namespace: "default",
},
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{
Name: "foo",
Image: "busybox",
ImagePullPolicy: v1.PullIfNotPresent,
Command: []string{"testCommand"},
WorkingDir: "testWorkingDir",
RestartPolicy: &containerRestartPolicyAlways,
},
},
},
}
testLifeCycleHook(t, testPod, &testPod.Spec.InitContainers[0])
}
func TestStartSpec(t *testing.T) {
podStatus := &kubecontainer.PodStatus{
ContainerStatuses: []*kubecontainer.Status{

View File

@ -1518,6 +1518,62 @@ func TestComputePodActionsWithRestartableInitContainers(t *testing.T) {
ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}),
},
},
"livenessProbe has not been run; start the nothing": {
mutatePodFn: func(pod *v1.Pod) { pod.Spec.RestartPolicy = v1.RestartPolicyAlways },
mutateStatusFn: func(pod *v1.Pod, status *kubecontainer.PodStatus) {
m.livenessManager.Remove(status.ContainerStatuses[1].ID)
status.ContainerStatuses = status.ContainerStatuses[:2]
},
actions: podActions{
SandboxID: baseStatus.SandboxStatuses[0].Id,
InitContainersToStart: []int{2},
ContainersToStart: []int{},
ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}),
},
},
"livenessProbe in progress; start the next": {
mutatePodFn: func(pod *v1.Pod) { pod.Spec.RestartPolicy = v1.RestartPolicyAlways },
mutateStatusFn: func(pod *v1.Pod, status *kubecontainer.PodStatus) {
m.livenessManager.Set(status.ContainerStatuses[1].ID, proberesults.Unknown, basePod)
status.ContainerStatuses = status.ContainerStatuses[:2]
},
actions: podActions{
SandboxID: baseStatus.SandboxStatuses[0].Id,
InitContainersToStart: []int{2},
ContainersToStart: []int{},
ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}),
},
resetStatusFn: func(status *kubecontainer.PodStatus) {
m.livenessManager.Remove(status.ContainerStatuses[1].ID)
},
},
"livenessProbe has completed; start the next": {
mutatePodFn: func(pod *v1.Pod) { pod.Spec.RestartPolicy = v1.RestartPolicyAlways },
mutateStatusFn: func(pod *v1.Pod, status *kubecontainer.PodStatus) {
status.ContainerStatuses = status.ContainerStatuses[:2]
},
actions: podActions{
SandboxID: baseStatus.SandboxStatuses[0].Id,
InitContainersToStart: []int{2},
ContainersToStart: []int{},
ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}),
},
},
"kill and recreate the restartable init container if the liveness check has failed": {
mutatePodFn: func(pod *v1.Pod) { pod.Spec.RestartPolicy = v1.RestartPolicyAlways },
mutateStatusFn: func(pod *v1.Pod, status *kubecontainer.PodStatus) {
m.livenessManager.Set(status.ContainerStatuses[2].ID, proberesults.Failure, basePod)
},
actions: podActions{
SandboxID: baseStatus.SandboxStatuses[0].Id,
InitContainersToStart: []int{2},
ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{2}),
ContainersToStart: []int{0, 1, 2},
},
resetStatusFn: func(status *kubecontainer.PodStatus) {
m.livenessManager.Remove(status.ContainerStatuses[2].ID)
},
},
"startupProbe has not been run; do nothing": {
mutatePodFn: func(pod *v1.Pod) { pod.Spec.RestartPolicy = v1.RestartPolicyAlways },
mutateStatusFn: func(pod *v1.Pod, status *kubecontainer.PodStatus) {
@ -1740,7 +1796,9 @@ func TestComputePodActionsWithRestartableInitContainers(t *testing.T) {
},
} {
pod, status := makeBasePodAndStatusWithRestartableInitContainers()
m.livenessManager.Set(status.ContainerStatuses[1].ID, proberesults.Success, basePod)
m.startupManager.Set(status.ContainerStatuses[1].ID, proberesults.Success, basePod)
m.livenessManager.Set(status.ContainerStatuses[2].ID, proberesults.Success, basePod)
m.startupManager.Set(status.ContainerStatuses[2].ID, proberesults.Success, basePod)
if test.mutatePodFn != nil {
test.mutatePodFn(pod)
@ -1769,12 +1827,14 @@ func makeBasePodAndStatusWithRestartableInitContainers() (*v1.Pod, *kubecontaine
Name: "restartable-init-2",
Image: "bar-image",
RestartPolicy: &containerRestartPolicyAlways,
LivenessProbe: &v1.Probe{},
StartupProbe: &v1.Probe{},
},
{
Name: "restartable-init-3",
Image: "bar-image",
RestartPolicy: &containerRestartPolicyAlways,
LivenessProbe: &v1.Probe{},
StartupProbe: &v1.Probe{},
},
}

View File

@ -153,7 +153,11 @@ func TestAddRemovePodsWithRestartableInitContainer(t *testing.T) {
},
{
desc: "pod with sidecar (sidecar containers feature enabled)",
probePaths: []probeKey{{"restartable_init_container_pod", "restartable-init", readiness}},
probePaths: []probeKey{
{"restartable_init_container_pod", "restartable-init", liveness},
{"restartable_init_container_pod", "restartable-init", readiness},
{"restartable_init_container_pod", "restartable-init", startup},
},
enableSidecarContainers: true,
},
}
@ -179,7 +183,9 @@ func TestAddRemovePodsWithRestartableInitContainer(t *testing.T) {
Name: "init",
}, {
Name: "restartable-init",
LivenessProbe: defaultProbe,
ReadinessProbe: defaultProbe,
StartupProbe: defaultProbe,
RestartPolicy: containerRestartPolicy(tc.enableSidecarContainers),
}},
Containers: []v1.Container{{

View File

@ -55,6 +55,21 @@ func GenerateContainersReadyCondition(spec *v1.PodSpec, containerStatuses []v1.C
}
unknownContainers := []string{}
unreadyContainers := []string{}
for _, container := range spec.InitContainers {
if !kubetypes.IsRestartableInitContainer(&container) {
continue
}
if containerStatus, ok := podutil.GetContainerStatus(containerStatuses, container.Name); ok {
if !containerStatus.Ready {
unreadyContainers = append(unreadyContainers, container.Name)
}
} else {
unknownContainers = append(unknownContainers, container.Name)
}
}
for _, container := range spec.Containers {
if containerStatus, ok := podutil.GetContainerStatus(containerStatuses, container.Name); ok {
if !containerStatus.Ready {

View File

@ -30,6 +30,10 @@ import (
"k8s.io/utils/pointer"
)
var (
containerRestartPolicyAlways = v1.ContainerRestartPolicyAlways
)
func TestGenerateContainersReadyCondition(t *testing.T) {
tests := []struct {
spec *v1.PodSpec
@ -112,6 +116,74 @@ func TestGenerateContainersReadyCondition(t *testing.T) {
podPhase: v1.PodSucceeded,
expectReady: getPodCondition(v1.ContainersReady, v1.ConditionFalse, PodCompleted, ""),
},
{
spec: &v1.PodSpec{
InitContainers: []v1.Container{
{Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways},
},
Containers: []v1.Container{
{Name: "regular-1"},
},
},
containerStatuses: []v1.ContainerStatus{
getReadyStatus("regular-1"),
},
podPhase: v1.PodRunning,
expectReady: getPodCondition(v1.ContainersReady, v1.ConditionFalse, ContainersNotReady, "containers with unknown status: [restartable-init-1]"),
},
{
spec: &v1.PodSpec{
InitContainers: []v1.Container{
{Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways},
{Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways},
},
Containers: []v1.Container{
{Name: "regular-1"},
},
},
containerStatuses: []v1.ContainerStatus{
getReadyStatus("restartable-init-1"),
getReadyStatus("restartable-init-2"),
getReadyStatus("regular-1"),
},
podPhase: v1.PodRunning,
expectReady: getPodCondition(v1.ContainersReady, v1.ConditionTrue, "", ""),
},
{
spec: &v1.PodSpec{
InitContainers: []v1.Container{
{Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways},
{Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways},
},
Containers: []v1.Container{
{Name: "regular-1"},
},
},
containerStatuses: []v1.ContainerStatus{
getReadyStatus("restartable-init-1"),
getReadyStatus("regular-1"),
},
podPhase: v1.PodRunning,
expectReady: getPodCondition(v1.ContainersReady, v1.ConditionFalse, ContainersNotReady, "containers with unknown status: [restartable-init-2]"),
},
{
spec: &v1.PodSpec{
InitContainers: []v1.Container{
{Name: "restartable-init-1", RestartPolicy: &containerRestartPolicyAlways},
{Name: "restartable-init-2", RestartPolicy: &containerRestartPolicyAlways},
},
Containers: []v1.Container{
{Name: "regular-1"},
},
},
containerStatuses: []v1.ContainerStatus{
getReadyStatus("restartable-init-1"),
getNotReadyStatus("restartable-init-2"),
getReadyStatus("regular-1"),
},
podPhase: v1.PodRunning,
expectReady: getPodCondition(v1.ContainersReady, v1.ConditionFalse, ContainersNotReady, "containers with unready status: [restartable-init-2]"),
},
}
for i, test := range tests {

View File

@ -349,8 +349,9 @@ func (m *manager) SetContainerReadiness(podUID types.UID, containerID kubecontai
status.Conditions = append(status.Conditions, condition)
}
}
updateConditionFunc(v1.PodReady, GeneratePodReadyCondition(&pod.Spec, status.Conditions, status.ContainerStatuses, status.Phase))
updateConditionFunc(v1.ContainersReady, GenerateContainersReadyCondition(&pod.Spec, status.ContainerStatuses, status.Phase))
allContainerStatuses := append(status.InitContainerStatuses, status.ContainerStatuses...)
updateConditionFunc(v1.PodReady, GeneratePodReadyCondition(&pod.Spec, status.Conditions, allContainerStatuses, status.Phase))
updateConditionFunc(v1.ContainersReady, GenerateContainersReadyCondition(&pod.Spec, allContainerStatuses, status.Phase))
m.updateStatusInternal(pod, status, false, false)
}

File diff suppressed because it is too large Load Diff

View File

@ -253,6 +253,254 @@ var _ = SIGDescribe("Container Lifecycle Hook", func() {
})
})
var _ = SIGDescribe("[NodeAlphaFeature:SidecarContainers][Feature:SidecarContainers] Restartable Init Container Lifecycle Hook", func() {
f := framework.NewDefaultFramework("restartable-init-container-lifecycle-hook")
f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
var podClient *e2epod.PodClient
const (
podCheckInterval = 1 * time.Second
postStartWaitTimeout = 2 * time.Minute
preStopWaitTimeout = 30 * time.Second
)
ginkgo.Context("when create a pod with lifecycle hook", func() {
var (
targetIP, targetURL, targetNode string
httpPorts = []v1.ContainerPort{
{
ContainerPort: 8080,
Protocol: v1.ProtocolTCP,
},
}
httpsPorts = []v1.ContainerPort{
{
ContainerPort: 9090,
Protocol: v1.ProtocolTCP,
},
}
httpsArgs = []string{
"netexec",
"--http-port", "9090",
"--udp-port", "9091",
"--tls-cert-file", "/localhost.crt",
"--tls-private-key-file", "/localhost.key",
}
)
podHandleHookRequest := e2epod.NewAgnhostPodFromContainers(
"", "pod-handle-http-request", nil,
e2epod.NewAgnhostContainer("container-handle-http-request", nil, httpPorts, "netexec"),
e2epod.NewAgnhostContainer("container-handle-https-request", nil, httpsPorts, httpsArgs...),
)
ginkgo.BeforeEach(func(ctx context.Context) {
node, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
framework.ExpectNoError(err)
targetNode = node.Name
nodeSelection := e2epod.NodeSelection{}
e2epod.SetAffinity(&nodeSelection, targetNode)
e2epod.SetNodeSelection(&podHandleHookRequest.Spec, nodeSelection)
podClient = e2epod.NewPodClient(f)
ginkgo.By("create the container to handle the HTTPGet hook request.")
newPod := podClient.CreateSync(ctx, podHandleHookRequest)
targetIP = newPod.Status.PodIP
targetURL = targetIP
if strings.Contains(targetIP, ":") {
targetURL = fmt.Sprintf("[%s]", targetIP)
}
})
testPodWithHook := func(ctx context.Context, podWithHook *v1.Pod) {
ginkgo.By("create the pod with lifecycle hook")
podClient.CreateSync(ctx, podWithHook)
const (
defaultHandler = iota
httpsHandler
)
handlerContainer := defaultHandler
if podWithHook.Spec.InitContainers[0].Lifecycle.PostStart != nil {
ginkgo.By("check poststart hook")
if podWithHook.Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet != nil {
if v1.URISchemeHTTPS == podWithHook.Spec.InitContainers[0].Lifecycle.PostStart.HTTPGet.Scheme {
handlerContainer = httpsHandler
}
}
gomega.Eventually(ctx, func(ctx context.Context) error {
return podClient.MatchContainerOutput(ctx, podHandleHookRequest.Name, podHandleHookRequest.Spec.Containers[handlerContainer].Name,
`GET /echo\?msg=poststart`)
}, postStartWaitTimeout, podCheckInterval).Should(gomega.BeNil())
}
ginkgo.By("delete the pod with lifecycle hook")
podClient.DeleteSync(ctx, podWithHook.Name, *metav1.NewDeleteOptions(15), e2epod.DefaultPodDeletionTimeout)
if podWithHook.Spec.InitContainers[0].Lifecycle.PreStop != nil {
ginkgo.By("check prestop hook")
if podWithHook.Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet != nil {
if v1.URISchemeHTTPS == podWithHook.Spec.InitContainers[0].Lifecycle.PreStop.HTTPGet.Scheme {
handlerContainer = httpsHandler
}
}
gomega.Eventually(ctx, func(ctx context.Context) error {
return podClient.MatchContainerOutput(ctx, podHandleHookRequest.Name, podHandleHookRequest.Spec.Containers[handlerContainer].Name,
`GET /echo\?msg=prestop`)
}, preStopWaitTimeout, podCheckInterval).Should(gomega.BeNil())
}
}
/*
Release: v1.28
Testname: Pod Lifecycle with restartable init container, post start exec hook
Description: When a post start handler is specified in the container
lifecycle using a 'Exec' action, then the handler MUST be invoked after
the start of the container. A server pod is created that will serve http
requests, create a second pod with a container lifecycle specifying a
post start that invokes the server pod using ExecAction to validate that
the post start is executed.
*/
ginkgo.It("should execute poststart exec hook properly", func(ctx context.Context) {
lifecycle := &v1.Lifecycle{
PostStart: &v1.LifecycleHandler{
Exec: &v1.ExecAction{
Command: []string{"sh", "-c", "curl http://" + targetURL + ":8080/echo?msg=poststart"},
},
},
}
podWithHook := getSidecarPodWithHook("pod-with-poststart-exec-hook", imageutils.GetE2EImage(imageutils.Agnhost), lifecycle)
testPodWithHook(ctx, podWithHook)
})
/*
Release: v1.28
Testname: Pod Lifecycle with restartable init container, prestop exec hook
Description: When a pre-stop handler is specified in the container
lifecycle using a 'Exec' action, then the handler MUST be invoked before
the container is terminated. A server pod is created that will serve http
requests, create a second pod with a container lifecycle specifying a
pre-stop that invokes the server pod using ExecAction to validate that
the pre-stop is executed.
*/
ginkgo.It("should execute prestop exec hook properly", func(ctx context.Context) {
lifecycle := &v1.Lifecycle{
PreStop: &v1.LifecycleHandler{
Exec: &v1.ExecAction{
Command: []string{"sh", "-c", "curl http://" + targetURL + ":8080/echo?msg=prestop"},
},
},
}
podWithHook := getSidecarPodWithHook("pod-with-prestop-exec-hook", imageutils.GetE2EImage(imageutils.Agnhost), lifecycle)
testPodWithHook(ctx, podWithHook)
})
/*
Release: v1.28
Testname: Pod Lifecycle with restartable init container, post start http hook
Description: When a post start handler is specified in the container
lifecycle using a HttpGet action, then the handler MUST be invoked after
the start of the container. A server pod is created that will serve http
requests, create a second pod on the same node with a container lifecycle
specifying a post start that invokes the server pod to validate that the
post start is executed.
*/
ginkgo.It("should execute poststart http hook properly", func(ctx context.Context) {
lifecycle := &v1.Lifecycle{
PostStart: &v1.LifecycleHandler{
HTTPGet: &v1.HTTPGetAction{
Path: "/echo?msg=poststart",
Host: targetIP,
Port: intstr.FromInt(8080),
},
},
}
podWithHook := getSidecarPodWithHook("pod-with-poststart-http-hook", imageutils.GetPauseImageName(), lifecycle)
// make sure we spawn the test pod on the same node as the webserver.
nodeSelection := e2epod.NodeSelection{}
e2epod.SetAffinity(&nodeSelection, targetNode)
e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
testPodWithHook(ctx, podWithHook)
})
/*
Release : v1.28
Testname: Pod Lifecycle with restartable init container, poststart https hook
Description: When a post-start handler is specified in the container
lifecycle using a 'HttpGet' action, then the handler MUST be invoked
before the container is terminated. A server pod is created that will
serve https requests, create a second pod on the same node with a
container lifecycle specifying a post-start that invokes the server pod
to validate that the post-start is executed.
*/
ginkgo.It("should execute poststart https hook properly [MinimumKubeletVersion:1.23]", func(ctx context.Context) {
lifecycle := &v1.Lifecycle{
PostStart: &v1.LifecycleHandler{
HTTPGet: &v1.HTTPGetAction{
Scheme: v1.URISchemeHTTPS,
Path: "/echo?msg=poststart",
Host: targetIP,
Port: intstr.FromInt(9090),
},
},
}
podWithHook := getSidecarPodWithHook("pod-with-poststart-https-hook", imageutils.GetPauseImageName(), lifecycle)
// make sure we spawn the test pod on the same node as the webserver.
nodeSelection := e2epod.NodeSelection{}
e2epod.SetAffinity(&nodeSelection, targetNode)
e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
testPodWithHook(ctx, podWithHook)
})
/*
Release : v1.28
Testname: Pod Lifecycle with restartable init container, prestop http hook
Description: When a pre-stop handler is specified in the container
lifecycle using a 'HttpGet' action, then the handler MUST be invoked
before the container is terminated. A server pod is created that will
serve http requests, create a second pod on the same node with a
container lifecycle specifying a pre-stop that invokes the server pod to
validate that the pre-stop is executed.
*/
ginkgo.It("should execute prestop http hook properly", func(ctx context.Context) {
lifecycle := &v1.Lifecycle{
PreStop: &v1.LifecycleHandler{
HTTPGet: &v1.HTTPGetAction{
Path: "/echo?msg=prestop",
Host: targetIP,
Port: intstr.FromInt(8080),
},
},
}
podWithHook := getSidecarPodWithHook("pod-with-prestop-http-hook", imageutils.GetPauseImageName(), lifecycle)
// make sure we spawn the test pod on the same node as the webserver.
nodeSelection := e2epod.NodeSelection{}
e2epod.SetAffinity(&nodeSelection, targetNode)
e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
testPodWithHook(ctx, podWithHook)
})
/*
Release : v1.28
Testname: Pod Lifecycle with restartable init container, prestop https hook
Description: When a pre-stop handler is specified in the container
lifecycle using a 'HttpGet' action, then the handler MUST be invoked
before the container is terminated. A server pod is created that will
serve https requests, create a second pod on the same node with a
container lifecycle specifying a pre-stop that invokes the server pod to
validate that the pre-stop is executed.
*/
ginkgo.It("should execute prestop https hook properly [MinimumKubeletVersion:1.23]", func(ctx context.Context) {
lifecycle := &v1.Lifecycle{
PreStop: &v1.LifecycleHandler{
HTTPGet: &v1.HTTPGetAction{
Scheme: v1.URISchemeHTTPS,
Path: "/echo?msg=prestop",
Host: targetIP,
Port: intstr.FromInt(9090),
},
},
}
podWithHook := getSidecarPodWithHook("pod-with-prestop-https-hook", imageutils.GetPauseImageName(), lifecycle)
// make sure we spawn the test pod on the same node as the webserver.
nodeSelection := e2epod.NodeSelection{}
e2epod.SetAffinity(&nodeSelection, targetNode)
e2epod.SetNodeSelection(&podWithHook.Spec, nodeSelection)
testPodWithHook(ctx, podWithHook)
})
})
})
func getPodWithHook(name string, image string, lifecycle *v1.Lifecycle) *v1.Pod {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
@ -269,3 +517,30 @@ func getPodWithHook(name string, image string, lifecycle *v1.Lifecycle) *v1.Pod
},
}
}
func getSidecarPodWithHook(name string, image string, lifecycle *v1.Lifecycle) *v1.Pod {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{
Name: name,
Image: image,
Lifecycle: lifecycle,
RestartPolicy: func() *v1.ContainerRestartPolicy {
restartPolicy := v1.ContainerRestartPolicyAlways
return &restartPolicy
}(),
},
},
Containers: []v1.Container{
{
Name: "main",
Image: imageutils.GetPauseImageName(),
},
},
},
}
}

View File

@ -845,7 +845,7 @@ var _ = SIGDescribe("[NodeAlphaFeature:SidecarContainers] Containers Lifecycle "
framework.ExpectNoError(results.StartsBefore(restartableInit2, init3))
})
ginkgo.It("should run both restartable init cotnainers and third init container together", func() {
ginkgo.It("should run both restartable init containers and third init container together", func() {
framework.ExpectNoError(results.RunTogether(restartableInit2, restartableInit1))
framework.ExpectNoError(results.RunTogether(restartableInit1, init3))
framework.ExpectNoError(results.RunTogether(restartableInit2, init3))
@ -856,7 +856,7 @@ var _ = SIGDescribe("[NodeAlphaFeature:SidecarContainers] Containers Lifecycle "
framework.ExpectNoError(results.ExitsBefore(init3, regular1))
})
ginkgo.It("should run both restartable init cotnainers and a regular container together", func() {
ginkgo.It("should run both restartable init containers and a regular container together", func() {
framework.ExpectNoError(results.RunTogether(restartableInit1, regular1))
framework.ExpectNoError(results.RunTogether(restartableInit2, regular1))
})
@ -1249,7 +1249,6 @@ var _ = SIGDescribe("[NodeAlphaFeature:SidecarContainers] Containers Lifecycle "
ginkgo.It("should be running restartable init container and a failed Init container in parallel", func() {
framework.ExpectNoError(results.RunTogether(restartableInit1, init1))
})
// TODO: check preStop hooks when they are enabled
})
})
@ -1661,7 +1660,6 @@ var _ = SIGDescribe("[NodeAlphaFeature:SidecarContainers] Containers Lifecycle "
ginkgo.It("should be running restartable init container and a failed Init container in parallel", func() {
framework.ExpectNoError(results.RunTogether(restartableInit1, init1))
})
// TODO: check preStop hooks when they are enabled
})
})
@ -2075,11 +2073,10 @@ var _ = SIGDescribe("[NodeAlphaFeature:SidecarContainers] Containers Lifecycle "
ginkgo.It("should be running restartable init container and a failed Init container in parallel", func() {
framework.ExpectNoError(results.RunTogether(restartableInit1, init1))
})
// TODO: check preStop hooks when they are enabled
})
})
ginkgo.It("should launch restartable init cotnainers serially considering the startup probe", func() {
ginkgo.It("should launch restartable init containers serially considering the startup probe", func() {
restartableInit1 := "restartable-init-1"
restartableInit2 := "restartable-init-2"
@ -2158,7 +2155,7 @@ var _ = SIGDescribe("[NodeAlphaFeature:SidecarContainers] Containers Lifecycle "
framework.ExpectNoError(results.StartsBefore(restartableInit2, regular1))
})
ginkgo.It("should not launch next container if the restartable init cotnainer failed to complete startup probe", func() {
ginkgo.It("should call the container's preStop hook and not launch next container if the restartable init container's startup probe fails", func() {
restartableInit1 := "restartable-init-1"
regular1 := "regular-1"
@ -2174,16 +2171,30 @@ var _ = SIGDescribe("[NodeAlphaFeature:SidecarContainers] Containers Lifecycle "
Name: restartableInit1,
Image: busyboxImage,
Command: ExecCommand(restartableInit1, execCommand{
StartDelay: 30,
Delay: 600,
TerminationSeconds: 15,
ExitCode: 0,
}),
StartupProbe: &v1.Probe{
PeriodSeconds: 1,
InitialDelaySeconds: 5,
FailureThreshold: 1,
ProbeHandler: v1.ProbeHandler{
Exec: &v1.ExecAction{
Command: []string{"test", "-f", "started"},
Command: []string{
"sh",
"-c",
"exit 1",
},
},
},
},
Lifecycle: &v1.Lifecycle{
PreStop: &v1.LifecycleHandler{
Exec: &v1.ExecAction{
Command: ExecCommand(prefixedName(PreStopPrefix, restartableInit1), execCommand{
Delay: 1,
ExitCode: 0,
}),
},
},
},
@ -2208,7 +2219,7 @@ var _ = SIGDescribe("[NodeAlphaFeature:SidecarContainers] Containers Lifecycle "
client := e2epod.NewPodClient(f)
pod = client.Create(context.TODO(), pod)
ginkgo.By("Waiting for the restartable init cotnainer to restart")
ginkgo.By("Waiting for the restartable init container to restart")
err := WaitForPodInitContainerRestartCount(context.TODO(), f.ClientSet, pod.Namespace, pod.Name, 0, 2, 2*time.Minute)
framework.ExpectNoError(err)
@ -2222,6 +2233,92 @@ var _ = SIGDescribe("[NodeAlphaFeature:SidecarContainers] Containers Lifecycle "
results := parseOutput(pod)
ginkgo.By("Analyzing results")
framework.ExpectNoError(results.RunTogether(restartableInit1, prefixedName(PreStopPrefix, restartableInit1)))
framework.ExpectNoError(results.Starts(prefixedName(PreStopPrefix, restartableInit1)))
framework.ExpectNoError(results.Exits(restartableInit1))
framework.ExpectNoError(results.DoesntStart(regular1))
})
ginkgo.It("should call the container's preStop hook and start the next container if the restartable init container's liveness probe fails", func() {
restartableInit1 := "restartable-init-1"
regular1 := "regular-1"
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "restartable-init-container-failed-startup",
},
Spec: v1.PodSpec{
RestartPolicy: v1.RestartPolicyAlways,
InitContainers: []v1.Container{
{
Name: restartableInit1,
Image: busyboxImage,
Command: ExecCommand(restartableInit1, execCommand{
Delay: 600,
TerminationSeconds: 15,
ExitCode: 0,
}),
LivenessProbe: &v1.Probe{
InitialDelaySeconds: 5,
FailureThreshold: 1,
ProbeHandler: v1.ProbeHandler{
Exec: &v1.ExecAction{
Command: []string{
"sh",
"-c",
"exit 1",
},
},
},
},
Lifecycle: &v1.Lifecycle{
PreStop: &v1.LifecycleHandler{
Exec: &v1.ExecAction{
Command: ExecCommand(prefixedName(PreStopPrefix, restartableInit1), execCommand{
Delay: 1,
ExitCode: 0,
}),
},
},
},
RestartPolicy: &containerRestartPolicyAlways,
},
},
Containers: []v1.Container{
{
Name: regular1,
Image: busyboxImage,
Command: ExecCommand(regular1, execCommand{
Delay: 1,
ExitCode: 0,
}),
},
},
},
}
preparePod(pod)
client := e2epod.NewPodClient(f)
pod = client.Create(context.TODO(), pod)
ginkgo.By("Waiting for the restartable init container to restart")
err := WaitForPodInitContainerRestartCount(context.TODO(), f.ClientSet, pod.Namespace, pod.Name, 0, 2, 2*time.Minute)
framework.ExpectNoError(err)
err = WaitForPodContainerRestartCount(context.TODO(), f.ClientSet, pod.Namespace, pod.Name, 0, 1, 2*time.Minute)
framework.ExpectNoError(err)
pod, err = client.Get(context.TODO(), pod.Name, metav1.GetOptions{})
framework.ExpectNoError(err)
results := parseOutput(pod)
ginkgo.By("Analyzing results")
framework.ExpectNoError(results.RunTogether(restartableInit1, prefixedName(PreStopPrefix, restartableInit1)))
framework.ExpectNoError(results.Starts(prefixedName(PreStopPrefix, restartableInit1)))
framework.ExpectNoError(results.Exits(restartableInit1))
framework.ExpectNoError(results.Starts(regular1))
})
})