plugin/k8s_extenral: Supports fallthrough option (#5959)

* Add fallthrough option to k8s_external plugin to allow transitioning control to the next plugin if the domain is not found
* Exit on start up if required plugin is not present.

Signed-off-by: vanceli <vanceli@tencent.com>

---------

Signed-off-by: vanceli <vanceli@tencent.com>
Co-authored-by: vanceli <vanceli@tencent.com>
This commit is contained in:
Vancl 2023-03-24 20:52:44 +08:00 committed by GitHub
parent 48c40ae1cd
commit 47dceabfc6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 66 additions and 22 deletions

View file

@ -10,8 +10,7 @@ This plugin allows an additional zone to resolve the external IP address(es) of
service and headless services. This plugin is only useful if the *kubernetes* plugin is also loaded. service and headless services. This plugin is only useful if the *kubernetes* plugin is also loaded.
The plugin uses an external zone to resolve in-cluster IP addresses. It only handles queries for A, The plugin uses an external zone to resolve in-cluster IP addresses. It only handles queries for A,
AAAA, SRV, and PTR records; all others result in NODATA responses. To make it a proper DNS zone, it handles AAAA, SRV, and PTR records; To make it a proper DNS zone, it handles SOA and NS queries for the apex of the zone.
SOA and NS queries for the apex of the zone.
By default the apex of the zone will look like the following (assuming the zone used is `example.org`): By default the apex of the zone will look like the following (assuming the zone used is `example.org`):
@ -67,6 +66,14 @@ k8s_external [ZONE...] {
* if there is a headless service with external IPs set, external IPs will be resolved * if there is a headless service with external IPs set, external IPs will be resolved
If the queried domain does not exist, you can fall through to next plugin by adding the `fallthrough` option.
~~~
k8s_external [ZONE...] {
fallthrough [ZONE...]
}
~~~
## Examples ## Examples
Enable names under `example.org` to be resolved to in-cluster DNS addresses. Enable names under `example.org` to be resolved to in-cluster DNS addresses.
@ -106,6 +113,18 @@ zone transfers. Notifies are not supported.
} }
~~~ ~~~
With the `fallthrough` option, if the queried domain does not exist, it will be passed to the next plugin that matches the zone.
~~~
. {
kubernetes cluster.local
k8s_external example.org {
fallthrough
}
forward . 8.8.8.8
}
~~~
# See Also # See Also
For some background see [resolve external IP address](https://github.com/kubernetes/dns/issues/242). For some background see [resolve external IP address](https://github.com/kubernetes/dns/issues/242).

View file

@ -16,6 +16,7 @@ import (
"github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/etcd/msg" "github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/pkg/fall"
"github.com/coredns/coredns/plugin/pkg/upstream" "github.com/coredns/coredns/plugin/pkg/upstream"
"github.com/coredns/coredns/request" "github.com/coredns/coredns/request"
@ -39,6 +40,7 @@ type Externaler interface {
type External struct { type External struct {
Next plugin.Handler Next plugin.Handler
Zones []string Zones []string
Fall fall.F
hostmaster string hostmaster string
apex string apex string
@ -68,10 +70,6 @@ func (e *External) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r) return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r)
} }
if e.externalFunc == nil {
return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r)
}
state.Zone = zone state.Zone = zone
for _, z := range e.Zones { for _, z := range e.Zones {
// TODO(miek): save this in the External struct. // TODO(miek): save this in the External struct.
@ -93,6 +91,10 @@ func (e *External) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms
m.Authoritative = true m.Authoritative = true
if len(svc) == 0 { if len(svc) == 0 {
if e.Fall.Through(state.Name()) && rcode == dns.RcodeNameError {
return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r)
}
m.Rcode = rcode m.Rcode = rcode
m.Ns = []dns.RR{e.soa(state)} m.Ns = []dns.RR{e.soa(state)}
w.WriteMsg(m) w.WriteMsg(m)

View file

@ -1,6 +1,7 @@
package external package external
import ( import (
"errors"
"strconv" "strconv"
"github.com/coredns/caddy" "github.com/coredns/caddy"
@ -9,7 +10,9 @@ import (
"github.com/coredns/coredns/plugin/pkg/upstream" "github.com/coredns/coredns/plugin/pkg/upstream"
) )
func init() { plugin.Register("k8s_external", setup) } const pluginName = "k8s_external"
func init() { plugin.Register(pluginName, setup) }
func setup(c *caddy.Controller) error { func setup(c *caddy.Controller) error {
e, err := parse(c) e, err := parse(c)
@ -21,14 +24,18 @@ func setup(c *caddy.Controller) error {
c.OnStartup(func() error { c.OnStartup(func() error {
m := dnsserver.GetConfig(c).Handler("kubernetes") m := dnsserver.GetConfig(c).Handler("kubernetes")
if m == nil { if m == nil {
return nil return plugin.Error(pluginName, errors.New("kubernetes plugin not loaded"))
} }
if x, ok := m.(Externaler); ok {
e.externalFunc = x.External x, ok := m.(Externaler)
e.externalAddrFunc = x.ExternalAddress if !ok {
e.externalServicesFunc = x.ExternalServices return plugin.Error(pluginName, errors.New("kubernetes plugin does not implement the Externaler interface"))
e.externalSerialFunc = x.ExternalSerial
} }
e.externalFunc = x.External
e.externalAddrFunc = x.ExternalAddress
e.externalServicesFunc = x.ExternalServices
e.externalSerialFunc = x.ExternalSerial
return nil return nil
}) })
@ -70,6 +77,8 @@ func parse(c *caddy.Controller) (*External, error) {
e.apex = args[0] e.apex = args[0]
case "headless": case "headless":
e.headless = true e.headless = true
case "fallthrough":
e.Fall.SetZonesFromArgs(c.RemainingArgs())
default: default:
return nil, c.Errf("unknown property '%s'", c.Val()) return nil, c.Errf("unknown property '%s'", c.Val())
} }

View file

@ -4,24 +4,33 @@ import (
"testing" "testing"
"github.com/coredns/caddy" "github.com/coredns/caddy"
"github.com/coredns/coredns/plugin/pkg/fall"
) )
func TestSetup(t *testing.T) { func TestSetup(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
shouldErr bool shouldErr bool
expectedZone string expectedZone string
expectedApex string expectedApex string
expectedHeadless bool expectedHeadless bool
expectedFallthrough fall.F
}{ }{
{`k8s_external`, false, "", "dns", false}, {`k8s_external`, false, "", "dns", false, fall.Zero},
{`k8s_external example.org`, false, "example.org.", "dns", false}, {`k8s_external example.org`, false, "example.org.", "dns", false, fall.Zero},
{`k8s_external example.org { {`k8s_external example.org {
apex testdns apex testdns
}`, false, "example.org.", "testdns", false}, }`, false, "example.org.", "testdns", false, fall.Zero},
{`k8s_external example.org { {`k8s_external example.org {
headless headless
}`, false, "example.org.", "dns", true}, }`, false, "example.org.", "dns", true, fall.Zero},
{`k8s_external example.org {
fallthrough
}`, false, "example.org.", "dns", false, fall.Root},
{`k8s_external example.org {
fallthrough ip6.arpa inaddr.arpa foo.com
}`, false, "example.org.", "dns", false,
fall.F{Zones: []string{"ip6.arpa.", "inaddr.arpa.", "foo.com."}}},
} }
for i, test := range tests { for i, test := range tests {
@ -53,5 +62,10 @@ func TestSetup(t *testing.T) {
t.Errorf("Test %d, expected headless %q for input %s, got: %v", i, test.expectedApex, test.input, e.headless) t.Errorf("Test %d, expected headless %q for input %s, got: %v", i, test.expectedApex, test.input, e.headless)
} }
} }
if !test.shouldErr {
if !e.Fall.Equal(test.expectedFallthrough) {
t.Errorf("Test %d, expected to be initialized with fallthrough %q for input %s, got: %v", i, test.expectedFallthrough, test.input, e.Fall)
}
}
} }
} }