plugin/kubernetes: Add support for dual stack ClusterIP Services (#4339)
* support dual stack clusterIPs Signed-off-by: Chris O'Haver <cohaver@infoblox.com> * stickler Signed-off-by: Chris O'Haver <cohaver@infoblox.com> * fix ClusterIPs make Signed-off-by: Chris O'Haver <cohaver@infoblox.com>
This commit is contained in:
parent
302434e392
commit
51c05679e6
14 changed files with 204 additions and 111 deletions
|
@ -190,7 +190,7 @@ var svcIndexExternal = map[string][]*object.Service{
|
||||||
Name: "svc1",
|
Name: "svc1",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
Type: api.ServiceTypeClusterIP,
|
Type: api.ServiceTypeClusterIP,
|
||||||
ClusterIP: "10.0.0.1",
|
ClusterIPs: []string{"10.0.0.1"},
|
||||||
ExternalIPs: []string{"1.2.3.4"},
|
ExternalIPs: []string{"1.2.3.4"},
|
||||||
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
||||||
},
|
},
|
||||||
|
@ -200,7 +200,7 @@ var svcIndexExternal = map[string][]*object.Service{
|
||||||
Name: "svc6",
|
Name: "svc6",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
Type: api.ServiceTypeClusterIP,
|
Type: api.ServiceTypeClusterIP,
|
||||||
ClusterIP: "10.0.0.3",
|
ClusterIPs: []string{"10.0.0.3"},
|
||||||
ExternalIPs: []string{"1:2::5"},
|
ExternalIPs: []string{"1:2::5"},
|
||||||
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
||||||
},
|
},
|
||||||
|
|
|
@ -200,11 +200,13 @@ func svcIPIndexFunc(obj interface{}) ([]string, error) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errObj
|
return nil, errObj
|
||||||
}
|
}
|
||||||
|
idx := make([]string, len(svc.ClusterIPs)+len(svc.ExternalIPs))
|
||||||
|
copy(idx, svc.ClusterIPs)
|
||||||
if len(svc.ExternalIPs) == 0 {
|
if len(svc.ExternalIPs) == 0 {
|
||||||
return []string{svc.ClusterIP}, nil
|
return idx, nil
|
||||||
}
|
}
|
||||||
|
copy(idx[len(svc.ClusterIPs):], svc.ExternalIPs)
|
||||||
return append([]string{svc.ClusterIP}, svc.ExternalIPs...), nil
|
return idx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func svcNameNamespaceIndexFunc(obj interface{}) ([]string, error) {
|
func svcNameNamespaceIndexFunc(obj interface{}) ([]string, error) {
|
||||||
|
|
|
@ -105,7 +105,7 @@ var svcIndexExternal = map[string][]*object.Service{
|
||||||
Name: "svc1",
|
Name: "svc1",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
Type: api.ServiceTypeClusterIP,
|
Type: api.ServiceTypeClusterIP,
|
||||||
ClusterIP: "10.0.0.1",
|
ClusterIPs: []string{"10.0.0.1"},
|
||||||
ExternalIPs: []string{"1.2.3.4"},
|
ExternalIPs: []string{"1.2.3.4"},
|
||||||
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
||||||
},
|
},
|
||||||
|
@ -115,7 +115,7 @@ var svcIndexExternal = map[string][]*object.Service{
|
||||||
Name: "svc6",
|
Name: "svc6",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
Type: api.ServiceTypeClusterIP,
|
Type: api.ServiceTypeClusterIP,
|
||||||
ClusterIP: "10.0.0.3",
|
ClusterIPs: []string{"10.0.0.3"},
|
||||||
ExternalIPs: []string{"1:2::5"},
|
ExternalIPs: []string{"1:2::5"},
|
||||||
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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"),
|
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) {
|
func TestServeDNS(t *testing.T) {
|
||||||
|
@ -539,10 +563,10 @@ func (APIConnServeTest) PodIndex(ip string) []*object.Pod {
|
||||||
var svcIndex = map[string][]*object.Service{
|
var svcIndex = map[string][]*object.Service{
|
||||||
"svc1.testns": {
|
"svc1.testns": {
|
||||||
{
|
{
|
||||||
Name: "svc1",
|
Name: "svc1",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
Type: api.ServiceTypeClusterIP,
|
Type: api.ServiceTypeClusterIP,
|
||||||
ClusterIP: "10.0.0.1",
|
ClusterIPs: []string{"10.0.0.1"},
|
||||||
Ports: []api.ServicePort{
|
Ports: []api.ServicePort{
|
||||||
{Name: "http", Protocol: "tcp", Port: 80},
|
{Name: "http", Protocol: "tcp", Port: 80},
|
||||||
},
|
},
|
||||||
|
@ -550,10 +574,10 @@ var svcIndex = map[string][]*object.Service{
|
||||||
},
|
},
|
||||||
"svcempty.testns": {
|
"svcempty.testns": {
|
||||||
{
|
{
|
||||||
Name: "svcempty",
|
Name: "svcempty",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
Type: api.ServiceTypeClusterIP,
|
Type: api.ServiceTypeClusterIP,
|
||||||
ClusterIP: "10.0.0.1",
|
ClusterIPs: []string{"10.0.0.1"},
|
||||||
Ports: []api.ServicePort{
|
Ports: []api.ServicePort{
|
||||||
{Name: "http", Protocol: "tcp", Port: 80},
|
{Name: "http", Protocol: "tcp", Port: 80},
|
||||||
},
|
},
|
||||||
|
@ -561,10 +585,10 @@ var svcIndex = map[string][]*object.Service{
|
||||||
},
|
},
|
||||||
"svc6.testns": {
|
"svc6.testns": {
|
||||||
{
|
{
|
||||||
Name: "svc6",
|
Name: "svc6",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
Type: api.ServiceTypeClusterIP,
|
Type: api.ServiceTypeClusterIP,
|
||||||
ClusterIP: "1234:abcd::1",
|
ClusterIPs: []string{"1234:abcd::1"},
|
||||||
Ports: []api.ServicePort{
|
Ports: []api.ServicePort{
|
||||||
{Name: "http", Protocol: "tcp", Port: 80},
|
{Name: "http", Protocol: "tcp", Port: 80},
|
||||||
},
|
},
|
||||||
|
@ -572,10 +596,10 @@ var svcIndex = map[string][]*object.Service{
|
||||||
},
|
},
|
||||||
"hdls1.testns": {
|
"hdls1.testns": {
|
||||||
{
|
{
|
||||||
Name: "hdls1",
|
Name: "hdls1",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
Type: api.ServiceTypeClusterIP,
|
Type: api.ServiceTypeClusterIP,
|
||||||
ClusterIP: api.ClusterIPNone,
|
ClusterIPs: []string{api.ClusterIPNone},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"external.testns": {
|
"external.testns": {
|
||||||
|
@ -602,23 +626,33 @@ var svcIndex = map[string][]*object.Service{
|
||||||
},
|
},
|
||||||
"hdlsprtls.testns": {
|
"hdlsprtls.testns": {
|
||||||
{
|
{
|
||||||
Name: "hdlsprtls",
|
Name: "hdlsprtls",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
Type: api.ServiceTypeClusterIP,
|
Type: api.ServiceTypeClusterIP,
|
||||||
ClusterIP: api.ClusterIPNone,
|
ClusterIPs: []string{api.ClusterIPNone},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"svc1.unexposedns": {
|
"svc1.unexposedns": {
|
||||||
{
|
{
|
||||||
Name: "svc1",
|
Name: "svc1",
|
||||||
Namespace: "unexposedns",
|
Namespace: "unexposedns",
|
||||||
Type: api.ServiceTypeClusterIP,
|
Type: api.ServiceTypeClusterIP,
|
||||||
ClusterIP: "10.0.0.2",
|
ClusterIPs: []string{"10.0.0.2"},
|
||||||
Ports: []api.ServicePort{
|
Ports: []api.ServicePort{
|
||||||
{Name: "http", Protocol: "tcp", Port: 80},
|
{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] }
|
func (APIConnServeTest) SvcIndex(s string) []*object.Service { return svcIndex[s] }
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/kubernetes/object"
|
"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) {
|
func testProcessor(t *testing.T, processor cache.ProcessFunc, idx cache.Indexer) {
|
||||||
obj := &api.Service{
|
obj := &api.Service{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "service1", Namespace: "test1"},
|
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{
|
obj2 := &api.Service{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "service2", Namespace: "test1"},
|
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
|
// Add the objects
|
||||||
|
@ -47,8 +56,8 @@ func testProcessor(t *testing.T, processor cache.ProcessFunc, idx cache.Indexer)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("object in index was incorrect type")
|
t.Fatal("object in index was incorrect type")
|
||||||
}
|
}
|
||||||
if svc.ClusterIP != obj.Spec.ClusterIP {
|
if fmt.Sprintf("%v", svc.ClusterIPs) != fmt.Sprintf("%v", obj.Spec.ClusterIPs) {
|
||||||
t.Fatalf("expected %v, got %v", obj.Spec.ClusterIP, svc.ClusterIP)
|
t.Fatalf("expected '%v', got '%v'", obj.Spec.ClusterIPs, svc.ClusterIPs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update an object
|
// Update an object
|
||||||
|
@ -71,8 +80,8 @@ func testProcessor(t *testing.T, processor cache.ProcessFunc, idx cache.Indexer)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("object in index was incorrect type")
|
t.Fatal("object in index was incorrect type")
|
||||||
}
|
}
|
||||||
if svc.ClusterIP != obj.Spec.ClusterIP {
|
if fmt.Sprintf("%v", svc.ClusterIPs) != fmt.Sprintf("%v", obj.Spec.ClusterIPs) {
|
||||||
t.Fatalf("expected %v, got %v", obj.Spec.ClusterIP, svc.ClusterIP)
|
t.Fatalf("expected '%v', got '%v'", obj.Spec.ClusterIPs, svc.ClusterIPs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete an object
|
// Delete an object
|
||||||
|
|
|
@ -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
|
// If "ignore empty_service" option is set and no endpoints exist, return NXDOMAIN unless
|
||||||
// it's a headless or externalName service (covered below).
|
// it's a headless or externalName service (covered below).
|
||||||
if k.opts.ignoreEmptyService && svc.ClusterIP != api.ClusterIPNone && svc.Type != api.ServiceTypeExternalName {
|
if k.opts.ignoreEmptyService && svc.Type != api.ServiceTypeExternalName && !svc.Headless() { // serve NXDOMAIN if no endpoint is able to answer
|
||||||
// serve NXDOMAIN if no endpoint is able to answer
|
|
||||||
podsCount := 0
|
podsCount := 0
|
||||||
for _, ep := range endpointsListFunc() {
|
for _, ep := range endpointsListFunc() {
|
||||||
for _, eps := range ep.Subsets {
|
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
|
// Endpoint query or headless service
|
||||||
if svc.ClusterIP == api.ClusterIPNone || r.endpoint != "" {
|
if svc.Headless() || r.endpoint != "" {
|
||||||
if endpointsList == nil {
|
if endpointsList == nil {
|
||||||
endpointsList = endpointsListFunc()
|
endpointsList = endpointsListFunc()
|
||||||
}
|
}
|
||||||
|
@ -485,18 +496,6 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
|
||||||
continue
|
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
|
// ClusterIP service
|
||||||
for _, p := range svc.Ports {
|
for _, p := range svc.Ports {
|
||||||
if !(match(r.port, p.Name) && match(r.protocol, string(p.Protocol))) {
|
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
|
err = nil
|
||||||
|
|
||||||
s := msg.Service{Host: svc.ClusterIP, Port: int(p.Port), TTL: k.ttl}
|
for _, ip := range svc.ClusterIPs {
|
||||||
s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/")
|
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)
|
services = append(services, s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return services, err
|
return services, err
|
||||||
|
|
|
@ -71,17 +71,25 @@ func (APIConnServiceTest) Modified() int64 { return 0
|
||||||
func (APIConnServiceTest) SvcIndex(string) []*object.Service {
|
func (APIConnServiceTest) SvcIndex(string) []*object.Service {
|
||||||
svcs := []*object.Service{
|
svcs := []*object.Service{
|
||||||
{
|
{
|
||||||
Name: "svc1",
|
Name: "svc1",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
ClusterIP: "10.0.0.1",
|
ClusterIPs: []string{"10.0.0.1"},
|
||||||
Ports: []api.ServicePort{
|
Ports: []api.ServicePort{
|
||||||
{Name: "http", Protocol: "tcp", Port: 80},
|
{Name: "http", Protocol: "tcp", Port: 80},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "hdls1",
|
Name: "svc-dual-stack",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
ClusterIP: api.ClusterIPNone,
|
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",
|
Name: "external",
|
||||||
|
@ -99,17 +107,25 @@ func (APIConnServiceTest) SvcIndex(string) []*object.Service {
|
||||||
func (APIConnServiceTest) ServiceList() []*object.Service {
|
func (APIConnServiceTest) ServiceList() []*object.Service {
|
||||||
svcs := []*object.Service{
|
svcs := []*object.Service{
|
||||||
{
|
{
|
||||||
Name: "svc1",
|
Name: "svc1",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
ClusterIP: "10.0.0.1",
|
ClusterIPs: []string{"10.0.0.1"},
|
||||||
Ports: []api.ServicePort{
|
Ports: []api.ServicePort{
|
||||||
{Name: "http", Protocol: "tcp", Port: 80},
|
{Name: "http", Protocol: "tcp", Port: 80},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "hdls1",
|
Name: "svc-dual-stack",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
ClusterIP: api.ClusterIPNone,
|
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",
|
Name: "external",
|
||||||
|
@ -256,19 +272,29 @@ func TestServices(t *testing.T) {
|
||||||
type svcTest struct {
|
type svcTest struct {
|
||||||
qname string
|
qname string
|
||||||
qtype uint16
|
qtype uint16
|
||||||
answer svcAns
|
answer []svcAns
|
||||||
}
|
}
|
||||||
tests := []svcTest{
|
tests := []svcTest{
|
||||||
// Cluster IP Services
|
// 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: "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: "_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: "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
|
// 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
|
// 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 {
|
for i, test := range tests {
|
||||||
|
@ -281,16 +307,18 @@ func TestServices(t *testing.T) {
|
||||||
t.Errorf("Test %d: got error '%v'", i, e)
|
t.Errorf("Test %d: got error '%v'", i, e)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(svcs) != 1 {
|
if len(svcs) != len(test.answer) {
|
||||||
t.Errorf("Test %d, expected 1 answer, got %v", i, len(svcs))
|
t.Errorf("Test %d, expected %v answer, got %v", i, len(test.answer), len(svcs))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if test.answer.host != svcs[0].Host {
|
for j := range svcs {
|
||||||
t.Errorf("Test %d, expected host '%v', got '%v'", i, test.answer.host, svcs[0].Host)
|
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.key != svcs[0].Key {
|
}
|
||||||
t.Errorf("Test %d, expected key '%v', got '%v'", i, test.answer.key, svcs[0].Key)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
api "k8s.io/api/core/v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func isDefaultNS(name, zone string) bool {
|
func isDefaultNS(name, zone string) bool {
|
||||||
|
@ -37,7 +36,7 @@ func (k *Kubernetes) nsAddrs(external bool, zone string) []dns.RR {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
svcName := strings.Join([]string{svc.Name, svc.Namespace, Svc, zone}, ".")
|
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 a headless service, use the endpoints IPs
|
||||||
for _, s := range endpoint.Subsets {
|
for _, s := range endpoint.Subsets {
|
||||||
for _, a := range s.Addresses {
|
for _, a := range s.Addresses {
|
||||||
|
@ -46,8 +45,10 @@ func (k *Kubernetes) nsAddrs(external bool, zone string) []dns.RR {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
svcNames = append(svcNames, svcName)
|
for _, clusterIP := range svc.ClusterIPs {
|
||||||
svcIPs = append(svcIPs, net.ParseIP(svc.ClusterIP))
|
svcNames = append(svcNames, svcName)
|
||||||
|
svcIPs = append(svcIPs, net.ParseIP(clusterIP))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,19 +37,19 @@ func (a APIConnTest) SvcIndex(s string) []*object.Service {
|
||||||
func (APIConnTest) ServiceList() []*object.Service {
|
func (APIConnTest) ServiceList() []*object.Service {
|
||||||
svcs := []*object.Service{
|
svcs := []*object.Service{
|
||||||
{
|
{
|
||||||
Name: "dns-service",
|
Name: "dns-service",
|
||||||
Namespace: "kube-system",
|
Namespace: "kube-system",
|
||||||
ClusterIP: "10.0.0.111",
|
ClusterIPs: []string{"10.0.0.111"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "hdls-dns-service",
|
Name: "hdls-dns-service",
|
||||||
Namespace: "kube-system",
|
Namespace: "kube-system",
|
||||||
ClusterIP: api.ClusterIPNone,
|
ClusterIPs: []string{api.ClusterIPNone},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "dns6-service",
|
Name: "dns6-service",
|
||||||
Namespace: "kube-system",
|
Namespace: "kube-system",
|
||||||
ClusterIP: "10::111",
|
ClusterIPs: []string{"10::111"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return svcs
|
return svcs
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (l *EndpointLatencyRecorder) record() {
|
||||||
// don't change very often (comparing to much more frequent endpoints changes), cases when this method
|
// 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
|
// will return wrong answer should be relatively rare. Because of that we intentionally accept this
|
||||||
// flaw to keep the solution simple.
|
// 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() {
|
if !isHeadless || l.TT.IsZero() {
|
||||||
return
|
return
|
||||||
|
|
|
@ -15,7 +15,7 @@ type Service struct {
|
||||||
Name string
|
Name string
|
||||||
Namespace string
|
Namespace string
|
||||||
Index string
|
Index string
|
||||||
ClusterIP string
|
ClusterIPs []string
|
||||||
Type api.ServiceType
|
Type api.ServiceType
|
||||||
ExternalName string
|
ExternalName string
|
||||||
Ports []api.ServicePort
|
Ports []api.ServicePort
|
||||||
|
@ -40,13 +40,19 @@ func ToService(obj meta.Object) (meta.Object, error) {
|
||||||
Name: svc.GetName(),
|
Name: svc.GetName(),
|
||||||
Namespace: svc.GetNamespace(),
|
Namespace: svc.GetNamespace(),
|
||||||
Index: ServiceKey(svc.GetName(), svc.GetNamespace()),
|
Index: ServiceKey(svc.GetName(), svc.GetNamespace()),
|
||||||
ClusterIP: svc.Spec.ClusterIP,
|
|
||||||
Type: svc.Spec.Type,
|
Type: svc.Spec.Type,
|
||||||
ExternalName: svc.Spec.ExternalName,
|
ExternalName: svc.Spec.ExternalName,
|
||||||
|
|
||||||
ExternalIPs: make([]string, len(svc.Status.LoadBalancer.Ingress)+len(svc.Spec.ExternalIPs)),
|
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 {
|
if len(svc.Spec.Ports) == 0 {
|
||||||
// Add sentinel if there are no ports.
|
// Add sentinel if there are no ports.
|
||||||
s.Ports = []api.ServicePort{{Port: -1}}
|
s.Ports = []api.ServicePort{{Port: -1}}
|
||||||
|
@ -70,6 +76,11 @@ func ToService(obj meta.Object) (meta.Object, error) {
|
||||||
return s, nil
|
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{}
|
var _ runtime.Object = &Service{}
|
||||||
|
|
||||||
// DeepCopyObject implements the ObjectKind interface.
|
// DeepCopyObject implements the ObjectKind interface.
|
||||||
|
@ -79,12 +90,13 @@ func (s *Service) DeepCopyObject() runtime.Object {
|
||||||
Name: s.Name,
|
Name: s.Name,
|
||||||
Namespace: s.Namespace,
|
Namespace: s.Namespace,
|
||||||
Index: s.Index,
|
Index: s.Index,
|
||||||
ClusterIP: s.ClusterIP,
|
|
||||||
Type: s.Type,
|
Type: s.Type,
|
||||||
ExternalName: s.ExternalName,
|
ExternalName: s.ExternalName,
|
||||||
|
ClusterIPs: make([]string, len(s.ClusterIPs)),
|
||||||
Ports: make([]api.ServicePort, len(s.Ports)),
|
Ports: make([]api.ServicePort, len(s.Ports)),
|
||||||
ExternalIPs: make([]string, len(s.ExternalIPs)),
|
ExternalIPs: make([]string, len(s.ExternalIPs)),
|
||||||
}
|
}
|
||||||
|
copy(s1.ClusterIPs, s.ClusterIPs)
|
||||||
copy(s1.Ports, s.Ports)
|
copy(s1.Ports, s.Ports)
|
||||||
copy(s1.ExternalIPs, s.ExternalIPs)
|
copy(s1.ExternalIPs, s.ExternalIPs)
|
||||||
return s1
|
return s1
|
||||||
|
|
|
@ -30,10 +30,10 @@ func (APIConnReverseTest) SvcIndex(svc string) []*object.Service {
|
||||||
}
|
}
|
||||||
svcs := []*object.Service{
|
svcs := []*object.Service{
|
||||||
{
|
{
|
||||||
Name: "svc1",
|
Name: "svc1",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
ClusterIP: "192.168.1.100",
|
ClusterIPs: []string{"192.168.1.100"},
|
||||||
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return svcs
|
return svcs
|
||||||
|
@ -46,10 +46,10 @@ func (APIConnReverseTest) SvcIndexReverse(ip string) []*object.Service {
|
||||||
}
|
}
|
||||||
svcs := []*object.Service{
|
svcs := []*object.Service{
|
||||||
{
|
{
|
||||||
Name: "svc1",
|
Name: "svc1",
|
||||||
Namespace: "testns",
|
Namespace: "testns",
|
||||||
ClusterIP: "192.168.1.100",
|
ClusterIPs: []string{"192.168.1.100"},
|
||||||
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
Ports: []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return svcs
|
return svcs
|
||||||
|
|
|
@ -50,13 +50,16 @@ func (k *Kubernetes) Transfer(zone string, serial uint32) (<-chan []dns.RR, erro
|
||||||
switch svc.Type {
|
switch svc.Type {
|
||||||
|
|
||||||
case api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer:
|
case api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer:
|
||||||
clusterIP := net.ParseIP(svc.ClusterIP)
|
clusterIP := net.ParseIP(svc.ClusterIPs[0])
|
||||||
if clusterIP != nil {
|
if clusterIP != nil {
|
||||||
s := msg.Service{Host: svc.ClusterIP, TTL: k.ttl}
|
var host string
|
||||||
s.Key = strings.Join(svcBase, "/")
|
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
|
// Change host from IP to Name for SRV records
|
||||||
host := emitAddressRecord(ch, s)
|
host = emitAddressRecord(ch, s)
|
||||||
|
}
|
||||||
|
|
||||||
for _, p := range svc.Ports {
|
for _, p := range svc.Ports {
|
||||||
s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl}
|
s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl}
|
||||||
|
|
|
@ -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.
|
_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
|
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
|
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 A 10.0.0.1
|
||||||
svc1.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc1.testns.svc.cluster.local.
|
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.
|
_http._tcp.svc1.testns.svc.cluster.local. 5 IN SRV 0 100 80 svc1.testns.svc.cluster.local.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue