Skip to content

Commit c429b6e

Browse files
committed
Report observed time when kubelet logs failed graceful termination
This should allow better insight into when to look for problems in intervals than the pod start time.
1 parent a97874f commit c429b6e

2 files changed

Lines changed: 76 additions & 1 deletion

File tree

test/extended/apiserver/graceful_termination.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"regexp"
99
"strings"
10+
"time"
1011

1112
g "github.com/onsi/ginkgo/v2"
1213
o "github.com/onsi/gomega"
@@ -60,6 +61,8 @@ var _ = g.Describe("[sig-api-machinery][Feature:APIServer][Late]", func() {
6061
g.It("kubelet terminates kube-apiserver gracefully extended", func() {
6162
var finalMessageBuilder strings.Builder
6263
terminationRegexp := regexp.MustCompile(`Previous pod .* did not terminate gracefully`)
64+
// klog timestamp format: W0120 22:20:50.473381
65+
klogTimestampRegexp := regexp.MustCompile(`^[IWEF](\d{4}) (\d{2}:\d{2}:\d{2}\.\d+)`)
6366

6467
masters, err := oc.AsAdmin().KubeClient().CoreV1().Nodes().List(context.Background(), metav1.ListOptions{LabelSelector: "node-role.kubernetes.io/master"})
6568
o.Expect(err).NotTo(o.HaveOccurred())
@@ -79,7 +82,8 @@ var _ = g.Describe("[sig-api-machinery][Feature:APIServer][Late]", func() {
7982
for kasTerminationFileScanner.Scan() {
8083
line := kasTerminationFileScanner.Text()
8184
if terminationRegexp.MatchString(line) {
82-
finalMessageBuilder.WriteString(fmt.Sprintf("\n kube-apiserver on node %s wasn't gracefully terminated, reason: %s", master.Name, line))
85+
observedAt := parseKlogTimestamp(line, klogTimestampRegexp)
86+
finalMessageBuilder.WriteString(fmt.Sprintf("\n kube-apiserver on node %s wasn't gracefully terminated (observed at %s), reason: %s", master.Name, observedAt, line))
8387
}
8488
}
8589
o.Expect(kasTerminationFileScanner.Err()).NotTo(o.HaveOccurred())
@@ -185,3 +189,20 @@ func extractAPIServerNameFromAuditFile(auditFileName string) string {
185189
func isKASTerminationLogFile(fileName string) bool {
186190
return strings.Contains(fileName, "termination")
187191
}
192+
193+
// parseKlogTimestamp extracts and formats the klog timestamp from a log line.
194+
// klog timestamps have the format: W0120 22:20:50.473381 (MMDD HH:MM:SS.ffffff without year).
195+
// We use the current year as klog does not include it.
196+
func parseKlogTimestamp(line string, klogTimestampRegexp *regexp.Regexp) string {
197+
matches := klogTimestampRegexp.FindStringSubmatch(line)
198+
if len(matches) < 3 {
199+
return "unknown"
200+
}
201+
// matches[1] = "0120" (MMDD), matches[2] = "22:20:50.473381"
202+
timestampStr := fmt.Sprintf("%d-%s-%s", time.Now().Year(), matches[1][:2]+"-"+matches[1][2:], matches[2])
203+
t, err := time.Parse("2006-01-02-15:04:05.000000", timestampStr)
204+
if err != nil {
205+
return matches[1] + " " + matches[2]
206+
}
207+
return t.Format(time.RFC3339)
208+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package apiserver
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"testing"
7+
"time"
8+
)
9+
10+
func TestParseKlogTimestamp(t *testing.T) {
11+
klogTimestampRegexp := regexp.MustCompile(`^[IWEF](\d{4}) (\d{2}:\d{2}:\d{2}\.\d+)`)
12+
currentYear := time.Now().Year()
13+
14+
tests := []struct {
15+
name string
16+
line string
17+
expected string
18+
}{
19+
{
20+
name: "warning log line with graceful termination message",
21+
line: `W0120 22:20:50.473381 1 main.go:136] Previous pod kube-apiserver-ip-10-0-48-191.us-west-2.compute.internal started at 2026-01-20 22:01:27.176604246 +0000 UTC did not terminate gracefully`,
22+
expected: fmt.Sprintf("%d-01-20T22:20:50Z", currentYear),
23+
},
24+
{
25+
name: "info level log line",
26+
line: `I0315 10:05:30.123456 1 main.go:100] some info message`,
27+
expected: fmt.Sprintf("%d-03-15T10:05:30Z", currentYear),
28+
},
29+
{
30+
name: "error level log line",
31+
line: `E1231 23:59:59.999999 1 main.go:200] error message`,
32+
expected: fmt.Sprintf("%d-12-31T23:59:59Z", currentYear),
33+
},
34+
{
35+
name: "no klog prefix",
36+
line: `some random log line without a timestamp`,
37+
expected: "unknown",
38+
},
39+
{
40+
name: "empty line",
41+
line: ``,
42+
expected: "unknown",
43+
},
44+
}
45+
46+
for _, tt := range tests {
47+
t.Run(tt.name, func(t *testing.T) {
48+
result := parseKlogTimestamp(tt.line, klogTimestampRegexp)
49+
if result != tt.expected {
50+
t.Errorf("parseKlogTimestamp() = %q, want %q", result, tt.expected)
51+
}
52+
})
53+
}
54+
}

0 commit comments

Comments
 (0)