Skip to content

Commit f6b14aa

Browse files
chore(aws): document and test behavior for ALB and NLB (#6063)
* chore(ingress): added tests to cover behaviour for AWS ALB and NLB Signed-off-by: ivan katliarchuk <[email protected]> * fix(domain-exclusion): domain exclusion filter fix Signed-off-by: ivan katliarchuk <[email protected]> * chore(ingress): added tests to cover behaviour for AWS ALB and NLB * fix(domain-exclusion): domain exclusion filter fix Signed-off-by: ivan katliarchuk <[email protected]> * fix(domain-exclusion): domain exclusion filter fix Signed-off-by: ivan katliarchuk <[email protected]> * fix(domain-exclusion): domain exclusion filter fix Signed-off-by: ivan katliarchuk <[email protected]> * fix(domain-exclusion): domain exclusion filter fix Signed-off-by: ivan katliarchuk <[email protected]> * chore(ingress): added tests to cover behaviour for AWS ALB and NLB Co-authored-by: vflaux <[email protected]> --------- Signed-off-by: ivan katliarchuk <[email protected]> Co-authored-by: vflaux <[email protected]>
1 parent 318b82f commit f6b14aa

File tree

2 files changed

+201
-0
lines changed

2 files changed

+201
-0
lines changed

docs/tutorials/aws-load-balancer-controller.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,128 @@ spec:
183183

184184
The above Ingress object will result in the creation of an ALB with a dualstack
185185
interface.
186+
187+
## Frontend Network Load Balancer (NLB)
188+
189+
The AWS Load Balancer Controller supports [fronting ALBs with an NLB][6] for improved performance
190+
and static IP addresses. When this feature is enabled, the controller creates both an ALB and an
191+
NLB, resulting in two hostnames in the Ingress status.
192+
193+
[6]: https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/guide/ingress/annotations/#enable-frontend-nlb
194+
195+
### Known Issue with Internal ALBs
196+
197+
When using an internal ALB (`alb.ingress.kubernetes.io/scheme: internal`) with frontend NLB,
198+
ExternalDNS may create DNS records pointing to the ALB instead of the NLB due to alphabetical
199+
ordering:
200+
201+
- Internal ALB hostname: `internal-k8s-myapp-alb.us-east-1.elb.amazonaws.com`
202+
- NLB hostname: `k8s-myapp-nlb-123456789.elb.us-east-1.amazonaws.com`
203+
204+
When multiple targets exist, Route53 selects the first one alphabetically, which incorrectly
205+
selects the internal ALB. See [issue #5661][7] for details.
206+
207+
[7]: https://github.com/kubernetes-sigs/external-dns/issues/5661
208+
209+
### Workarounds
210+
211+
There are several approaches to ensure DNS records point to the correct (NLB) target:
212+
213+
#### Option 1: Combine load balancer naming with target annotation (Recommended)
214+
215+
Use [`alb.ingress.kubernetes.io/load-balancer-name`][8] to create predictable hostnames, then
216+
explicitly reference the NLB using [`external-dns.alpha.kubernetes.io/target`][9]:
217+
218+
[8]: https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/guide/ingress/annotations/#load-balancer-name
219+
[9]: https://kubernetes-sigs.github.io/external-dns/latest/docs/annotations/annotations/#external-dnsalphakubernetesiotarget
220+
221+
```yaml
222+
apiVersion: networking.k8s.io/v1
223+
kind: Ingress
224+
metadata:
225+
annotations:
226+
alb.ingress.kubernetes.io/scheme: internal
227+
alb.ingress.kubernetes.io/enable-frontend-nlb: "true"
228+
alb.ingress.kubernetes.io/frontend-nlb-scheme: internal
229+
alb.ingress.kubernetes.io/load-balancer-name: myapp-alb
230+
external-dns.alpha.kubernetes.io/target: k8s-myapp-nlb.elb.us-east-1.amazonaws.com
231+
name: echoserver
232+
spec:
233+
ingressClassName: alb
234+
rules:
235+
- host: echoserver.example.org
236+
http:
237+
paths:
238+
- path: /
239+
backend:
240+
service:
241+
name: echoserver
242+
port:
243+
number: 80
244+
pathType: Prefix
245+
```
246+
247+
**Benefits**:
248+
249+
- Predictable, consistent load balancer naming across environments
250+
- Explicit control over which target ExternalDNS uses
251+
- Works reliably with internal ALBs
252+
- No need to lookup auto-generated NLB names
253+
254+
**NLB hostname pattern**: When you set `load-balancer-name: myapp-alb`, the NLB hostname
255+
becomes `k8s-myapp-nlb.elb.<region>.amazonaws.com` (note the `-nlb` suffix).
256+
257+
**ALB internal hostname pattern**: When you set `load-balancer-name: myapp-alb`, the ALB hostname
258+
becomes `internal-myapp-nlb.<region>.elb.amazonaws.com` (note the `internal-` suffix).
259+
260+
#### Option 2: Use the target annotation only
261+
262+
If you cannot control the load balancer name, explicitly specify the NLB hostname:
263+
264+
```yaml
265+
apiVersion: networking.k8s.io/v1
266+
kind: Ingress
267+
metadata:
268+
annotations:
269+
alb.ingress.kubernetes.io/scheme: internal
270+
alb.ingress.kubernetes.io/enable-frontend-nlb: "true"
271+
alb.ingress.kubernetes.io/frontend-nlb-scheme: internal
272+
external-dns.alpha.kubernetes.io/target: k8s-myapp-nlb-123456789.elb.us-east-1.amazonaws.com
273+
name: echoserver
274+
spec:
275+
ingressClassName: alb
276+
rules:
277+
- host: echoserver.example.org
278+
http:
279+
paths:
280+
- path: /
281+
backend:
282+
service:
283+
name: echoserver
284+
port:
285+
number: 80
286+
pathType: Prefix
287+
```
288+
289+
**Note**: You'll need to lookup the auto-generated NLB hostname after the controller creates it.
290+
291+
#### Option 3: Use a DNSEndpoint resource
292+
293+
Create a [`DNSEndpoint`][10] custom resource to explicitly define the DNS record:
294+
295+
```yaml
296+
apiVersion: externaldns.k8s.io/v1alpha1
297+
kind: DNSEndpoint
298+
metadata:
299+
name: echoserver-dns
300+
spec:
301+
endpoints:
302+
- dnsName: echoserver.example.org
303+
recordType: CNAME
304+
targets:
305+
- k8s-myapp-nlb-123456789.elb.us-east-1.amazonaws.com
306+
```
307+
308+
This approach is useful when you want to manage DNS records independently of the Ingress resource.
309+
310+
[10]:https://kubernetes-sigs.github.io/external-dns/latest/docs/tutorials/crd/

source/ingress_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,6 +1611,82 @@ func TestIngressWithConfiguration(t *testing.T) {
16111611
{DNSName: "abc.example.com", Targets: endpoint.Targets{"1.2.3.4"}, RecordType: endpoint.RecordTypeA},
16121612
},
16131613
},
1614+
{
1615+
title: "ingress with when AWS ALB controller and NLB type generates two targets for CNAME",
1616+
ingresses: []*networkv1.Ingress{
1617+
{
1618+
ObjectMeta: metav1.ObjectMeta{
1619+
Name: "my-ingress",
1620+
Namespace: "default",
1621+
Annotations: map[string]string{
1622+
"alb.ingress.kubernetes.io/enable-frontend-nlb": "true",
1623+
"alb.ingress.kubernetes.io/frontend-nlb-scheme": "internal",
1624+
},
1625+
},
1626+
Spec: networkv1.IngressSpec{
1627+
IngressClassName: testutils.ToPtr("alb"),
1628+
Rules: []networkv1.IngressRule{
1629+
{Host: "some.subdomain.mydomain.com"},
1630+
},
1631+
},
1632+
Status: networkv1.IngressStatus{
1633+
LoadBalancer: networkv1.IngressLoadBalancerStatus{
1634+
Ingress: []networkv1.IngressLoadBalancerIngress{
1635+
{Hostname: "internal-k8s-some-domain.us-east-1.elb.amazonaws.com"},
1636+
{Hostname: "k8s-another-domain-nlb-123456789.elb.us-east-1.amazonaws.com"},
1637+
},
1638+
},
1639+
},
1640+
},
1641+
},
1642+
expected: []*endpoint.Endpoint{
1643+
{
1644+
DNSName: "some.subdomain.mydomain.com",
1645+
RecordType: endpoint.RecordTypeCNAME,
1646+
Targets: endpoint.Targets{
1647+
"internal-k8s-some-domain.us-east-1.elb.amazonaws.com",
1648+
"k8s-another-domain-nlb-123456789.elb.us-east-1.amazonaws.com",
1649+
},
1650+
},
1651+
},
1652+
},
1653+
{
1654+
title: "ingress with when AWS ALB controller and NLB with target annotation and CNAME with single target",
1655+
ingresses: []*networkv1.Ingress{
1656+
{
1657+
ObjectMeta: metav1.ObjectMeta{
1658+
Name: "my-ingress",
1659+
Namespace: "default",
1660+
Annotations: map[string]string{
1661+
"alb.ingress.kubernetes.io/enable-frontend-nlb": "true",
1662+
"alb.ingress.kubernetes.io/frontend-nlb-scheme": "internal",
1663+
annotations.TargetKey: "k8s-another-domain-nlb-123456789.elb.us-east-1.amazonaws.com",
1664+
},
1665+
},
1666+
Spec: networkv1.IngressSpec{
1667+
IngressClassName: testutils.ToPtr("alb"),
1668+
Rules: []networkv1.IngressRule{
1669+
{Host: "some.subdomain.mydomain.com"},
1670+
},
1671+
},
1672+
Status: networkv1.IngressStatus{
1673+
LoadBalancer: networkv1.IngressLoadBalancerStatus{
1674+
Ingress: []networkv1.IngressLoadBalancerIngress{
1675+
{Hostname: "internal-k8s-some-domain.us-east-1.elb.amazonaws.com"},
1676+
{Hostname: "k8s-another-domain-nlb-123456789.elb.us-east-1.amazonaws.com"},
1677+
},
1678+
},
1679+
},
1680+
},
1681+
},
1682+
expected: []*endpoint.Endpoint{
1683+
{
1684+
DNSName: "some.subdomain.mydomain.com",
1685+
RecordType: endpoint.RecordTypeCNAME,
1686+
Targets: endpoint.Targets{"k8s-another-domain-nlb-123456789.elb.us-east-1.amazonaws.com"},
1687+
},
1688+
},
1689+
},
16141690
} {
16151691
t.Run(tt.title, func(t *testing.T) {
16161692
kubeClient := fake.NewClientset()

0 commit comments

Comments
 (0)