Skip to content

Commit

Permalink
Merge pull request #4721 from mjlshen/4713-aws-sd-ipv6
Browse files Browse the repository at this point in the history
Add AWS_INSTANCE_IPV6 support to the AWS-SD provider
  • Loading branch information
k8s-ci-robot authored Oct 22, 2024
2 parents b834fef + 0b4c0e8 commit 4033eec
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 9 deletions.
26 changes: 26 additions & 0 deletions docs/tutorials/aws-sd.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,32 @@ spec:

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

## IPv6 Support

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
also create AAAA DNS records.

```yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com
external-dns.alpha.kubernetes.io/ttl: "60"
spec:
ipFamilies:
- "IPv6"
type: NodePort
ports:
- port: 80
name: http
targetPort: 80
selector:
app: nginx
```

: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.

## Clean up

Expand Down
29 changes: 21 additions & 8 deletions provider/awssd/aws_sd.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const (
sdNamespaceTypePrivate = "private"

sdInstanceAttrIPV4 = "AWS_INSTANCE_IPV4"
sdInstanceAttrIPV6 = "AWS_INSTANCE_IPV6"
sdInstanceAttrCname = "AWS_INSTANCE_CNAME"
sdInstanceAttrAlias = "AWS_ALIAS_DNS_NAME"
)
Expand Down Expand Up @@ -186,10 +187,15 @@ func (p *AWSSDProvider) instancesToEndpoint(ns *sdtypes.NamespaceSummary, srv *s
newEndpoint.RecordType = endpoint.RecordTypeCNAME
newEndpoint.Targets = append(newEndpoint.Targets, inst.Attributes[sdInstanceAttrAlias])

// IP-based target
// IPv4-based target
} else if inst.Attributes[sdInstanceAttrIPV4] != "" {
newEndpoint.RecordType = endpoint.RecordTypeA
newEndpoint.Targets = append(newEndpoint.Targets, inst.Attributes[sdInstanceAttrIPV4])

// IPv6-based target
} else if inst.Attributes[sdInstanceAttrIPV6] != "" {
newEndpoint.RecordType = endpoint.RecordTypeAAAA
newEndpoint.Targets = append(newEndpoint.Targets, inst.Attributes[sdInstanceAttrIPV6])
} else {
log.Warnf("Invalid instance \"%v\" found in service \"%v\"", inst, srv.Name)
}
Expand Down Expand Up @@ -485,15 +491,18 @@ func (p *AWSSDProvider) RegisterInstance(ctx context.Context, service *sdtypes.S

attr := make(map[string]string)

if ep.RecordType == endpoint.RecordTypeCNAME {
switch ep.RecordType {
case endpoint.RecordTypeCNAME:
if p.isAWSLoadBalancer(target) {
attr[sdInstanceAttrAlias] = target
} else {
attr[sdInstanceAttrCname] = target
}
} else if ep.RecordType == endpoint.RecordTypeA {
case endpoint.RecordTypeA:
attr[sdInstanceAttrIPV4] = target
} else {
case endpoint.RecordTypeAAAA:
attr[sdInstanceAttrIPV6] = target
default:
return fmt.Errorf("invalid endpoint type (%v)", ep)
}

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

// determine service routing policy based on endpoint type
func (p *AWSSDProvider) routingPolicyFromEndpoint(ep *endpoint.Endpoint) sdtypes.RoutingPolicy {
if ep.RecordType == endpoint.RecordTypeA {
if ep.RecordType == endpoint.RecordTypeA || ep.RecordType == endpoint.RecordTypeAAAA {
return sdtypes.RoutingPolicyMultivalue
}

return sdtypes.RoutingPolicyWeighted
}

// determine service type (A, CNAME) from given endpoint
// determine service type (A, AAAA, CNAME) from given endpoint
func (p *AWSSDProvider) serviceTypeFromEndpoint(ep *endpoint.Endpoint) sdtypes.RecordType {
if ep.RecordType == endpoint.RecordTypeCNAME {
switch ep.RecordType {
case endpoint.RecordTypeCNAME:
// FIXME service type is derived from the first target only. Theoretically this may be problem.
// But I don't see a scenario where one endpoint contains targets of different types.
if p.isAWSLoadBalancer(ep.Targets[0]) {
// ALIAS target uses DNS record of type A
return sdtypes.RecordTypeA
}
return sdtypes.RecordTypeCname
case endpoint.RecordTypeAAAA:
return sdtypes.RecordTypeAaaa
default:
return sdtypes.RecordTypeA
}
return sdtypes.RecordTypeA
}

// determine if a given hostname belongs to an AWS load balancer
Expand Down
73 changes: 72 additions & 1 deletion provider/awssd/aws_sd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,19 @@ func TestAWSSDProvider_Records(t *testing.T) {
}},
},
},
"aaaa-srv": {
Id: aws.String("aaaa-srv"),
Name: aws.String("service4"),
Description: aws.String("owner-id"),
DnsConfig: &sdtypes.DnsConfig{
NamespaceId: aws.String("private"),
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
DnsRecords: []sdtypes.DnsRecord{{
Type: sdtypes.RecordTypeAaaa,
TTL: aws.Int64(100),
}},
},
},
},
}

