From ab74d3acf23613332ad7c91bea00092ed319e5fb Mon Sep 17 00:00:00 2001 From: Dave Brown <503582241@qq.com> Date: Thu, 12 Jun 2025 02:22:07 +0800 Subject: [PATCH] add args: startup_timeout for kubernetes plugin (#7068) Signed-off-by: mangoyhuang Co-authored-by: mangoyhuang --- plugin/kubernetes/README.md | 5 +++- plugin/kubernetes/kubernetes.go | 6 ++-- plugin/kubernetes/setup.go | 13 +++++++++ plugin/kubernetes/setup_test.go | 49 +++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/plugin/kubernetes/README.md b/plugin/kubernetes/README.md index 5bb60b281..1ee53be37 100644 --- a/plugin/kubernetes/README.md +++ b/plugin/kubernetes/README.md @@ -43,6 +43,7 @@ kubernetes [ZONES...] { fallthrough [ZONES...] ignore empty_service multicluster [ZONES...] + startup_timeout DURATION } ``` @@ -106,6 +107,8 @@ kubernetes [ZONES...] { Services API (MCS-API). Specifying this option is generally paired with the installation of an MCS-API implementation and the ServiceImport and ServiceExport CRDs. The plugin MUST be authoritative for the zones listed here. +* `startup_timeout` specifies the **DURATION** value that limits the time to wait for informer cache synced + when the kubernetes plugin starts. If not specified, the default timeout will be 5s. Enabling zone transfer is done by using the *transfer* plugin. @@ -115,7 +118,7 @@ When CoreDNS starts with the *kubernetes* plugin enabled, it will delay serving until it can connect to the Kubernetes API and synchronize all object watches. If this cannot happen within 5 seconds, then CoreDNS will start serving DNS while the *kubernetes* plugin continues to try to connect and synchronize all object watches. CoreDNS will answer SERVFAIL to any request made for a Kubernetes record -that has not yet been synchronized. +that has not yet been synchronized. You can also determine how long to wait by specifying `startup_timeout`. ## Monitoring Kubernetes Endpoints diff --git a/plugin/kubernetes/kubernetes.go b/plugin/kubernetes/kubernetes.go index d1c007bda..8adc80c80 100644 --- a/plugin/kubernetes/kubernetes.go +++ b/plugin/kubernetes/kubernetes.go @@ -48,7 +48,8 @@ type Kubernetes struct { opts dnsControlOpts primaryZoneIndex int localIPs []net.IP - autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath. + autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath. + startupTimeout time.Duration // startupTimeout set timeout of startup } // Upstreamer is used to resolve CNAME or other external targets @@ -276,8 +277,7 @@ func (k *Kubernetes) InitKubeCache(ctx context.Context) (onStart func() error, o k.APIConn.Run() }() - timeout := 5 * time.Second - timeoutTicker := time.NewTicker(timeout) + timeoutTicker := time.NewTicker(k.startupTimeout) defer timeoutTicker.Stop() logDelay := 500 * time.Millisecond logTicker := time.NewTicker(logDelay) diff --git a/plugin/kubernetes/setup.go b/plugin/kubernetes/setup.go index afd523325..cc73e0fe5 100644 --- a/plugin/kubernetes/setup.go +++ b/plugin/kubernetes/setup.go @@ -7,6 +7,7 @@ import ( "slices" "strconv" "strings" + "time" "github.com/coredns/caddy" "github.com/coredns/coredns/core/dnsserver" @@ -112,6 +113,7 @@ func ParseStanza(c *caddy.Controller) (*Kubernetes, error) { k8s.Upstream = upstream.New() + k8s.startupTimeout = time.Second * 5 for c.NextBlock() { switch c.Val() { case "endpoint_pod_names": @@ -231,6 +233,17 @@ func ParseStanza(c *caddy.Controller) (*Kubernetes, error) { k8s.ClientConfig = config case "multicluster": k8s.opts.multiclusterZones = plugin.OriginsFromArgsOrServerBlock(c.RemainingArgs(), []string{}) + case "startup_timeout": + args := c.RemainingArgs() + if len(args) == 0 { + return nil, c.ArgErr() + } else { + var err error + k8s.startupTimeout, err = time.ParseDuration(args[0]) + if err != nil { + return nil, fmt.Errorf("failed to parse startup_timeout: %v, %s", args[0], err) + } + } default: return nil, c.Errf("unknown property '%s'", c.Val()) } diff --git a/plugin/kubernetes/setup_test.go b/plugin/kubernetes/setup_test.go index ffcc4b912..097569c1c 100644 --- a/plugin/kubernetes/setup_test.go +++ b/plugin/kubernetes/setup_test.go @@ -4,6 +4,7 @@ import ( "slices" "strings" "testing" + "time" "github.com/coredns/caddy" "github.com/coredns/coredns/plugin/pkg/fall" @@ -11,6 +12,8 @@ import ( meta "k8s.io/apimachinery/pkg/apis/meta/v1" ) +var defaultStartupTimeout = time.Second * 5 + func TestKubernetesParse(t *testing.T) { tests := []struct { input string // Corefile data as string @@ -22,6 +25,7 @@ func TestKubernetesParse(t *testing.T) { expectedNamespaceLabelSelector string // expected namespace label selector value expectedPodMode string expectedFallthrough fall.F + expectedStartupTimeout time.Duration }{ // positive { @@ -34,6 +38,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local test.local`, @@ -45,6 +50,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -57,6 +63,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -70,6 +77,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -83,6 +91,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -96,6 +105,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -109,6 +119,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -122,6 +133,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -135,6 +147,7 @@ func TestKubernetesParse(t *testing.T) { "istio-injection=enabled", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -149,6 +162,7 @@ func TestKubernetesParse(t *testing.T) { "istio-injection=enabled", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local test.local { @@ -165,6 +179,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Root, + defaultStartupTimeout, }, // negative { @@ -179,6 +194,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -192,6 +208,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -205,6 +222,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -218,6 +236,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, // pods disabled { @@ -232,6 +251,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, // pods insecure { @@ -246,6 +266,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeInsecure, fall.Zero, + defaultStartupTimeout, }, // pods verified { @@ -260,6 +281,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeVerified, fall.Zero, + defaultStartupTimeout, }, // pods invalid { @@ -274,6 +296,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeVerified, fall.Zero, + defaultStartupTimeout, }, // fallthrough with zones { @@ -288,6 +311,7 @@ func TestKubernetesParse(t *testing.T) { "", podModeDisabled, fall.F{Zones: []string{"ip6.arpa.", "inaddr.arpa.", "foo.com."}}, + defaultStartupTimeout, }, // More than one Kubernetes not allowed { @@ -301,6 +325,7 @@ kubernetes cluster.local`, "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -314,6 +339,7 @@ kubernetes cluster.local`, "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -327,6 +353,7 @@ kubernetes cluster.local`, "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -340,6 +367,7 @@ kubernetes cluster.local`, "", podModeDisabled, fall.Zero, + defaultStartupTimeout, }, { `kubernetes coredns.local { @@ -353,6 +381,22 @@ kubernetes cluster.local`, "", podModeDisabled, fall.Zero, + defaultStartupTimeout, + }, + { + `kubernetes coredns.local { + kubeconfig file context + startup_timeout 1s +}`, + false, + "", + 1, + 0, + "", + "", + podModeDisabled, + fall.Zero, + time.Second * 1, }, } @@ -414,6 +458,11 @@ kubernetes cluster.local`, if !k8sController.Fall.Equal(test.expectedFallthrough) { t.Errorf("Test %d: Expected kubernetes controller to be initialized with fallthrough '%v'. Instead found fallthrough '%v' for input '%s'", i, test.expectedFallthrough, k8sController.Fall, test.input) } + + // startupTimeout + if k8sController.startupTimeout.String() != test.expectedStartupTimeout.String() { + t.Errorf("Test %d: Expected kubernetes controller to be initialized with startupTimeout '%v'. Instead found startupTimeout '%v' for input '%s'", i, test.expectedStartupTimeout, k8sController.startupTimeout, test.input) + } } }