Merge pull request #3600 from johngmyers/ipv6-headless

Support AAAA records from headless services
This commit is contained in:
Kubernetes Prow Robot 2023-05-19 06:44:30 -07:00 committed by GitHub
commit fd501ddd7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 405 additions and 20 deletions

View File

@ -271,7 +271,7 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri
endpointsType := getEndpointsTypeFromAnnotations(svc.Annotations)
targetsByHeadlessDomain := make(map[string]endpoint.Targets)
targetsByHeadlessDomainAndType := make(map[endpointKey]endpoint.Targets)
for _, subset := range endpointsObject.Subsets {
addresses := subset.Addresses
if svc.Spec.PublishNotReadyAddresses || sc.alwaysPublishNotReadyAddresses {
@ -324,18 +324,29 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri
log.Debugf("Generating matching endpoint %s with EndpointAddress IP %s", headlessDomain, address.IP)
}
}
targetsByHeadlessDomain[headlessDomain] = append(targetsByHeadlessDomain[headlessDomain], targets...)
for _, target := range targets {
key := endpointKey{
dnsName: headlessDomain,
recordType: suitableType(target),
}
targetsByHeadlessDomainAndType[key] = append(targetsByHeadlessDomainAndType[key], target)
}
}
}
}
headlessDomains := []string{}
for headlessDomain := range targetsByHeadlessDomain {
headlessDomains = append(headlessDomains, headlessDomain)
headlessKeys := []endpointKey{}
for headlessKey := range targetsByHeadlessDomainAndType {
headlessKeys = append(headlessKeys, headlessKey)
}
sort.Strings(headlessDomains)
for _, headlessDomain := range headlessDomains {
allTargets := targetsByHeadlessDomain[headlessDomain]
sort.Slice(headlessKeys, func(i, j int) bool {
if headlessKeys[i].dnsName != headlessKeys[j].dnsName {
return headlessKeys[i].dnsName < headlessKeys[j].dnsName
}
return headlessKeys[i].recordType < headlessKeys[j].recordType
})
for _, headlessKey := range headlessKeys {
allTargets := targetsByHeadlessDomainAndType[headlessKey]
targets := []string{}
deduppedTargets := map[string]struct{}{}
@ -350,9 +361,9 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri
}
if ttl.IsConfigured() {
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(headlessDomain, endpoint.RecordTypeA, ttl, targets...))
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(headlessKey.dnsName, headlessKey.recordType, ttl, targets...))
} else {
endpoints = append(endpoints, endpoint.NewEndpoint(headlessDomain, endpoint.RecordTypeA, targets...))
endpoints = append(endpoints, endpoint.NewEndpoint(headlessKey.dnsName, headlessKey.recordType, targets...))
}
}

View File

