Skip to content

Commit 4033eec

Browse files
authored
Merge pull request #4721 from mjlshen/4713-aws-sd-ipv6
Add AWS_INSTANCE_IPV6 support to the AWS-SD provider
2 parents b834fef + 0b4c0e8 commit 4033eec

3 files changed

Lines changed: 119 additions & 9 deletions

File tree

docs/tutorials/aws-sd.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,32 @@ spec:
304304

305305
This will set the TTL for the DNS record to 60 seconds.
306306

307+
## IPv6 Support
308+
309+
If your Kubernetes cluster is configured with IPv6 support, such as an [EKS cluster with IPv6 support](https://docs.aws.amazon.com/eks/latest/userguide/deploy-ipv6-cluster.html), ExternalDNS can
310+
also create AAAA DNS records.
311+
312+
```yaml
313+
apiVersion: v1
314+
kind: Service
315+
metadata:
316+
name: nginx
317+
annotations:
318+
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com
319+
external-dns.alpha.kubernetes.io/ttl: "60"
320+
spec:
321+
ipFamilies:
322+
- "IPv6"
323+
type: NodePort
324+
ports:
325+
- port: 80
326+
name: http
327+
targetPort: 80
328+
selector:
329+
app: nginx
330+
```
331+
332+
:information_source: The AWS-SD provider does not currently support dualstack load balancers and will only create A records for these at this time. See the AWS provider and the [AWS Load Balancer Controller Tutorial](./aws-load-balancer-controller.md) for dualstack load balancer support.
307333

308334
## Clean up
309335

provider/awssd/aws_sd.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const (
4141
sdNamespaceTypePrivate = "private"
4242

4343
sdInstanceAttrIPV4 = "AWS_INSTANCE_IPV4"
44+
sdInstanceAttrIPV6 = "AWS_INSTANCE_IPV6"
4445
sdInstanceAttrCname = "AWS_INSTANCE_CNAME"
4546
sdInstanceAttrAlias = "AWS_ALIAS_DNS_NAME"
4647
)
@@ -186,10 +187,15 @@ func (p *AWSSDProvider) instancesToEndpoint(ns *sdtypes.NamespaceSummary, srv *s
186187
newEndpoint.RecordType = endpoint.RecordTypeCNAME
187188
newEndpoint.Targets = append(newEndpoint.Targets, inst.Attributes[sdInstanceAttrAlias])
188189

189-
// IP-based target
190+
// IPv4-based target
190191
} else if inst.Attributes[sdInstanceAttrIPV4] != "" {
191192
newEndpoint.RecordType = endpoint.RecordTypeA
192193
newEndpoint.Targets = append(newEndpoint.Targets, inst.Attributes[sdInstanceAttrIPV4])
194+
195+
// IPv6-based target
196+
} else if inst.Attributes[sdInstanceAttrIPV6] != "" {
197+
newEndpoint.RecordType = endpoint.RecordTypeAAAA
198+
newEndpoint.Targets = append(newEndpoint.Targets, inst.Attributes[sdInstanceAttrIPV6])
193199
} else {
194200
log.Warnf("Invalid instance \"%v\" found in service \"%v\"", inst, srv.Name)
195201
}
@@ -485,15 +491,18 @@ func (p *AWSSDProvider) RegisterInstance(ctx context.Context, service *sdtypes.S
485491

486492
attr := make(map[string]string)
487493

488-
if ep.RecordType == endpoint.RecordTypeCNAME {
494+
switch ep.RecordType {
495+
case endpoint.RecordTypeCNAME:
489496
if p.isAWSLoadBalancer(target) {
490497
attr[sdInstanceAttrAlias] = target
491498
} else {
492499
attr[sdInstanceAttrCname] = target
493500
}
494-
} else if ep.RecordType == endpoint.RecordTypeA {
501+
case endpoint.RecordTypeA:
495502
attr[sdInstanceAttrIPV4] = target
496-
} else {
503+
case endpoint.RecordTypeAAAA:
504+
attr[sdInstanceAttrIPV6] = target
505+
default:
497506
return fmt.Errorf("invalid endpoint type (%v)", ep)
498507
}
499508

@@ -597,25 +606,29 @@ func (p *AWSSDProvider) parseHostname(hostname string) (namespace string, servic
597606

598607
// determine service routing policy based on endpoint type
599608
func (p *AWSSDProvider) routingPolicyFromEndpoint(ep *endpoint.Endpoint) sdtypes.RoutingPolicy {
600-
if ep.RecordType == endpoint.RecordTypeA {
609+
if ep.RecordType == endpoint.RecordTypeA || ep.RecordType == endpoint.RecordTypeAAAA {
601610
return sdtypes.RoutingPolicyMultivalue
602611
}
603612

604613
return sdtypes.RoutingPolicyWeighted
605614
}
606615

607-
// determine service type (A, CNAME) from given endpoint
616+
// determine service type (A, AAAA, CNAME) from given endpoint
608617
func (p *AWSSDProvider) serviceTypeFromEndpoint(ep *endpoint.Endpoint) sdtypes.RecordType {
609-
if ep.RecordType == endpoint.RecordTypeCNAME {
618+
switch ep.RecordType {
619+
case endpoint.RecordTypeCNAME:
610620
// FIXME service type is derived from the first target only. Theoretically this may be problem.
611621
// But I don't see a scenario where one endpoint contains targets of different types.
612622
if p.isAWSLoadBalancer(ep.Targets[0]) {
613623
// ALIAS target uses DNS record of type A
614624
return sdtypes.RecordTypeA
615625
}
616626
return sdtypes.RecordTypeCname
627+
case endpoint.RecordTypeAAAA:
628+
return sdtypes.RecordTypeAaaa
629+
default:
630+
return sdtypes.RecordTypeA
617631
}
618-
return sdtypes.RecordTypeA
619632
}
620633

621634
// determine if a given hostname belongs to an AWS load balancer

provider/awssd/aws_sd_test.go

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,19 @@ func TestAWSSDProvider_Records(t *testing.T) {
307307
}},
308308
},
309309
},
310+
"aaaa-srv": {
311+
Id: aws.String("aaaa-srv"),
312+
Name: aws.String("service4"),
313+
Description: aws.String("owner-id"),
314+
DnsConfig: &sdtypes.DnsConfig{
315+
NamespaceId: aws.String("private"),
316+
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
317+
DnsRecords: []sdtypes.DnsRecord{{
318+
Type: sdtypes.RecordTypeAaaa,
319+
TTL: aws.Int64(100),
320+
}},
321+
},
322+
},
310323
},
311324
}
312325

@@ -341,12 +354,21 @@ func TestAWSSDProvider_Records(t *testing.T) {
341354
},
342355
},
343356
},
357+
"aaaa-srv": {
358+
"0000:0000:0000:0000:abcd:abcd:abcd:abcd": {
359+
Id: aws.String("0000:0000:0000:0000:abcd:abcd:abcd:abcd"),
360+
Attributes: map[string]string{
361+
sdInstanceAttrIPV6: "0000:0000:0000:0000:abcd:abcd:abcd:abcd",
362+
},
363+
},
364+
},
344365
}
345366

