diff --git a/docs/tutorials/aws-load-balancer-controller.md b/docs/tutorials/aws-load-balancer-controller.md index 98bc5da693..e4954a5717 100644 --- a/docs/tutorials/aws-load-balancer-controller.md +++ b/docs/tutorials/aws-load-balancer-controller.md @@ -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. diff --git a/source/service.go b/source/service.go index ac63f9c5b1..0e022d6b4c 100644 --- a/source/service.go +++ b/source/service.go @@ -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) @@ -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...) } @@ -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 } @@ -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, ".") diff --git a/source/service_test.go b/source/service_test.go index 543a173af0..7c0877684f 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -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",