aboutsummaryrefslogtreecommitdiff
path: root/plugin/kubernetes
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/kubernetes')
-rw-r--r--plugin/kubernetes/controller.go6
-rw-r--r--plugin/kubernetes/external.go92
-rw-r--r--plugin/kubernetes/external_test.go139
-rw-r--r--plugin/kubernetes/ns.go4
-rw-r--r--plugin/kubernetes/object/service.go12
5 files changed, 252 insertions, 1 deletions
diff --git a/plugin/kubernetes/controller.go b/plugin/kubernetes/controller.go
index df90fcf82..9a2e9994a 100644
--- a/plugin/kubernetes/controller.go
+++ b/plugin/kubernetes/controller.go
@@ -172,7 +172,11 @@ func svcIPIndexFunc(obj interface{}) ([]string, error) {
if !ok {
return nil, errObj
}
- return []string{svc.ClusterIP}, nil
+ if len(svc.ExternalIPs) == 0 {
+ return []string{svc.ClusterIP}, nil
+ }
+
+ return append([]string{svc.ClusterIP}, svc.ExternalIPs...), nil
}
func svcNameNamespaceIndexFunc(obj interface{}) ([]string, error) {
diff --git a/plugin/kubernetes/external.go b/plugin/kubernetes/external.go
new file mode 100644
index 000000000..1770872b3
--- /dev/null
+++ b/plugin/kubernetes/external.go
@@ -0,0 +1,92 @@
+package kubernetes
+
+import (
+ "strings"
+
+ "github.com/coredns/coredns/plugin/etcd/msg"
+ "github.com/coredns/coredns/plugin/kubernetes/object"
+ "github.com/coredns/coredns/plugin/pkg/dnsutil"
+ "github.com/coredns/coredns/request"
+
+ "github.com/miekg/dns"
+)
+
+// External implements the ExternalFunc call from the external plugin.
+// It returns any services matching in the services' ExternalIPs.
+func (k *Kubernetes) External(state request.Request) ([]msg.Service, int) {
+ base, _ := dnsutil.TrimZone(state.Name(), state.Zone)
+
+ segs := dns.SplitDomainName(base)
+ last := len(segs) - 1
+ if last < 0 {
+ return nil, dns.RcodeServerFailure
+ }
+ // We dealing with a fairly normal domain name here, but; we still need to have the service
+ // and the namespace:
+ // service.namespace.<base>
+ //
+ // for service (and SRV) you can also say _tcp, and port (i.e. _http), we need those be picked
+ // up, unless they are not specified, then we use an internal wildcard.
+ port := "*"
+ protocol := "*"
+ namespace := segs[last]
+ if !k.namespaceExposed(namespace) || !k.namespace(namespace) {
+ return nil, dns.RcodeNameError
+ }
+
+ last--
+ if last < 0 {
+ return nil, dns.RcodeSuccess
+ }
+
+ service := segs[last]
+ last--
+ if last == 1 {
+ protocol = stripUnderscore(segs[last])
+ port = stripUnderscore(segs[last-1])
+ last -= 2
+ }
+
+ if last != -1 {
+ // too long
+ return nil, dns.RcodeNameError
+ }
+
+ idx := object.ServiceKey(service, namespace)
+ serviceList := k.APIConn.SvcIndex(idx)
+
+ services := []msg.Service{}
+ zonePath := msg.Path(state.Zone, coredns)
+ rcode := dns.RcodeNameError
+
+ for _, svc := range serviceList {
+ if namespace != svc.Namespace {
+ continue
+ }
+ if service != svc.Name {
+ continue
+ }
+
+ for _, ip := range svc.ExternalIPs {
+ for _, p := range svc.Ports {
+ if !(match(port, p.Name) && match(protocol, string(p.Protocol))) {
+ continue
+ }
+ rcode = dns.RcodeSuccess
+ s := msg.Service{Host: ip, Port: int(p.Port), TTL: k.ttl}
+ s.Key = strings.Join([]string{zonePath, svc.Namespace, svc.Name}, "/")
+
+ services = append(services, s)
+ }
+ }
+ }
+ return services, rcode
+}
+
+// ExternalAddress returns the external service address(es) for the CoreDNS service.
+func (k *Kubernetes) ExternalAddress(state request.Request) []dns.RR {
+ // This is probably wrong, because of all the fallback behavior of k.nsAddr, i.e. can get
+ // an address that isn't reacheable from outside the cluster.
+ rrs := []dns.RR{k.nsAddr()}
+ return rrs
+}
diff --git a/plugin/kubernetes/external_test.go b/plugin/kubernetes/external_test.go
new file mode 100644
index 000000000..7fb5469e1
--- /dev/null
+++ b/plugin/kubernetes/external_test.go
@@ -0,0 +1,139 @@
+package kubernetes
+
+import (
+ "testing"
+
+ "github.com/coredns/coredns/plugin/etcd/msg"
+ "github.com/coredns/coredns/plugin/kubernetes/object"
+ "github.com/coredns/coredns/plugin/pkg/watch"
+ "github.com/coredns/coredns/plugin/test"
+ "github.com/coredns/coredns/request"
+
+ "github.com/miekg/dns"
+ api "k8s.io/api/core/v1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var extCases = []struct {
+ Qname string
+ Qtype uint16
+ Msg []msg.Service
+ Rcode int
+}{
+ {
+ Qname: "svc1.testns.example.org.", Rcode: dns.RcodeSuccess,
+ Msg: []msg.Service{
+ msg.Service{Host: "1.2.3.4", Port: 80, TTL: 5, Key: "/c/org/example/testns/svc1"},
+ },
+ },
+ {
+ Qname: "svc6.testns.example.org.", Rcode: dns.RcodeSuccess,
+ Msg: []msg.Service{
+ msg.Service{Host: "1:2::5", Port: 80, TTL: 5, Key: "/c/org/example/testns/svc1"},
+ },
+ },
+ {
+ Qname: "*._not-udp-or-tcp.svc1.testns.example.com.", Rcode: dns.RcodeSuccess,
+ },
+ {
+ Qname: "_http._tcp.svc1.testns.example.com.", Rcode: dns.RcodeSuccess,
+ Msg: []msg.Service{
+ msg.Service{Host: "1.2.3.4", Port: 80, TTL: 5, Key: "/c/org/example/testns/svc1"},
+ },
+ },
+ {
+ Qname: "svc0.testns.example.com.", Rcode: dns.RcodeNameError,
+ },
+ {
+ Qname: "svc0.svc-nons.example.com.", Rcode: dns.RcodeNameError,
+ },
+}
+
+func TestExternal(t *testing.T) {
+ k := New([]string{"cluster.local."})
+ k.APIConn = &external{}
+ k.Next = test.NextHandler(dns.RcodeSuccess, nil)
+ k.Namespaces = map[string]struct{}{"testns": struct{}{}}
+
+ for i, tc := range extCases {
+ state := testRequest(tc.Qname)
+
+ svc, rcode := k.External(state)
+
+ if x := tc.Rcode; x != rcode {
+ t.Errorf("Test %d, expected rcode %d, got %d\n", i, x, rcode)
+ }
+
+ if len(svc) != len(tc.Msg) {
+ t.Errorf("Test %d, expected %d for messages, got %d\n", i, len(tc.Msg), len(svc))
+ }
+
+ for j, s := range svc {
+ if x := tc.Msg[j].Key; x != s.Key {
+ t.Errorf("Test %d, expected key %s, got %s\n", i, x, s.Key)
+ }
+ return
+ }
+ }
+}
+
+type external struct{}
+
+func (external) HasSynced() bool { return true }
+func (external) Run() { return }
+func (external) Stop() error { return nil }
+func (external) EpIndexReverse(string) []*object.Endpoints { return nil }
+func (external) SvcIndexReverse(string) []*object.Service { return nil }
+func (external) Modified() int64 { return 0 }
+func (external) SetWatchChan(watch.Chan) {}
+func (external) Watch(string) error { return nil }
+func (external) StopWatching(string) {}
+func (external) EpIndex(s string) []*object.Endpoints { return nil }
+func (external) EndpointsList() []*object.Endpoints { return nil }
+func (external) GetNodeByName(name string) (*api.Node, error) { return nil, nil }
+func (external) SvcIndex(s string) []*object.Service { return svcIndexExternal[s] }
+func (external) PodIndex(string) []*object.Pod { return nil }
+
+func (external) GetNamespaceByName(name string) (*api.Namespace, error) {
+ return &api.Namespace{
+ ObjectMeta: meta.ObjectMeta{
+ Name: name,
+ },
+ }, nil
+}
+
+var svcIndexExternal = map[string][]*object.Service{
+ "svc1.testns": {
+ {
+ Name: "svc1",
+ Namespace: "testns",
+ Type: api.ServiceTypeClusterIP,
+ ClusterIP: "10.0.0.1",
+ ExternalIPs: []string{"1.2.3.4"},
+ Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
+ },
+ },
+ "svc6.testns": {
+ {
+ Name: "svc6",
+ Namespace: "testns",
+ Type: api.ServiceTypeClusterIP,
+ ClusterIP: "10.0.0.3",
+ ExternalIPs: []string{"1:2::5"},
+ Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
+ },
+ },
+}
+
+func (external) ServiceList() []*object.Service {
+ var svcs []*object.Service
+ for _, svc := range svcIndexExternal {
+ svcs = append(svcs, svc...)
+ }
+ return svcs
+}
+
+func testRequest(name string) request.Request {
+ m := new(dns.Msg).SetQuestion(name, dns.TypeA)
+ return request.Request{W: &test.ResponseWriter{}, Req: m, Zone: "example.org."}
+}
diff --git a/plugin/kubernetes/ns.go b/plugin/kubernetes/ns.go
index 2ccb51ef3..f3d33ee22 100644
--- a/plugin/kubernetes/ns.go
+++ b/plugin/kubernetes/ns.go
@@ -12,6 +12,10 @@ func isDefaultNS(name, zone string) bool {
return strings.Index(name, defaultNSName) == 0 && strings.Index(name, zone) == len(defaultNSName)
}
+// nsAddr return the A record for the CoreDNS service in the cluster. If it fails that it fallsback
+// on the local address of the machine we're running on.
+//
+// This function is rather expensive to run.
func (k *Kubernetes) nsAddr() *dns.A {
var (
svcName string
diff --git a/plugin/kubernetes/object/service.go b/plugin/kubernetes/object/service.go
index be010e96b..af9a42b48 100644
--- a/plugin/kubernetes/object/service.go
+++ b/plugin/kubernetes/object/service.go
@@ -16,6 +16,9 @@ type Service struct {
ExternalName string
Ports []api.ServicePort
+ // ExternalIPs we may want to export.
+ ExternalIPs []string
+
*Empty
}
@@ -37,6 +40,8 @@ func ToService(obj interface{}) interface{} {
ClusterIP: svc.Spec.ClusterIP,
Type: svc.Spec.Type,
ExternalName: svc.Spec.ExternalName,
+
+ ExternalIPs: make([]string, len(svc.Status.LoadBalancer.Ingress)+len(svc.Spec.ExternalIPs)),
}
if len(svc.Spec.Ports) == 0 {
@@ -47,6 +52,11 @@ func ToService(obj interface{}) interface{} {
copy(s.Ports, svc.Spec.Ports)
}
+ li := copy(s.ExternalIPs, svc.Spec.ExternalIPs)
+ for i, lb := range svc.Status.LoadBalancer.Ingress {
+ s.ExternalIPs[li+i] = lb.IP
+ }
+
*svc = api.Service{}
return s
@@ -65,8 +75,10 @@ func (s *Service) DeepCopyObject() runtime.Object {
Type: s.Type,
ExternalName: s.ExternalName,
Ports: make([]api.ServicePort, len(s.Ports)),
+ ExternalIPs: make([]string, len(s.ExternalIPs)),
}
copy(s1.Ports, s.Ports)
+ copy(s1.ExternalIPs, s.ExternalIPs)
return s1
}