Skip to content

Commit cf2db49

Browse files
authored
applied native sidecar fix (#1914)
1 parent 05a7a20 commit cf2db49

File tree

2 files changed

+309
-1
lines changed

2 files changed

+309
-1
lines changed

pkg/k8sutil/pod.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,26 @@ const (
2525
PodStatusReasonInitCrashLoopBackOff PodStatusReason = "Init:CrashLoopBackOff"
2626
)
2727

28+
// isNativeSidecar checks if an init container is a native sidecar.
29+
// Native sidecars are init containers with restartPolicy: Always (Kubernetes 1.28+).
30+
// They run continuously alongside main containers, unlike traditional init containers
31+
// which must complete before main containers start.
32+
func isNativeSidecar(pod *corev1.Pod, initContainerIndex int) bool {
33+
// Bounds check - ensure the index is valid
34+
if initContainerIndex >= len(pod.Spec.InitContainers) {
35+
return false
36+
}
37+
38+
initContainer := pod.Spec.InitContainers[initContainerIndex]
39+
40+
// Check if RestartPolicy is set to Always
41+
if initContainer.RestartPolicy != nil && *initContainer.RestartPolicy == corev1.ContainerRestartPolicyAlways {
42+
return true
43+
}
44+
45+
return false
46+
}
47+
2848
// reference: https://github.com/kubernetes/kubernetes/blob/e8fcd0de98d50f4019561a6b7a0287f5c059267a/pkg/printers/internalversion/printers.go#L741
2949
func GetPodStatusReason(pod *corev1.Pod) (string, string) {
3050
reason := string(pod.Status.Phase)
@@ -55,6 +75,11 @@ func GetPodStatusReason(pod *corev1.Pod) (string, string) {
5575
case container.State.Waiting != nil && len(container.State.Waiting.Reason) > 0 && container.State.Waiting.Reason != "PodInitializing":
5676
reason = "Init:" + container.State.Waiting.Reason
5777
initializing = true
78+
case isNativeSidecar(pod, i) && container.State.Running != nil:
79+
// Native sidecar running - this is expected, not stuck initializing.
80+
// Native sidecars (init containers with restartPolicy: Always) are designed
81+
// to run continuously, so a Running state means successful initialization.
82+
continue
5883
default:
5984
reason = fmt.Sprintf("Init:%d/%d", i, len(pod.Spec.InitContainers))
6085
initializing = true

pkg/k8sutil/pod_test.go

Lines changed: 284 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package k8sutil
22

33
import (
4+
"testing"
5+
46
"github.com/stretchr/testify/require"
57
corev1 "k8s.io/api/core/v1"
6-
"testing"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
79
)
810

911
func TestIsPodUnhealthy(t *testing.T) {
@@ -99,3 +101,284 @@ func TestIsPodUnhealthy(t *testing.T) {
99101
})
100102
}
101103
}
104+
105+
// TestGetPodStatusReason_HealthyNativeSidecar tests that a pod with a running native sidecar
106+
// is correctly reported as "Running" and not stuck initializing.
107+
func TestGetPodStatusReason_HealthyNativeSidecar(t *testing.T) {
108+
startedTrue := true
109+
restartPolicyAlways := corev1.ContainerRestartPolicyAlways
110+
111+
pod := &corev1.Pod{
112+
Spec: corev1.PodSpec{
113+
InitContainers: []corev1.Container{
114+
{
115+
Name: "istio-proxy",
116+
Image: "istio/proxyv2:1.20",
117+
RestartPolicy: &restartPolicyAlways, // Native sidecar!
118+
},
119+
},
120+
Containers: []corev1.Container{
121+
{
122+
Name: "app",
123+
Image: "myapp:latest",
124+
},
125+
},
126+
},
127+
Status: corev1.PodStatus{
128+
Phase: corev1.PodRunning,
129+
Conditions: []corev1.PodCondition{
130+
{
131+
Type: corev1.PodReady,
132+
Status: corev1.ConditionTrue,
133+
},
134+
},
135+
InitContainerStatuses: []corev1.ContainerStatus{
136+
{
137+
Name: "istio-proxy",
138+
Ready: true,
139+
Started: &startedTrue,
140+
State: corev1.ContainerState{
141+
Running: &corev1.ContainerStateRunning{
142+
StartedAt: metav1.Now(),
143+
},
144+
},
145+
},
146+
},
147+
ContainerStatuses: []corev1.ContainerStatus{
148+
{
149+
Name: "app",
150+
Ready: true,
151+
Started: &startedTrue,
152+
State: corev1.ContainerState{
153+
Running: &corev1.ContainerStateRunning{
154+
StartedAt: metav1.Now(),
155+
},
156+
},
157+
},
158+
},
159+
},
160+
}
161+
162+
reason, message := GetPodStatusReason(pod)
163+
164+
// Should report as Running, not Init:0/1
165+
if reason != "Running" {
166+
t.Errorf("Expected reason 'Running', got '%s'", reason)
167+
}
168+
169+
if message != "" {
170+
t.Errorf("Expected empty message, got '%s'", message)
171+
}
172+
}
173+
174+
// TestIsPodUnhealthy_HealthyNativeSidecar tests that a pod with a healthy running native sidecar
175+
// is not marked as unhealthy.
176+
func TestIsPodUnhealthy_HealthyNativeSidecar(t *testing.T) {
177+
startedTrue := true
178+
restartPolicyAlways := corev1.ContainerRestartPolicyAlways
179+
180+
pod := &corev1.Pod{
181+
Spec: corev1.PodSpec{
182+
InitContainers: []corev1.Container{
183+
{
184+
Name: "istio-proxy",
185+
RestartPolicy: &restartPolicyAlways,
186+
},
187+
},
188+
Containers: []corev1.Container{
189+
{
190+
Name: "app",
191+
},
192+
},
193+
},
194+
Status: corev1.PodStatus{
195+
Phase: corev1.PodRunning,
196+
Conditions: []corev1.PodCondition{
197+
{
198+
Type: corev1.PodReady,
199+
Status: corev1.ConditionTrue,
200+
},
201+
},
202+
InitContainerStatuses: []corev1.ContainerStatus{
203+
{
204+
Name: "istio-proxy",
205+
Ready: true,
206+
Started: &startedTrue,
207+
State: corev1.ContainerState{
208+
Running: &corev1.ContainerStateRunning{},
209+
},
210+
},
211+
},
212+
ContainerStatuses: []corev1.ContainerStatus{
213+
{
214+
Name: "app",
215+
Ready: true,
216+
Started: &startedTrue,
217+
State: corev1.ContainerState{
218+
Running: &corev1.ContainerStateRunning{},
219+
},
220+
},
221+
},
222+
},
223+
}
224+
225+
unhealthy := IsPodUnhealthy(pod)
226+
227+
if unhealthy {
228+
t.Error("Pod with healthy native sidecar should not be marked as unhealthy")
229+
}
230+
}
231+
232+
// TestGetPodStatusReason_TraditionalInitAndNativeSidecar tests that a pod with both
233+
// a completed traditional init container and a running native sidecar is reported as "Running".
234+
func TestGetPodStatusReason_TraditionalInitAndNativeSidecar(t *testing.T) {
235+
startedTrue := true
236+
restartPolicyAlways := corev1.ContainerRestartPolicyAlways
237+
238+
pod := &corev1.Pod{
239+
Spec: corev1.PodSpec{
240+
InitContainers: []corev1.Container{
241+
{
242+
Name: "init-setup",
243+
// No RestartPolicy = traditional init container
244+
},
245+
{
246+
Name: "istio-proxy",
247+
RestartPolicy: &restartPolicyAlways, // Native sidecar
248+
},
249+
},
250+
Containers: []corev1.Container{
251+
{
252+
Name: "app",
253+
},
254+
},
255+
},
256+
Status: corev1.PodStatus{
257+
Phase: corev1.PodRunning,
258+
Conditions: []corev1.PodCondition{
259+
{
260+
Type: corev1.PodReady,
261+
Status: corev1.ConditionTrue,
262+
},
263+
},
264+
InitContainerStatuses: []corev1.ContainerStatus{
265+
{
266+
Name: "init-setup",
267+
State: corev1.ContainerState{
268+
Terminated: &corev1.ContainerStateTerminated{
269+
ExitCode: 0,
270+
Reason: "Completed",
271+
},
272+
},
273+
},
274+
{
275+
Name: "istio-proxy",
276+
Ready: true,
277+
Started: &startedTrue,
278+
State: corev1.ContainerState{
279+
Running: &corev1.ContainerStateRunning{},
280+
},
281+
},
282+
},
283+
ContainerStatuses: []corev1.ContainerStatus{
284+
{
285+
Name: "app",
286+
Ready: true,
287+
Started: &startedTrue,
288+
State: corev1.ContainerState{
289+
Running: &corev1.ContainerStateRunning{},
290+
},
291+
},
292+
},
293+
},
294+
}
295+
296+
reason, _ := GetPodStatusReason(pod)
297+
298+
if reason != "Running" {
299+
t.Errorf("Expected reason 'Running', got '%s'", reason)
300+
}
301+
}
302+
303+
// TestGetPodStatusReason_NativeSidecarCrashLoopBackOff tests that a native sidecar
304+
// in CrashLoopBackOff is still correctly detected as an error.
305+
func TestGetPodStatusReason_NativeSidecarCrashLoopBackOff(t *testing.T) {
306+
restartPolicyAlways := corev1.ContainerRestartPolicyAlways
307+
308+
pod := &corev1.Pod{
309+
Spec: corev1.PodSpec{
310+
InitContainers: []corev1.Container{
311+
{
312+
Name: "istio-proxy",
313+
RestartPolicy: &restartPolicyAlways,
314+
},
315+
},
316+
Containers: []corev1.Container{
317+
{
318+
Name: "app",
319+
},
320+
},
321+
},
322+
Status: corev1.PodStatus{
323+
Phase: corev1.PodPending,
324+
InitContainerStatuses: []corev1.ContainerStatus{
325+
{
326+
Name: "istio-proxy",
327+
Ready: false,
328+
State: corev1.ContainerState{
329+
Waiting: &corev1.ContainerStateWaiting{
330+
Reason: "CrashLoopBackOff",
331+
Message: "Back-off 5m0s restarting failed container",
332+
},
333+
},
334+
},
335+
},
336+
},
337+
}
338+
339+
reason, _ := GetPodStatusReason(pod)
340+
341+
// Should still catch the error
342+
if reason != "Init:CrashLoopBackOff" {
343+
t.Errorf("Expected reason 'Init:CrashLoopBackOff', got '%s'", reason)
344+
}
345+
}
346+
347+
// TestGetPodStatusReason_TraditionalInitStuck tests that a traditional init container
348+
// that is stuck running is still correctly detected as stuck initializing.
349+
func TestGetPodStatusReason_TraditionalInitStuck(t *testing.T) {
350+
pod := &corev1.Pod{
351+
Spec: corev1.PodSpec{
352+
InitContainers: []corev1.Container{
353+
{
354+
Name: "init-setup",
355+
// No RestartPolicy = traditional init
356+
},
357+
},
358+
Containers: []corev1.Container{
359+
{
360+
Name: "app",
361+
},
362+
},
363+
},
364+
Status: corev1.PodStatus{
365+
Phase: corev1.PodPending,
366+
InitContainerStatuses: []corev1.ContainerStatus{
367+
{
368+
Name: "init-setup",
369+
Ready: false,
370+
State: corev1.ContainerState{
371+
Running: &corev1.ContainerStateRunning{},
372+
},
373+
},
374+
},
375+
},
376+
}
377+
378+
reason, _ := GetPodStatusReason(pod)
379+
380+
// Traditional init running = stuck
381+
if reason != "Init:0/1" {
382+
t.Errorf("Expected reason 'Init:0/1', got '%s'", reason)
383+
}
384+
}

0 commit comments

Comments
 (0)