Add fallthrough support for Kubernetes (#626)

* Add fallthrough support for Kubernetes

This enables registering other services in the same zone as
Kubernetes services. This also re-orders the middleware chain
so that Kubernetes comes before other types, in order to make
this work out-of-the-box.

* Remove extra line
This commit is contained in:
John Belamaric 2017-04-19 16:08:30 -04:00 committed by GitHub
parent 5a60090933
commit 1c53d4130e
7 changed files with 122 additions and 9 deletions

View file

@ -26,11 +26,11 @@ var directives = []string{
"loadbalance", "loadbalance",
"dnssec", "dnssec",
"reverse", "reverse",
"kubernetes",
"file", "file",
"auto", "auto",
"secondary", "secondary",
"etcd", "etcd",
"kubernetes",
"proxy", "proxy",
"whoami", "whoami",
"erratic", "erratic",

View file

@ -34,11 +34,11 @@
120:loadbalance:loadbalance 120:loadbalance:loadbalance
130:dnssec:dnssec 130:dnssec:dnssec
140:reverse:reverse 140:reverse:reverse
150:file:file 150:kubernetes:kubernetes
160:auto:auto 160:file:file
170:secondary:secondary 170:auto:auto
180:etcd:etcd 180:secondary:secondary
190:kubernetes:kubernetes 190:etcd:etcd
200:proxy:proxy 200:proxy:proxy
210:whoami:whoami 210:whoami:whoami
220:erratic:erratic 220:erratic:erratic

View file

@ -71,6 +71,9 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
_, _, err = middleware.A(&k, zone, state, nil, middleware.Options{}) _, _, err = middleware.A(&k, zone, state, nil, middleware.Options{})
} }
if k.IsNameError(err) { if k.IsNameError(err) {
if k.Fallthrough {
return middleware.NextOrFailure(k.Name(), k.Next, ctx, w, r)
}
// Make err nil when returning here, so we don't log spam for NXDOMAIN. // Make err nil when returning here, so we don't log spam for NXDOMAIN.
return middleware.BackendError(&k, zone, dns.RcodeNameError, state, nil /*debug*/, nil /* err */, middleware.Options{}) return middleware.BackendError(&k, zone, dns.RcodeNameError, state, nil /*debug*/, nil /* err */, middleware.Options{})
} }

View file

@ -43,6 +43,7 @@ type Kubernetes struct {
Selector *labels.Selector Selector *labels.Selector
PodMode string PodMode string
ReverseCidrs []net.IPNet ReverseCidrs []net.IPNet
Fallthrough bool
} }
const ( const (

View file

@ -155,6 +155,13 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
continue continue
} }
return nil, c.ArgErr() return nil, c.ArgErr()
case "fallthrough":
args := c.RemainingArgs()
if len(args) == 0 {
k8s.Fallthrough = true
continue
}
return nil, c.ArgErr()
} }
} }
return k8s, nil return k8s, nil

View file

@ -27,6 +27,7 @@ func TestKubernetesParse(t *testing.T) {
expectedLabelSelector string // expected label selector value expectedLabelSelector string // expected label selector value
expectedPodMode string expectedPodMode string
expectedCidrs []net.IPNet expectedCidrs []net.IPNet
expectedFallthrough bool
}{ }{
// positive // positive
{ {
@ -40,6 +41,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"kubernetes keyword with multiple zones", "kubernetes keyword with multiple zones",
@ -52,6 +54,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"kubernetes keyword with zone and empty braces", "kubernetes keyword with zone and empty braces",
@ -65,6 +68,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"endpoint keyword with url", "endpoint keyword with url",
@ -79,6 +83,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"namespaces keyword with one namespace", "namespaces keyword with one namespace",
@ -93,6 +98,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"namespaces keyword with multiple namespaces", "namespaces keyword with multiple namespaces",
@ -107,6 +113,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"resync period in seconds", "resync period in seconds",
@ -121,6 +128,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"resync period in minutes", "resync period in minutes",
@ -135,6 +143,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"basic label selector", "basic label selector",
@ -149,6 +158,7 @@ func TestKubernetesParse(t *testing.T) {
"environment=prod", "environment=prod",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"multi-label selector", "multi-label selector",
@ -163,6 +173,7 @@ func TestKubernetesParse(t *testing.T) {
"application=nginx,environment in (production,qa,staging)", "application=nginx,environment in (production,qa,staging)",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"fully specified valid config", "fully specified valid config",
@ -171,6 +182,7 @@ func TestKubernetesParse(t *testing.T) {
endpoint http://localhost:8080 endpoint http://localhost:8080
namespaces demo test namespaces demo test
labels environment in (production, staging, qa),application=nginx labels environment in (production, staging, qa),application=nginx
fallthrough
}`, }`,
false, false,
"", "",
@ -180,6 +192,7 @@ func TestKubernetesParse(t *testing.T) {
"application=nginx,environment in (production,qa,staging)", "application=nginx,environment in (production,qa,staging)",
defaultPodMode, defaultPodMode,
nil, nil,
true,
}, },
// negative // negative
{ {
@ -193,6 +206,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"kubernetes keyword without a zone", "kubernetes keyword without a zone",
@ -205,6 +219,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"endpoint keyword without an endpoint value", "endpoint keyword without an endpoint value",
@ -219,6 +234,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"namespace keyword without a namespace value", "namespace keyword without a namespace value",
@ -233,6 +249,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"resyncperiod keyword without a duration value", "resyncperiod keyword without a duration value",
@ -247,6 +264,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"resync period no units", "resync period no units",
@ -261,6 +279,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"resync period invalid", "resync period invalid",
@ -275,6 +294,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"labels with no selector value", "labels with no selector value",
@ -289,6 +309,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
{ {
"labels with invalid selector value", "labels with invalid selector value",
@ -303,6 +324,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
}, },
// pods disabled // pods disabled
{ {
@ -318,6 +340,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
PodModeDisabled, PodModeDisabled,
nil, nil,
false,
}, },
// pods insecure // pods insecure
{ {
@ -333,6 +356,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
PodModeInsecure, PodModeInsecure,
nil, nil,
false,
}, },
// pods verified // pods verified
{ {
@ -348,6 +372,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
PodModeVerified, PodModeVerified,
nil, nil,
false,
}, },
// pods invalid // pods invalid
{ {
@ -363,6 +388,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
PodModeVerified, PodModeVerified,
nil, nil,
false,
}, },
// cidrs ok // cidrs ok
{ {
@ -378,6 +404,7 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
[]net.IPNet{parseCidr("10.0.0.0/24"), parseCidr("10.0.1.0/24")}, []net.IPNet{parseCidr("10.0.0.0/24"), parseCidr("10.0.1.0/24")},
false,
}, },
// cidrs ok // cidrs ok
{ {
@ -393,6 +420,23 @@ func TestKubernetesParse(t *testing.T) {
"", "",
defaultPodMode, defaultPodMode,
nil, nil,
false,
},
// fallthrough invalid
{
"Extra params for fallthrough",
`kubernetes coredns.local {
fallthrough junk
}`,
true,
"Wrong argument count",
-1,
0,
defaultResyncPeriod,
"",
defaultPodMode,
nil,
false,
}, },
} }
@ -466,6 +510,11 @@ func TestKubernetesParse(t *testing.T) {
t.Errorf("Test %d: Expected kubernetes controller to be initialized with cidr '%s'. Instead found cidr '%s' for input '%s'", i, test.expectedCidrs[j].String(), foundCidrs[j].String(), test.input) t.Errorf("Test %d: Expected kubernetes controller to be initialized with cidr '%s'. Instead found cidr '%s' for input '%s'", i, test.expectedCidrs[j].String(), foundCidrs[j].String(), test.input)
} }
} }
// fallthrough
foundFallthrough := k8sController.Fallthrough
if foundFallthrough != test.expectedFallthrough {
t.Errorf("Test %d: Expected kubernetes controller to be initialized with fallthrough '%v'. Instead found fallthrough '%v' for input '%s'", i, test.expectedFallthrough, foundFallthrough, test.input)
}
} }
} }

