Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP Always create AAAA alias records in route53 #3605

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 3 additions & 42 deletions docs/tutorials/aws-load-balancer-controller.md
Expand Up @@ -103,9 +103,9 @@ traffic 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, an A and AAAA for each of `echoserver.mycluster.example.org` and
`echoserver.example.org`, which all alias the ALB that is associated with the
Ingress object. As the ALB is IPv4-only, the AAAA alias records have no effect.

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 @@ -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
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
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
44 changes: 25 additions & 19 deletions provider/aws/aws.go
Expand Up @@ -360,8 +360,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 @@ -611,15 +615,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 @@ -631,9 +628,10 @@ 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 ep.RecordType != endpoint.RecordTypeCNAME {
if ep.RecordType != endpoint.RecordTypeCNAME && ep.RecordType != endpoint.RecordTypeAAAA {
ep.DeleteProviderSpecificProperty(providerSpecificAlias)
} else if aliasString, ok := ep.GetProviderSpecificProperty(providerSpecificAlias); ok {
alias = aliasString == "true"
Expand Down Expand Up @@ -661,15 +659,21 @@ func (p *AWSProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoin
} else {
ep.DeleteProviderSpecificProperty(providerSpecificEvaluateTargetHealth)
}

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 @@ -678,17 +682,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 == "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 @@ -758,7 +761,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 @@ -965,8 +968,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 ""
}
isAlias, exists := ep.GetProviderSpecificProperty(providerSpecificAlias)
if exists && isAlias == "true" && ep.RecordType == endpoint.RecordTypeCNAME && len(ep.Targets) > 0 {
if exists && isAlias == "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