Commit de8ee94d authored by Lantao Liu's avatar Lantao Liu

Stop container in unknown state before recreate or remove.

parent 4992402f
...@@ -104,6 +104,12 @@ func (r *FakeRuntimeService) AssertCalls(calls []string) error { ...@@ -104,6 +104,12 @@ func (r *FakeRuntimeService) AssertCalls(calls []string) error {
return nil return nil
} }
func (r *FakeRuntimeService) GetCalls() []string {
r.Lock()
defer r.Unlock()
return append([]string{}, r.Called...)
}
func (r *FakeRuntimeService) InjectError(f string, err error) { func (r *FakeRuntimeService) InjectError(f string, err error) {
r.Lock() r.Lock()
defer r.Unlock() defer r.Unlock()
......
...@@ -141,12 +141,17 @@ func (m *kubeGenericRuntimeManager) getImageUser(image string) (*int64, string, ...@@ -141,12 +141,17 @@ func (m *kubeGenericRuntimeManager) getImageUser(image string) (*int64, string,
return new(int64), "", nil return new(int64), "", nil
} }
// isContainerFailed returns true if container has exited and exitcode is not zero. // isInitContainerFailed returns true if container has exited and exitcode is not zero
func isContainerFailed(status *kubecontainer.ContainerStatus) bool { // or is in unknown state.
func isInitContainerFailed(status *kubecontainer.ContainerStatus) bool {
if status.State == kubecontainer.ContainerStateExited && status.ExitCode != 0 { if status.State == kubecontainer.ContainerStateExited && status.ExitCode != 0 {
return true return true
} }
if status.State == kubecontainer.ContainerStateUnknown {
return true
}
return false return false
} }
......
...@@ -635,9 +635,14 @@ func (m *kubeGenericRuntimeManager) pruneInitContainersBeforeStart(pod *v1.Pod, ...@@ -635,9 +635,14 @@ func (m *kubeGenericRuntimeManager) pruneInitContainersBeforeStart(pod *v1.Pod,
for name := range initContainerNames { for name := range initContainerNames {
count := 0 count := 0
for _, status := range podStatus.ContainerStatuses { for _, status := range podStatus.ContainerStatuses {
if status.Name != name || !initContainerNames.Has(status.Name) || status.State != kubecontainer.ContainerStateExited { if status.Name != name || !initContainerNames.Has(status.Name) ||
(status.State != kubecontainer.ContainerStateExited &&
status.State != kubecontainer.ContainerStateUnknown) {
continue continue
} }
// Remove init containers in unknown state. It should have
// been stopped before pruneInitContainersBeforeStart is
// called.
count++ count++
// keep the first init container for this name // keep the first init container for this name
if count == 1 { if count == 1 {
...@@ -692,7 +697,7 @@ func (m *kubeGenericRuntimeManager) purgeInitContainers(pod *v1.Pod, podStatus * ...@@ -692,7 +697,7 @@ func (m *kubeGenericRuntimeManager) purgeInitContainers(pod *v1.Pod, podStatus *
} }
// findNextInitContainerToRun returns the status of the last failed container, the // findNextInitContainerToRun returns the status of the last failed container, the
// next init container to start, or done if there are no further init containers. // index of next init container to start, or done if there are no further init containers.
// Status is only returned if an init container is failed, in which case next will // Status is only returned if an init container is failed, in which case next will
// point to the current container. // point to the current container.
func findNextInitContainerToRun(pod *v1.Pod, podStatus *kubecontainer.PodStatus) (status *kubecontainer.ContainerStatus, next *v1.Container, done bool) { func findNextInitContainerToRun(pod *v1.Pod, podStatus *kubecontainer.PodStatus) (status *kubecontainer.ContainerStatus, next *v1.Container, done bool) {
...@@ -704,7 +709,7 @@ func findNextInitContainerToRun(pod *v1.Pod, podStatus *kubecontainer.PodStatus) ...@@ -704,7 +709,7 @@ func findNextInitContainerToRun(pod *v1.Pod, podStatus *kubecontainer.PodStatus)
for i := len(pod.Spec.InitContainers) - 1; i >= 0; i-- { for i := len(pod.Spec.InitContainers) - 1; i >= 0; i-- {
container := &pod.Spec.InitContainers[i] container := &pod.Spec.InitContainers[i]
status := podStatus.FindContainerStatusByName(container.Name) status := podStatus.FindContainerStatusByName(container.Name)
if status != nil && isContainerFailed(status) { if status != nil && isInitContainerFailed(status) {
return status, container, false return status, container, false
} }
} }
......
...@@ -55,6 +55,9 @@ type containerGCInfo struct { ...@@ -55,6 +55,9 @@ type containerGCInfo struct {
name string name string
// Creation time for the container. // Creation time for the container.
createTime time.Time createTime time.Time
// If true, the container is in unknown state. Garbage collector should try
// to stop containers before removal.
unknown bool
} }
// sandboxGCInfo is the internal information kept for sandboxes being considered for GC. // sandboxGCInfo is the internal information kept for sandboxes being considered for GC.
...@@ -122,6 +125,19 @@ func (cgc *containerGC) removeOldestN(containers []containerGCInfo, toRemove int ...@@ -122,6 +125,19 @@ func (cgc *containerGC) removeOldestN(containers []containerGCInfo, toRemove int
// Remove from oldest to newest (last to first). // Remove from oldest to newest (last to first).
numToKeep := len(containers) - toRemove numToKeep := len(containers) - toRemove
for i := len(containers) - 1; i >= numToKeep; i-- { for i := len(containers) - 1; i >= numToKeep; i-- {
if containers[i].unknown {
// Containers in known state could be running, we should try
// to stop it before removal.
id := kubecontainer.ContainerID{
Type: cgc.manager.runtimeName,
ID: containers[i].id,
}
message := "Container is in unknown state, try killing it before removal"
if err := cgc.manager.killContainer(nil, id, containers[i].name, message, nil); err != nil {
klog.Errorf("Failed to stop container %q: %v", containers[i].id, err)
continue
}
}
if err := cgc.manager.removeContainer(containers[i].id); err != nil { if err := cgc.manager.removeContainer(containers[i].id); err != nil {
klog.Errorf("Failed to remove container %q: %v", containers[i].id, err) klog.Errorf("Failed to remove container %q: %v", containers[i].id, err)
} }
...@@ -184,6 +200,7 @@ func (cgc *containerGC) evictableContainers(minAge time.Duration) (containersByE ...@@ -184,6 +200,7 @@ func (cgc *containerGC) evictableContainers(minAge time.Duration) (containersByE
id: container.Id, id: container.Id,
name: container.Metadata.Name, name: container.Metadata.Name,
createTime: createdAt, createTime: createdAt,
unknown: container.State == runtimeapi.ContainerState_CONTAINER_UNKNOWN,
} }
key := evictUnit{ key := evictUnit{
uid: labeledInfo.PodUID, uid: labeledInfo.PodUID,
......
...@@ -505,10 +505,19 @@ func (m *kubeGenericRuntimeManager) computePodActions(pod *v1.Pod, podStatus *ku ...@@ -505,10 +505,19 @@ func (m *kubeGenericRuntimeManager) computePodActions(pod *v1.Pod, podStatus *ku
initLastStatus, next, done := findNextInitContainerToRun(pod, podStatus) initLastStatus, next, done := findNextInitContainerToRun(pod, podStatus)
if !done { if !done {
if next != nil { if next != nil {
initFailed := initLastStatus != nil && isContainerFailed(initLastStatus) initFailed := initLastStatus != nil && isInitContainerFailed(initLastStatus)
if initFailed && !shouldRestartOnFailure(pod) { if initFailed && !shouldRestartOnFailure(pod) {
changes.KillPod = true changes.KillPod = true
} else { } else {
// Always try to stop containers in unknown state first.
if initLastStatus != nil && initLastStatus.State == kubecontainer.ContainerStateUnknown {
changes.ContainersToKill[initLastStatus.ID] = containerToKillInfo{
name: next.Name,
container: next,
message: fmt.Sprintf("Init container is in %q state, try killing it before restart",
initLastStatus.State),
}
}
changes.NextInitContainerToStart = next changes.NextInitContainerToStart = next
} }
} }
...@@ -540,6 +549,17 @@ func (m *kubeGenericRuntimeManager) computePodActions(pod *v1.Pod, podStatus *ku ...@@ -540,6 +549,17 @@ func (m *kubeGenericRuntimeManager) computePodActions(pod *v1.Pod, podStatus *ku
message := fmt.Sprintf("Container %+v is dead, but RestartPolicy says that we should restart it.", container) message := fmt.Sprintf("Container %+v is dead, but RestartPolicy says that we should restart it.", container)
klog.V(3).Infof(message) klog.V(3).Infof(message)
changes.ContainersToStart = append(changes.ContainersToStart, idx) changes.ContainersToStart = append(changes.ContainersToStart, idx)
if containerStatus != nil && containerStatus.State == kubecontainer.ContainerStateUnknown {
// If container is in unknown state, we don't know whether it
// is actually running or not, always try killing it before
// restart to avoid having 2 running instances of the same container.
changes.ContainersToKill[containerStatus.ID] = containerToKillInfo{
name: containerStatus.Name,
container: &pod.Spec.Containers[idx],
message: fmt.Sprintf("Container is in %q state, try killing it before restart",
containerStatus.State),
}
}
} }
continue continue
} }
......
...@@ -597,9 +597,10 @@ func TestPruneInitContainers(t *testing.T) { ...@@ -597,9 +597,10 @@ func TestPruneInitContainers(t *testing.T) {
} }
templates := []containerTemplate{ templates := []containerTemplate{
{pod: pod, container: &init1, attempt: 3, createdAt: 3, state: runtimeapi.ContainerState_CONTAINER_EXITED},
{pod: pod, container: &init1, attempt: 2, createdAt: 2, state: runtimeapi.ContainerState_CONTAINER_EXITED}, {pod: pod, container: &init1, attempt: 2, createdAt: 2, state: runtimeapi.ContainerState_CONTAINER_EXITED},
{pod: pod, container: &init1, attempt: 1, createdAt: 1, state: runtimeapi.ContainerState_CONTAINER_EXITED},
{pod: pod, container: &init2, attempt: 1, createdAt: 1, state: runtimeapi.ContainerState_CONTAINER_EXITED}, {pod: pod, container: &init2, attempt: 1, createdAt: 1, state: runtimeapi.ContainerState_CONTAINER_EXITED},
{pod: pod, container: &init1, attempt: 1, createdAt: 1, state: runtimeapi.ContainerState_CONTAINER_UNKNOWN},
{pod: pod, container: &init2, attempt: 0, createdAt: 0, state: runtimeapi.ContainerState_CONTAINER_EXITED}, {pod: pod, container: &init2, attempt: 0, createdAt: 0, state: runtimeapi.ContainerState_CONTAINER_EXITED},
{pod: pod, container: &init1, attempt: 0, createdAt: 0, state: runtimeapi.ContainerState_CONTAINER_EXITED}, {pod: pod, container: &init1, attempt: 0, createdAt: 0, state: runtimeapi.ContainerState_CONTAINER_EXITED},
} }
...@@ -928,6 +929,17 @@ func TestComputePodActions(t *testing.T) { ...@@ -928,6 +929,17 @@ func TestComputePodActions(t *testing.T) {
ContainersToKill: map[kubecontainer.ContainerID]containerToKillInfo{}, ContainersToKill: map[kubecontainer.ContainerID]containerToKillInfo{},
}, },
}, },
"Kill and recreate the container if the container is in unknown state": {
mutatePodFn: func(pod *v1.Pod) { pod.Spec.RestartPolicy = v1.RestartPolicyNever },
mutateStatusFn: func(status *kubecontainer.PodStatus) {
status.ContainerStatuses[1].State = kubecontainer.ContainerStateUnknown
},
actions: podActions{
SandboxID: baseStatus.SandboxStatuses[0].Id,
ContainersToKill: getKillMap(basePod, baseStatus, []int{1}),
ContainersToStart: []int{1},
},
},
} { } {
pod, status := makeBasePodAndStatus() pod, status := makeBasePodAndStatus()
if test.mutatePodFn != nil { if test.mutatePodFn != nil {
...@@ -952,6 +964,17 @@ func getKillMap(pod *v1.Pod, status *kubecontainer.PodStatus, cIndexes []int) ma ...@@ -952,6 +964,17 @@ func getKillMap(pod *v1.Pod, status *kubecontainer.PodStatus, cIndexes []int) ma
return m return m
} }
func getKillMapWithInitContainers(pod *v1.Pod, status *kubecontainer.PodStatus, cIndexes []int) map[kubecontainer.ContainerID]containerToKillInfo {
m := map[kubecontainer.ContainerID]containerToKillInfo{}
for _, i := range cIndexes {
m[status.ContainerStatuses[i].ID] = containerToKillInfo{
container: &pod.Spec.InitContainers[i],
name: pod.Spec.InitContainers[i].Name,
}
}
return m
}
func verifyActions(t *testing.T, expected, actual *podActions, desc string) { func verifyActions(t *testing.T, expected, actual *podActions, desc string) {
if actual.ContainersToKill != nil { if actual.ContainersToKill != nil {
// Clear the message field since we don't need to verify the message. // Clear the message field since we don't need to verify the message.
...@@ -985,7 +1008,7 @@ func TestComputePodActionsWithInitContainers(t *testing.T) { ...@@ -985,7 +1008,7 @@ func TestComputePodActionsWithInitContainers(t *testing.T) {
actions: podActions{ actions: podActions{
SandboxID: baseStatus.SandboxStatuses[0].Id, SandboxID: baseStatus.SandboxStatuses[0].Id,
ContainersToStart: []int{0, 1, 2}, ContainersToStart: []int{0, 1, 2},
ContainersToKill: getKillMap(basePod, baseStatus, []int{}), ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}),
}, },
}, },
"initialization in progress; do nothing": { "initialization in progress; do nothing": {
...@@ -1007,7 +1030,7 @@ func TestComputePodActionsWithInitContainers(t *testing.T) { ...@@ -1007,7 +1030,7 @@ func TestComputePodActionsWithInitContainers(t *testing.T) {
Attempt: uint32(1), Attempt: uint32(1),
NextInitContainerToStart: &basePod.Spec.InitContainers[0], NextInitContainerToStart: &basePod.Spec.InitContainers[0],
ContainersToStart: []int{}, ContainersToStart: []int{},
ContainersToKill: getKillMap(basePod, baseStatus, []int{}), ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}),
}, },
}, },
"initialization failed; restart the last init container if RestartPolicy == Always": { "initialization failed; restart the last init container if RestartPolicy == Always": {
...@@ -1019,7 +1042,7 @@ func TestComputePodActionsWithInitContainers(t *testing.T) { ...@@ -1019,7 +1042,7 @@ func TestComputePodActionsWithInitContainers(t *testing.T) {
SandboxID: baseStatus.SandboxStatuses[0].Id, SandboxID: baseStatus.SandboxStatuses[0].Id,
NextInitContainerToStart: &basePod.Spec.InitContainers[2], NextInitContainerToStart: &basePod.Spec.InitContainers[2],
ContainersToStart: []int{}, ContainersToStart: []int{},
ContainersToKill: getKillMap(basePod, baseStatus, []int{}), ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}),
}, },
}, },
"initialization failed; restart the last init container if RestartPolicy == OnFailure": { "initialization failed; restart the last init container if RestartPolicy == OnFailure": {
...@@ -1031,7 +1054,7 @@ func TestComputePodActionsWithInitContainers(t *testing.T) { ...@@ -1031,7 +1054,7 @@ func TestComputePodActionsWithInitContainers(t *testing.T) {
SandboxID: baseStatus.SandboxStatuses[0].Id, SandboxID: baseStatus.SandboxStatuses[0].Id,
NextInitContainerToStart: &basePod.Spec.InitContainers[2], NextInitContainerToStart: &basePod.Spec.InitContainers[2],
ContainersToStart: []int{}, ContainersToStart: []int{},
ContainersToKill: getKillMap(basePod, baseStatus, []int{}), ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}),
}, },
}, },
"initialization failed; kill pod if RestartPolicy == Never": { "initialization failed; kill pod if RestartPolicy == Never": {
...@@ -1043,7 +1066,43 @@ func TestComputePodActionsWithInitContainers(t *testing.T) { ...@@ -1043,7 +1066,43 @@ func TestComputePodActionsWithInitContainers(t *testing.T) {
KillPod: true, KillPod: true,
SandboxID: baseStatus.SandboxStatuses[0].Id, SandboxID: baseStatus.SandboxStatuses[0].Id,
ContainersToStart: []int{}, ContainersToStart: []int{},
ContainersToKill: getKillMap(basePod, baseStatus, []int{}), ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}),
},
},
"init container state unknown; kill and recreate the last init container if RestartPolicy == Always": {
mutatePodFn: func(pod *v1.Pod) { pod.Spec.RestartPolicy = v1.RestartPolicyAlways },
mutateStatusFn: func(status *kubecontainer.PodStatus) {
status.ContainerStatuses[2].State = kubecontainer.ContainerStateUnknown
},
actions: podActions{
SandboxID: baseStatus.SandboxStatuses[0].Id,
NextInitContainerToStart: &basePod.Spec.InitContainers[2],
ContainersToStart: []int{},
ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{2}),
},
},
"init container state unknown; kill and recreate the last init container if RestartPolicy == OnFailure": {
mutatePodFn: func(pod *v1.Pod) { pod.Spec.RestartPolicy = v1.RestartPolicyOnFailure },
mutateStatusFn: func(status *kubecontainer.PodStatus) {
status.ContainerStatuses[2].State = kubecontainer.ContainerStateUnknown
},
actions: podActions{
SandboxID: baseStatus.SandboxStatuses[0].Id,
NextInitContainerToStart: &basePod.Spec.InitContainers[2],
ContainersToStart: []int{},
ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{2}),
},
},
"init container state unknown; kill pod if RestartPolicy == Never": {
mutatePodFn: func(pod *v1.Pod) { pod.Spec.RestartPolicy = v1.RestartPolicyNever },
mutateStatusFn: func(status *kubecontainer.PodStatus) {
status.ContainerStatuses[2].State = kubecontainer.ContainerStateUnknown
},
actions: podActions{
KillPod: true,
SandboxID: baseStatus.SandboxStatuses[0].Id,
ContainersToStart: []int{},
ContainersToKill: getKillMapWithInitContainers(basePod, baseStatus, []int{}),
}, },
}, },
} { } {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment