1
16
17 package e2enode
18
19 import (
20 "context"
21 "time"
22
23 v1 "k8s.io/api/core/v1"
24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
26 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
27 kubelogs "k8s.io/kubernetes/pkg/kubelet/logs"
28 "k8s.io/kubernetes/test/e2e/framework"
29 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
30 admissionapi "k8s.io/pod-security-admission/api"
31
32 "github.com/onsi/ginkgo/v2"
33 "github.com/onsi/gomega"
34 )
35
36 const (
37 testContainerLogMaxFiles = 3
38 testContainerLogMaxSize = "40Ki"
39 testContainerLogMaxWorkers = 2
40 testContainerLogMonitorInterval = 3 * time.Second
41 rotationPollInterval = 5 * time.Second
42 rotationEventuallyTimeout = 3 * time.Minute
43 rotationConsistentlyTimeout = 2 * time.Minute
44 )
45
46 var _ = SIGDescribe("ContainerLogRotation", framework.WithSlow(), framework.WithSerial(), framework.WithDisruptive(), func() {
47 f := framework.NewDefaultFramework("container-log-rotation-test")
48 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
49 ginkgo.Context("when a container generates a lot of log", func() {
50 tempSetCurrentKubeletConfig(f, func(ctx context.Context, initialConfig *kubeletconfig.KubeletConfiguration) {
51 initialConfig.ContainerLogMaxFiles = testContainerLogMaxFiles
52 initialConfig.ContainerLogMaxSize = testContainerLogMaxSize
53 })
54
55 var logRotationPod *v1.Pod
56 ginkgo.BeforeEach(func(ctx context.Context) {
57 ginkgo.By("create log container")
58 pod := &v1.Pod{
59 ObjectMeta: metav1.ObjectMeta{
60 Name: "test-container-log-rotation",
61 },
62 Spec: v1.PodSpec{
63 RestartPolicy: v1.RestartPolicyNever,
64 Containers: []v1.Container{
65 {
66 Name: "log-container",
67 Image: busyboxImage,
68 Command: []string{
69 "sh",
70 "-c",
71
72 "while true; do echo hello world; sleep 0.001; done;",
73 },
74 },
75 },
76 },
77 }
78 logRotationPod = e2epod.NewPodClient(f).CreateSync(ctx, pod)
79 ginkgo.DeferCleanup(e2epod.NewPodClient(f).DeleteSync, logRotationPod.Name, metav1.DeleteOptions{}, time.Minute)
80 })
81
82 ginkgo.It("should be rotated and limited to a fixed amount of files", func(ctx context.Context) {
83
84 ginkgo.By("get container log path")
85 gomega.Expect(logRotationPod.Status.ContainerStatuses).To(gomega.HaveLen(1), "log rotation pod should have one container")
86 id := kubecontainer.ParseContainerID(logRotationPod.Status.ContainerStatuses[0].ContainerID).ID
87 r, _, err := getCRIClient()
88 framework.ExpectNoError(err, "should connect to CRI and obtain runtime service clients and image service client")
89 resp, err := r.ContainerStatus(context.Background(), id, false)
90 framework.ExpectNoError(err)
91 logPath := resp.GetStatus().GetLogPath()
92 ginkgo.By("wait for container log being rotated to max file limit")
93 gomega.Eventually(ctx, func() (int, error) {
94 logs, err := kubelogs.GetAllLogs(logPath)
95 if err != nil {
96 return 0, err
97 }
98 return len(logs), nil
99 }, rotationEventuallyTimeout, rotationPollInterval).Should(gomega.Equal(testContainerLogMaxFiles), "should eventually rotate to max file limit")
100 ginkgo.By("make sure container log number won't exceed max file limit")
101 gomega.Consistently(ctx, func() (int, error) {
102 logs, err := kubelogs.GetAllLogs(logPath)
103 if err != nil {
104 return 0, err
105 }
106 return len(logs), nil
107 }, rotationConsistentlyTimeout, rotationPollInterval).Should(gomega.BeNumerically("<=", testContainerLogMaxFiles), "should never exceed max file limit")
108 })
109 })
110 })
111
112 var _ = SIGDescribe("ContainerLogRotationWithMultipleWorkers", framework.WithSlow(), framework.WithSerial(), framework.WithDisruptive(), func() {
113 f := framework.NewDefaultFramework("container-log-rotation-test-multi-worker")
114 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
115 ginkgo.Context("when a container generates a lot of logs", func() {
116 tempSetCurrentKubeletConfig(f, func(ctx context.Context, initialConfig *kubeletconfig.KubeletConfiguration) {
117 initialConfig.ContainerLogMaxFiles = testContainerLogMaxFiles
118 initialConfig.ContainerLogMaxSize = testContainerLogMaxSize
119 initialConfig.ContainerLogMaxWorkers = testContainerLogMaxWorkers
120 initialConfig.ContainerLogMonitorInterval = metav1.Duration{Duration: testContainerLogMonitorInterval}
121 })
122
123 var logRotationPods []*v1.Pod
124 ginkgo.BeforeEach(func(ctx context.Context) {
125 ginkgo.By("create log container 1")
126 for _, name := range []string{"test-container-log-rotation", "test-container-log-rotation-1"} {
127 pod := &v1.Pod{
128 ObjectMeta: metav1.ObjectMeta{
129 Name: name,
130 },
131 Spec: v1.PodSpec{
132 RestartPolicy: v1.RestartPolicyNever,
133 Containers: []v1.Container{
134 {
135 Name: "log-container",
136 Image: busyboxImage,
137 Command: []string{
138 "sh",
139 "-c",
140
141 "while true; do echo hello world; sleep 0.001; done;",
142 },
143 },
144 },
145 },
146 }
147 logRotationPod := e2epod.NewPodClient(f).CreateSync(ctx, pod)
148 logRotationPods = append(logRotationPods, logRotationPod)
149 ginkgo.DeferCleanup(e2epod.NewPodClient(f).DeleteSync, logRotationPod.Name, metav1.DeleteOptions{}, time.Minute)
150 }
151 })
152
153 ginkgo.It("should be rotated and limited to a fixed amount of files", func(ctx context.Context) {
154 ginkgo.By("get container log path")
155 var logPaths []string
156 for _, pod := range logRotationPods {
157 gomega.Expect(pod.Status.ContainerStatuses).To(gomega.HaveLen(1), "log rotation pod should have one container")
158 id := kubecontainer.ParseContainerID(pod.Status.ContainerStatuses[0].ContainerID).ID
159 r, _, err := getCRIClient()
160 framework.ExpectNoError(err, "should connect to CRI and obtain runtime service clients and image service client")
161 resp, err := r.ContainerStatus(context.Background(), id, false)
162 framework.ExpectNoError(err)
163 logPaths = append(logPaths, resp.GetStatus().GetLogPath())
164 }
165
166 ginkgo.By("wait for container log being rotated to max file limit")
167 gomega.Eventually(ctx, func() (int, error) {
168 var logFiles []string
169 for _, logPath := range logPaths {
170 logs, err := kubelogs.GetAllLogs(logPath)
171 if err != nil {
172 return 0, err
173 }
174 logFiles = append(logFiles, logs...)
175 }
176 return len(logFiles), nil
177 }, rotationEventuallyTimeout, rotationPollInterval).Should(gomega.Equal(testContainerLogMaxFiles*2), "should eventually rotate to max file limit")
178 ginkgo.By("make sure container log number won't exceed max file limit")
179
180 gomega.Consistently(ctx, func() (int, error) {
181 var logFiles []string
182 for _, logPath := range logPaths {
183 logs, err := kubelogs.GetAllLogs(logPath)
184 if err != nil {
185 return 0, err
186 }
187 logFiles = append(logFiles, logs...)
188 }
189 return len(logFiles), nil
190 }, rotationConsistentlyTimeout, rotationPollInterval).Should(gomega.BeNumerically("<=", testContainerLogMaxFiles*2), "should never exceed max file limit")
191 })
192 })
193 })
194
View as plain text