diff --git a/middleware/kubernetes/README.md b/middleware/kubernetes/README.md index 14e63c163..23643ac1a 100644 --- a/middleware/kubernetes/README.md +++ b/middleware/kubernetes/README.md @@ -10,8 +10,17 @@ to deploy CoreDNS in Kubernetes](https://github.com/coredns/deployment/tree/mast ## Syntax +~~~ +kubernetes [ZONES...] +~~~ + +With only the directive specified, the *kubernetes* middleware will default to the zone specified in +the server's block. It will handle all queries in that zone and connect to Kubernetes in-cluster. It +will not provide PTR records for services, or A records for pods. If **ZONES** is used is specifies +all the zones the middleware should be authoritative for. + ``` -kubernetes ZONE [ZONE...] [ +kubernetes [ZONES...] { resyncperiod DURATION endpoint URL tls CERT KEY CACERT] @@ -23,7 +32,6 @@ kubernetes ZONE [ZONE...] [ fallthrough } ``` - * `resyncperiod` specifies the Kubernetes data API **DURATION** period. * `endpoint` specifies the **URL** for a remove k8s API endpoint. If omitted, it will connect to k8s in-cluster using the cluster service account. @@ -63,13 +71,10 @@ kubernetes ZONE [ZONE...] [ ## Examples -**Example 1:** This is a minimal configuration with no options other than zone. It will handle all queries in the `cluster.local` zone and connect to Kubernetes in-cluster, but it will not provide PTR records for services, or A records for pods. - - kubernetes cluster.local - -**Example 2:** Handle all queries in the `cluster.local` zone. Connect to Kubernetes in-cluster. - Handle all `PTR` requests for `10.0.0.0/16` . Verify the existence of pods when answering pod - requests. Resolve upstream records against `10.102.3.10`. +Handle all queries in the `cluster.local` zone. Connect to Kubernetes in-cluster. +Als handl all `PTR` requests for `10.0.0.0/16` . Verify the existence of pods when answering pod +requests. Resolve upstream records against `10.102.3.10`. Note we show the entire server block +here: 10.0.0.0/16 cluster.local { kubernetes { @@ -78,38 +83,36 @@ kubernetes ZONE [ZONE...] [ } } -**Selective Exposure Example:** Handle all queries in the `cluster.local` zone. Connect to Kubernetes in-cluster. Only expose objects in the test and staging namespaces. - Resolve upstream records using the servers configured in `/etc/resolv.conf`. +Or you can selective expose some namespaces: kubernetes cluster.local { namespaces test staging + } -**Federation Example:** Handle all queries in the `cluster.local` zone. Connect to Kubernetes in-cluster. Handle federated service requests in the `prod` and `stage` federations. - Resolve upstream records using the servers configured in `/etc/resolv.conf`. +If you want to use federation, just use the `federation` option. Here we handle all service requests +in the `prod` and `stage` federations. We resolve upstream records using the servers configured in +`/etc/resolv.conf`. - cluster.local { - kubernetes { + . { + kubernetes cluster.local { federation prod prod.feddomain.com federation stage stage.feddomain.com upstream /etc/resolv.conf } } -**Out-Of-Cluster Example:** Handle all queries in the `cluster.local` zone. Connect to Kubernetes from outside the cluster. - Verify the existence of pods when answering pod requests. Resolve upstream records against `10.102.3.10`. +And finally we connect to Kubernetes from outside the cluster: kubernetes cluster.local { endpoint https://k8s-endpoint:8443 tls cert key cacert - pods verified - upstream 10.102.3.10:53 } - ## Wildcard -Some query labels accept a wildcard value to match any value. If a label is a valid wildcard (\*, or the word "any"), then that label will match all values. The labels that accept wildcards are: +Some query labels accept a wildcard value to match any value. If a label is a valid wildcard (\*, +or the word "any"), then that label will match all values. The labels that accept wildcards are: * _service_ in an `A` record request: _service_.namespace.svc.zone. * e.g. `*.ns.svc.myzone.local` diff --git a/middleware/kubernetes/setup.go b/middleware/kubernetes/setup.go index 130dca083..8b367e1a2 100644 --- a/middleware/kubernetes/setup.go +++ b/middleware/kubernetes/setup.go @@ -87,10 +87,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { } } - if k8s.Zones == nil || len(k8s.Zones) < 1 { - return nil, errors.New("zone name must be provided for kubernetes middleware") - } - k8s.primaryZone = -1 for i, z := range k8s.Zones { if strings.HasSuffix(z, "in-addr.arpa.") || strings.HasSuffix(z, "ip6.arpa.") { diff --git a/middleware/kubernetes/setup_test.go b/middleware/kubernetes/setup_test.go index 6ad27ea2e..20a276a4c 100644 --- a/middleware/kubernetes/setup_test.go +++ b/middleware/kubernetes/setup_test.go @@ -17,7 +17,6 @@ func parseCidr(cidr string) net.IPNet { func TestKubernetesParse(t *testing.T) { tests := []struct { - description string // Human-facing description of test case input string // Corefile data as string shouldErr bool // true if test case is exected to produce an error. expectedErrContent string // substring from the expected error. Empty for positive cases. @@ -29,11 +28,9 @@ func TestKubernetesParse(t *testing.T) { expectedCidrs []net.IPNet expectedFallthrough bool expectedUpstreams []string - expectedFederations []Federation }{ // positive { - "kubernetes keyword with one zone", `kubernetes coredns.local`, false, "", @@ -45,10 +42,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "kubernetes keyword with multiple zones", `kubernetes coredns.local test.local`, false, "", @@ -60,10 +55,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "kubernetes keyword with zone and empty braces", `kubernetes coredns.local { }`, false, @@ -76,10 +69,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "endpoint keyword with url", `kubernetes coredns.local { endpoint http://localhost:9090 }`, @@ -93,10 +84,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "namespaces keyword with one namespace", `kubernetes coredns.local { namespaces demo }`, @@ -110,10 +99,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - nil, }, { - "namespaces keyword with multiple namespaces", `kubernetes coredns.local { namespaces demo test }`, @@ -127,10 +114,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "resync period in seconds", `kubernetes coredns.local { resyncperiod 30s }`, @@ -144,10 +129,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "resync period in minutes", `kubernetes coredns.local { resyncperiod 15m }`, @@ -161,10 +144,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "basic label selector", `kubernetes coredns.local { labels environment=prod }`, @@ -178,10 +159,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "multi-label selector", `kubernetes coredns.local { labels environment in (production, staging, qa),application=nginx }`, @@ -195,10 +174,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "fully specified valid config", `kubernetes coredns.local test.local { resyncperiod 15m endpoint http://localhost:8080 @@ -216,11 +193,9 @@ func TestKubernetesParse(t *testing.T) { nil, true, nil, - []Federation{}, }, // negative { - "no kubernetes keyword", "", true, "kubernetes setup called without keyword 'kubernetes' in Corefile", @@ -232,25 +207,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "kubernetes keyword without a zone", - `kubernetes`, - true, - "zone name must be provided for kubernetes middleware", - -1, - 0, - defaultResyncPeriod, - "", - PodModeDisabled, - nil, - false, - nil, - []Federation{}, - }, - { - "endpoint keyword without an endpoint value", `kubernetes coredns.local { endpoint }`, @@ -264,10 +222,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "namespace keyword without a namespace value", `kubernetes coredns.local { namespaces }`, @@ -281,10 +237,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "resyncperiod keyword without a duration value", `kubernetes coredns.local { resyncperiod }`, @@ -298,10 +252,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "resync period no units", `kubernetes coredns.local { resyncperiod 15 }`, @@ -315,10 +267,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "resync period invalid", `kubernetes coredns.local { resyncperiod abc }`, @@ -332,10 +282,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "labels with no selector value", `kubernetes coredns.local { labels }`, @@ -349,10 +297,8 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, { - "labels with invalid selector value", `kubernetes coredns.local { labels environment in (production, qa }`, @@ -366,11 +312,9 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, // pods disabled { - "pods disabled", `kubernetes coredns.local { pods disabled }`, @@ -384,11 +328,9 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, // pods insecure { - "pods insecure", `kubernetes coredns.local { pods insecure }`, @@ -402,11 +344,9 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, // pods verified { - "pods verified", `kubernetes coredns.local { pods verified }`, @@ -420,11 +360,9 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, // pods invalid { - "invalid pods mode", `kubernetes coredns.local { pods giant_seed }`, @@ -438,11 +376,9 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, // cidrs ok { - "valid cidrs", `kubernetes coredns.local { cidrs 10.0.0.0/24 10.0.1.0/24 }`, @@ -456,11 +392,9 @@ func TestKubernetesParse(t *testing.T) { []net.IPNet{parseCidr("10.0.0.0/24"), parseCidr("10.0.1.0/24")}, false, nil, - []Federation{}, }, // cidrs ok { - "invalid cidr: hard", `kubernetes coredns.local { cidrs hard dry }`, @@ -474,11 +408,9 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, // fallthrough invalid { - "Extra params for fallthrough", `kubernetes coredns.local { fallthrough junk }`, @@ -492,11 +424,9 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, }, // Valid upstream { - "valid upstream", `kubernetes coredns.local { upstream 13.14.15.16:53 }`, @@ -510,11 +440,9 @@ func TestKubernetesParse(t *testing.T) { nil, false, []string{"13.14.15.16:53"}, - []Federation{}, }, // Invalid upstream { - "valid upstream", `kubernetes coredns.local { upstream 13.14.15.16orange }`, @@ -528,47 +456,6 @@ func TestKubernetesParse(t *testing.T) { nil, false, nil, - []Federation{}, - }, - // Valid federations - { - "valid upstream", - `kubernetes coredns.local { - federation foo bar.crawl.com - federation fed era.tion.com -}`, - false, - "", - 1, - 0, - defaultResyncPeriod, - "", - PodModeDisabled, - nil, - false, - nil, - []Federation{ - {name: "foo", zone: "bar.crawl.com"}, - {name: "fed", zone: "era.tion.com"}, - }, - }, - // Invalid federations - { - "valid upstream", - `kubernetes coredns.local { - federation starship -}`, - true, - `incorrect number of arguments for federation`, - -1, - 0, - defaultResyncPeriod, - "", - PodModeDisabled, - nil, - false, - nil, - []Federation{}, }, } @@ -671,3 +558,70 @@ func TestKubernetesParse(t *testing.T) { } } } + +func TestKubernetesParseFederation(t *testing.T) { + tests := []struct { + input string // Corefile data as string + shouldErr bool // true if test case is exected to produce an error. + expectedErrContent string // substring from the expected error. Empty for positive cases. + expectedFederations []Federation + }{ + // Valid federations + { + `kubernetes coredns.local { + federation foo bar.crawl.com + federation fed era.tion.com +}`, + false, + "", + []Federation{ + {name: "foo", zone: "bar.crawl.com"}, + {name: "fed", zone: "era.tion.com"}, + }, + }, + // Invalid federations + { + `kubernetes coredns.local { + federation starship +}`, + true, + `incorrect number of arguments for federation`, + []Federation{}, + }, + } + + for i, test := range tests { + c := caddy.NewTestController("dns", test.input) + k8sController, err := kubernetesParse(c) + + if test.shouldErr && err == nil { + t.Errorf("Test %d: Expected error, but did not find error for input '%s'. Error was: '%v'", i, test.input, err) + } + + if err != nil { + if !test.shouldErr { + t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) + continue + } + + if test.shouldErr && (len(test.expectedErrContent) < 1) { + t.Fatalf("Test %d: Test marked as expecting an error, but no expectedErrContent provided for input '%s'. Error was: '%v'", i, test.input, err) + } + + if !strings.Contains(err.Error(), test.expectedErrContent) { + t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) + } + continue + } + + foundFed := k8sController.Federations + if len(foundFed) != len(test.expectedFederations) { + t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d fedrations. Instead found %d fedrations for input '%s'", i, len(test.expectedFederations), len(foundFed), test.input) + } + for j, fed := range test.expectedFederations { + if fed != foundFed[j] { + t.Errorf("Test %d: Expected kubernetes controller to be initialized with federation '%s'. Instead found federation '%s' for input '%s'", i, test.expectedFederations[j], foundFed[j], test.input) + } + } + } +}