aboutsummaryrefslogtreecommitdiff
path: root/middleware/kubernetes/kubernetes.go
diff options
context:
space:
mode:
authorGravatar Chris O'Haver <cohaver@infoblox.com> 2017-05-22 16:05:48 -0400
committerGravatar John Belamaric <jbelamaric@infoblox.com> 2017-05-22 16:05:48 -0400
commit7f950e496ab8ae518d8f78f3a80225604dd90da3 (patch)
tree5e03de273df60b8072c649551be90c55714e00e3 /middleware/kubernetes/kubernetes.go
parent024f56682dcaaaae2dd990d6fae3b54c8d17c467 (diff)
downloadcoredns-7f950e496ab8ae518d8f78f3a80225604dd90da3.tar.gz
coredns-7f950e496ab8ae518d8f78f3a80225604dd90da3.tar.zst
coredns-7f950e496ab8ae518d8f78f3a80225604dd90da3.zip
Handle K8s middleware NS record (#662)
* commit for testing in cluster * commit for testing in cluster * refactor and add ns.dns record * Release 007 * reduce heap allocations * gofmt * revert accidental Makefile commits * restore prior rcode for disabled pod mode * revert Makefile deltas * add unit tests * more unit tests * make isRequestInReverseRange easier to test * more unit tests * addressing review feedback * commit setup.go
Diffstat (limited to 'middleware/kubernetes/kubernetes.go')
-rw-r--r--middleware/kubernetes/kubernetes.go141
1 files changed, 76 insertions, 65 deletions
diff --git a/middleware/kubernetes/kubernetes.go b/middleware/kubernetes/kubernetes.go
index 01e0c9115..5b7b7bc3c 100644
--- a/middleware/kubernetes/kubernetes.go
+++ b/middleware/kubernetes/kubernetes.go
@@ -28,22 +28,23 @@ import (
// Kubernetes implements a middleware that connects to a Kubernetes cluster.
type Kubernetes struct {
- Next middleware.Handler
- Zones []string
- primaryZone int
- Proxy proxy.Proxy // Proxy for looking up names during the resolution process
- APIEndpoint string
- APICertAuth string
- APIClientCert string
- APIClientKey string
- APIConn *dnsController
- ResyncPeriod time.Duration
- Namespaces []string
- LabelSelector *unversionedapi.LabelSelector
- Selector *labels.Selector
- PodMode string
- ReverseCidrs []net.IPNet
- Fallthrough bool
+ Next middleware.Handler
+ Zones []string
+ primaryZone int
+ Proxy proxy.Proxy // Proxy for looking up names during the resolution process
+ APIEndpoint string
+ APICertAuth string
+ APIClientCert string
+ APIClientKey string
+ APIConn dnsController
+ ResyncPeriod time.Duration
+ Namespaces []string
+ LabelSelector *unversionedapi.LabelSelector
+ Selector *labels.Selector
+ PodMode string
+ ReverseCidrs []net.IPNet
+ Fallthrough bool
+ interfaceAddrs InterfaceAddrser
}
const (
@@ -83,36 +84,49 @@ type recordRequest struct {
var errNoItems = errors.New("no items found")
var errNsNotExposed = errors.New("namespace is not exposed")
var errInvalidRequest = errors.New("invalid query name")
+var errZoneNotFound = errors.New("zone not found")
+var errApiBadPodType = errors.New("expected type *api.Pod")
+var errPodsDisabled = errors.New("pod records disabled")
// Services implements the ServiceBackend interface.
-func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) ([]msg.Service, []msg.Service, error) {
+func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) (svcs []msg.Service, debug []msg.Service, err error) {
- r, e := k.parseRequest(state.Name(), state.Type())
+ r, e := k.parseRequest(state.Name(), state.QType())
if e != nil {
return nil, nil, e
}
switch state.Type() {
case "A", "SRV":
+ if state.Type() == "A" && isDefaultNS(state.Name(), r) {
+ // If this is an A request for "ns.dns", respond with a "fake" record for coredns.
+ // SOA records always use this hardcoded name
+ svcs = append(svcs, k.defaultNSMsg(r))
+ return svcs, nil, nil
+ }
s, e := k.Records(r)
return s, nil, e // Haven't implemented debug queries yet.
case "TXT":
- s, e := k.recordsForTXT(r)
- return s, nil, e
+ err := k.recordsForTXT(r, &svcs)
+ return svcs, nil, err
+ case "NS":
+ err = k.recordsForNS(r, &svcs)
+ return svcs, nil, err
}
return nil, nil, nil
}
-func (k *Kubernetes) recordsForTXT(r recordRequest) ([]msg.Service, error) {
+func (k *Kubernetes) recordsForTXT(r recordRequest, svcs *[]msg.Service) (err error) {
switch r.typeName {
case "dns-version":
s := msg.Service{
Text: DNSSchemaVersion,
TTL: 28800,
- Key: msg.Path(r.typeName+"."+r.zone, "coredns")}
- return []msg.Service{s}, nil
+ Key: msg.Path(strings.Join([]string{r.typeName, r.zone}, "."), "coredns")}
+ *svcs = append(*svcs, s)
+ return nil
}
- return nil, nil
+ return nil
}
// PrimaryZone will return the first non-reverse zone being handled by this middleware
@@ -122,6 +136,7 @@ func (k *Kubernetes) PrimaryZone() string {
// Reverse implements the ServiceBackend interface.
func (k *Kubernetes) Reverse(state request.Request, exact bool, opt middleware.Options) ([]msg.Service, []msg.Service, error) {
+
ip := dnsutil.ExtractAddressFromReverse(state.Name())
if ip == "" {
return nil, nil, nil
@@ -131,8 +146,8 @@ func (k *Kubernetes) Reverse(state request.Request, exact bool, opt middleware.O
return records, nil, nil
}
-func (k *Kubernetes) isRequestInReverseRange(state request.Request) bool {
- ip := dnsutil.ExtractAddressFromReverse(state.Name())
+func (k *Kubernetes) isRequestInReverseRange(name string) bool {
+ ip := dnsutil.ExtractAddressFromReverse(name)
for _, c := range k.ReverseCidrs {
if c.Contains(net.ParseIP(ip)) {
return true
@@ -186,7 +201,8 @@ func (k *Kubernetes) getClientConfig() (*rest.Config, error) {
}
// InitKubeCache initializes a new Kubernetes cache.
-func (k *Kubernetes) InitKubeCache() error {
+
+func (k *Kubernetes) InitKubeCache() (err error) {
config, err := k.getClientConfig()
if err != nil {
@@ -216,12 +232,11 @@ func (k *Kubernetes) InitKubeCache() error {
return err
}
-func (k *Kubernetes) parseRequest(lowerCasedName, qtype string) (r recordRequest, err error) {
+func (k *Kubernetes) parseRequest(lowerCasedName string, qtype uint16) (r recordRequest, err error) {
// 3 Possible cases
// SRV Request: _port._protocol.service.namespace.type.zone
// A Request (endpoint): endpoint.service.namespace.type.zone
// A Request (service): service.namespace.type.zone
-
// separate zone from rest of lowerCasedName
var segs []string
for _, z := range k.Zones {
@@ -234,11 +249,19 @@ func (k *Kubernetes) parseRequest(lowerCasedName, qtype string) (r recordRequest
}
}
if r.zone == "" {
- return r, errors.New("zone not found")
+ return r, errZoneNotFound
+ }
+
+ if qtype == dns.TypeNS {
+ return r, nil
+ }
+
+ if qtype == dns.TypeA && isDefaultNS(lowerCasedName, r) {
+ return r, nil
}
offset := 0
- if qtype == "SRV" {
+ if qtype == dns.TypeSRV {
if len(segs) != 5 {
return r, errInvalidRequest
}
@@ -268,7 +291,7 @@ func (k *Kubernetes) parseRequest(lowerCasedName, qtype string) (r recordRequest
}
offset = 2
}
- if qtype == "A" && len(segs) == 4 {
+ if qtype == dns.TypeA && len(segs) == 4 {
// This is an endpoint A record request. Get first element as endpoint.
r.endpoint = segs[0]
offset = 1
@@ -282,7 +305,7 @@ func (k *Kubernetes) parseRequest(lowerCasedName, qtype string) (r recordRequest
return r, nil
}
- if len(segs) == 1 && qtype == "TXT" {
+ if len(segs) == 1 && qtype == dns.TypeTXT {
r.typeName = segs[0]
return r, nil
}
@@ -328,37 +351,35 @@ func endpointHostname(addr api.EndpointAddress) string {
return ""
}
-func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, zone string) []msg.Service {
- var records []msg.Service
+func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, zone string) (records []msg.Service) {
+ zonePath := msg.Path(zone, "coredns")
for _, svc := range services {
-
- key := svc.name + "." + svc.namespace + ".svc." + zone
-
if svc.addr == api.ClusterIPNone {
// This is a headless service, create records for each endpoint
for _, ep := range svc.endpoints {
- ephostname := endpointHostname(ep.addr)
s := msg.Service{
- Key: msg.Path(strings.ToLower(ephostname+"."+key), "coredns"),
- Host: ep.addr.IP, Port: int(ep.port.Port),
+ Key: strings.Join([]string{zonePath, "svc", svc.namespace, svc.name, endpointHostname(ep.addr)}, "/"),
+ Host: ep.addr.IP,
+ Port: int(ep.port.Port),
}
records = append(records, s)
-
}
} else {
// Create records for each exposed port...
for _, p := range svc.ports {
- s := msg.Service{Key: msg.Path(strings.ToLower(key), "coredns"), Host: svc.addr, Port: int(p.Port)}
+ s := msg.Service{
+ Key: strings.Join([]string{zonePath, "svc", svc.namespace, svc.name}, "/"),
+ Host: svc.addr,
+ Port: int(p.Port)}
records = append(records, s)
}
}
}
for _, p := range pods {
- key := p.name + "." + p.namespace + ".pod." + zone
s := msg.Service{
- Key: msg.Path(strings.ToLower(key), "coredns"),
+ Key: strings.Join([]string{zonePath, "pod", p.namespace, p.name}, "/"),
Host: p.addr,
}
records = append(records, s)
@@ -376,7 +397,7 @@ func ipFromPodName(podname string) string {
func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error) {
if k.PodMode == PodModeDisabled {
- return pods, errors.New("pod records disabled")
+ return pods, errPodsDisabled
}
var ip string
@@ -393,16 +414,13 @@ func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error)
}
// PodModeVerified
- objList, err := k.APIConn.podLister.Indexer.ByIndex(podIPIndex, ip)
- if err != nil {
- return nil, err
- }
+ objList := k.APIConn.PodIndex(ip)
nsWildcard := symbolContainsWildcard(namespace)
for _, o := range objList {
p, ok := o.(*api.Pod)
if !ok {
- return nil, errors.New("expected type *api.Pod")
+ return nil, errApiBadPodType
}
// If namespace has a wildcard, filter results against Corefile namespace list.
if nsWildcard && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(p.Namespace, k.Namespaces)) {
@@ -461,10 +479,8 @@ func (k *Kubernetes) findServices(r recordRequest) ([]service, error) {
continue
}
// Headless service
- endpointsList, err := k.APIConn.epLister.List()
- if err != nil {
- continue
- }
+ endpointsList := k.APIConn.EndpointsList()
+
for _, ep := range endpointsList.Items {
if ep.ObjectMeta.Name != svc.Name || ep.ObjectMeta.Namespace != svc.Namespace {
continue
@@ -500,24 +516,19 @@ func symbolMatches(queryString, candidateString string, wildcard bool) bool {
// If a service cluster ip does not match, it checks all endpoints
func (k *Kubernetes) getServiceRecordForIP(ip, name string) []msg.Service {
// First check services with cluster ips
- svcList, err := k.APIConn.svcLister.List(labels.Everything())
- if err != nil {
- return nil
- }
+ svcList := k.APIConn.ServiceList()
+
for _, service := range svcList {
if (len(k.Namespaces) > 0) && !dnsstrings.StringInSlice(service.Namespace, k.Namespaces) {
continue
}
if service.Spec.ClusterIP == ip {
- domain := service.Name + "." + service.Namespace + ".svc." + k.PrimaryZone()
+ domain := strings.Join([]string{service.Name, service.Namespace, "svc", k.PrimaryZone()}, ".")
return []msg.Service{{Host: domain}}
}
}
// If no cluster ips match, search endpoints
- epList, err := k.APIConn.epLister.List()
- if err != nil {
- return nil
- }
+ epList := k.APIConn.EndpointsList()
for _, ep := range epList.Items {
if (len(k.Namespaces) > 0) && !dnsstrings.StringInSlice(ep.ObjectMeta.Namespace, k.Namespaces) {
continue
@@ -525,7 +536,7 @@ func (k *Kubernetes) getServiceRecordForIP(ip, name string) []msg.Service {
for _, eps := range ep.Subsets {
for _, addr := range eps.Addresses {
if addr.IP == ip {
- domain := endpointHostname(addr) + "." + ep.ObjectMeta.Name + "." + ep.ObjectMeta.Namespace + ".svc." + k.PrimaryZone()
+ domain := strings.Join([]string{endpointHostname(addr), ep.ObjectMeta.Name, ep.ObjectMeta.Namespace, "svc", k.PrimaryZone()}, ".")
return []msg.Service{{Host: domain}}
}
}