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.
|
// care what plugin above them are doing.
|
||||||
var Directives = []string{
|
var Directives = []string{
|
||||||
"tls",
|
"tls",
|
||||||
|
"reload",
|
||||||
"nsid",
|
"nsid",
|
||||||
"root",
|
"root",
|
||||||
"bind",
|
"bind",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
_ "github.com/coredns/coredns/plugin/nsid"
|
_ "github.com/coredns/coredns/plugin/nsid"
|
||||||
_ "github.com/coredns/coredns/plugin/pprof"
|
_ "github.com/coredns/coredns/plugin/pprof"
|
||||||
_ "github.com/coredns/coredns/plugin/proxy"
|
_ "github.com/coredns/coredns/plugin/proxy"
|
||||||
|
_ "github.com/coredns/coredns/plugin/reload"
|
||||||
_ "github.com/coredns/coredns/plugin/reverse"
|
_ "github.com/coredns/coredns/plugin/reverse"
|
||||||
_ "github.com/coredns/coredns/plugin/rewrite"
|
_ "github.com/coredns/coredns/plugin/rewrite"
|
||||||
_ "github.com/coredns/coredns/plugin/root"
|
_ "github.com/coredns/coredns/plugin/root"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
# log:log
|
# log:log
|
||||||
|
|
||||||
tls:tls
|
tls:tls
|
||||||
|
reload:reload
|
||||||
nsid:nsid
|
nsid:nsid
|
||||||
root:root
|
root:root
|
||||||
bind:bind
|
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