Skip to content

Commit ccab9a9

Browse files
authored
Merge pull request #4469 from thameezb/feat-support-dual-stack-gateway-api
feat: support dual stack for gateway api
2 parents 4944248 + e9968d8 commit ccab9a9

File tree

3 files changed

+134
-38
lines changed

3 files changed

+134
-38
lines changed

docs/sources/gateway.md

+74-38
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ sources create DNS entries based on their respective `gateway.networking.k8s.io`
55

66
## Filtering the Routes considered
77

8-
These sources support the `--label-filter` flag, which filters *Route resources
8+
These sources support the `--label-filter` flag, which filters \*Route resources
99
by a set of labels.
1010

1111
## Domain names
@@ -16,67 +16,103 @@ of [domain names from the *Route](#domain-names-from-route).
1616
It then iterates over each of the `status.parents` with
1717
a [matching Gateway](#matching-gateways) and at least one [matching listener](#matching-listeners).
1818
For each matching listener, if the
19-
listener has a `hostname`, it narrows the set of domain names from the *Route to the portion
19+
listener has a `hostname`, it narrows the set of domain names from the \*Route to the portion
2020
that overlaps the `hostname`. If a matching listener does not have a `hostname`, it uses
2121
the un-narrowed set of domain names.
2222

2323
### Domain names from Route
2424

25-
The set of domain names from a *Route is sourced from the following places:
25+
The set of domain names from a \*Route is sourced from the following places:
2626

27-
* If the *Route is a GRPCRoute, HTTPRoute, or TLSRoute, adds each of the`spec.hostnames`.
27+
- If the \*Route is a GRPCRoute, HTTPRoute, or TLSRoute, adds each of the`spec.hostnames`.
2828

29-
* Adds the hostnames from any `external-dns.alpha.kubernetes.io/hostname` annotation on the *Route.
30-
This behavior is suppressed if the `--ignore-hostname-annotation` flag was specified.
29+
- Adds the hostnames from any `external-dns.alpha.kubernetes.io/hostname` annotation on the \*Route.
30+
This behavior is suppressed if the `--ignore-hostname-annotation` flag was specified.
3131

32-
* If no endpoints were produced by the previous steps
33-
or the `--combine-fqdn-annotation` flag was specified, then adds hostnames
34-
generated from any`--fqdn-template` flag.
32+
- If no endpoints were produced by the previous steps
33+
or the `--combine-fqdn-annotation` flag was specified, then adds hostnames
34+
generated from any`--fqdn-template` flag.
3535

36-
* If no endpoints were produced by the previous steps, each
37-
attached Gateway listener will use its `hostname`, if present.
36+
- If no endpoints were produced by the previous steps, each
37+
attached Gateway listener will use its `hostname`, if present.
3838

3939
### Matching Gateways
4040

41-
Matching Gateways are discovered by iterating over the *Route's `status.parents`:
41+
Matching Gateways are discovered by iterating over the \*Route's `status.parents`:
4242

43-
* Ignores parents with a `parentRef.group` other than
44-
`gateway.networking.k8s.io` or a `parentRef.kind` other than `Gateway`.
43+
- Ignores parents with a `parentRef.group` other than
44+
`gateway.networking.k8s.io` or a `parentRef.kind` other than `Gateway`.
4545

46-
* If the `--gateway-namespace` flag was specified, ignores parents with a `parentRef.namespace` other
47-
than the specified value.
46+
- If the `--gateway-namespace` flag was specified, ignores parents with a `parentRef.namespace` other
47+
than the specified value.
4848

49-
* If the `--gateway-label-filter` flag was specified, ignores parents whose Gateway does not match the
50-
specified label filter.
49+
- If the `--gateway-label-filter` flag was specified, ignores parents whose Gateway does not match the
50+
specified label filter.
5151

52-
* Ignores parents whose Gateway either does not exist or has not accepted the route.
52+
- Ignores parents whose Gateway either does not exist or has not accepted the route.
5353

5454
### Matching listeners
5555

5656
Iterates over all listeners for the parent's `parentRef.sectionName`:
5757

58-
* Ignores listeners whose `protocol` field does not match the kind of the *Route per the following table:
58+
- Ignores listeners whose `protocol` field does not match the kind of the \*Route per the following table:
5959

60-
| kind | protocols |
61-
|------------|-------------|
62-
| GRPCRoute | HTTP, HTTPS |
63-
| HTTPRoute | HTTP, HTTPS |
64-
| TCPRoute | TCP |
65-
| TLSRoute | TLS |
66-
| UDPRoute | UDP |
60+
| kind | protocols |
61+
| --------- | ----------- |
62+
| GRPCRoute | HTTP, HTTPS |
63+
| HTTPRoute | HTTP, HTTPS |
64+
| TCPRoute | TCP |
65+
| TLSRoute | TLS |
66+
| UDPRoute | UDP |
6767

68-
* If the parent's `parentRef.port` port is specified, ignores listeners without a matching `port`.
68+
- If the parent's `parentRef.port` port is specified, ignores listeners without a matching `port`.
6969

70-
* Ignores listeners which specify an `allowedRoutes` which does not allow the route.
70+
- Ignores listeners which specify an `allowedRoutes` which does not allow the route.
7171

7272
## Targets
7373

74-
The targets of the DNS entries created from a *Route are sourced from the following places:
75-
76-
1. If a matching parent Gateway has an `external-dns.alpha.kubernetes.io/target` annotation, uses
77-
the values from that.
78-
79-
2. Otherwise, iterates over that parent Gateway's `status.addresses`,
80-
adding each address's `value`.
81-
82-
The targets from each parent Gateway matching the *Route are then combined and de-duplicated.
74+
The targets of the DNS entries created from a \*Route are sourced from the following places:
75+
76+
1. If a matching parent Gateway has an `external-dns.alpha.kubernetes.io/target` annotation, uses
77+
the values from that.
78+
79+
2. Otherwise, iterates over that parent Gateway's `status.addresses`,
80+
adding each address's `value`.
81+
82+
The targets from each parent Gateway matching the \*Route are then combined and de-duplicated.
83+
84+
## Dualstack Routes
85+
86+
Gateway resources may be served from an external-loadbalancer which may support both IPv4 and "dualstack" (both IPv4 and IPv6) interfaces.
87+
External DNS Controller uses the `external-dns.alpha.kubernetes.io/dualstack` annotation to determine this. If this annotation is
88+
set to `true` then ExternalDNS will create two records (one A record
89+
and one AAAA record) for each hostname associated with the Route resource.
90+
91+
Example:
92+
93+
```yaml
94+
apiVersion: gateway.networking.k8s.io/v1
95+
kind: HTTPRoute
96+
metadata:
97+
annotations:
98+
external-dns.alpha.kubernetes.io/dualstack: "true"
99+
name: echo
100+
spec:
101+
hostnames:
102+
- echoserver.example.org
103+
rules:
104+
- backendRefs:
105+
- group: ""
106+
kind: Service
107+
name: echo
108+
port: 1027
109+
weight: 1
110+
matches:
111+
- path:
112+
type: PathPrefix
113+
value: /echo
114+
```
115+
116+
The above HTTPRoute resource is backed by a dualstack Gateway.
117+
ExternalDNS will create both an A `echoserver.example.org` record and
118+
an AAAA record of the same name, that each are aliases for the same LB.

source/gateway.go

+15
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ import (
4545
const (
4646
gatewayGroup = "gateway.networking.k8s.io"
4747
gatewayKind = "Gateway"
48+
// gatewayAPIDualstackAnnotationKey is the annotation used for determining if a Gateway Route is dualstack
49+
gatewayAPIDualstackAnnotationKey = "external-dns.alpha.kubernetes.io/dualstack"
50+
// gatewayAPIDualstackAnnotationValue is the value of the Gateway Route dualstack annotation that indicates it is dualstack
51+
gatewayAPIDualstackAnnotationValue = "true"
4852
)
4953

5054
type gatewayRoute interface {
@@ -236,6 +240,7 @@ func (src *gatewayRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpo
236240
for host, targets := range hostTargets {
237241
endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific, setIdentifier, resource)...)
238242
}
243+
setDualstackLabel(rt, endpoints)
239244
log.Debugf("Endpoints generated from %s %s/%s: %v", src.rtKind, meta.Namespace, meta.Name, endpoints)
240245
}
241246
return endpoints, nil
@@ -610,3 +615,13 @@ func selectorsEqual(a, b labels.Selector) bool {
610615
}
611616
return true
612617
}
618+
619+
func setDualstackLabel(rt gatewayRoute, endpoints []*endpoint.Endpoint) {
620+
val, ok := rt.Metadata().Annotations[gatewayAPIDualstackAnnotationKey]
621+
if ok && val == gatewayAPIDualstackAnnotationValue {
622+
log.Debugf("Adding dualstack label to GatewayRoute %s/%s.", rt.Metadata().Namespace, rt.Metadata().Name)
623+
for _, ep := range endpoints {
624+
ep.Labels[endpoint.DualstackLabelKey] = "true"
625+
}
626+
}
627+
}

source/gateway_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"strings"
2121
"testing"
2222

23+
"sigs.k8s.io/external-dns/endpoint"
2324
v1 "sigs.k8s.io/gateway-api/apis/v1"
2425
)
2526

