Skip to content

Commit

Permalink
Always create AAAA alias records in route53
Browse files Browse the repository at this point in the history
  • Loading branch information
johngmyers committed Jun 10, 2023
1 parent b3885c3 commit 0a60b91
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 205 deletions.
39 changes: 0 additions & 39 deletions docs/tutorials/aws-load-balancer-controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,42 +137,3 @@ spec:
In the above example we create a default path that works for any hostname, and
make use of the `external-dns.alpha.kubernetes.io/hostname` annotation to create
multiple aliases for the resulting ALB.

## Dualstack ALBs

AWS [supports][4] both IPv4 and "dualstack" (both IPv4 and IPv6) interfaces for ALBs.
The AWS Load Balancer Controller uses the `alb.ingress.kubernetes.io/ip-address-type`
annotation (which defaults to `ipv4`) to determine this. If this annotation is
set to `dualstack` then ExternalDNS will create two alias records (one A record
and one AAAA record) for each hostname associated with the Ingress object.

[4]: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#ip-address-type

Example:

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/ip-address-type: dualstack
name: echoserver
spec:
ingressClassName: alb
rules:
- host: echoserver.example.org
http:
paths:
- path: /
backend:
service:
name: echoserver
port:
number: 80
pathType: Prefix
```

The above Ingress object will result in the creation of an ALB with a dualstack
interface. ExternalDNS will create both an A `echoserver.example.org` record and
an AAAA record of the same name, that each are aliases for the same ALB.
47 changes: 5 additions & 42 deletions docs/tutorials/kube-ingress-aws.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ traffic to skipper which will forward to the echoserver application.

If the `--source=ingress` argument is specified, then ExternalDNS will create DNS
records based on the hosts specified in ingress objects. The above example would
result in two alias records being created, `echoserver.mycluster.example.org` and
`echoserver.example.org`, which both alias the ALB that is associated with the
Ingress object.
result in four alias records being created: `echoserver.mycluster.example.org` and
`echoserver.example.org`, each domain with an A and AAAA, all aliasing the ALB that
is associated with the Ingress object.

Note that the above example makes use of the YAML anchor feature to avoid having
to repeat the http section for multiple hosts that use the exact same paths. If
Expand Down Expand Up @@ -198,43 +198,6 @@ In the above example we create a default path that works for any hostname, and
make use of the `external-dns.alpha.kubernetes.io/hostname` annotation to create
multiple aliases for the resulting ALB.

## Dualstack ALBs

AWS [supports](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#ip-address-type) both IPv4 and "dualstack" (both IPv4 and IPv6) interfaces for ALBs.
The Kubernetes Ingress AWS controller supports the `alb.ingress.kubernetes.io/ip-address-type`
annotation (which defaults to `ipv4`) to determine this. If this annotation is
set to `dualstack` then ExternalDNS will create two alias records (one A record
and one AAAA record) for each hostname associated with the Ingress object.


Example:

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
alb.ingress.kubernetes.io/ip-address-type: dualstack
name: echoserver
spec:
ingressClassName: skipper
rules:
- host: echoserver.example.org
http:
paths:
- path: /
backend:
service:
name: echoserver
port:
number: 80
pathType: Prefix
```

The above Ingress object will result in the creation of an ALB with a dualstack
interface. ExternalDNS will create both an A `echoserver.example.org` record and
an AAAA record of the same name, that each are aliases for the same ALB.

## NLBs