Expand Down Expand Up @@ -341,12 +354,21 @@ func TestAWSSDProvider_Records(t *testing.T) {
},
},
},
"aaaa-srv": {
"0000:0000:0000:0000:abcd:abcd:abcd:abcd": {
Id: aws.String("0000:0000:0000:0000:abcd:abcd:abcd:abcd"),
Attributes: map[string]string{
sdInstanceAttrIPV6: "0000:0000:0000:0000:abcd:abcd:abcd:abcd",
},
},
},
}

expectedEndpoints := []*endpoint.Endpoint{
{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"}},
{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"}},
{DNSName: "service3.private.com", Targets: endpoint.Targets{"cname.target.com"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 80, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
{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"}},
}

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

// AAAA type
provider.CreateService(context.Background(), aws.String("private"), aws.String("AAAA-srv"), &endpoint.Endpoint{
Labels: map[string]string{
endpoint.AWSSDDescriptionLabel: "AAAA-srv",
},
RecordType: endpoint.RecordTypeAAAA,
RecordTTL: 60,
Targets: endpoint.Targets{"::1234:5678:"},
})
expectedServices["AAAA-srv"] = &sdtypes.Service{
Name: aws.String("AAAA-srv"),
Description: aws.String("AAAA-srv"),
DnsConfig: &sdtypes.DnsConfig{
RoutingPolicy: sdtypes.RoutingPolicyMultivalue,
DnsRecords: []sdtypes.DnsRecord{{
Type: sdtypes.RecordTypeAaaa,
TTL: aws.Int64(60),
}},
},
NamespaceId: aws.String("private"),
}

// CNAME type
provider.CreateService(context.Background(), aws.String("private"), aws.String("CNAME-srv"), &endpoint.Endpoint{
Labels: map[string]string{
Expand Down Expand Up @@ -768,6 +812,19 @@ func TestAWSSDProvider_RegisterInstance(t *testing.T) {
}},
},
},
"aaaa-srv": {
Id: aws.String("aaaa-srv"),
Name: aws.String("service4"),
Description: aws.String("owner-id"),
DnsConfig: &sdtypes.DnsConfig{
NamespaceId: aws.String("private"),
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
DnsRecords: []sdtypes.DnsRecord{{
Type: sdtypes.RecordTypeAaaa,
TTL: aws.Int64(100),
}},
},
},
},
}

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

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

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

// IPv6-based instance
provider.RegisterInstance(context.Background(), services["private"]["aaaa-srv"], &endpoint.Endpoint{
RecordType: endpoint.RecordTypeAAAA,
DNSName: "service4.private.com.",
RecordTTL: 300,
Targets: endpoint.Targets{"0000:0000:0000:0000:abcd:abcd:abcd:abcd"},
})
expectedInstances["0000:0000:0000:0000:abcd:abcd:abcd:abcd"] = &sdtypes.Instance{
Id: aws.String("0000:0000:0000:0000:abcd:abcd:abcd:abcd"),
Attributes: map[string]string{
sdInstanceAttrIPV6: "0000:0000:0000:0000:abcd:abcd:abcd:abcd",
},
}

// validate instances
for _, srvInst := range api.instances {
for id, inst := range srvInst {
Expand Down

0 comments on commit 4033eec

Please sign in to comment.