346367
expectedEndpoints := []*endpoint.Endpoint{
347368
{DNSName: "service1.private.com", Targets: endpoint.Targets{"1.2.3.4", "1.2.3.5"}, RecordType: endpoint.RecordTypeA, RecordTTL: 100, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
348369
{DNSName: "service2.private.com", Targets: endpoint.Targets{"load-balancer.us-east-1.elb.amazonaws.com"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 100, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
349370
{DNSName: "service3.private.com", Targets: endpoint.Targets{"cname.target.com"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 80, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
371+
{DNSName: "service4.private.com", Targets: endpoint.Targets{"0000:0000:0000:0000:abcd:abcd:abcd:abcd"}, RecordType: endpoint.RecordTypeAAAA, RecordTTL: 100, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
350372
}
351373

352374
api := &AWSSDClientStub{
@@ -557,6 +579,28 @@ func TestAWSSDProvider_CreateService(t *testing.T) {
557579
NamespaceId: aws.String("private"),
558580
}
559581

582+
// AAAA type
583+
provider.CreateService(context.Background(), aws.String("private"), aws.String("AAAA-srv"), &endpoint.Endpoint{
584+
Labels: map[string]string{
585+
endpoint.AWSSDDescriptionLabel: "AAAA-srv",
586+
},
587+
RecordType: endpoint.RecordTypeAAAA,
588+
RecordTTL: 60,
589+
Targets: endpoint.Targets{"::1234:5678:"},
590+
})
591+
expectedServices["AAAA-srv"] = &sdtypes.Service{
592+
Name: aws.String("AAAA-srv"),
593+
Description: aws.String("AAAA-srv"),
594+
DnsConfig: &sdtypes.DnsConfig{
595+
RoutingPolicy: sdtypes.RoutingPolicyMultivalue,
596+
DnsRecords: []sdtypes.DnsRecord{{
597+
Type: sdtypes.RecordTypeAaaa,
598+
TTL: aws.Int64(60),
599+
}},
600+
},
601+
NamespaceId: aws.String("private"),
602+
}
603+
560604
// CNAME type
561605
provider.CreateService(context.Background(), aws.String("private"), aws.String("CNAME-srv"), &endpoint.Endpoint{
562606
Labels: map[string]string{
@@ -768,6 +812,19 @@ func TestAWSSDProvider_RegisterInstance(t *testing.T) {
768812
}},
769813
},
770814
},
815+
"aaaa-srv": {
816+
Id: aws.String("aaaa-srv"),
817+
Name: aws.String("service4"),
818+
Description: aws.String("owner-id"),
819+
DnsConfig: &sdtypes.DnsConfig{
820+
NamespaceId: aws.String("private"),
821+
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
822+
DnsRecords: []sdtypes.DnsRecord{{
823+
Type: sdtypes.RecordTypeAaaa,
824+
TTL: aws.Int64(100),
825+
}},
826+
},
827+
},
771828
},
772829
}
773830

@@ -781,7 +838,7 @@ func TestAWSSDProvider_RegisterInstance(t *testing.T) {
781838

782839
expectedInstances := make(map[string]*sdtypes.Instance)
783840

784-
// IP-based instance
841+
// IPv4-based instance
785842
provider.RegisterInstance(context.Background(), services["private"]["a-srv"], &endpoint.Endpoint{
786843
RecordType: endpoint.RecordTypeA,
787844
DNSName: "service1.private.com.",
@@ -849,6 +906,20 @@ func TestAWSSDProvider_RegisterInstance(t *testing.T) {
849906
},
850907
}
851908

909+
// IPv6-based instance
910+
provider.RegisterInstance(context.Background(), services["private"]["aaaa-srv"], &endpoint.Endpoint{
911+
RecordType: endpoint.RecordTypeAAAA,
912+
DNSName: "service4.private.com.",
913+
RecordTTL: 300,
914+
Targets: endpoint.Targets{"0000:0000:0000:0000:abcd:abcd:abcd:abcd"},
915+
})
916+
expectedInstances["0000:0000:0000:0000:abcd:abcd:abcd:abcd"] = &sdtypes.Instance{
917+
Id: aws.String("0000:0000:0000:0000:abcd:abcd:abcd:abcd"),
918+
Attributes: map[string]string{
919+
sdInstanceAttrIPV6: "0000:0000:0000:0000:abcd:abcd:abcd:abcd",
920+
},
921+
}
922+
852923
// validate instances
853924
for _, srvInst := range api.instances {
854925
for id, inst := range srvInst {

0 commit comments

Comments
 (0)