diff --git a/Makefile b/Makefile index be24ff476e..c5b4317edb 100644 --- a/Makefile +++ b/Makefile @@ -81,13 +81,13 @@ BINARY ?= external-dns SOURCES = $(shell find . -name '*.go') IMAGE_STAGING = gcr.io/k8s-staging-external-dns/$(BINARY) REGISTRY ?= us.gcr.io/k8s-artifacts-prod/external-dns -IMAGE ?= $(REGISTRY)/$(BINARY) -VERSION ?= $(shell git describe --tags --always --dirty --match "v*") +IMAGE ?= 165463520094.dkr.ecr.ap-northeast-1.amazonaws.com/ops-spaas/external-dns +VERSION ?= v0.14.2-patch02 BUILD_FLAGS ?= -v LDFLAGS ?= -X sigs.k8s.io/external-dns/pkg/apis/externaldns.Version=$(VERSION) -w -s ARCH ?= amd64 SHELL = /bin/bash -IMG_PLATFORM ?= linux/amd64,linux/arm64,linux/arm/v7 +IMG_PLATFORM ?= linux/amd64,linux/arm64 IMG_PUSH ?= true IMG_SBOM ?= none 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..8e884d6cef 100644 --- a/source/service.go +++ b/source/service.go @@ -63,6 +63,17 @@ 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" + // nlbCreateAaaaRecordAnnotationKey is the annotation used for determining if an AAAA record should be created for an NLB + nlbCreateAaaaRecordAnnotationKey = "service.spaas.smartnews.net/create-aaaa-record" + // nlbDisableAaaaRecordAnnotationValue is the value of the NLB AAAA annotation that indicates it should not create an AAAA record + nlbDisableAaaaRecordAnnotationValue = "false" +) + // 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 +208,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 +248,6 @@ func (sc *serviceSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e for _, ep := range endpoints { sort.Sort(ep.Targets) } - return endpoints, nil } @@ -459,6 +470,19 @@ func (sc *serviceSource) setResourceLabel(service *v1.Service, endpoints []*endp } } +func (sc *serviceSource) setDualstackLabel(service *v1.Service, endpoints []*endpoint.Endpoint) { + dualstackAnnotationValue, dualstackAnnotationExists := service.Annotations[nlbDualstackAnnotationKey] + if dualstackAnnotationExists && dualstackAnnotationValue == nlbDualstackAnnotationValue { + createAaaaAnnotationValue, createAaaaAnnotationExists := service.Annotations[nlbCreateAaaaRecordAnnotationKey] + if !createAaaaAnnotationExists || createAaaaAnnotationValue != nlbDisableAaaaRecordAnnotationValue { + 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",