diff --git a/plugin/k8s_external/external_test.go b/plugin/k8s_external/external_test.go index 45584b6b1..e51fc6895 100644 --- a/plugin/k8s_external/external_test.go +++ b/plugin/k8s_external/external_test.go @@ -190,7 +190,7 @@ var svcIndexExternal = map[string][]*object.Service{ Name: "svc1", Namespace: "testns", Type: api.ServiceTypeClusterIP, - ClusterIP: "10.0.0.1", + ClusterIPs: []string{"10.0.0.1"}, ExternalIPs: []string{"1.2.3.4"}, Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, }, @@ -200,7 +200,7 @@ var svcIndexExternal = map[string][]*object.Service{ Name: "svc6", Namespace: "testns", Type: api.ServiceTypeClusterIP, - ClusterIP: "10.0.0.3", + ClusterIPs: []string{"10.0.0.3"}, ExternalIPs: []string{"1:2::5"}, Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, }, diff --git a/plugin/kubernetes/controller.go b/plugin/kubernetes/controller.go index 2319cf203..d10d9f313 100644 --- a/plugin/kubernetes/controller.go +++ b/plugin/kubernetes/controller.go @@ -200,11 +200,13 @@ func svcIPIndexFunc(obj interface{}) ([]string, error) { if !ok { return nil, errObj } + idx := make([]string, len(svc.ClusterIPs)+len(svc.ExternalIPs)) + copy(idx, svc.ClusterIPs) if len(svc.ExternalIPs) == 0 { - return []string{svc.ClusterIP}, nil + return idx, nil } - - return append([]string{svc.ClusterIP}, svc.ExternalIPs...), nil + copy(idx[len(svc.ClusterIPs):], svc.ExternalIPs) + return idx, nil } func svcNameNamespaceIndexFunc(obj interface{}) ([]string, error) { diff --git a/plugin/kubernetes/external_test.go b/plugin/kubernetes/external_test.go index 4855cfc63..560983e4c 100644 --- a/plugin/kubernetes/external_test.go +++ b/plugin/kubernetes/external_test.go @@ -105,7 +105,7 @@ var svcIndexExternal = map[string][]*object.Service{ Name: "svc1", Namespace: "testns", Type: api.ServiceTypeClusterIP, - ClusterIP: "10.0.0.1", + ClusterIPs: []string{"10.0.0.1"}, ExternalIPs: []string{"1.2.3.4"}, Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, }, @@ -115,7 +115,7 @@ var svcIndexExternal = map[string][]*object.Service{ Name: "svc6", Namespace: "testns", Type: api.ServiceTypeClusterIP, - ClusterIP: "10.0.0.3", + ClusterIPs: []string{"10.0.0.3"}, ExternalIPs: []string{"1:2::5"}, Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, }, diff --git a/plugin/kubernetes/handler_test.go b/plugin/kubernetes/handler_test.go index f5630ccdd..71609a6de 100644 --- a/plugin/kubernetes/handler_test.go +++ b/plugin/kubernetes/handler_test.go @@ -372,6 +372,30 @@ var dnsTestCases = []test.Case{ test.SOA("cluster.local. 5 IN SOA ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"), }, }, + // Dual Stack ClusterIP Services + { + Qname: "svc-dual-stack.testns.svc.cluster.local.", Qtype: dns.TypeA, + Rcode: dns.RcodeSuccess, + Answer: []dns.RR{ + test.A("svc-dual-stack.testns.svc.cluster.local. 5 IN A 10.0.0.3"), + }, + }, + { + Qname: "svc-dual-stack.testns.svc.cluster.local.", Qtype: dns.TypeAAAA, + Rcode: dns.RcodeSuccess, + Answer: []dns.RR{ + test.AAAA("svc-dual-stack.testns.svc.cluster.local. 5 IN AAAA 10::3"), + }, + }, + { + Qname: "svc-dual-stack.testns.svc.cluster.local.", Qtype: dns.TypeSRV, + Rcode: dns.RcodeSuccess, + Answer: []dns.RR{test.SRV("svc-dual-stack.testns.svc.cluster.local. 5 IN SRV 0 50 80 svc-dual-stack.testns.svc.cluster.local.")}, + Extra: []dns.RR{ + test.A("svc-dual-stack.testns.svc.cluster.local. 5 IN A 10.0.0.3"), + test.AAAA("svc-dual-stack.testns.svc.cluster.local. 5 IN AAAA 10::3"), + }, + }, } func TestServeDNS(t *testing.T) { @@ -539,10 +563,10 @@ func (APIConnServeTest) PodIndex(ip string) []*object.Pod { var svcIndex = map[string][]*object.Service{ "svc1.testns": { { - Name: "svc1", - Namespace: "testns", - Type: api.ServiceTypeClusterIP, - ClusterIP: "10.0.0.1", + Name: "svc1", + Namespace: "testns", + Type: api.ServiceTypeClusterIP, + ClusterIPs: []string{"10.0.0.1"}, Ports: []api.ServicePort{ {Name: "http", Protocol: "tcp", Port: 80}, }, @@ -550,10 +574,10 @@ var svcIndex = map[string][]*object.Service{ }, "svcempty.testns": { { - Name: "svcempty", - Namespace: "testns", - Type: api.ServiceTypeClusterIP, - ClusterIP: "10.0.0.1", + Name: "svcempty", + Namespace: "testns", + Type: api.ServiceTypeClusterIP, + ClusterIPs: []string{"10.0.0.1"}, Ports: []api.ServicePort{ {Name: "http", Protocol: "tcp", Port: 80}, }, @@ -561,10 +585,10 @@ var svcIndex = map[string][]*object.Service{ }, "svc6.testns": { { - Name: "svc6", - Namespace: "testns", - Type: api.ServiceTypeClusterIP, - ClusterIP: "1234:abcd::1", + Name: "svc6", + Namespace: "testns", + Type: api.ServiceTypeClusterIP, + ClusterIPs: []string{"1234:abcd::1"}, Ports: []api.ServicePort{ {Name: "http", Protocol: "tcp", Port: 80}, }, @@ -572,10 +596,10 @@ var svcIndex = map[string][]*object.Service{ }, "hdls1.testns": { { - Name: "hdls1", - Namespace: "testns", - Type: api.ServiceTypeClusterIP, - ClusterIP: api.ClusterIPNone, + Name: "hdls1", + Namespace: "testns", + Type: api.ServiceTypeClusterIP, + ClusterIPs: []string{api.ClusterIPNone}, }, }, "external.testns": { @@ -602,23 +626,33 @@ var svcIndex = map[string][]*object.Service{ }, "hdlsprtls.testns": { { - Name: "hdlsprtls", - Namespace: "testns", - Type: api.ServiceTypeClusterIP, - ClusterIP: api.ClusterIPNone, + Name: "hdlsprtls", + Namespace: "testns", + Type: api.ServiceTypeClusterIP, + ClusterIPs: []string{api.ClusterIPNone}, }, }, "svc1.unexposedns": { { - Name: "svc1", - Namespace: "unexposedns", - Type: api.ServiceTypeClusterIP, - ClusterIP: "10.0.0.2", + Name: "svc1", + Namespace: "unexposedns", + Type: api.ServiceTypeClusterIP, + ClusterIPs: []string{"10.0.0.2"}, Ports: []api.ServicePort{ {Name: "http", Protocol: "tcp", Port: 80}, }, }, }, + "svc-dual-stack.testns": { + { + Name: "svc-dual-stack", + Namespace: "testns", + Type: api.ServiceTypeClusterIP, + ClusterIPs: []string{"10.0.0.3", "10::3"}, Ports: []api.ServicePort{ + {Name: "http", Protocol: "tcp", Port: 80}, + }, + }, + }, } func (APIConnServeTest) SvcIndex(s string) []*object.Service { return svcIndex[s] } diff --git a/plugin/kubernetes/informer_test.go b/plugin/kubernetes/informer_test.go index 7aa9d1e83..ae68b5cfe 100644 --- a/plugin/kubernetes/informer_test.go +++ b/plugin/kubernetes/informer_test.go @@ -1,6 +1,7 @@ package kubernetes import ( + "fmt" "testing" "github.com/coredns/coredns/plugin/kubernetes/object" @@ -21,11 +22,19 @@ func TestDefaultProcessor(t *testing.T) { func testProcessor(t *testing.T, processor cache.ProcessFunc, idx cache.Indexer) { obj := &api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "service1", Namespace: "test1"}, - Spec: api.ServiceSpec{ClusterIP: "1.2.3.4", Ports: []api.ServicePort{{Port: 80}}}, + Spec: api.ServiceSpec{ + ClusterIP: "1.2.3.4", + ClusterIPs: []string{"1.2.3.4"}, + Ports: []api.ServicePort{{Port: 80}}, + }, } obj2 := &api.Service{ ObjectMeta: metav1.ObjectMeta{Name: "service2", Namespace: "test1"}, - Spec: api.ServiceSpec{ClusterIP: "5.6.7.8", Ports: []api.ServicePort{{Port: 80}}}, + Spec: api.ServiceSpec{ + ClusterIP: "5.6.7.8", + ClusterIPs: []string{"5.6.7.8"}, + Ports: []api.ServicePort{{Port: 80}}, + }, } // Add the objects @@ -47,8 +56,8 @@ func testProcessor(t *testing.T, processor cache.ProcessFunc, idx cache.Indexer) if !ok { t.Fatal("object in index was incorrect type") } - if svc.ClusterIP != obj.Spec.ClusterIP { - t.Fatalf("expected %v, got %v", obj.Spec.ClusterIP, svc.ClusterIP) + if fmt.Sprintf("%v", svc.ClusterIPs) != fmt.Sprintf("%v", obj.Spec.ClusterIPs) { + t.Fatalf("expected '%v', got '%v'", obj.Spec.ClusterIPs, svc.ClusterIPs) } // Update an object @@ -71,8 +80,8 @@ func testProcessor(t *testing.T, processor cache.ProcessFunc, idx cache.Indexer) if !ok { t.Fatal("object in index was incorrect type") } - if svc.ClusterIP != obj.Spec.ClusterIP { - t.Fatalf("expected %v, got %v", obj.Spec.ClusterIP, svc.ClusterIP) + if fmt.Sprintf("%v", svc.ClusterIPs) != fmt.Sprintf("%v", obj.Spec.ClusterIPs) { + t.Fatalf("expected '%v', got '%v'", obj.Spec.ClusterIPs, svc.ClusterIPs) } // Delete an object diff --git a/plugin/kubernetes/kubernetes.go b/plugin/kubernetes/kubernetes.go index 48d489330..a7fa37804 100644 --- a/plugin/kubernetes/kubernetes.go +++ b/plugin/kubernetes/kubernetes.go @@ -433,8 +433,7 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg. // If "ignore empty_service" option is set and no endpoints exist, return NXDOMAIN unless // it's a headless or externalName service (covered below). - if k.opts.ignoreEmptyService && svc.ClusterIP != api.ClusterIPNone && svc.Type != api.ServiceTypeExternalName { - // serve NXDOMAIN if no endpoint is able to answer + if k.opts.ignoreEmptyService && svc.Type != api.ServiceTypeExternalName && !svc.Headless() { // serve NXDOMAIN if no endpoint is able to answer podsCount := 0 for _, ep := range endpointsListFunc() { for _, eps := range ep.Subsets { @@ -447,8 +446,20 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg. } } + // External service + if svc.Type == api.ServiceTypeExternalName { + s := msg.Service{Key: strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/"), Host: svc.ExternalName, TTL: k.ttl} + if t, _ := s.HostType(); t == dns.TypeCNAME { + s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") + services = append(services, s) + + err = nil + } + continue + } + // Endpoint query or headless service - if svc.ClusterIP == api.ClusterIPNone || r.endpoint != "" { + if svc.Headless() || r.endpoint != "" { if endpointsList == nil { endpointsList = endpointsListFunc() } @@ -485,18 +496,6 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg. continue } - // External service - if svc.Type == api.ServiceTypeExternalName { - s := msg.Service{Key: strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/"), Host: svc.ExternalName, TTL: k.ttl} - if t, _ := s.HostType(); t == dns.TypeCNAME { - s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") - services = append(services, s) - - err = nil - } - continue - } - // ClusterIP service for _, p := range svc.Ports { if !(match(r.port, p.Name) && match(r.protocol, string(p.Protocol))) { @@ -505,10 +504,11 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg. err = nil - s := msg.Service{Host: svc.ClusterIP, Port: int(p.Port), TTL: k.ttl} - s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") - - services = append(services, s) + for _, ip := range svc.ClusterIPs { + s := msg.Service{Host: ip, Port: int(p.Port), TTL: k.ttl} + s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") + services = append(services, s) + } } } return services, err diff --git a/plugin/kubernetes/kubernetes_test.go b/plugin/kubernetes/kubernetes_test.go index 7458a3bdc..a342d9a23 100644 --- a/plugin/kubernetes/kubernetes_test.go +++ b/plugin/kubernetes/kubernetes_test.go @@ -71,17 +71,25 @@ func (APIConnServiceTest) Modified() int64 { return 0 func (APIConnServiceTest) SvcIndex(string) []*object.Service { svcs := []*object.Service{ { - Name: "svc1", - Namespace: "testns", - ClusterIP: "10.0.0.1", + Name: "svc1", + Namespace: "testns", + ClusterIPs: []string{"10.0.0.1"}, Ports: []api.ServicePort{ {Name: "http", Protocol: "tcp", Port: 80}, }, }, { - Name: "hdls1", - Namespace: "testns", - ClusterIP: api.ClusterIPNone, + Name: "svc-dual-stack", + Namespace: "testns", + ClusterIPs: []string{"10.0.0.2", "10::2"}, + Ports: []api.ServicePort{ + {Name: "http", Protocol: "tcp", Port: 80}, + }, + }, + { + Name: "hdls1", + Namespace: "testns", + ClusterIPs: []string{api.ClusterIPNone}, }, { Name: "external", @@ -99,17 +107,25 @@ func (APIConnServiceTest) SvcIndex(string) []*object.Service { func (APIConnServiceTest) ServiceList() []*object.Service { svcs := []*object.Service{ { - Name: "svc1", - Namespace: "testns", - ClusterIP: "10.0.0.1", + Name: "svc1", + Namespace: "testns", + ClusterIPs: []string{"10.0.0.1"}, Ports: []api.ServicePort{ {Name: "http", Protocol: "tcp", Port: 80}, }, }, { - Name: "hdls1", - Namespace: "testns", - ClusterIP: api.ClusterIPNone, + Name: "svc-dual-stack", + Namespace: "testns", + ClusterIPs: []string{"10.0.0.2", "10::2"}, + Ports: []api.ServicePort{ + {Name: "http", Protocol: "tcp", Port: 80}, + }, + }, + { + Name: "hdls1", + Namespace: "testns", + ClusterIPs: []string{api.ClusterIPNone}, }, { Name: "external", @@ -256,19 +272,29 @@ func TestServices(t *testing.T) { type svcTest struct { qname string qtype uint16 - answer svcAns + answer []svcAns } tests := []svcTest{ // Cluster IP Services - {qname: "svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "10.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1"}}, - {qname: "_http._tcp.svc1.testns.svc.interwebs.test.", qtype: dns.TypeSRV, answer: svcAns{host: "10.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1"}}, - {qname: "ep1a.svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "172.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1/ep1a"}}, + {qname: "svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: []svcAns{{host: "10.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1"}}}, + {qname: "_http._tcp.svc1.testns.svc.interwebs.test.", qtype: dns.TypeSRV, answer: []svcAns{{host: "10.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1"}}}, + {qname: "ep1a.svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: []svcAns{{host: "172.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1/ep1a"}}}, + + // Dual-Stack Cluster IP Service + { + qname: "_http._tcp.svc-dual-stack.testns.svc.interwebs.test.", + qtype: dns.TypeSRV, + answer: []svcAns{ + {host: "10.0.0.2", key: "/" + coredns + "/test/interwebs/svc/testns/svc-dual-stack"}, + {host: "10::2", key: "/" + coredns + "/test/interwebs/svc/testns/svc-dual-stack"}, + }, + }, // External Services - {qname: "external.testns.svc.interwebs.test.", qtype: dns.TypeCNAME, answer: svcAns{host: "coredns.io", key: "/" + coredns + "/test/interwebs/svc/testns/external"}}, + {qname: "external.testns.svc.interwebs.test.", qtype: dns.TypeCNAME, answer: []svcAns{{host: "coredns.io", key: "/" + coredns + "/test/interwebs/svc/testns/external"}}}, // Headless Services - {qname: "hdls1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "172.0.0.2", key: "/" + coredns + "/test/interwebs/svc/testns/hdls1/172-0-0-2"}}, + {qname: "hdls1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: []svcAns{{host: "172.0.0.2", key: "/" + coredns + "/test/interwebs/svc/testns/hdls1/172-0-0-2"}}}, } for i, test := range tests { @@ -281,16 +307,18 @@ func TestServices(t *testing.T) { t.Errorf("Test %d: got error '%v'", i, e) continue } - if len(svcs) != 1 { - t.Errorf("Test %d, expected 1 answer, got %v", i, len(svcs)) + if len(svcs) != len(test.answer) { + t.Errorf("Test %d, expected %v answer, got %v", i, len(test.answer), len(svcs)) continue } - if test.answer.host != svcs[0].Host { - t.Errorf("Test %d, expected host '%v', got '%v'", i, test.answer.host, svcs[0].Host) - } - if test.answer.key != svcs[0].Key { - t.Errorf("Test %d, expected key '%v', got '%v'", i, test.answer.key, svcs[0].Key) + for j := range svcs { + if test.answer[j].host != svcs[j].Host { + t.Errorf("Test %d, expected host '%v', got '%v'", i, test.answer[j].host, svcs[j].Host) + } + if test.answer[j].key != svcs[j].Key { + t.Errorf("Test %d, expected key '%v', got '%v'", i, test.answer[j].key, svcs[j].Key) + } } } } diff --git a/plugin/kubernetes/ns.go b/plugin/kubernetes/ns.go index ba687cb13..84f4e344b 100644 --- a/plugin/kubernetes/ns.go +++ b/plugin/kubernetes/ns.go @@ -5,7 +5,6 @@ import ( "strings" "github.com/miekg/dns" - api "k8s.io/api/core/v1" ) func isDefaultNS(name, zone string) bool { @@ -37,7 +36,7 @@ func (k *Kubernetes) nsAddrs(external bool, zone string) []dns.RR { continue } svcName := strings.Join([]string{svc.Name, svc.Namespace, Svc, zone}, ".") - if svc.ClusterIP == api.ClusterIPNone { + if svc.Headless() { // For a headless service, use the endpoints IPs for _, s := range endpoint.Subsets { for _, a := range s.Addresses { @@ -46,8 +45,10 @@ func (k *Kubernetes) nsAddrs(external bool, zone string) []dns.RR { } } } else { - svcNames = append(svcNames, svcName) - svcIPs = append(svcIPs, net.ParseIP(svc.ClusterIP)) + for _, clusterIP := range svc.ClusterIPs { + svcNames = append(svcNames, svcName) + svcIPs = append(svcIPs, net.ParseIP(clusterIP)) + } } } } diff --git a/plugin/kubernetes/ns_test.go b/plugin/kubernetes/ns_test.go index 682303104..6370675cd 100644 --- a/plugin/kubernetes/ns_test.go +++ b/plugin/kubernetes/ns_test.go @@ -37,19 +37,19 @@ func (a APIConnTest) SvcIndex(s string) []*object.Service { func (APIConnTest) ServiceList() []*object.Service { svcs := []*object.Service{ { - Name: "dns-service", - Namespace: "kube-system", - ClusterIP: "10.0.0.111", + Name: "dns-service", + Namespace: "kube-system", + ClusterIPs: []string{"10.0.0.111"}, }, { - Name: "hdls-dns-service", - Namespace: "kube-system", - ClusterIP: api.ClusterIPNone, + Name: "hdls-dns-service", + Namespace: "kube-system", + ClusterIPs: []string{api.ClusterIPNone}, }, { - Name: "dns6-service", - Namespace: "kube-system", - ClusterIP: "10::111", + Name: "dns6-service", + Namespace: "kube-system", + ClusterIPs: []string{"10::111"}, }, } return svcs diff --git a/plugin/kubernetes/object/metrics.go b/plugin/kubernetes/object/metrics.go index 929925cf1..f39744b8a 100644 --- a/plugin/kubernetes/object/metrics.go +++ b/plugin/kubernetes/object/metrics.go @@ -67,7 +67,7 @@ func (l *EndpointLatencyRecorder) record() { // don't change very often (comparing to much more frequent endpoints changes), cases when this method // will return wrong answer should be relatively rare. Because of that we intentionally accept this // flaw to keep the solution simple. - isHeadless := len(l.Services) == 1 && l.Services[0].ClusterIP == api.ClusterIPNone + isHeadless := len(l.Services) == 1 && l.Services[0].Headless() if !isHeadless || l.TT.IsZero() { return diff --git a/plugin/kubernetes/object/service.go b/plugin/kubernetes/object/service.go index be1404ea0..812b272e2 100644 --- a/plugin/kubernetes/object/service.go +++ b/plugin/kubernetes/object/service.go @@ -15,7 +15,7 @@ type Service struct { Name string Namespace string Index string - ClusterIP string + ClusterIPs []string Type api.ServiceType ExternalName string Ports []api.ServicePort @@ -40,13 +40,19 @@ func ToService(obj meta.Object) (meta.Object, error) { Name: svc.GetName(), Namespace: svc.GetNamespace(), Index: ServiceKey(svc.GetName(), svc.GetNamespace()), - 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.ClusterIPs) > 0 { + s.ClusterIPs = make([]string, len(svc.Spec.ClusterIPs)) + copy(s.ClusterIPs, svc.Spec.ClusterIPs) + } else { + s.ClusterIPs = []string{svc.Spec.ClusterIP} + } + if len(svc.Spec.Ports) == 0 { // Add sentinel if there are no ports. s.Ports = []api.ServicePort{{Port: -1}} @@ -70,6 +76,11 @@ func ToService(obj meta.Object) (meta.Object, error) { return s, nil } +// Headless returns true if the service is headless +func (s *Service) Headless() bool { + return s.ClusterIPs[0] == api.ClusterIPNone +} + var _ runtime.Object = &Service{} // DeepCopyObject implements the ObjectKind interface. @@ -79,12 +90,13 @@ func (s *Service) DeepCopyObject() runtime.Object { Name: s.Name, Namespace: s.Namespace, Index: s.Index, - ClusterIP: s.ClusterIP, Type: s.Type, ExternalName: s.ExternalName, + ClusterIPs: make([]string, len(s.ClusterIPs)), Ports: make([]api.ServicePort, len(s.Ports)), ExternalIPs: make([]string, len(s.ExternalIPs)), } + copy(s1.ClusterIPs, s.ClusterIPs) copy(s1.Ports, s.Ports) copy(s1.ExternalIPs, s.ExternalIPs) return s1 diff --git a/plugin/kubernetes/reverse_test.go b/plugin/kubernetes/reverse_test.go index 3b3b6872b..2e888f064 100644 --- a/plugin/kubernetes/reverse_test.go +++ b/plugin/kubernetes/reverse_test.go @@ -30,10 +30,10 @@ func (APIConnReverseTest) SvcIndex(svc string) []*object.Service { } svcs := []*object.Service{ { - Name: "svc1", - Namespace: "testns", - ClusterIP: "192.168.1.100", - Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, + Name: "svc1", + Namespace: "testns", + ClusterIPs: []string{"192.168.1.100"}, + Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, }, } return svcs @@ -46,10 +46,10 @@ func (APIConnReverseTest) SvcIndexReverse(ip string) []*object.Service { } svcs := []*object.Service{ { - Name: "svc1", - Namespace: "testns", - ClusterIP: "192.168.1.100", - Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, + Name: "svc1", + Namespace: "testns", + ClusterIPs: []string{"192.168.1.100"}, + Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, }, } return svcs diff --git a/plugin/kubernetes/xfr.go b/plugin/kubernetes/xfr.go index 550a70dfd..812604966 100644 --- a/plugin/kubernetes/xfr.go +++ b/plugin/kubernetes/xfr.go @@ -50,13 +50,16 @@ func (k *Kubernetes) Transfer(zone string, serial uint32) (<-chan []dns.RR, erro switch svc.Type { case api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer: - clusterIP := net.ParseIP(svc.ClusterIP) + clusterIP := net.ParseIP(svc.ClusterIPs[0]) if clusterIP != nil { - s := msg.Service{Host: svc.ClusterIP, TTL: k.ttl} - s.Key = strings.Join(svcBase, "/") + var host string + for _, ip := range svc.ClusterIPs { + s := msg.Service{Host: ip, TTL: k.ttl} + s.Key = strings.Join(svcBase, "/") - // Change host from IP to Name for SRV records - host := emitAddressRecord(ch, s) + // Change host from IP to Name for SRV records + host = emitAddressRecord(ch, s) + } for _, p := range svc.Ports { s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl} diff --git a/plugin/kubernetes/xfr_test.go b/plugin/kubernetes/xfr_test.go index b5f13ad6e..39c4ed226 100644 --- a/plugin/kubernetes/xfr_test.go +++ b/plugin/kubernetes/xfr_test.go @@ -113,6 +113,10 @@ hdls1.testns.svc.cluster.local. 5 IN AAAA 5678:abcd::2 _http._tcp.hdls1.testns.svc.cluster.local. 5 IN SRV 0 16 80 5678-abcd--2.hdls1.testns.svc.cluster.local. hdlsprtls.testns.svc.cluster.local. 5 IN A 172.0.0.20 172-0-0-20.hdlsprtls.testns.svc.cluster.local. 5 IN A 172.0.0.20 +svc-dual-stack.testns.svc.cluster.local. 5 IN A 10.0.0.3 +svc-dual-stack.testns.svc.cluster.local. 5 IN AAAA 10::3 +svc-dual-stack.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc-dual-stack.testns.svc.cluster.local. +_http._tcp.svc-dual-stack.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc-dual-stack.testns.svc.cluster.local. svc1.testns.svc.cluster.local. 5 IN A 10.0.0.1 svc1.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc1.testns.svc.cluster.local. _http._tcp.svc1.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc1.testns.svc.cluster.local.