diff options
Diffstat (limited to 'plugin/kubernetes')
-rw-r--r-- | plugin/kubernetes/controller.go | 6 | ||||
-rw-r--r-- | plugin/kubernetes/external.go | 92 | ||||
-rw-r--r-- | plugin/kubernetes/external_test.go | 139 | ||||
-rw-r--r-- | plugin/kubernetes/ns.go | 4 | ||||
-rw-r--r-- | plugin/kubernetes/object/service.go | 12 |
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 } |