View file

@ -3,6 +3,7 @@
package test package test
import ( import (
"os"
"testing" "testing"
"time" "time"
@ -372,6 +373,23 @@ var dnsTestCasesAllNSExposed = []test.Case{
}, },
} }
var dnsTestCasesFallthrough = []test.Case{
{
Qname: "f.b.svc.cluster.local.", Qtype: dns.TypeA,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.A("f.b.svc.cluster.local. 303 IN A 10.10.10.11"),
},
},
{
Qname: "foo.cluster.local.", Qtype: dns.TypeA,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.A("foo.cluster.local. 303 IN A 10.10.10.10"),
},
},
}
func createTestServer(t *testing.T, corefile string) (*caddy.Instance, string) { func createTestServer(t *testing.T, corefile string) (*caddy.Instance, string) {
server, err := CoreDNSServer(corefile) server, err := CoreDNSServer(corefile)
if err != nil { if err != nil {
@ -424,9 +442,6 @@ func TestKubernetesIntegration(t *testing.T) {
`.:0 { `.:0 {
kubernetes cluster.local 0.0.10.in-addr.arpa { kubernetes cluster.local 0.0.10.in-addr.arpa {
endpoint http://localhost:8080 endpoint http://localhost:8080
#endpoint https://kubernetes/
#tls admin.pem admin-key.pem ca.pem
#tls k8s_auth/client2.crt k8s_auth/client2.key k8s_auth/ca2.crt
namespaces test-1 namespaces test-1
pods disabled pods disabled
} }
@ -501,3 +516,41 @@ func TestKubernetesIntegrationAllNSExposed(t *testing.T) {
` `
doIntegrationTests(t, corefile, dnsTestCasesAllNSExposed) doIntegrationTests(t, corefile, dnsTestCasesAllNSExposed)
} }
func TestKubernetesIntegrationFallthrough(t *testing.T) {
dbfile, rmFunc, err := TempFile(os.TempDir(), clusterLocal)
if err != nil {
t.Fatalf("Could not create TempFile for fallthrough: %s", err)
}
defer rmFunc()
corefile :=
`.:0 {
file ` + dbfile + ` cluster.local
kubernetes cluster.local {
endpoint http://localhost:8080
cidrs 10.0.0.0/24
namespaces test-1
fallthrough
}
erratic {
drop 0
}
`
cases := append(dnsTestCases, dnsTestCasesFallthrough...)
doIntegrationTests(t, corefile, cases)
}
const clusterLocal = `; cluster.local test file for fallthrough
cluster.local. IN SOA sns.dns.icann.org. noc.dns.icann.org. 2015082541 7200 3600 1209600 3600
cluster.local. IN NS b.iana-servers.net.
cluster.local. IN NS a.iana-servers.net.
cluster.local. IN A 127.0.0.1
cluster.local. IN A 127.0.0.2
foo.cluster.local. IN A 10.10.10.10
f.b.svc.cluster.local. IN A 10.10.10.11
*.w.cluster.local. IN TXT "Wildcard"
a.b.svc.cluster.local. IN TXT "Not a wildcard"
cname.cluster.local. IN CNAME www.example.net.
service.namespace.svc.cluster.local. IN SRV 8080 10 10 cluster.local.
`