aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/dnsserver/zdirectives.go1
-rw-r--r--core/plugin/zplugin.go1
-rw-r--r--plugin.cfg1
-rw-r--r--plugin/reload/README.md58
-rw-r--r--plugin/reload/reload.go65
-rw-r--r--plugin/reload/setup.go72
-rw-r--r--plugin/reload/setup_test.go39
7 files changed, 237 insertions, 0 deletions
diff --git a/core/dnsserver/zdirectives.go b/core/dnsserver/zdirectives.go
index ba8873936..f0017e82c 100644
--- a/core/dnsserver/zdirectives.go
+++ b/core/dnsserver/zdirectives.go
@@ -11,6 +11,7 @@ package dnsserver
// care what plugin above them are doing.
var Directives = []string{
"tls",
+ "reload",
"nsid",
"root",
"bind",
diff --git a/core/plugin/zplugin.go b/core/plugin/zplugin.go
index c893a6139..a81c8ef9b 100644
--- a/core/plugin/zplugin.go
+++ b/core/plugin/zplugin.go
@@ -26,6 +26,7 @@ import (
_ "github.com/coredns/coredns/plugin/nsid"
_ "github.com/coredns/coredns/plugin/pprof"
_ "github.com/coredns/coredns/plugin/proxy"
+ _ "github.com/coredns/coredns/plugin/reload"
_ "github.com/coredns/coredns/plugin/reverse"
_ "github.com/coredns/coredns/plugin/rewrite"
_ "github.com/coredns/coredns/plugin/root"
diff --git a/plugin.cfg b/plugin.cfg
index d3efb3889..2b34faa63 100644
--- a/plugin.cfg
+++ b/plugin.cfg
@@ -20,6 +20,7 @@
# log:log
tls:tls
+reload:reload
nsid:nsid
root:root
bind:bind
diff --git a/plugin/reload/README.md b/plugin/reload/README.md
new file mode 100644
index 000000000..64e5b1d93
--- /dev/null
+++ b/plugin/reload/README.md
@@ -0,0 +1,58 @@
+# reload
+
+## Name
+
+*reload* - allows automatic reload of a changed Corefile
+
+## Description
+
+This plugin periodically checks if the Corefile has changed by reading
+it and calculating its MD5 checksum. If the file has changed, it reloads
+CoreDNS with the new Corefile. This eliminates the need to send a SIGHUP
+or SIGUSR1 after changing the Corefile.
+
+The reloads are graceful - you should not see any loss of service when the
+reload happens. Even if the new Corefile has an error, CoreDNS will continue
+to run the old config and an error message will be printed to the log.
+
+In some environments (for example, Kubernetes), there may be many CoreDNS
+instances that started very near the same time and all share a common
+Corefile. To prevent these all from reloading at the same time, some
+jitter is added to the reload check interval. This is jitter from the
+perspective of multiple CoreDNS instances; each instance still checks on a
+regular interval, but all of these instances will have their reloads spread
+out across the jitter duration. This isn't strictly necessary given that the
+reloads are graceful, and can be disabled by setting the jitter to `0s`.
+
+Jitter is re-calculated whenever the Corefile is reloaded.
+
+## Syntax
+
+~~~ txt
+reload [INTERVAL] [JITTER]
+~~~
+
+* The plugin will check for changes every **INTERVAL**, subject to +/- the **JITTER** duration
+* **INTERVAL** and **JITTER** are Golang (durations)[https://golang.org/pkg/time/#ParseDuration]
+* Default **INTERVAL** is 30s, default **JITTER** is 15s
+* If **JITTER** is more than half of **INTERVAL**, it will be set to half of **INTERVAL**
+
+## Examples
+
+Check with the default intervals:
+
+~~~ corefile
+. {
+ reload
+ erratic
+}
+~~~
+
+Check every 10 seconds (jitter is automatically set to 10 / 2 = 5 in this case):
+
+~~~ corefile
+. {
+ reload 10s
+ erratic
+}
+~~~
diff --git a/plugin/reload/reload.go b/plugin/reload/reload.go
new file mode 100644
index 000000000..e469b4526
--- /dev/null
+++ b/plugin/reload/reload.go
@@ -0,0 +1,65 @@
+package reload
+
+import (
+ "crypto/md5"
+ "log"
+ "time"
+
+ "github.com/mholt/caddy"
+)
+
+// reload periodically checks if the Corefile has changed, and reloads if so
+
+type reload struct {
+ instance *caddy.Instance
+ interval time.Duration
+ sum [md5.Size]byte
+ stopped bool
+ quit chan bool
+}
+
+func hook(event caddy.EventName, info interface{}) error {
+ if event != caddy.InstanceStartupEvent {
+ return nil
+ }
+
+ // if reload is removed from the Corefile, then the hook
+ // is still registered but setup is never called again
+ // so we need a flag to tell us not to reload
+ if r.stopped {
+ return nil
+ }
+
+ // this should be an instance. ok to panic if not
+ r.instance = info.(*caddy.Instance)
+ r.sum = md5.Sum(r.instance.Caddyfile().Body())
+
+ go func() {
+ tick := time.NewTicker(r.interval)
+
+ for {
+ select {
+ case <-tick.C:
+ corefile, err := caddy.LoadCaddyfile(r.instance.Caddyfile().ServerType())
+ if err != nil {
+ continue
+ }
+ s := md5.Sum(corefile.Body())
+ if s != r.sum {
+ _, err := r.instance.Restart(corefile)
+ if err != nil {
+ log.Printf("[ERROR] Corefile changed but reload failed: %s\n", err)
+ continue
+ }
+ // we are done, this hook gets called again with new instance
+ r.stopped = true
+ return
+ }
+ case <-r.quit:
+ return
+ }
+ }
+ }()
+
+ return nil
+}
diff --git a/plugin/reload/setup.go b/plugin/reload/setup.go
new file mode 100644
index 000000000..af6fe1334
--- /dev/null
+++ b/plugin/reload/setup.go
@@ -0,0 +1,72 @@
+package reload
+
+import (
+ "math/rand"
+ "sync"
+ "time"
+
+ "github.com/coredns/coredns/plugin"
+
+ "github.com/mholt/caddy"
+)
+
+func init() {
+ caddy.RegisterPlugin("reload", caddy.Plugin{
+ ServerType: "dns",
+ Action: setup,
+ })
+}
+
+var r *reload
+var once sync.Once
+
+func setup(c *caddy.Controller) error {
+ c.Next() // 'reload'
+ args := c.RemainingArgs()
+
+ if len(args) > 2 {
+ return plugin.Error("reload", c.ArgErr())
+ }
+
+ i := defaultInterval
+ if len(args) > 0 {
+ d, err := time.ParseDuration(args[0])
+ if err != nil {
+ return err
+ }
+ i = d
+ }
+
+ j := defaultJitter
+ if len(args) > 1 {
+ d, err := time.ParseDuration(args[1])
+ if err != nil {
+ return err
+ }
+ j = d
+ }
+
+ if j > i/2 {
+ j = i / 2
+ }
+
+ jitter := time.Duration(rand.Int63n(j.Nanoseconds()) - (j.Nanoseconds() / 2))
+ i = i + jitter
+
+ r = &reload{interval: i, quit: make(chan bool)}
+ once.Do(func() {
+ caddy.RegisterEventHook("reload", hook)
+ })
+
+ c.OnFinalShutdown(func() error {
+ r.quit <- true
+ return nil
+ })
+
+ return nil
+}
+
+const (
+ defaultInterval = 30 * time.Second
+ defaultJitter = 15 * time.Second
+)
diff --git a/plugin/reload/setup_test.go b/plugin/reload/setup_test.go
new file mode 100644
index 000000000..3c488bf2b
--- /dev/null
+++ b/plugin/reload/setup_test.go
@@ -0,0 +1,39 @@
+package reload
+
+import (
+ "testing"
+
+ "github.com/mholt/caddy"
+)
+
+func TestSetupReload(t *testing.T) {
+ c := caddy.NewTestController("dns", `reload`)
+ if err := setup(c); err != nil {
+ t.Fatalf("Expected no errors, but got: %v", err)
+ }
+
+ c = caddy.NewTestController("dns", `reload 10s`)
+ if err := setup(c); err != nil {
+ t.Fatalf("Expected no errors, but got: %v", err)
+ }
+
+ c = caddy.NewTestController("dns", `reload 10s 2s`)
+ if err := setup(c); err != nil {
+ t.Fatalf("Expected no errors, but got: %v", err)
+ }
+
+ c = caddy.NewTestController("dns", `reload foo`)
+ if err := setup(c); err == nil {
+ t.Fatalf("Expected errors, but got: %v", err)
+ }
+
+ c = caddy.NewTestController("dns", `reload 10s foo`)
+ if err := setup(c); err == nil {
+ t.Fatalf("Expected errors, but got: %v", err)
+ }
+
+ c = caddy.NewTestController("dns", `reload 10s 5s foo`)
+ if err := setup(c); err == nil {
+ t.Fatalf("Expected errors, but got: %v", err)
+ }
+}