AWS has
Expand Down Expand Up @@ -279,8 +242,8 @@ status:
- hostname: kube-ing-lb-atedkrlml7iu-1681027139.$region.elb.amazonaws.com
```

ExternalDNS will create a A-records `echoserver.example.org`, that
use AWS ALIAS record to automatically maintain IP addresses of the NLB.
ExternalDNS will create `echoserver.example.org` A and AAAA alias records
to automatically maintain IP addresses of the NLB.

## RouteGroup (optional)

Expand Down
3 changes: 0 additions & 3 deletions endpoint/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ const (
// supposed to be inserted by AWS SD Provider, and parsed into OwnerLabelKey and ResourceLabelKey key by AWS SD Registry
AWSSDDescriptionLabel = "aws-sd-description"

// DualstackLabelKey is the name of the label that identifies dualstack endpoints
DualstackLabelKey = "dualstack"

// txtEncryptionNonce label for keep same nonce for same txt records, for prevent different result of encryption for same txt record, it can cause issues for some providers
txtEncryptionNonce = "txt-encryption-nonce"
)
Expand Down
42 changes: 24 additions & 18 deletions provider/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,12 @@ func (p *AWSProvider) records(ctx context.Context, zones map[string]*route53.Hos
if ttl == 0 {
ttl = recordTTL
}
recordType := aws.StringValue(r.Type)
if recordType == endpoint.RecordTypeA {
recordType = endpoint.RecordTypeCNAME
}
ep := endpoint.
NewEndpointWithTTL(wildcardUnescape(aws.StringValue(r.Name)), endpoint.RecordTypeCNAME, ttl, aws.StringValue(r.AliasTarget.DNSName)).
NewEndpointWithTTL(wildcardUnescape(aws.StringValue(r.Name)), recordType, ttl, aws.StringValue(r.AliasTarget.DNSName)).
WithProviderSpecific(providerSpecificEvaluateTargetHealth, fmt.Sprintf("%t", aws.BoolValue(r.AliasTarget.EvaluateTargetHealth))).
WithProviderSpecific(providerSpecificAlias, "true")
newEndpoints = append(newEndpoints, ep)
Expand Down Expand Up @@ -645,15 +649,8 @@ func (p *AWSProvider) newChanges(action string, endpoints []*endpoint.Endpoint)
changes := make(Route53Changes, 0, len(endpoints))

for _, endpoint := range endpoints {
change, dualstack := p.newChange(action, endpoint)
change := p.newChange(action, endpoint)
changes = append(changes, change)
if dualstack {
// make a copy of change, modify RRS type to AAAA, then add new change
rrs := *change.ResourceRecordSet
change2 := &Route53Change{Change: route53.Change{Action: change.Action, ResourceRecordSet: &rrs}}
change2.ResourceRecordSet.Type = aws.String(route53.RRTypeAaaa)
changes = append(changes, change2)
}
}

return changes
Expand All @@ -665,6 +662,7 @@ func (p *AWSProvider) newChanges(action string, endpoints []*endpoint.Endpoint)
// Example: CNAME endpoints pointing to ELBs will have a `alias` provider-specific property
// added to match the endpoints generated from existing alias records in Route53.
func (p *AWSProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
var additionalEndpoints []*endpoint.Endpoint
for _, ep := range endpoints {
alias := false
if aliasString, ok := ep.GetProviderSpecificProperty(providerSpecificAlias); ok {
Expand All @@ -685,15 +683,21 @@ func (p *AWSProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoin
Value: fmt.Sprintf("%t", p.evaluateTargetHealth),
})
}

if alias {
epAAAA := *ep
epAAAA.RecordType = endpoint.RecordTypeAAAA
additionalEndpoints = append(additionalEndpoints, &epAAAA)
}
}
return endpoints
return append(endpoints, additionalEndpoints...)
}

// newChange returns a route53 Change and a boolean indicating if there should also be a change to a AAAA record
// returned Change is based on the given record by the given action, e.g.
// action=ChangeActionCreate returns a change for creation of the record and
// action=ChangeActionDelete returns a change for deletion of the record.
func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53Change, bool) {
func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) *Route53Change {
change := &Route53Change{
Change: route53.Change{
Action: aws.String(action),
Expand All @@ -702,17 +706,16 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
},
},
}
dualstack := false
if targetHostedZone := isAWSAlias(ep); targetHostedZone != "" {
evalTargetHealth := p.evaluateTargetHealth
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificEvaluateTargetHealth); ok {
evalTargetHealth = prop.Value == "true"
}
// If the endpoint has a Dualstack label, append a change for AAAA record as well.
if val, ok := ep.Labels[endpoint.DualstackLabelKey]; ok {
dualstack = val == "true"
if ep.RecordType == endpoint.RecordTypeCNAME {
change.ResourceRecordSet.Type = aws.String(route53.RRTypeA)
} else {
change.ResourceRecordSet.Type = aws.String(ep.RecordType)
}
change.ResourceRecordSet.Type = aws.String(route53.RRTypeA)
change.ResourceRecordSet.AliasTarget = &route53.AliasTarget{
DNSName: aws.String(ep.Targets[0]),
HostedZoneId: aws.String(cleanZoneID(targetHostedZone)),
Expand Down Expand Up @@ -782,7 +785,7 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
change.OwnedRecord = ownedRecord
}

return change, dualstack
return change
}

// searches for `changes` that are contained in `queue` and returns the `changes` separated by whether they were found in the queue (`foundChanges`) or not (`notFoundChanges`)
Expand Down Expand Up @@ -989,8 +992,11 @@ func useAlias(ep *endpoint.Endpoint, preferCNAME bool) bool {
// isAWSAlias determines if a given endpoint is supposed to create an AWS Alias record
// and (if so) returns the target hosted zone ID
func isAWSAlias(ep *endpoint.Endpoint) string {
if ep.RecordType != endpoint.RecordTypeCNAME && ep.RecordType != endpoint.RecordTypeAAAA {
return ""
}
prop, exists := ep.GetProviderSpecificProperty(providerSpecificAlias)
if exists && prop.Value == "true" && ep.RecordType == endpoint.RecordTypeCNAME && len(ep.Targets) > 0 {
if exists && prop.Value == "true" && len(ep.Targets) > 0 {
// alias records can only point to canonical hosted zones (e.g. to ELBs) or other records in the same zone

if hostedZoneID, ok := ep.GetProviderSpecificProperty(providerSpecificTargetHostedZone); ok {
Expand Down

0 comments on commit 0a60b91

Please sign in to comment.