Skip to content

Commit b543944

Browse files
committed
feat: add diagnostic hint log when driver cannot reach kube-apiserver on startup
/kind feature **What this PR does / why we need it**: Motivation: When the driver fails to start because it cannot reach the kube-apiserver (e.g. RESTMapper/discovery client construction in ctrl.NewManager, or cache sync in mgr.Start failing due to DNS, connection refused, or timeouts), the existing logs ("failed to start manager", "failed to run manager") give no hint about the likely root cause. Issue #1753 reports exactly this: a driver pod failed to initialize with only a generic wrapped error, with no guidance that network connectivity to the kube-apiserver should be checked. Approach: Add an `isNetworkError` helper in cmd/secrets-store-csi-driver/main.go that classifies an error as network-related if it unwraps to a `net.Error` (covers `*net.DNSError`, `*net.OpError`, etc.) or its message contains one of a small set of well-known connectivity failure substrings ("connection refused", "no such host", "i/o timeout", "context deadline exceeded"). When this helper returns true at the two manager-startup failure points, an additional klog.ErrorS call logs: "the driver is unable to communicate with the kube-apiserver, check network connectivity between the node and the kube-apiserver". This mirrors the existing isMaxRecvMsgSizeError pattern in pkg/secrets-store/provider_client.go. This change is purely additive: it does not alter control flow, return values, exit codes, or any existing log message. The original error is still logged and returned/panicked exactly as before; only an extra diagnostic log line is emitted when the error looks network-related. User-visible behavior (pod restarts on manager start failure) is unchanged before/after this fix — the benefit is a clearer, more actionable log line for operators debugging a non-starting driver pod, not a change in recovery behavior. Validation: - `go build ./...` passes. - `go test ./cmd/... -v` passes, including the new table-driven TestIsNetworkError covering nil errors, unrelated errors, wrapped net.DNSError, and each of the matched substring cases. - `gofmt -l` and `go vet ./cmd/...` report no issues. Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
1 parent 267a614 commit b543944

2 files changed

Lines changed: 103 additions & 0 deletions

File tree

cmd/secrets-store-csi-driver/main.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ package main
1818

1919
import (
2020
"context"
21+
"errors"
2122
"flag"
2223
"fmt"
24+
"net"
2325
"net/http"
2426
_ "net/http/pprof" // #nosec
2527
"strings"
@@ -160,6 +162,9 @@ func mainErr() error {
160162
})
161163
if err != nil {
162164
klog.ErrorS(err, "failed to start manager")
165+
if isNetworkError(err) {
166+
klog.ErrorS(err, "the driver is unable to communicate with the kube-apiserver, check network connectivity between the node and the kube-apiserver")
167+
}
163168
return err
164169
}
165170

@@ -190,6 +195,9 @@ func mainErr() error {
190195
klog.Info("starting manager")
191196
if err := mgr.Start(ctx); err != nil {
192197
klog.ErrorS(err, "failed to run manager")
198+
if isNetworkError(err) {
199+
klog.ErrorS(err, "the driver is unable to communicate with the kube-apiserver, check network connectivity between the node and the kube-apiserver")
200+
}
193201
panic(err)
194202
}
195203
}()
@@ -229,3 +237,21 @@ func getKlogLevel() klog.Level {
229237

230238
return -1
231239
}
240+
241+
// isNetworkError returns true if err looks like it originates from a failure
242+
// to reach the kube-apiserver (DNS resolution, connection refused, timeout,
243+
// etc.), as opposed to some other manager startup failure.
244+
func isNetworkError(err error) bool {
245+
if err == nil {
246+
return false
247+
}
248+
var netErr net.Error
249+
if errors.As(err, &netErr) {
250+
return true
251+
}
252+
msg := err.Error()
253+
return strings.Contains(msg, "connection refused") ||
254+
strings.Contains(msg, "no such host") ||
255+
strings.Contains(msg, "i/o timeout") ||
256+
strings.Contains(msg, "context deadline exceeded")
257+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
Copyright 2026 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
"net"
23+
"testing"
24+
)
25+
26+
func TestIsNetworkError(t *testing.T) {
27+
cases := []struct {
28+
name string
29+
err error
30+
want bool
31+
}{
32+
{
33+
name: "nil error",
34+
err: nil,
35+
want: false,
36+
},
37+
{
38+
name: "unrelated error",
39+
err: errors.New("failed to create controller"),
40+
want: false,
41+
},
42+
{
43+
name: "wrapped net.Error",
44+
err: fmt.Errorf("could not create RESTMapper from config: %w", &net.DNSError{Err: "no such host", Name: "kubernetes.default.svc", IsNotFound: true}),
45+
want: true,
46+
},
47+
{
48+
name: "connection refused string",
49+
err: errors.New("Get \"https://10.0.0.1:443/api\": dial tcp 10.0.0.1:443: connect: connection refused"),
50+
want: true,
51+
},
52+
{
53+
name: "no such host string",
54+
err: errors.New("dial tcp: lookup kubernetes.default.svc: no such host"),
55+
want: true,
56+
},
57+
{
58+
name: "i/o timeout string",
59+
err: errors.New("dial tcp 10.0.0.1:443: i/o timeout"),
60+
want: true,
61+
},
62+
{
63+
name: "context deadline exceeded string",
64+
err: errors.New("Get \"https://10.0.0.1:443/api\": context deadline exceeded"),
65+
want: true,
66+
},
67+
}
68+
69+
for _, test := range cases {
70+
t.Run(test.name, func(t *testing.T) {
71+
got := isNetworkError(test.err)
72+
if got != test.want {
73+
t.Errorf("isNetworkError() = %v, want %v", got, test.want)
74+
}
75+
})
76+
}
77+
}

0 commit comments

Comments
 (0)