@@ -245,3 +246,47 @@ func TestIsDNS1123Domain(t *testing.T) {
245246
})
246247
}
247248
}
249+
250+
func TestDualStackLabel(t *testing.T) {
251+
tests := []struct {
252+
desc string
253+
in map[string](string)
254+
setsLabel bool
255+
}{
256+
{
257+
desc: "empty-annotation",
258+
setsLabel: false,
259+
},
260+
{
261+
desc: "correct-annotation-key-and-value",
262+
in: map[string]string{gatewayAPIDualstackAnnotationKey: gatewayAPIDualstackAnnotationValue},
263+
setsLabel: true,
264+
},
265+
{
266+
desc: "correct-annotation-key-incorrect-value",
267+
in: map[string]string{gatewayAPIDualstackAnnotationKey: "foo"},
268+
setsLabel: false,
269+
},
270+
{
271+
desc: "incorrect-annotation-key-correct-value",
272+
in: map[string]string{"FOO": gatewayAPIDualstackAnnotationValue},
273+
setsLabel: false,
274+
},
275+
}
276+
for _, tt := range tests {
277+
t.Run(tt.desc, func(t *testing.T) {
278+
endpoints := make([]*endpoint.Endpoint, 0)
279+
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
280+
281+
rt := &gatewayHTTPRoute{}
282+
rt.Metadata().Annotations = tt.in
283+
284+
setDualstackLabel(rt, endpoints)
285+
got := endpoints[0].Labels[endpoint.DualstackLabelKey] == "true"
286+
287+
if got != tt.setsLabel {
288+
t.Errorf("setDualstackLabel(%q); got: %v; want: %v", tt.in, got, tt.setsLabel)
289+
}
290+
})
291+
}
292+
}

0 commit comments

Comments
 (0)