Skip to content

Commit

Permalink
Merge pull request #4 from smartnews/feat/add-support-for-dualstack-a…
Browse files Browse the repository at this point in the history
…ws-nlbs

feat/add support for dualstack aws nlbs
  • Loading branch information
nakamume authored Oct 17, 2024
2 parents 59bfa53 + a6f0d5f commit eba0e24
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 4 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
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.
26 changes: 25 additions & 1 deletion source/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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...)
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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, ".")

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

0 comments on commit eba0e24

Please sign in to comment.