Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 65 additions & 6 deletions docs/sources/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,74 @@ Iterates over all listeners for the parent's `parentRef.sectionName`:

## Targets

The targets of the DNS entries created from a \*Route are sourced from the following places:
Targets are derived from a combination of Route annotations, Gateway annotations, and
the Gateway's `status.addresses`. How these sources are combined is controlled (per Route)
by the optional strategy annotation:

1. If a matching parent Gateway has an `external-dns.alpha.kubernetes.io/target` annotation, uses
the values from that.
```yaml
external-dns.alpha.kubernetes.io/target-strategy: <strategy>
```

Supported strategies (on all Gateway *Route resources):

| Strategy | Behavior | Fallback Order | Publishes Multiple Target Sets? |
|------------------|----------|----------------|----------------------------------|
| `route-preferred` (default) | Use Route targets if present; otherwise Gateway targets if present; otherwise addresses | Route → Gateway → Addresses | No (at most one set) |
| `route-only` | Use Route targets only if present; otherwise fall back directly to addresses (Gateway targets are ignored even if set) | Route → Addresses | No |
| `gateway-only` | Use Gateway targets only if present; otherwise fall back to addresses (Route targets are ignored) | Gateway → Addresses | No |
| `merge` | Combine Route and Gateway targets (deduped). If neither annotation supplies targets, fall back to addresses | (Route ∪ Gateway) → Addresses | Yes |

Where the individual sources come from:

1. Route targets: values from a non-empty `external-dns.alpha.kubernetes.io/target` annotation on the Route.
2. Gateway targets: values from a non-empty `external-dns.alpha.kubernetes.io/target` annotation on the matching parent Gateway.
3. Addresses: each `value` in the parent Gateway's `status.addresses` field.

Notes & nuances:

- The empty string (`external-dns.alpha.kubernetes.io/target: ""`) on a Route or Gateway does not disable the other annotation; it simply contributes no targets.
- Under `route-only`, any Gateway target annotation is intentionally ignored even if populated.
- Under `gateway-only`, any Route target annotation is intentionally ignored.
- Under `merge`, duplicate targets between Route and Gateway are removed before creating DNS records.
- If (after applying the strategy rules) no annotation targets are selected, Gateway `status.addresses` are always used as a safety fallback to avoid producing zero endpoints for an otherwise valid attachment.

Example usages on a *Route:

```yaml
metadata:
annotations:
external-dns.alpha.kubernetes.io/target-strategy: route-only
```

Publishes the hostname(s) from the route using the Gateway addresses, ignoring target annotation on the Gateway.

```yaml
metadata:
annotations:
external-dns.alpha.kubernetes.io/target: canary.lb.example.net
external-dns.alpha.kubernetes.io/target-strategy: route-preferred
```

Publishes only `canary.lb.example.net` (even if the Gateway also has a target annotation).

```yaml
metadata:
annotations:
external-dns.alpha.kubernetes.io/target-strategy: merge
```

Route has no target annotation; if the Gateway has `external-dns.alpha.kubernetes.io/target: edge.lb.example.net` the record will include that value; otherwise the Gateway addresses are used.

```yaml
metadata:
annotations:
external-dns.alpha.kubernetes.io/target: direct.lb.example.net
external-dns.alpha.kubernetes.io/target-strategy: merge
```

2. Otherwise, iterates over that parent Gateway's `status.addresses`,
adding each address's `value`.
If the Gateway also has `external-dns.alpha.kubernetes.io/target: edge.lb.example.net` both targets are published (deduped if identical).

The targets from each parent Gateway matching the \*Route are then combined and de-duplicated.
The combined targets from each matching parent Gateway are gathered per hostname and de-duplicated before generating DNS records.

## Dualstack Routes

Expand Down
7 changes: 7 additions & 0 deletions source/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,11 @@ const (
ControllerValue = "dns-controller"
// InternalHostnameKey The annotation used for defining the desired hostname
InternalHostnameKey = AnnotationKeyPrefix + "internal-hostname"
// TargetStrategyKey controls how targets are selected for sources that support multiple target origins (e.g. Gateway Route sources).
// Supported values (for Gateway Route sources):
// route-preferred -> prefer Route target annotation if non-empty; otherwise fall back to Gateway target annotation (if any) or gateway addresses
// route-only -> only use the Route target annotation if non-empty; otherwise fall back to gateway addresses
// gateway-only -> only use the Gateway target annotation (if any) otherwise fall back to gateway addresses
// merge -> merge Route and Gateway target annotations (deduped); if neither present use gateway addresses
TargetStrategyKey = AnnotationKeyPrefix + "target-strategy"
)
39 changes: 38 additions & 1 deletion source/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,44 @@ func (c *gatewayRouteResolver) resolve(rt gatewayRoute) (map[string]endpoint.Tar
if !ok {
continue
}
override := annotations.TargetsFromTargetAnnotation(gw.gateway.Annotations)
// Determine target selection strategy.
strategy := meta.Annotations[targetStrategyAnnotationKey]
// Normalize unexpected values to "" which defaults to current semantics but here
// we intentionally treat empty (or unknown) as "merge" for explicit behavior.
switch strategy {
case "route-preferred", "route-only", "gateway-only", "merge":
// valid, keep as is
default:
strategy = "route-preferred"
}
var override endpoint.Targets

routeTargets := annotations.TargetsFromTargetAnnotation(meta.Annotations)
gatewayTargets := annotations.TargetsFromTargetAnnotation(gw.gateway.Annotations)

switch strategy {
case "route-preferred":
if len(routeTargets) > 0 {
override = append(override, routeTargets...)
} else if len(gatewayTargets) > 0 {
override = append(override, gatewayTargets...)
}
case "route-only":
if len(routeTargets) > 0 {
override = append(override, routeTargets...)
}
case "gateway-only":
if len(gatewayTargets) > 0 {
override = append(override, gatewayTargets...)
}
case "merge":
if len(routeTargets) > 0 {
override = append(override, routeTargets...)
}
if len(gatewayTargets) > 0 {
override = append(override, gatewayTargets...)
}
}
hostTargets[host] = append(hostTargets[host], override...)
if len(override) == 0 {
for _, addr := range gw.gateway.Status.Addresses {
Expand Down
Loading
Loading