Change:
Today the function () only account for the regular containers finish time, presumably assuming that init containers have completed before before. However, with the sidecar (restartable init) containers, sidecar containers will always finish later than regular containers. And those needs to be accounted for the calculation. /kind bug The sidecar finish time will be accounted when calculating the job's finish time. Sidecar container with prolonged termination logic on a job will result in incorrect finish time reporting. /sig apps /priority backlog KEP:
/assign
/triage accepted

Discussion:
"time" v1 "k8s.io/api/core/v1" <ins> "k8s.io/apimachinery/pkg/util/sets" utilfeature "k8s.io/apiserver/pkg/util/feature" </ins> "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" apipod "k8s.io/kubernetes/pkg/api/v1/pod" <ins> "k8s.io/kubernetes/pkg/features" </ins> "k8s.io/utils/clock" "k8s.io/utils/ptr" )

} func getFinishTimeFromContainers(p *v1.Pod) *time.Time { <del> var finishTime *time.Time for _, containerState := range p.Status.ContainerStatuses { if containerState.State.Terminated == nil { return nil </del> <ins> finishTime := latestFinishTime(nil, p.Status.ContainerStatuses, nil) if utilfeature.DefaultFeatureGate.Enabled(features.SidecarContainers) { // We need to check InitContainerStatuses here also, // because with the sidecar (restartable init) containers, // sidecar containers will always finish later than regular containers. names := sets.New[string]() for _, c := range p.Spec.InitContainers { if c.RestartPolicy != nil && *c.RestartPolicy == v1.ContainerRestartPolicyAlways { names.Insert(c.Name) } } finishTime = latestFinishTime(finishTime, p.Status.InitContainerStatuses, func(status v1.ContainerStatus) bool { return names.Has(status.Name) }) } return finishTime } func latestFinishTime(prevFinishTime *time.Time, cs []v1.ContainerStatus, check func(status v1.ContainerStatus) bool) *time.Time { var finishTime = prevFinishTime for _, containerState := range cs { if check != nil && !check(containerState) { continue </ins> } <del> if containerState.State.Terminated.FinishedAt.Time.IsZero() { </del> <ins> if containerState.State.Terminated == nil || containerState.State.Terminated.FinishedAt.Time.IsZero() { </ins> return nil } if finishTime == nil || finishTime.Before(containerState.State.Terminated.FinishedAt.Time) {

"github.com/google/go-cmp/cmp" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" <ins> utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" </ins> "k8s.io/klog/v2/ktesting" <ins> "k8s.io/kubernetes/pkg/features" </ins> clocktesting "k8s.io/utils/clock/testing" "k8s.io/utils/ptr" )

} func TestGetFinishedTime(t *testing.T) { <ins> featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SidecarContainers, true) </ins> defaultTestTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) defaultTestTimeMinus30s := defaultTestTime.Add(-30 * time.Second) <ins> containerRestartPolicyAlways := v1.ContainerRestartPolicyAlways </ins> testCases := map[string]struct { pod            v1.Pod wantFinishTime time.Time

}, wantFinishTime: defaultTestTime, }, <ins> // In this case, init container is stopped after the regular containers. // This is because with the sidecar (restartable init) containers, // sidecar containers will always finish later than regular containers. "Pod with sidecar container and all containers terminated": { pod: v1.Pod{ Spec: v1.PodSpec{ InitContainers: []v1.Container{ { Name:          "sidecar", RestartPolicy: &containerRestartPolicyAlways, }, }, }, Status: v1.PodStatus{ ContainerStatuses: []v1.ContainerStatus{ { State: v1.ContainerState{ Terminated: &v1.ContainerStateTerminated{FinishedAt: metav1.NewTime(defaultTestTime.Add(-1 * time.Second))}, }, }, }, InitContainerStatuses: []v1.ContainerStatus{ { Name: "sidecar", State: v1.ContainerState{ Terminated: &v1.ContainerStateTerminated{FinishedAt: metav1.NewTime(defaultTestTime)}, }, }, }, }, }, wantFinishTime: defaultTestTime, }, </ins> } for name, tc := range testCases {