1
2
3
4
19
20 package e2enode
21
22 import (
23 "context"
24 "fmt"
25 "os/exec"
26 "time"
27
28 v1 "k8s.io/api/core/v1"
29 "k8s.io/apimachinery/pkg/api/resource"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/watch"
32 "k8s.io/client-go/tools/cache"
33 watchtools "k8s.io/client-go/tools/watch"
34 "k8s.io/kubernetes/test/e2e/framework"
35 e2enode "k8s.io/kubernetes/test/e2e/framework/node"
36 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
37 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
38 testutils "k8s.io/kubernetes/test/utils"
39 imageutils "k8s.io/kubernetes/test/utils/image"
40 admissionapi "k8s.io/pod-security-admission/api"
41
42 "github.com/onsi/ginkgo/v2"
43 "github.com/onsi/gomega"
44 "k8s.io/apimachinery/pkg/util/uuid"
45 )
46
47 type podCondition func(pod *v1.Pod) (bool, error)
48
49
50
51 func waitForPodsCondition(ctx context.Context, f *framework.Framework, podCount int, timeout time.Duration, condition podCondition) (runningPods []*v1.Pod) {
52 for start := time.Now(); time.Since(start) < timeout; time.Sleep(10 * time.Second) {
53 podList, err := e2epod.NewPodClient(f).List(ctx, metav1.ListOptions{})
54 if err != nil {
55 framework.Logf("Failed to list pods on node: %v", err)
56 continue
57 }
58
59 runningPods = []*v1.Pod{}
60 for i := range podList.Items {
61 pod := podList.Items[i]
62 if r, err := condition(&pod); err != nil || !r {
63 continue
64 }
65 runningPods = append(runningPods, &pod)
66 }
67 framework.Logf("Running pod count %d", len(runningPods))
68 if len(runningPods) >= podCount {
69 break
70 }
71 }
72 return runningPods
73 }
74
75 var _ = SIGDescribe("Restart", framework.WithSerial(), framework.WithSlow(), framework.WithDisruptive(), func() {
76 const (
77
78
79
80
81
82 podCount = 100
83 podCreationInterval = 100 * time.Millisecond
84 recoverTimeout = 5 * time.Minute
85 startTimeout = 3 * time.Minute
86
87
88 minPods = 50
89 restartCount = 6
90 )
91
92 f := framework.NewDefaultFramework("restart-test")
93 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
94 ginkgo.Context("Container Runtime", func() {
95 ginkgo.Context("Network", func() {
96 ginkgo.It("should recover from ip leak", func(ctx context.Context) {
97 pods := newTestPods(podCount, false, imageutils.GetPauseImageName(), "restart-container-runtime-test")
98 ginkgo.By(fmt.Sprintf("Trying to create %d pods on node", len(pods)))
99 createBatchPodWithRateControl(ctx, f, pods, podCreationInterval)
100 ginkgo.DeferCleanup(deletePodsSync, f, pods)
101
102
103
104 runningPods := waitForPodsCondition(ctx, f, podCount, startTimeout, testutils.PodRunningReadyOrSucceeded)
105 if len(runningPods) < minPods {
106 framework.Failf("Failed to start %d pods, cannot test that restarting container runtime doesn't leak IPs", minPods)
107 }
108
109 for i := 0; i < restartCount; i++ {
110 ginkgo.By(fmt.Sprintf("Killing container runtime iteration %d", i))
111
112 var pid int
113 gomega.Eventually(ctx, func() error {
114 runtimePids, err := getPidsForProcess(framework.TestContext.ContainerRuntimeProcessName, framework.TestContext.ContainerRuntimePidFile)
115 if err != nil {
116 return err
117 }
118 if len(runtimePids) != 1 {
119 return fmt.Errorf("unexpected container runtime pid list: %+v", runtimePids)
120 }
121
122 pid = runtimePids[0]
123 if _, err := exec.Command("sudo", "ps", "-p", fmt.Sprintf("%d", pid)).CombinedOutput(); err != nil {
124 return err
125 }
126 return nil
127 }, 1*time.Minute, 2*time.Second).Should(gomega.BeNil())
128 if stdout, err := exec.Command("sudo", "kill", "-SIGKILL", fmt.Sprintf("%d", pid)).CombinedOutput(); err != nil {
129 framework.Failf("Failed to kill container runtime (pid=%d): %v, stdout: %q", pid, err, string(stdout))
130 }
131
132 time.Sleep(20 * time.Second)
133 }
134
135 ginkgo.By("Checking currently Running/Ready pods")
136 postRestartRunningPods := waitForPodsCondition(ctx, f, len(runningPods), recoverTimeout, testutils.PodRunningReadyOrSucceeded)
137 if len(postRestartRunningPods) == 0 {
138 framework.Failf("Failed to start *any* pods after container runtime restart, this might indicate an IP leak")
139 }
140 ginkgo.By("Confirm no containers have terminated")
141 for _, pod := range postRestartRunningPods {
142 if c := testutils.TerminatedContainers(pod); len(c) != 0 {
143 framework.Failf("Pod %q has failed containers %+v after container runtime restart, this might indicate an IP leak", pod.Name, c)
144 }
145 }
146 ginkgo.By(fmt.Sprintf("Container runtime restart test passed with %d pods", len(postRestartRunningPods)))
147 })
148 })
149 })
150 ginkgo.Context("Kubelet", func() {
151 ginkgo.It("should correctly account for terminated pods after restart", func(ctx context.Context) {
152 node := getLocalNode(ctx, f)
153 cpus := node.Status.Allocatable[v1.ResourceCPU]
154 numCpus := int((&cpus).Value())
155 if numCpus < 1 {
156 e2eskipper.Skipf("insufficient CPU available for kubelet restart test")
157 }
158 if numCpus > 18 {
159
160 e2eskipper.Skipf("test will return false positives on a machine with >18 cores")
161 }
162
163
164
165
166 podCountRestartNever := numCpus
167 ginkgo.By(fmt.Sprintf("creating %d RestartNever pods on node", podCountRestartNever))
168 restartNeverPods := newTestPods(podCountRestartNever, false, imageutils.GetE2EImage(imageutils.BusyBox), "restart-kubelet-test")
169 for _, pod := range restartNeverPods {
170 pod.Spec.RestartPolicy = "Never"
171 pod.Spec.Containers[0].Command = []string{"echo", "hi"}
172 pod.Spec.Containers[0].Resources.Limits = v1.ResourceList{
173 v1.ResourceCPU: resource.MustParse("950m"),
174 }
175 }
176 createBatchPodWithRateControl(ctx, f, restartNeverPods, podCreationInterval)
177 ginkgo.DeferCleanup(deletePodsSync, f, restartNeverPods)
178 completedPods := waitForPodsCondition(ctx, f, podCountRestartNever, startTimeout, testutils.PodSucceeded)
179
180 if len(completedPods) < podCountRestartNever {
181 framework.Failf("Failed to run sufficient restartNever pods, got %d but expected %d", len(completedPods), podCountRestartNever)
182 }
183
184 podCountRestartAlways := (numCpus / 2) + 1
185 ginkgo.By(fmt.Sprintf("creating %d RestartAlways pods on node", podCountRestartAlways))
186 restartAlwaysPods := newTestPods(podCountRestartAlways, false, imageutils.GetPauseImageName(), "restart-kubelet-test")
187 for _, pod := range restartAlwaysPods {
188 pod.Spec.Containers[0].Resources.Limits = v1.ResourceList{
189 v1.ResourceCPU: resource.MustParse("1"),
190 }
191 }
192 createBatchPodWithRateControl(ctx, f, restartAlwaysPods, podCreationInterval)
193 ginkgo.DeferCleanup(deletePodsSync, f, restartAlwaysPods)
194
195 numAllPods := podCountRestartNever + podCountRestartAlways
196 allPods := waitForPodsCondition(ctx, f, numAllPods, startTimeout, testutils.PodRunningReadyOrSucceeded)
197 if len(allPods) < numAllPods {
198 framework.Failf("Failed to run sufficient restartAlways pods, got %d but expected %d", len(allPods), numAllPods)
199 }
200
201 ginkgo.By("killing and restarting kubelet")
202
203 startKubelet := stopKubelet()
204 startKubelet()
205
206
207
208
209
210 ginkgo.By("verifying restartNever pods succeed and restartAlways pods stay running")
211 for start := time.Now(); time.Since(start) < startTimeout && ctx.Err() == nil; time.Sleep(10 * time.Second) {
212 postRestartRunningPods := waitForPodsCondition(ctx, f, numAllPods, recoverTimeout, testutils.PodRunningReadyOrSucceeded)
213 if len(postRestartRunningPods) < numAllPods {
214 framework.Failf("less pods are running after node restart, got %d but expected %d", len(postRestartRunningPods), numAllPods)
215 }
216 }
217 })
218
219 ginkgo.It("should delete pods which are marked as terminal and have a deletion timestamp set after restart", func(ctx context.Context) {
220 podName := "terminal-restart-pod" + string(uuid.NewUUID())
221 gracePeriod := int64(30)
222 podSpec := e2epod.MustMixinRestrictedPodSecurity(&v1.Pod{
223 ObjectMeta: metav1.ObjectMeta{
224 Name: podName,
225 },
226 Spec: v1.PodSpec{
227 TerminationGracePeriodSeconds: &gracePeriod,
228 RestartPolicy: v1.RestartPolicyNever,
229 Containers: []v1.Container{
230 {
231 Name: podName,
232 Image: imageutils.GetE2EImage(imageutils.BusyBox),
233 Command: []string{"sh", "-c"},
234 Args: []string{`
235 sleep 9999999 &
236 PID=$!
237
238 _term () {
239 kill $PID
240 echo "Caught SIGTERM!"
241 }
242
243 trap _term SIGTERM
244 wait $PID
245 trap - TERM
246
247 # Wait for the long running sleep to exit
248 wait $PID
249
250 exit 0
251 `,
252 },
253 },
254 },
255 },
256 })
257 ginkgo.By(fmt.Sprintf("Creating a pod (%v/%v) with restart policy: %v", f.Namespace.Name, podName, podSpec.Spec.RestartPolicy))
258 pod := e2epod.NewPodClient(f).Create(ctx, podSpec)
259
260 ginkgo.By(fmt.Sprintf("Waiting for the pod (%v/%v) to be running", f.Namespace.Name, pod.Name))
261 err := e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)
262 framework.ExpectNoError(err, "Failed to await for the pod to be running: (%v/%v)", f.Namespace.Name, pod.Name)
263
264 w := &cache.ListWatch{
265 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
266 return f.ClientSet.CoreV1().Pods(f.Namespace.Name).Watch(ctx, options)
267 },
268 }
269
270 podsList, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).List(ctx, metav1.ListOptions{})
271 framework.ExpectNoError(err, "Failed to list pods in namespace: %s", f.Namespace.Name)
272
273 ginkgo.By(fmt.Sprintf("Deleting the pod (%v/%v) to set a deletion timestamp", pod.Namespace, pod.Name))
274 time.Sleep(time.Second)
275 err = e2epod.NewPodClient(f).Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod})
276 framework.ExpectNoError(err, "Failed to delete the pod: %q", pod.Name)
277
278 ctxUntil, cancel := context.WithTimeout(ctx, f.Timeouts.PodStart)
279 defer cancel()
280
281 ginkgo.By(fmt.Sprintf("Started watch for pod (%v/%v) to enter succeeded phase", pod.Namespace, pod.Name))
282 _, err = watchtools.Until(ctxUntil, podsList.ResourceVersion, w, func(event watch.Event) (bool, error) {
283 if pod, ok := event.Object.(*v1.Pod); ok {
284 found := pod.ObjectMeta.Name == podName &&
285 pod.ObjectMeta.Namespace == f.Namespace.Name &&
286 pod.Status.Phase == v1.PodSucceeded
287 if !found {
288 ginkgo.By(fmt.Sprintf("Observed Pod (%s/%s) in phase %v", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name, pod.Status.Phase))
289 return false, nil
290 }
291 ginkgo.By(fmt.Sprintf("Found Pod (%s/%s) in phase %v", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name, pod.Status.Phase))
292 return found, nil
293 }
294 ginkgo.By(fmt.Sprintf("Observed event: %+v", event.Object))
295 return false, nil
296 })
297 ginkgo.By("Ended watch for pod entering succeeded phase")
298 framework.ExpectNoError(err, "failed to see event that pod (%s/%s) enter succeeded phase: %v", pod.Namespace, pod.Name, err)
299
300
301
302 ginkgo.By("Stopping the kubelet")
303 startKubelet := stopKubelet()
304
305 gomega.Eventually(ctx, func() bool {
306 return kubeletHealthCheck(kubeletHealthCheckURL)
307 }, f.Timeouts.PodStart, f.Timeouts.Poll).Should(gomega.BeFalse())
308
309 ginkgo.By("Starting the kubelet")
310 startKubelet()
311
312
313 gomega.Eventually(ctx, func() bool {
314 return kubeletHealthCheck(kubeletHealthCheckURL)
315 }, f.Timeouts.PodStart, f.Timeouts.Poll).Should(gomega.BeTrue())
316
317
318 gomega.Eventually(ctx, func(ctx context.Context) bool {
319 nodes, err := e2enode.TotalReady(ctx, f.ClientSet)
320 framework.ExpectNoError(err)
321 return nodes == 1
322 }, time.Minute, f.Timeouts.Poll).Should(gomega.BeTrue())
323
324 ginkgo.By(fmt.Sprintf("After the kubelet is restarted, verify the pod (%s/%s) is deleted by kubelet", pod.Namespace, pod.Name))
325 gomega.Eventually(ctx, func(ctx context.Context) error {
326 return checkMirrorPodDisappear(ctx, f.ClientSet, pod.Name, pod.Namespace)
327 }, f.Timeouts.PodDelete, f.Timeouts.Poll).Should(gomega.BeNil())
328 })
329
330 ginkgo.It("should force-delete non-admissible pods created and deleted during kubelet restart", func(ctx context.Context) {
331 podName := "rejected-deleted-pod" + string(uuid.NewUUID())
332 gracePeriod := int64(30)
333 nodeName := getNodeName(ctx, f)
334 podSpec := e2epod.MustMixinRestrictedPodSecurity(&v1.Pod{
335 ObjectMeta: metav1.ObjectMeta{
336 Name: podName,
337 Namespace: f.Namespace.Name,
338 },
339 Spec: v1.PodSpec{
340 NodeName: nodeName,
341 NodeSelector: map[string]string{
342 "this-label": "does-not-exist-on-any-nodes",
343 },
344 TerminationGracePeriodSeconds: &gracePeriod,
345 RestartPolicy: v1.RestartPolicyNever,
346 Containers: []v1.Container{
347 {
348 Name: podName,
349 Image: imageutils.GetPauseImageName(),
350 },
351 },
352 },
353 })
354 ginkgo.By("Stopping the kubelet")
355 startKubelet := stopKubelet()
356
357
358 gomega.Eventually(ctx, func() bool {
359 return kubeletHealthCheck(kubeletHealthCheckURL)
360 }, f.Timeouts.PodStart, f.Timeouts.Poll).Should(gomega.BeFalse())
361
362
363
364 ginkgo.By(fmt.Sprintf("Creating a pod (%v/%v)", f.Namespace.Name, podName))
365 pod := e2epod.NewPodClient(f).Create(ctx, podSpec)
366
367 ginkgo.By(fmt.Sprintf("Deleting the pod (%v/%v) to set a deletion timestamp", pod.Namespace, pod.Name))
368 err := e2epod.NewPodClient(f).Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod})
369 framework.ExpectNoError(err, "Failed to delete the pod: %q", pod.Name)
370
371
372 ginkgo.By("Starting the kubelet")
373 startKubelet()
374
375
376 gomega.Eventually(ctx, func() bool {
377 return kubeletHealthCheck(kubeletHealthCheckURL)
378 }, f.Timeouts.PodStart, f.Timeouts.Poll).Should(gomega.BeTrue())
379
380
381 gomega.Eventually(ctx, func(ctx context.Context) bool {
382 nodes, err := e2enode.TotalReady(ctx, f.ClientSet)
383 framework.ExpectNoError(err)
384 return nodes == 1
385 }, time.Minute, f.Timeouts.Poll).Should(gomega.BeTrue())
386
387 ginkgo.By(fmt.Sprintf("After the kubelet is restarted, verify the pod (%v/%v) is deleted by kubelet", pod.Namespace, pod.Name))
388 gomega.Eventually(ctx, func(ctx context.Context) error {
389 return checkMirrorPodDisappear(ctx, f.ClientSet, pod.Name, pod.Namespace)
390 }, f.Timeouts.PodDelete, f.Timeouts.Poll).Should(gomega.BeNil())
391 })
392
393 ginkgo.It("should force-delete non-admissible pods that was admitted and running before kubelet restart", func(ctx context.Context) {
394 nodeLabelKey := "custom-label-key-required"
395 nodeLabelValueRequired := "custom-label-value-required-for-admission"
396 podName := "rejected-deleted-run" + string(uuid.NewUUID())
397 gracePeriod := int64(30)
398 nodeName := getNodeName(ctx, f)
399 pod := e2epod.MustMixinRestrictedPodSecurity(&v1.Pod{
400 ObjectMeta: metav1.ObjectMeta{
401 Name: podName,
402 Namespace: f.Namespace.Name,
403 },
404 Spec: v1.PodSpec{
405 NodeSelector: map[string]string{
406 nodeLabelKey: nodeLabelValueRequired,
407 },
408 NodeName: nodeName,
409 TerminationGracePeriodSeconds: &gracePeriod,
410 RestartPolicy: v1.RestartPolicyNever,
411 Containers: []v1.Container{
412 {
413 Name: podName,
414 Image: imageutils.GetPauseImageName(),
415 },
416 },
417 },
418 })
419
420 ginkgo.By(fmt.Sprintf("Adding node label for node (%v) to allow admission of pod (%v/%v)", nodeName, f.Namespace.Name, podName))
421 e2enode.AddOrUpdateLabelOnNode(f.ClientSet, nodeName, nodeLabelKey, nodeLabelValueRequired)
422 ginkgo.DeferCleanup(func() { e2enode.RemoveLabelOffNode(f.ClientSet, nodeName, nodeLabelKey) })
423
424
425 ginkgo.By(fmt.Sprintf("Creating a pod (%v/%v)", f.Namespace.Name, podName))
426 pod = e2epod.NewPodClient(f).Create(ctx, pod)
427
428 ginkgo.By(fmt.Sprintf("Waiting for the pod (%v/%v) to be running", f.Namespace.Name, pod.Name))
429 err := e2epod.WaitForPodNameRunningInNamespace(ctx, f.ClientSet, pod.Name, f.Namespace.Name)
430 framework.ExpectNoError(err, "Failed to await for the pod to be running: (%v/%v)", f.Namespace.Name, pod.Name)
431
432 ginkgo.By("Stopping the kubelet")
433 startKubelet := stopKubelet()
434
435
436 gomega.Eventually(ctx, func() bool {
437 return kubeletHealthCheck(kubeletHealthCheckURL)
438 }, f.Timeouts.PodStart, f.Timeouts.Poll).Should(gomega.BeFalse())
439
440 ginkgo.By(fmt.Sprintf("Deleting the pod (%v/%v) to set a deletion timestamp", pod.Namespace, pod.Name))
441 err = e2epod.NewPodClient(f).Delete(ctx, pod.Name, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod})
442 framework.ExpectNoError(err, "Failed to delete the pod: %q", pod.Name)
443
444 ginkgo.By(fmt.Sprintf("Removing node label for node (%v) to ensure the pod (%v/%v) is rejected after kubelet restart", nodeName, f.Namespace.Name, podName))
445 e2enode.RemoveLabelOffNode(f.ClientSet, nodeName, nodeLabelKey)
446
447
448 ginkgo.By("Starting the kubelet")
449 startKubelet()
450
451
452 gomega.Eventually(ctx, func() bool {
453 return kubeletHealthCheck(kubeletHealthCheckURL)
454 }, f.Timeouts.PodStart, f.Timeouts.Poll).Should(gomega.BeTrue())
455
456
457 gomega.Eventually(ctx, func(ctx context.Context) bool {
458 nodes, err := e2enode.TotalReady(ctx, f.ClientSet)
459 framework.ExpectNoError(err)
460 return nodes == 1
461 }, time.Minute, f.Timeouts.Poll).Should(gomega.BeTrue())
462
463 ginkgo.By(fmt.Sprintf("Once Kubelet is restarted, verify the pod (%v/%v) is deleted by kubelet", pod.Namespace, pod.Name))
464 gomega.Eventually(ctx, func(ctx context.Context) error {
465 return checkMirrorPodDisappear(ctx, f.ClientSet, pod.Name, pod.Namespace)
466 }, f.Timeouts.PodDelete, f.Timeouts.Poll).Should(gomega.BeNil())
467 })
468 })
469
470 })
471
View as plain text