From 9da88ba24384c499e202b2ceeeb48f58114e977a Mon Sep 17 00:00:00 2001 From: Kevin Nathaniel Wijaya Date: Fri, 10 Jan 2025 17:15:41 +0700 Subject: [PATCH 1/2] add ready plugin to vendor --- .../coredns/coredns/plugin/ready/README.md | 58 +++++++++++++ .../coredns/coredns/plugin/ready/list.go | 56 +++++++++++++ .../coredns/coredns/plugin/ready/readiness.go | 7 ++ .../coredns/coredns/plugin/ready/ready.go | 81 +++++++++++++++++++ .../coredns/coredns/plugin/ready/setup.go | 73 +++++++++++++++++ vendor/modules.txt | 1 + 6 files changed, 276 insertions(+) create mode 100644 vendor/github.com/coredns/coredns/plugin/ready/README.md create mode 100644 vendor/github.com/coredns/coredns/plugin/ready/list.go create mode 100644 vendor/github.com/coredns/coredns/plugin/ready/readiness.go create mode 100644 vendor/github.com/coredns/coredns/plugin/ready/ready.go create mode 100644 vendor/github.com/coredns/coredns/plugin/ready/setup.go diff --git a/vendor/github.com/coredns/coredns/plugin/ready/README.md b/vendor/github.com/coredns/coredns/plugin/ready/README.md new file mode 100644 index 000000000..d2e430de7 --- /dev/null +++ b/vendor/github.com/coredns/coredns/plugin/ready/README.md @@ -0,0 +1,58 @@ +# ready + +## Name + +*ready* - enables a readiness check HTTP endpoint. + +## Description + +By enabling *ready* an HTTP endpoint on port 8181 will return 200 OK, when all plugins that are able +to signal readiness have done so. If some are not ready yet the endpoint will return a 503 with the +body containing the list of plugins that are not ready. Once a plugin has signaled it is ready it +will not be queried again. + +Each Server Block that enables the *ready* plugin will have the plugins *in that server block* +report readiness into the /ready endpoint that runs on the same port. This also means that the +*same* plugin with different configurations (in potentially *different* Server Blocks) will have +their readiness reported as the union of their respective readinesses. + +## Syntax + +~~~ +ready [ADDRESS] +~~~ + +*ready* optionally takes an address; the default is `:8181`. The path is fixed to `/ready`. The +readiness endpoint returns a 200 response code and the word "OK" when this server is ready. It +returns a 503 otherwise *and* the list of plugins that are not ready. + +## Plugins + +Any plugin wanting to signal readiness will need to implement the `ready.Readiness` interface by +implementing a method `Ready() bool` that returns true when the plugin is ready and false otherwise. + +## Examples + +Let *ready* report readiness for both the `.` and `example.org` servers (assuming the *whois* +plugin also exports readiness): + +~~~ txt +. { + ready + erratic +} + +example.org { + ready + whoami +} + +~~~ + +Run *ready* on a different port. + +~~~ txt +. { + ready localhost:8091 +} +~~~ diff --git a/vendor/github.com/coredns/coredns/plugin/ready/list.go b/vendor/github.com/coredns/coredns/plugin/ready/list.go new file mode 100644 index 000000000..c24628730 --- /dev/null +++ b/vendor/github.com/coredns/coredns/plugin/ready/list.go @@ -0,0 +1,56 @@ +package ready + +import ( + "sort" + "strings" + "sync" +) + +// list is a structure that holds the plugins that signals readiness for this server block. +type list struct { + sync.RWMutex + rs []Readiness + names []string +} + +// Reset resets l +func (l *list) Reset() { + l.Lock() + defer l.Unlock() + l.rs = nil + l.names = nil +} + +// Append adds a new readiness to l. +func (l *list) Append(r Readiness, name string) { + l.Lock() + defer l.Unlock() + l.rs = append(l.rs, r) + l.names = append(l.names, name) +} + +// Ready return true when all plugins ready, if the returned value is false the string +// contains a comma separated list of plugins that are not ready. +func (l *list) Ready() (bool, string) { + l.RLock() + defer l.RUnlock() + ok := true + s := []string{} + for i, r := range l.rs { + if r == nil { + continue + } + if !r.Ready() { + ok = false + s = append(s, l.names[i]) + } else { + // if ok, this plugin is ready and will not be queried anymore. + l.rs[i] = nil + } + } + if ok { + return true, "" + } + sort.Strings(s) + return false, strings.Join(s, ",") +} diff --git a/vendor/github.com/coredns/coredns/plugin/ready/readiness.go b/vendor/github.com/coredns/coredns/plugin/ready/readiness.go new file mode 100644 index 000000000..7aca5dfc2 --- /dev/null +++ b/vendor/github.com/coredns/coredns/plugin/ready/readiness.go @@ -0,0 +1,7 @@ +package ready + +// The Readiness interface needs to be implemented by each plugin willing to provide a readiness check. +type Readiness interface { + // Ready is called by ready to see whether the plugin is ready. + Ready() bool +} diff --git a/vendor/github.com/coredns/coredns/plugin/ready/ready.go b/vendor/github.com/coredns/coredns/plugin/ready/ready.go new file mode 100644 index 000000000..2002e4a90 --- /dev/null +++ b/vendor/github.com/coredns/coredns/plugin/ready/ready.go @@ -0,0 +1,81 @@ +// Package ready is used to signal readiness of the CoreDNS process. Once all +// plugins have called in the plugin will signal readiness by returning a 200 +// OK on the HTTP handler (on port 8181). If not ready yet, the handler will +// return a 503. +package ready + +import ( + "io" + "net" + "net/http" + "sync" + + clog "github.com/coredns/coredns/plugin/pkg/log" + "github.com/coredns/coredns/plugin/pkg/reuseport" + "github.com/coredns/coredns/plugin/pkg/uniq" +) + +var ( + log = clog.NewWithPlugin("ready") + plugins = &list{} + uniqAddr = uniq.New() +) + +type ready struct { + Addr string + + sync.RWMutex + ln net.Listener + done bool + mux *http.ServeMux +} + +func (rd *ready) onStartup() error { + ln, err := reuseport.Listen("tcp", rd.Addr) + if err != nil { + return err + } + + rd.Lock() + rd.ln = ln + rd.mux = http.NewServeMux() + rd.done = true + rd.Unlock() + + rd.mux.HandleFunc("/ready", func(w http.ResponseWriter, _ *http.Request) { + rd.Lock() + defer rd.Unlock() + if !rd.done { + w.WriteHeader(http.StatusServiceUnavailable) + io.WriteString(w, "Shutting down") + return + } + ok, todo := plugins.Ready() + if ok { + w.WriteHeader(http.StatusOK) + io.WriteString(w, http.StatusText(http.StatusOK)) + return + } + log.Infof("Still waiting on: %q", todo) + w.WriteHeader(http.StatusServiceUnavailable) + io.WriteString(w, todo) + }) + + go func() { http.Serve(rd.ln, rd.mux) }() + + return nil +} + +func (rd *ready) onFinalShutdown() error { + rd.Lock() + defer rd.Unlock() + if !rd.done { + return nil + } + + uniqAddr.Unset(rd.Addr) + + rd.ln.Close() + rd.done = false + return nil +} diff --git a/vendor/github.com/coredns/coredns/plugin/ready/setup.go b/vendor/github.com/coredns/coredns/plugin/ready/setup.go new file mode 100644 index 000000000..e5657f62f --- /dev/null +++ b/vendor/github.com/coredns/coredns/plugin/ready/setup.go @@ -0,0 +1,73 @@ +package ready + +import ( + "net" + + "github.com/coredns/caddy" + "github.com/coredns/coredns/core/dnsserver" + "github.com/coredns/coredns/plugin" +) + +func init() { plugin.Register("ready", setup) } + +func setup(c *caddy.Controller) error { + addr, err := parse(c) + if err != nil { + return plugin.Error("ready", err) + } + rd := &ready{Addr: addr} + + uniqAddr.Set(addr, rd.onStartup) + c.OnStartup(func() error { uniqAddr.Set(addr, rd.onStartup); return nil }) + c.OnRestartFailed(func() error { uniqAddr.Set(addr, rd.onStartup); return nil }) + + c.OnStartup(func() error { return uniqAddr.ForEach() }) + c.OnRestartFailed(func() error { return uniqAddr.ForEach() }) + + c.OnStartup(func() error { + plugins.Reset() + for _, p := range dnsserver.GetConfig(c).Handlers() { + if r, ok := p.(Readiness); ok { + plugins.Append(r, p.Name()) + } + } + return nil + }) + c.OnRestartFailed(func() error { + for _, p := range dnsserver.GetConfig(c).Handlers() { + if r, ok := p.(Readiness); ok { + plugins.Append(r, p.Name()) + } + } + return nil + }) + + c.OnRestart(rd.onFinalShutdown) + c.OnFinalShutdown(rd.onFinalShutdown) + + return nil +} + +func parse(c *caddy.Controller) (string, error) { + addr := ":8181" + i := 0 + for c.Next() { + if i > 0 { + return "", plugin.ErrOnce + } + i++ + args := c.RemainingArgs() + + switch len(args) { + case 0: + case 1: + addr = args[0] + if _, _, e := net.SplitHostPort(addr); e != nil { + return "", e + } + default: + return "", c.ArgErr() + } + } + return addr, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 69bb08b5d..1484bb40a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -108,6 +108,7 @@ github.com/coredns/coredns/plugin/pkg/uniq github.com/coredns/coredns/plugin/pkg/up github.com/coredns/coredns/plugin/pkg/upstream github.com/coredns/coredns/plugin/pprof +github.com/coredns/coredns/plugin/ready github.com/coredns/coredns/plugin/reload github.com/coredns/coredns/plugin/rewrite github.com/coredns/coredns/plugin/template From 6951d4d50d3c7795e65171004093a7b5ca296fc4 Mon Sep 17 00:00:00 2001 From: Kevin Nathaniel Wijaya Date: Fri, 10 Jan 2025 17:20:42 +0700 Subject: [PATCH 2/2] implement readiness plugin on node-local-dns --- cmd/node-cache/app/cache_app.go | 8 +++++++- cmd/node-cache/main.go | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cmd/node-cache/app/cache_app.go b/cmd/node-cache/app/cache_app.go index 098874c3f..a8c4fb825 100644 --- a/cmd/node-cache/app/cache_app.go +++ b/cmd/node-cache/app/cache_app.go @@ -49,7 +49,8 @@ type ConfigParams struct { CoreFile string // Path to config file used by node-cache KubednsCMPath string // Directory where kube-dns configmap will be mounted UpstreamSvcName string // Name of the service whose clusterIP is the upstream for node-cache for cluster domain - HealthPort string // port for the healthcheck + HealthPort string // port for the liveness healthcheck from health plugin + ReadyPort string // port for the readiness healthcheck from ready plugin SetupIptables bool SkipTeardown bool // Indicates whether the iptables rules and interface should be torn down } @@ -142,6 +143,11 @@ func (c *CacheApp) initIptables() { "--dport", c.params.HealthPort, "-j", "NOTRACK", "-m", "comment", "--comment", iptablesCommentSkipConntrack}}, {utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "tcp", "-s", localIP, "--sport", c.params.HealthPort, "-j", "NOTRACK", "-m", "comment", "--comment", iptablesCommentSkipConntrack}}, + // skip connection tracking for healthcheck requests generated by readiness probe to ready plugin + {utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "tcp", "-d", localIP, + "--dport", c.params.ReadyPort, "-j", "NOTRACK", "-m", "comment", "--comment", iptablesCommentSkipConntrack}}, + {utiliptables.Table("raw"), utiliptables.ChainOutput, []string{"-p", "tcp", "-s", localIP, + "--sport", c.params.ReadyPort, "-j", "NOTRACK", "-m", "comment", "--comment", iptablesCommentSkipConntrack}}, }...) } c.iptables = newIPTables(c.isIPv6()) diff --git a/cmd/node-cache/main.go b/cmd/node-cache/main.go index e8f8eab6a..8f334b5da 100644 --- a/cmd/node-cache/main.go +++ b/cmd/node-cache/main.go @@ -43,6 +43,7 @@ import ( _ "github.com/coredns/coredns/plugin/loop" _ "github.com/coredns/coredns/plugin/metrics" _ "github.com/coredns/coredns/plugin/pprof" + _ "github.com/coredns/coredns/plugin/ready" _ "github.com/coredns/coredns/plugin/reload" _ "github.com/coredns/coredns/plugin/rewrite" _ "github.com/coredns/coredns/plugin/template" @@ -89,6 +90,7 @@ func parseAndValidateFlags() (*app.ConfigParams, error) { flag.StringVar(¶ms.KubednsCMPath, "kubednscm", "", "Path where the kube-dns configmap will be mounted") flag.StringVar(¶ms.UpstreamSvcName, "upstreamsvc", "kube-dns", "Service name whose cluster IP is upstream for node-cache") flag.StringVar(¶ms.HealthPort, "health-port", "8080", "port used by health plugin") + flag.StringVar(¶ms.ReadyPort, "ready-port", "8181", "port used by ready plugin") flag.BoolVar(¶ms.SkipTeardown, "skipteardown", false, "indicates whether iptables rules should be torn down on exit") flag.Parse() @@ -118,6 +120,9 @@ func parseAndValidateFlags() (*app.ConfigParams, error) { if _, err := strconv.Atoi(params.HealthPort); err != nil { return nil, fmt.Errorf("invalid healthcheck port specified - %q", params.HealthPort) } + if _, err := strconv.Atoi(params.ReadyPort); err != nil { + return nil, fmt.Errorf("invalid ready port specified - %q", params.ReadyPort) + } if f = flag.Lookup("conf"); f != nil { params.CoreFile = f.Value.String() clog.Infof("Using Corefile %s", params.CoreFile)