recovery: retry transient kubelet proxy probes
This commit is contained in:
parent
390f1da114
commit
cecd82cff3
@ -345,7 +345,24 @@ func (o *Orchestrator) readyNodeCandidates(ctx context.Context) ([]readyNodeCand
|
||||
// Why: the apiserver node proxy is the path Jenkins uses for pod exec; checking
|
||||
// it catches Ready-but-unusable nodes before agents start failing websockets.
|
||||
func (o *Orchestrator) kubeletProxyHealthy(ctx context.Context, node string) (bool, error) {
|
||||
out, err := o.kubectl(ctx, 10*time.Second, "get", "--raw", fmt.Sprintf("/api/v1/nodes/%s/proxy/healthz", node))
|
||||
var lastErr error
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
healthy, err := o.kubeletProxyHealthyOnce(ctx, node)
|
||||
if err == nil || !isTransientKubeletProxyCheckErr(err) {
|
||||
return healthy, err
|
||||
}
|
||||
lastErr = err
|
||||
o.log.Printf("warning: transient kubelet proxy health check failure for %s: %v; retrying", node, err)
|
||||
}
|
||||
return false, lastErr
|
||||
}
|
||||
|
||||
// kubeletProxyHealthyOnce runs one orchestration or CLI step.
|
||||
// Signature: (o *Orchestrator) kubeletProxyHealthyOnce(ctx context.Context, node string) (bool, error).
|
||||
// Why: transient apiserver/kubectl cancellations should be retried by the caller
|
||||
// without losing the exact kubelet proxy error when the node is really broken.
|
||||
func (o *Orchestrator) kubeletProxyHealthyOnce(ctx context.Context, node string) (bool, error) {
|
||||
out, err := o.kubectl(ctx, 25*time.Second, "get", "--raw", fmt.Sprintf("/api/v1/nodes/%s/proxy/healthz", node), "--request-timeout=20s")
|
||||
if err != nil {
|
||||
if strings.TrimSpace(out) != "" {
|
||||
return false, fmt.Errorf("%w: %s", err, strings.TrimSpace(out))
|
||||
@ -355,6 +372,32 @@ func (o *Orchestrator) kubeletProxyHealthy(ctx context.Context, node string) (bo
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// isTransientKubeletProxyCheckErr runs one orchestration or CLI step.
|
||||
// Signature: isTransientKubeletProxyCheckErr(err error) bool.
|
||||
// Why: a killed or timed-out kubectl health probe is not proof the node proxy is
|
||||
// broken; retry before reporting or repairing a Ready node.
|
||||
func isTransientKubeletProxyCheckErr(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
msg := strings.ToLower(err.Error())
|
||||
transient := []string{
|
||||
"signal: killed",
|
||||
"context deadline exceeded",
|
||||
"deadline exceeded",
|
||||
"i/o timeout",
|
||||
"request canceled",
|
||||
"client.timeout",
|
||||
"timeout awaiting",
|
||||
}
|
||||
for _, needle := range transient {
|
||||
if strings.Contains(msg, needle) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isRepairableKubeletProxyErr runs one orchestration or CLI step.
|
||||
// Signature: isRepairableKubeletProxyErr(err error) bool.
|
||||
// Why: keep this repair narrow so Ananke restarts k3s-agent for the known
|
||||
|
||||
@ -329,6 +329,36 @@ func TestKubeletProxyHealthAndRepairableErrorHelpers(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("transient killed health check is retried", func(t *testing.T) {
|
||||
checks := 0
|
||||
orch := buildOrchestratorWithStubs(t, config.Config{}, []commandStub{
|
||||
{
|
||||
match: func(name string, args []string) bool {
|
||||
if checks > 0 {
|
||||
return false
|
||||
}
|
||||
if !matchContains("kubectl", "get --raw /api/v1/nodes/titan-07/proxy/healthz")(name, args) {
|
||||
return false
|
||||
}
|
||||
checks++
|
||||
return true
|
||||
},
|
||||
err: errors.New("signal: killed"),
|
||||
},
|
||||
{
|
||||
match: matchContains("kubectl", "get --raw /api/v1/nodes/titan-07/proxy/healthz"),
|
||||
out: "ok",
|
||||
},
|
||||
})
|
||||
healthy, err := orch.kubeletProxyHealthy(context.Background(), "titan-07")
|
||||
if !healthy || err != nil {
|
||||
t.Fatalf("expected retry to pass, healthy=%v err=%v", healthy, err)
|
||||
}
|
||||
if checks != 1 {
|
||||
t.Fatalf("expected one transient failure before retry, got %d", checks)
|
||||
}
|
||||
})
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
err error
|
||||
@ -344,4 +374,8 @@ func TestKubeletProxyHealthAndRepairableErrorHelpers(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if !isTransientKubeletProxyCheckErr(errors.New("signal: killed")) {
|
||||
t.Fatalf("expected signal killed to be transient kubelet proxy check error")
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user