-
Notifications
You must be signed in to change notification settings - Fork 102
AAP-46484 Capture k8s pod diagnostics #1332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
55b3047
b27cda5
a174a48
cb9a50b
88414e8
64b7e79
41a3d29
49afc98
a6b1263
2fb30e2
362a739
a5527cb
f520d48
7c8926c
735f110
5dead7d
d56c4c6
3ecf827
9b938c7
22939d4
c528965
c021f09
bcbc220
03d347a
1f42903
0f11125
3a20984
e0df53d
c4db94b
30743b8
9608c06
bc6320c
3f6de50
0e37103
f0c3698
0db61e5
fd67a69
3a33387
cc16392
95e21e0
7424c77
71c5e47
7002182
677c2b3
65604ab
c73562e
568681c
78358bf
0c1f67d
1ad624c
2fad163
6e02cf3
4ac5aca
6cb833e
b6c66a9
3f10768
5aaf190
9c35093
3db8495
fd4a5c0
afca14c
0a7e38b
8bc7f36
e477eb8
e5da583
bf86624
2bb31a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,7 @@ import ( | |
| // KubeUnit implements the WorkUnit interface. | ||
| type KubeUnit struct { | ||
| BaseWorkUnitForWorkUnit | ||
| KubePodStateHelper | ||
| KubeAPIWrapperInstance KubeAPIer | ||
| authMethod string | ||
| streamMethod string | ||
|
|
@@ -177,6 +178,8 @@ var ErrPodFailed = fmt.Errorf("pod failed to start") | |
| // ErrImagePullBackOff is returned when the image for the container in the Pod cannot be pulled. | ||
| var ErrImagePullBackOff = fmt.Errorf("container failed to start") | ||
|
|
||
| const containerName = "worker" | ||
|
|
||
| // podRunningAndReady is a completion criterion for pod ready to be attached to. | ||
| func podRunningAndReady(kw KubeUnit) func(event watch.Event) (bool, error) { | ||
| imagePullBackOffRetries := 3 | ||
|
|
@@ -249,7 +252,7 @@ func (kw *KubeUnit) kubeLoggingConnectionHandler(timestamps bool, sinceTime time | |
| podNamespace := kw.Pod.Namespace | ||
| podName := kw.Pod.Name | ||
| podOptions := &corev1.PodLogOptions{ | ||
| Container: "worker", | ||
| Container: containerName, | ||
| Follow: true, | ||
| } | ||
| if timestamps { | ||
|
|
@@ -400,6 +403,15 @@ func (kw *KubeUnit) KubeLoggingWithReconnect(streamWait *sync.WaitGroup, stdout | |
| podName, | ||
| ) | ||
|
|
||
| // timeout := int64(10) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Take out commented out code please, The tests are enough for now
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| // _, err = kw.CapturePodStatus(kw.Pod, stdout.Size(), &timeout) | ||
| // if err != nil { | ||
| // kw.GetWorkceptor().nc.GetLogger().Info("Detected error while retrieving pod status for %s/%s.", | ||
| // podNamespace, | ||
| // podName, | ||
| // ) | ||
| // } | ||
|
|
||
|
lranjbar marked this conversation as resolved.
|
||
| return | ||
| } | ||
|
|
||
|
|
@@ -486,7 +498,7 @@ func (kw *KubeUnit) CreatePod(env map[string]string) error { | |
| foundWorker := false | ||
| spec = &pod.Spec | ||
| for i := range spec.Containers { | ||
| if spec.Containers[i].Name == "worker" { | ||
| if spec.Containers[i].Name == containerName { | ||
| spec.Containers[i].Stdin = true | ||
| spec.Containers[i].StdinOnce = true | ||
| foundWorker = true | ||
|
|
@@ -517,7 +529,7 @@ func (kw *KubeUnit) CreatePod(env map[string]string) error { | |
| } | ||
| spec = &corev1.PodSpec{ | ||
| Containers: []corev1.Container{{ | ||
| Name: "worker", | ||
| Name: containerName, | ||
| Image: ked.Image, | ||
| Command: command, | ||
| Args: params, | ||
|
|
@@ -601,7 +613,7 @@ func (kw *KubeUnit) CreatePod(env map[string]string) error { | |
| if err == ErrPodCompleted { | ||
| // Hao: shouldn't we also call kw.Cancel() in these cases? | ||
| for _, cstat := range kw.Pod.Status.ContainerStatuses { | ||
| if cstat.Name == "worker" { | ||
| if cstat.Name == containerName { | ||
| if cstat.State.Terminated != nil && cstat.State.Terminated.ExitCode != 0 { | ||
| return fmt.Errorf("container failed with exit code %d: %s", cstat.State.Terminated.ExitCode, cstat.State.Terminated.Message) | ||
| } | ||
|
|
@@ -632,7 +644,7 @@ func (kw *KubeUnit) CreatePod(env map[string]string) error { | |
| } | ||
|
|
||
| for _, cstat := range kw.Pod.Status.ContainerStatuses { | ||
| if cstat.Name == "worker" { | ||
| if cstat.Name == containerName { | ||
| if cstat.State.Waiting != nil { | ||
| return fmt.Errorf("%s, %s", err.Error(), cstat.State.Waiting.Reason) | ||
| } | ||
|
|
@@ -732,7 +744,7 @@ func (kw *KubeUnit) runWorkUsingLogger() { | |
|
|
||
| req.VersionedParams( | ||
| &corev1.PodExecOptions{ | ||
| Container: "worker", | ||
| Container: containerName, | ||
| Stdin: true, | ||
| Stdout: false, | ||
| Stderr: false, | ||
|
|
@@ -912,7 +924,21 @@ func (kw *KubeUnit) runWorkUsingLogger() { | |
| return | ||
| } | ||
|
|
||
| // only transition from WorkStateRunning to WorkStateSucceeded if WorkStateFailed is set we do not override | ||
| // pod, err := kw.KubeAPIWrapperInstance.Get(kw.GetContext(), kw.clientset, podNamespace, podName, metav1.GetOptions{}) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above The tests are enough for now |
||
| // if err != nil { | ||
| // kw.GetWorkceptor().nc.GetLogger().Warning("Failed to retrieve pod for diagnostics: %v", err) | ||
| // } | ||
| // timeout := int64(10) | ||
| // ok, _ := kw.CapturePodStatus(pod, stdout.Size(), &timeout) | ||
| // if !ok { | ||
| // // If the pod did not succeed, we already updated the status to WorkStateFailed | ||
| // // and we can return early. | ||
| // return | ||
| // } | ||
|
lranjbar marked this conversation as resolved.
|
||
|
|
||
| // Only transition to WorkStateSucceeded if the work unit is still running | ||
| // and has not already failed due to diagnostic or streaming errors. | ||
| // This ensures we don't override a previously set WorkStateFailed. | ||
| if kw.GetContext().Err() != context.Canceled && kw.Status().State == WorkStateRunning { | ||
| kw.UpdateBasicStatus(WorkStateSucceeded, "Finished", stdout.Size()) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| package workceptor | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| corev1 "k8s.io/api/core/v1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/watch" | ||
| "k8s.io/client-go/kubernetes" | ||
| ) | ||
|
|
||
| type KubePodStateHelper interface { | ||
| GetPodStatus(pod *corev1.Pod) (bool, error) | ||
| PodContainerHealthy(pod *corev1.Pod, containerName string) (bool, error) | ||
| PodHealthy(pod *corev1.Pod, containerName string) (bool, error) | ||
| WaitForPodCompleted(pod *corev1.Pod, clientset kubernetes.Interface, timeoutSeconds *int64) (*corev1.Pod, error) | ||
| } | ||
|
|
||
| // GetPodStatus checks if the pod has successfully completed its application logic and infrastructure is healthy. | ||
| func (kw KubeUnit) GetPodStatus(pod *corev1.Pod) (bool, error) { | ||
| if pod == nil { | ||
| return false, fmt.Errorf("pod is nil") | ||
| } | ||
|
|
||
| ok, err := kw.PodHealthy(pod, containerName) | ||
| if !ok || err != nil { | ||
| return ok, err | ||
| } | ||
|
|
||
| return ok, nil | ||
| } | ||
|
|
||
| // PodContainerHealthy checks if the pod has successfully completed its application logic. | ||
| // this is called after podInfrastructureSuccess has confirmed the pod is in a terminal state. | ||
| func (kw KubeUnit) PodContainerHealthy(pod *corev1.Pod, containerName string) (bool, error) { | ||
| if pod == nil { | ||
| return false, fmt.Errorf("pod is nil") | ||
| } | ||
|
|
||
| for _, cs := range pod.Status.ContainerStatuses { | ||
| if cs.Name == containerName { | ||
| if cs.State.Terminated == nil { // means it is waiting or running, so application logic has not completed yet. Normal behavior when job completes successfully. | ||
| return true, nil | ||
| } | ||
|
|
||
| if cs.State.Terminated.ExitCode != 0 { // exit code of 0 means success | ||
| return false, fmt.Errorf("container %s exited with code %d: %s", cs.Name, cs.State.Terminated.ExitCode, cs.State.Terminated.Reason) | ||
| } | ||
|
|
||
| return true, nil // container terminated with exit code of 0 | ||
| } | ||
| } | ||
|
|
||
| return false, fmt.Errorf("pod does not contain container %s", containerName) | ||
| } | ||
|
|
||
| // PodInfrastructureSuccess checks if the pod has either successfully started, is pending or is running, or has is successfully terminated. | ||
| // Any other state is considered an infrastructure failure. | ||
| func (kw KubeUnit) PodHealthy(pod *corev1.Pod, containerName string) (bool, error) { | ||
| if pod == nil { | ||
| return false, fmt.Errorf("pod is nil") | ||
| } | ||
|
|
||
| for _, cs := range pod.Status.ContainerStatuses { | ||
| // Check if this is the container we care about first | ||
| if cs.Name == containerName { | ||
| switch pod.Status.Phase { | ||
| case corev1.PodFailed: | ||
| ok, err := kw.PodContainerHealthy(pod, containerName) | ||
| if !ok || err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| return true, nil | ||
| case corev1.PodSucceeded: | ||
| return true, nil | ||
| case corev1.PodRunning, corev1.PodPending: | ||
| return true, nil | ||
| default: | ||
| return false, fmt.Errorf("unknown phase: %s", pod.Status.Phase) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return false, fmt.Errorf("pod does not contain container %s", containerName) | ||
| } | ||
|
|
||
| func (kw KubeUnit) WaitForPodCompleted(ctx context.Context, pod *corev1.Pod, clientset kubernetes.Interface, timeoutSeconds *int64) (*corev1.Pod, error) { | ||
| if pod == nil { | ||
| return nil, fmt.Errorf("pod is nil") | ||
| } | ||
|
|
||
| originalPhase := pod.Status.Phase | ||
|
|
||
| watcher, err := clientset.CoreV1().Pods(pod.Namespace).Watch(ctx, metav1.ListOptions{ | ||
| TimeoutSeconds: timeoutSeconds, | ||
| FieldSelector: "involvedObject.kind=Pod,involvedObject.name=" + pod.Name, | ||
| }) | ||
| if err != nil { | ||
| return pod, err | ||
| } | ||
|
|
||
| for event := range watcher.ResultChan() { | ||
| switch event.Type { | ||
| case watch.Error: | ||
| kw.GetWorkceptor().nc.GetLogger().Debug("Pod %s/%s event %s phase %s (error)", pod.Namespace, pod.Name, event.Type, pod.Status.Phase) | ||
|
|
||
| return pod, event.Object.(error) | ||
| default: | ||
| pod = event.Object.(*corev1.Pod) | ||
| if pod.Status.Phase != originalPhase { | ||
| kw.GetWorkceptor().nc.GetLogger().Debug("Pod %s/%s phase changed from %s to %s", pod.Namespace, pod.Name, originalPhase, pod.Status.Phase) | ||
|
|
||
| return pod, nil | ||
| } | ||
| kw.GetWorkceptor().nc.GetLogger().Debug("Pod %s/%s event %s phase %s (no change)", pod.Namespace, pod.Name, event.Type, pod.Status.Phase) | ||
|
|
||
| return pod, nil | ||
| } | ||
| } | ||
|
|
||
| kw.GetWorkceptor().nc.GetLogger().Debug("Pod %s/%s phase %s timeout (no change)", pod.Namespace, pod.Name, pod.Status.Phase) | ||
|
|
||
| return pod, nil | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.