aboutsummaryrefslogtreecommitdiff
path: root/middleware/kubernetes
diff options
context:
space:
mode:
authorGravatar Michael Richmond <mrichmon@users.noreply.github.com> 2016-08-12 20:44:08 -0700
committerGravatar GitHub <noreply@github.com> 2016-08-12 20:44:08 -0700
commitad2838b916e9177d4a4580a6a32f0506e722e1fa (patch)
treee4510d84a1da36a91418aabf012529af9af14d3c /middleware/kubernetes
parent3b7b9b49d5d4a94dcb924b90595bbfd976f0d87c (diff)
downloadcoredns-ad2838b916e9177d4a4580a6a32f0506e722e1fa.tar.gz
coredns-ad2838b916e9177d4a4580a6a32f0506e722e1fa.tar.zst
coredns-ad2838b916e9177d4a4580a6a32f0506e722e1fa.zip
Adding label selector support to Corefile (#208)
* Adding parsing for label selector to Corefile * Updating comment typo in k8sCorefile * Adding implementation of label support to filter exposed objects * Updating TODO list
Diffstat (limited to 'middleware/kubernetes')
-rw-r--r--middleware/kubernetes/README.md31
-rw-r--r--middleware/kubernetes/controller.go49
-rw-r--r--middleware/kubernetes/kubernetes.go38
3 files changed, 80 insertions, 38 deletions
diff --git a/middleware/kubernetes/README.md b/middleware/kubernetes/README.md
index 0ae9681d4..024e448db 100644
--- a/middleware/kubernetes/README.md
+++ b/middleware/kubernetes/README.md
@@ -44,6 +44,13 @@ This is the default kubernetes setup, with everything specified in full:
template {service}.{namespace}.{zone}
# Only expose the k8s namespace "demo"
namespaces demo
+ # Only expose the records for kubernetes objects
+ # that matches this label selector. The label
+ # selector syntax is described in the kubernetes
+ # API documentation: http://kubernetes.io/docs/user-guide/labels/
+ # Example selector below only exposes objects tagged as
+ # "application=nginx" in the staging or qa environments.
+ #labels environment in (staging, qa),application=nginx
}
# Perform DNS response caching for the coredns.local zone
# Cache timeout is provided by the integer in seconds
@@ -51,10 +58,13 @@ This is the default kubernetes setup, with everything specified in full:
}
~~~
-Notes:
+Defaults:
* If the `namespaces` keyword is omitted, all kubernetes namespaces are exposed.
* If the `template` keyword is omitted, the default template of "{service}.{namespace}.{zone}" is used.
* If the `resyncperiod` keyword is omitted, the default resync period is 5 minutes.
+* The `labels` keyword is only used when filtering of results based on kubernetes label selector syntax
+ is required. The label selector syntax is described in the kubernetes API documentation at:
+ http://kubernetes.io/docs/user-guide/labels/
### Basic Setup
@@ -191,7 +201,7 @@ mynginx.demo.coredns.local. 0 IN A 10.0.0.10
## Implementation Notes/Ideas
-### Basic Zone Mapping (implemented)
+### Basic Zone Mapping
The middleware is configured with a "zone" string. For
example: "zone = coredns.local".
@@ -200,8 +210,8 @@ to: "myservice.mynamespace.coredns.local".
The middleware should publish an A record for that service and a service record.
-Initial implementation just performs the above simple mapping. Subsequent
-revisions should allow different namespaces to be published under different zones.
+If multiple zone names are specified, the records for kubernetes objects are
+exposed in all listed zones.
For example:
@@ -262,11 +272,6 @@ return the IP addresses for all services with "nginx" in the service name.
TBD:
* How does this relate the the k8s load-balancer configuration?
-* Do wildcards search across namespaces? (Yes)
-* Initial implementation assumes that a namespace maps to the first DNS label
- below the zone managed by the kubernetes middleware. This assumption may
- need to be revised. (Template scheme for record names removes this assumption.)
-
## TODO
* SkyDNS compatibility/equivalency:
@@ -318,19 +323,19 @@ TBD:
* Additional features:
* Reverse IN-ADDR entries for services. (Is there any value in supporting
reverse lookup records?) (need tests, functionality should work based on @aledbf's code.)
- * How to support label specification in Corefile to allow use of labels to
- indicate zone? (Is this even useful?) For example, the following
+ * (done) ~~How to support label specification in Corefile to allow use of labels to
+ indicate zone? For example, the following
configuration exposes all services labeled for the "staging" environment
and tenant "customerB" in the zone "customerB.stage.local":
kubernetes customerB.stage.local {
# Use url for k8s API endpoint
endpoint http://localhost:8080
- label "environment" : "staging", "tenant" : "customerB"
+ labels environment in (staging),tenant=customerB
}
Note: label specification/selection is a killer feature for segmenting
- test vs staging vs prod environments.
+ test vs staging vs prod environments.~~ Need label testing.
* Implement IP selection and ordering (internal/external). Related to
wildcards and SkyDNS use of CNAMES.
* Flatten service and namespace names to valid DNS characters. (service names
diff --git a/middleware/kubernetes/controller.go b/middleware/kubernetes/controller.go
index 3dc88d2f1..3fbea313e 100644
--- a/middleware/kubernetes/controller.go
+++ b/middleware/kubernetes/controller.go
@@ -12,6 +12,7 @@ import (
"k8s.io/kubernetes/pkg/client/cache"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/controller/framework"
+ "k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/watch"
)
@@ -23,6 +24,8 @@ var (
type dnsController struct {
client *client.Client
+ selector *labels.Selector
+
endpController *framework.Controller
svcController *framework.Controller
nsController *framework.Controller
@@ -40,68 +43,87 @@ type dnsController struct {
}
// newDNSController creates a controller for coredns
-func newdnsController(kubeClient *client.Client, resyncPeriod time.Duration) *dnsController {
+func newdnsController(kubeClient *client.Client, resyncPeriod time.Duration, lselector *labels.Selector) *dnsController {
dns := dnsController{
client: kubeClient,
+ selector: lselector,
stopCh: make(chan struct{}),
}
dns.endpLister.Store, dns.endpController = framework.NewInformer(
&cache.ListWatch{
- ListFunc: endpointsListFunc(dns.client, namespace),
- WatchFunc: endpointsWatchFunc(dns.client, namespace),
+ ListFunc: endpointsListFunc(dns.client, namespace, dns.selector),
+ WatchFunc: endpointsWatchFunc(dns.client, namespace, dns.selector),
},
&api.Endpoints{}, resyncPeriod, framework.ResourceEventHandlerFuncs{})
dns.svcLister.Store, dns.svcController = framework.NewInformer(
&cache.ListWatch{
- ListFunc: serviceListFunc(dns.client, namespace),
- WatchFunc: serviceWatchFunc(dns.client, namespace),
+ ListFunc: serviceListFunc(dns.client, namespace, dns.selector),
+ WatchFunc: serviceWatchFunc(dns.client, namespace, dns.selector),
},
&api.Service{}, resyncPeriod, framework.ResourceEventHandlerFuncs{})
dns.nsLister.Store, dns.nsController = framework.NewInformer(
&cache.ListWatch{
- ListFunc: namespaceListFunc(dns.client),
- WatchFunc: namespaceWatchFunc(dns.client),
+ ListFunc: namespaceListFunc(dns.client, dns.selector),
+ WatchFunc: namespaceWatchFunc(dns.client, dns.selector),
},
&api.Namespace{}, resyncPeriod, framework.ResourceEventHandlerFuncs{})
return &dns
}
-func serviceListFunc(c *client.Client, ns string) func(api.ListOptions) (runtime.Object, error) {
+func serviceListFunc(c *client.Client, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) {
return func(opts api.ListOptions) (runtime.Object, error) {
+ if s != nil {
+ opts.LabelSelector = *s
+ }
return c.Services(ns).List(opts)
}
}
-func serviceWatchFunc(c *client.Client, ns string) func(options api.ListOptions) (watch.Interface, error) {
+func serviceWatchFunc(c *client.Client, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) {
return func(options api.ListOptions) (watch.Interface, error) {
+ if s != nil {
+ options.LabelSelector = *s
+ }
return c.Services(ns).Watch(options)
}
}
-func endpointsListFunc(c *client.Client, ns string) func(api.ListOptions) (runtime.Object, error) {
+func endpointsListFunc(c *client.Client, ns string, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) {
return func(opts api.ListOptions) (runtime.Object, error) {
+ if s != nil {
+ opts.LabelSelector = *s
+ }
return c.Endpoints(ns).List(opts)
}
}
-func endpointsWatchFunc(c *client.Client, ns string) func(options api.ListOptions) (watch.Interface, error) {
+func endpointsWatchFunc(c *client.Client, ns string, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) {
return func(options api.ListOptions) (watch.Interface, error) {
+ if s != nil {
+ options.LabelSelector = *s
+ }
return c.Endpoints(ns).Watch(options)
}
}
-func namespaceListFunc(c *client.Client) func(api.ListOptions) (runtime.Object, error) {
+func namespaceListFunc(c *client.Client, s *labels.Selector) func(api.ListOptions) (runtime.Object, error) {
return func(opts api.ListOptions) (runtime.Object, error) {
+ if s != nil {
+ opts.LabelSelector = *s
+ }
return c.Namespaces().List(opts)
}
}
-func namespaceWatchFunc(c *client.Client) func(options api.ListOptions) (watch.Interface, error) {
+func namespaceWatchFunc(c *client.Client, s *labels.Selector) func(options api.ListOptions) (watch.Interface, error) {
return func(options api.ListOptions) (watch.Interface, error) {
+ if s != nil {
+ options.LabelSelector = *s
+ }
return c.Namespaces().Watch(options)
}
}
@@ -149,7 +171,6 @@ func (dns *dnsController) GetNamespaceList() *api.NamespaceList {
}
func (dns *dnsController) GetServiceList() *api.ServiceList {
- log.Printf("[debug] here in GetServiceList")
svcList, err := dns.svcLister.List()
if err != nil {
return &api.ServiceList{}
diff --git a/middleware/kubernetes/kubernetes.go b/middleware/kubernetes/kubernetes.go
index 7c64580c1..4fa1e494b 100644
--- a/middleware/kubernetes/kubernetes.go
+++ b/middleware/kubernetes/kubernetes.go
@@ -15,20 +15,24 @@ import (
"github.com/miekg/dns"
"k8s.io/kubernetes/pkg/api"
- "k8s.io/kubernetes/pkg/client/unversioned"
+ unversionedapi "k8s.io/kubernetes/pkg/api/unversioned"
+ "k8s.io/kubernetes/pkg/labels"
+ unversionedclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
type Kubernetes struct {
- Next middleware.Handler
- Zones []string
- Proxy proxy.Proxy // Proxy for looking up names during the resolution process
- APIEndpoint string
- APIConn *dnsController
- ResyncPeriod time.Duration
- NameTemplate *nametemplate.NameTemplate
- Namespaces []string
+ Next middleware.Handler
+ Zones []string
+ Proxy proxy.Proxy // Proxy for looking up names during the resolution process
+ APIEndpoint string
+ APIConn *dnsController
+ ResyncPeriod time.Duration
+ NameTemplate *nametemplate.NameTemplate
+ Namespaces []string
+ LabelSelector *unversionedapi.LabelSelector
+ Selector *labels.Selector
}
func (g *Kubernetes) StartKubeCache() error {
@@ -45,14 +49,26 @@ func (g *Kubernetes) StartKubeCache() error {
log.Printf("[debug] error connecting to the client: %v", err)
return err
}
- kubeClient, err := unversioned.New(config)
+ kubeClient, err := unversionedclient.New(config)
if err != nil {
log.Printf("[ERROR] Failed to create kubernetes notification controller: %v", err)
return err
}
+ if g.LabelSelector == nil {
+ log.Printf("[INFO] Kubernetes middleware configured without a label selector. No label-based filtering will be operformed.")
+ } else {
+ var selector labels.Selector
+ selector, err = unversionedapi.LabelSelectorAsSelector(g.LabelSelector)
+ g.Selector = &selector
+ if err != nil {
+ log.Printf("[ERROR] Unable to create Selector for LabelSelector '%s'.Error was: %s", g.LabelSelector, err)
+ return err
+ }
+ log.Printf("[INFO] Kubernetes middleware configured with the label selector '%s'. Only kubernetes objects matching this label selector will be exposed.", unversionedapi.FormatLabelSelector(g.LabelSelector))
+ }
log.Printf("[debug] Starting kubernetes middleware with k8s API resync period: %s", g.ResyncPeriod)
- g.APIConn = newdnsController(kubeClient, g.ResyncPeriod)
+ g.APIConn = newdnsController(kubeClient, g.ResyncPeriod, g.Selector)
go g.APIConn.Run()