Merge pull request #4721 from mjlshen/4713-aws-sd-ipv6

Add AWS_INSTANCE_IPV6 support to the AWS-SD provider
This commit is contained in:
Kubernetes Prow Robot 2024-10-22 16:28:54 +01:00 committed by GitHub
commit 4033eec84b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 119 additions and 9 deletions

View File

@ -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

View File

@ -41,6 +41,7 @@ const (
sdNamespaceTypePrivate = "private"
sdInstanceAttrIPV4 = "AWS_INSTANCE_IPV4"
sdInstanceAttrIPV6 = "AWS_INSTANCE_IPV6"
sdInstanceAttrCname = "AWS_INSTANCE_CNAME"
sdInstanceAttrAlias = "AWS_ALIAS_DNS_NAME"
)
@ -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)
}
@ -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)
}
@ -597,16 +606,17 @@ 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]) {
@ -614,8 +624,11 @@ func (p *AWSSDProvider) serviceTypeFromEndpoint(ep *endpoint.Endpoint) sdtypes.R
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

View File

@ -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),
}},
},
},
},
}
@ -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{
@ -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{
@ -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),
}},
},
},
},
}
@ -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.",
@ -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 {