@ -2244,7 +2244,7 @@ func TestHeadlessServices(t *testing.T) {
expectError bool
}{
{
"annotated Headless services return endpoints for each selected Pod",
"annotated Headless services return IPv4 endpoints for each selected Pod",
"",
"testing",
"foo",
@ -2276,6 +2276,39 @@ func TestHeadlessServices(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 endpoints for each selected Pod",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
},
map[string]string{},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::2"},
[]string{"", ""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1"},
[]string{"foo-0", "foo-1"},
[]bool{true, true},
false,
[]v1.Node{},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
},
false,
},
{
"hostname annotated Headless services are ignored",
"",
@ -2306,7 +2339,7 @@ func TestHeadlessServices(t *testing.T) {
false,
},
{
"annotated Headless services return endpoints with TTL for each selected Pod",
"annotated Headless services return IPv4 endpoints with TTL for each selected Pod",
"",
"testing",
"foo",
@ -2339,6 +2372,40 @@ func TestHeadlessServices(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 endpoints with TTL for each selected Pod",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
ttlAnnotationKey: "1",
},
map[string]string{},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::2"},
[]string{"", ""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1"},
[]string{"foo-0", "foo-1"},
[]bool{true, true},
false,
[]v1.Node{},
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}, RecordTTL: endpoint.TTL(1)},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
},
false,
},
{
"annotated Headless services return endpoints for each selected Pod, which are in running state",
"",
@ -2436,7 +2503,7 @@ func TestHeadlessServices(t *testing.T) {
false,
},
{
"annotated Headless services return only a unique set of targets",
"annotated Headless services return only a unique set of IPv4 targets",
"",
"testing",
"foo",
@ -2467,7 +2534,38 @@ func TestHeadlessServices(t *testing.T) {
false,
},
{
"annotated Headless services return targets from pod annotation",
"annotated Headless services return only a unique set of IPv6 targets",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
},
map[string]string{},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::1", "2001:db8::2"},
[]string{"", "", ""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1", "foo-3"},
[]string{"", "", ""},
[]bool{true, true, true},
false,
[]v1.Node{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
},
false,
},
{
"annotated Headless services return IPv4 targets from pod annotation",
"",
"testing",
"foo",
@ -2500,7 +2598,40 @@ func TestHeadlessServices(t *testing.T) {
false,
},
{
"annotated Headless services return targets from node external IP if endpoints-type annotation is set",
"annotated Headless services return IPv6 targets from pod annotation",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
},
map[string]string{
targetAnnotationKey: "2001:db8::4",
},
v1.ClusterIPNone,
[]string{"2001:db8::1"},
[]string{""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo"},
[]string{"", "", ""},
[]bool{true, true, true},
false,
[]v1.Node{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
},
false,
},
{
"annotated Headless services return IPv4 targets from node external IP if endpoints-type annotation is set",
"",
"testing",
"foo",
@ -2543,7 +2674,98 @@ func TestHeadlessServices(t *testing.T) {
false,
},
{
"annotated Headless services return targets from hostIP if endpoints-type annotation is set",
"annotated Headless services return IPv6 targets from node external IP if endpoints-type annotation is set",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
endpointsTypeAnnotationKey: EndpointsTypeNodeExternalIP,
},
map[string]string{},
v1.ClusterIPNone,
[]string{"2001:db8::1"},
[]string{""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo"},
[]string{"", "", ""},
[]bool{true, true, true},
false,
[]v1.Node{
{
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Type: v1.NodeInternalIP,
Address: "2001:db8::4",
},
},
},
},
},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
},
false,
},
{
"annotated Headless services return dual-stack targets from node external IP if endpoints-type annotation is set",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
endpointsTypeAnnotationKey: EndpointsTypeNodeExternalIP,
},
map[string]string{},
v1.ClusterIPNone,
[]string{"1.1.1.1"},
[]string{""},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo"},
[]string{"", "", ""},
[]bool{true, true, true},
false,
[]v1.Node{
{
Status: v1.NodeStatus{
Addresses: []v1.NodeAddress{
{
Type: v1.NodeExternalIP,
Address: "1.2.3.4",
},
{
Type: v1.NodeInternalIP,
Address: "2001:db8::4",
},
},
},
},
},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
},
false,
},
{
"annotated Headless services return IPv4 targets from hostIP if endpoints-type annotation is set",
"",
"testing",
"foo",
@ -2574,6 +2796,38 @@ func TestHeadlessServices(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 targets from hostIP if endpoints-type annotation is set",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
endpointsTypeAnnotationKey: EndpointsTypeHostIP,
},
map[string]string{},
v1.ClusterIPNone,
[]string{"2001:db8::1"},
[]string{"2001:db8::4"},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo"},
[]string{"", "", ""},
[]bool{true, true, true},
false,
[]v1.Node{},
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
},
false,
},
} {
tc := tc
t.Run(tc.title, func(t *testing.T) {
@ -2716,7 +2970,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
expectError bool
}{
{
"annotated Headless services return endpoints for each selected Pod",
"annotated Headless services return IPv4 endpoints for each selected Pod",
"",
"testing",
"foo",
@ -2749,6 +3003,40 @@ func TestHeadlessServicesHostIP(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 endpoints for each selected Pod",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::2"},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1"},
[]string{"foo-0", "foo-1"},
[]bool{true, true},
[]*v1.ObjectReference{
{APIVersion: "", Kind: "Pod", Name: "foo-0"},
{APIVersion: "", Kind: "Pod", Name: "foo-1"},
},
false,
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
},
false,
},
{
"hostname annotated Headless services are ignored",
"",
@ -2780,7 +3068,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
false,
},
{
"annotated Headless services return endpoints with TTL for each selected Pod",
"annotated Headless services return IPv4 endpoints with TTL for each selected Pod",
"",
"testing",
"foo",
@ -2814,6 +3102,41 @@ func TestHeadlessServicesHostIP(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 endpoints with TTL for each selected Pod",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
ttlAnnotationKey: "1",
},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::2"},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1"},
[]string{"foo-0", "foo-1"},
[]bool{true, true},
[]*v1.ObjectReference{
{APIVersion: "", Kind: "Pod", Name: "foo-0"},
{APIVersion: "", Kind: "Pod", Name: "foo-1"},
},
false,
[]*endpoint.Endpoint{
{DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}, RecordTTL: endpoint.TTL(1)},
{DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
},
false,
},
{
"annotated Headless services return endpoints for each selected Pod, which are in running state",
"",
@ -2882,7 +3205,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
false,
},
{
"annotated Headless services return endpoints for pods missing hostname",
"annotated Headless services return IPv4 endpoints for pods missing hostname",
"",
"testing",
"foo",
@ -2913,6 +3236,38 @@ func TestHeadlessServicesHostIP(t *testing.T) {
},
false,
},
{
"annotated Headless services return IPv6 endpoints for pods missing hostname",
"",
"testing",
"foo",
v1.ServiceTypeClusterIP,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
},
v1.ClusterIPNone,
[]string{"2001:db8::1", "2001:db8::2"},
map[string]string{
"component": "foo",
},
[]string{},
[]string{"foo-0", "foo-1"},
[]string{"", ""},
[]bool{true, true},
[]*v1.ObjectReference{
{APIVersion: "", Kind: "Pod", Name: "foo-0"},
{APIVersion: "", Kind: "Pod", Name: "foo-1"},
},
false,
[]*endpoint.Endpoint{
{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
},
false,
},
{
"annotated Headless services without a targetRef has no endpoints",
"",
@ -3066,7 +3421,7 @@ func TestExternalServices(t *testing.T) {
expectError bool
}{
{
"external services return an A endpoint for the external name that is an IP address",
"external services return an A endpoint for the external name that is an IPv4 address",
"",
"testing",
"foo",
@ -3084,6 +3439,25 @@ func TestExternalServices(t *testing.T) {
},
false,
},
{
"external services return an AAAA endpoint for the external name that is an IPv6 address",
"",
"testing",
"foo",
v1.ServiceTypeExternalName,
"",
"",
false,
map[string]string{"component": "foo"},
map[string]string{
hostnameAnnotationKey: "service.example.org",
},
"2001:db8::111",
[]*endpoint.Endpoint{
{DNSName: "service.example.org", Targets: endpoint.Targets{"2001:db8::111"}, RecordType: endpoint.RecordTypeAAAA},
},
false,
},
{
"external services return a CNAME endpoint for the external name that is a domain",
"",