mirror of
https://github.com/coredns/coredns.git
synced 2025-10-28 00:34:24 -04:00
Reload hook (#1445)
* Add reload directive * gofmt * Fix default jitter and error message * remove unneeded call to NextArg, add a couple negative setup tests * Review feedback
This commit is contained in:
committed by
Miek Gieben
parent
80050766fb
commit
0b35d4d28f
@@ -11,6 +11,7 @@ package dnsserver
|
||||
// care what plugin above them are doing.
|
||||
var Directives = []string{
|
||||
"tls",
|
||||
"reload",
|
||||
"nsid",
|
||||
"root",
|
||||
"bind",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# log:log
|
||||
|
||||
tls:tls
|
||||
reload:reload
|
||||
nsid:nsid
|
||||
root:root
|
||||
bind:bind
|
||||
|
||||
58
plugin/reload/README.md
Normal file
58
plugin/reload/README.md
Normal file
@@ -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
|
||||
}
|
||||
~~~
|
||||
65
plugin/reload/reload.go
Normal file
65
plugin/reload/reload.go
Normal file
@@ -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
|
||||
}
|
||||
72
plugin/reload/setup.go
Normal file
72
plugin/reload/setup.go
Normal file
@@ -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
|
||||
)
|
||||
39
plugin/reload/setup_test.go
Normal file
39
plugin/reload/setup_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user