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

feat: add support for aws dualstack nlbs #4511

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/tutorials/aws-load-balancer-controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,37 @@ spec:
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.

## Dualstack NLBs

AWS supports both IPv4 and "dualstack" (both IPv4 and IPv6) interfaces for NLBs.
The AWS Load Balancer Controller uses the `service.beta.kubernetes.io/aws-load-balancer-ip-address-type`
[annotation][5] (which defaults to `ipv4`) to determine this. When this annotation is
set to `dualstack`, ExternalDNS create two alias records (one A record
and one AAAA record) for each hostname associated with the service object of type loadbalancer.

[5]: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.7/guide/service/annotations/#ip-address-type

Example:

```yaml
apiVersion: v1
kind: Service
metadata:
name: echoserver
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-ip-address-type: dualstack
spec:
selector:
app: echoserver
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: LoadBalancer
```

The above Service object will result in the creation of a NLB 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 NLB.
19 changes: 18 additions & 1 deletion source/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ type serviceSource struct {
labelSelector labels.Selector
}

const (
// nlbDualstackAnnotationKey is the annotation used for determining if an NLB svc is dualstack
nlbDualstackAnnotationKey = "service.beta.kubernetes.io/aws-load-balancer-ip-address-type"
// nlbDualstackAnnotationValue is the value of the NLB dualstack annotation that indicates it is dualstack
nlbDualstackAnnotationValue = "dualstack"
)

// NewServiceSource creates a new serviceSource with the given config.
func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool, alwaysPublishNotReadyAddresses bool, serviceTypeFilter []string, ignoreHostnameAnnotation bool, labelSelector labels.Selector, resolveLoadBalancerHostname bool) (Source, error) {
tmpl, err := parseTemplate(fqdnTemplate)
Expand Down Expand Up @@ -197,6 +204,7 @@ func (sc *serviceSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e

log.Debugf("Endpoints generated from service: %s/%s: %v", svc.Namespace, svc.Name, svcEndpoints)
sc.setResourceLabel(svc, svcEndpoints)
sc.setDualstackLabel(svc, svcEndpoints)
endpoints = append(endpoints, svcEndpoints...)
}

Expand Down Expand Up @@ -236,7 +244,6 @@ func (sc *serviceSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e
for _, ep := range endpoints {
sort.Sort(ep.Targets)
}

return endpoints, nil
}

Expand Down Expand Up @@ -459,6 +466,16 @@ func (sc *serviceSource) setResourceLabel(service *v1.Service, endpoints []*endp
}
}

func (sc *serviceSource) setDualstackLabel(service *v1.Service, endpoints []*endpoint.Endpoint) {
val, ok := service.Annotations[nlbDualstackAnnotationKey]
if ok && val == nlbDualstackAnnotationValue {
log.Debugf("Adding dualstack label to service %s/%s.", service.Namespace, service.Name)
for _, ep := range endpoints {
ep.Labels[endpoint.DualstackLabelKey] = "true"
}
}
}

func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, providerSpecific endpoint.ProviderSpecific, setIdentifier string, useClusterIP bool) (endpoints []*endpoint.Endpoint) {
hostname = strings.TrimSuffix(hostname, ".")

Expand Down
25 changes: 25 additions & 0 deletions source/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,31 @@ func testServiceSourceEndpoints(t *testing.T) {
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
},
},
{
title: "annotated dualstack services return an endpoint with dualstack label",
svcNamespace: "testing",
svcName: "foo",
svcType: v1.ServiceTypeLoadBalancer,
labels: map[string]string{},
annotations: map[string]string{
hostnameAnnotationKey: "foo.example.org.",
nlbDualstackAnnotationKey: nlbDualstackAnnotationValue,
},
externalIPs: []string{},
lbs: []string{"https://www.example.com"},
serviceTypesFilter: []string{},
expected: []*endpoint.Endpoint{
{
DNSName: "foo.example.org",
RecordType: endpoint.RecordTypeCNAME,
Targets: endpoint.Targets{"https://www.example.com"},
Labels: endpoint.Labels{
"resource": "service/testing/foo",
"dualstack": "true",
},
},
},
},
{
title: "hostname annotation on services is ignored",
svcNamespace: "testing",
Expand Down
Loading