Skip to content
Merged
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
33 changes: 27 additions & 6 deletions docs/advanced/fqdn-templating.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,22 @@ metadata:
```

```sh
# Single template
external-dns \
--provider=aws \
--source=service \
--fqdn-template="{{ .Name }}.example.com,{{ .Name }}.{{ .Namespace }}.example.tld"
--fqdn-template="{{ .Name }}.example.com"

# Multiple templates — specify the flag more than once
external-dns \
--provider=aws \
--source=service \
--fqdn-template="{{ .Name }}.example.com" \
--fqdn-template="{{ .Name }}.{{ .Namespace }}.example.tld"

# This will result in DNS entries like
>route53> my-service.example.com
>route53> my-service.my-namespace.example.tld
# route53> my-service.example.com
# route53> my-service.my-namespace.example.tld
```

### With Namespace
Expand Down Expand Up @@ -177,11 +185,16 @@ ExternalDNS allows specifying multiple FQDN templates, which can be useful when

> Be cautious, as this will create multiple DNS records per resource, potentially increasing the number of API calls to your DNS provider.

Specify `--fqdn-template` multiple times — one flag per template:

```yml
args:
--fqdn-template={{.Name}}.example.com,{{.Name}}.svc.example.com
- --fqdn-template={{.Name}}.example.com
- --fqdn-template={{.Name}}.svc.example.com
```

Duplicate templates and leading/trailing whitespace are ignored automatically.

### Conditional Templating combined with Annotations processing

In scenarios where you want to conditionally generate FQDNs based on annotations, you can use Go template functions like or to provide defaults.
Expand Down Expand Up @@ -416,15 +429,23 @@ This is helpful in scenarios such as:

## Tips

- If `--fqdn-template` is specified, ExternalDNS ignores any `external-dns.kubernetes.io/hostname` annotations.
- If `--fqdn-template` is specified, ExternalDNS ignores any `external-dns.kubernetes.io/hostname` annotations (unless `--combine-fqdn-annotation` is also set).
- You must still ensure the resulting FQDN is valid and unique.
- Since Go templates can be error-prone, test your template with simple examples before deploying. Mismatched field names or nil values (e.g., missing labels) will result in errors or skipped entries.

## FAQ

### Can I specify multiple global FQDN templates?

Yes, you can. Pass in a comma separated list to --fqdn-template. Beware this will double (triple, etc) the amount of DNS entries based on how many services, ingresses and so on you have and will get you faster towards the API request limit of your DNS provider.
Yes. Specify `--fqdn-template` more than once — one flag per template:

```sh
external-dns \
--fqdn-template="{{ .Name }}.example.com" \
--fqdn-template="{{ .Name }}.svc.example.com"
```

Beware: this will double (triple, etc.) the number of DNS entries based on how many services, ingresses, and so on you have, and will bring you faster towards the API request limit of your DNS provider. Duplicate templates are deduplicated automatically.

### Where to find template syntax

Expand Down
6 changes: 3 additions & 3 deletions docs/flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,9 @@ tags:
| `--webhook-provider-write-timeout=10s` | The write timeout for the webhook provider in duration format (default: 10s) |
| `--[no-]webhook-server` | When enabled, runs as a webhook server instead of a controller. (default: false). |
| `--[no-]combine-fqdn-annotation` | Combine FQDN template and Annotations instead of overwriting (default: false) |
| `--fqdn-template=""` | A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN. |
| `--target-template=""` | A templated string used to generate DNS targets (IP or hostname) from sources that support it (optional). Accepts comma separated list for multiple targets. |
| `--fqdn-target-template=""` | A template that returns host:target pairs (e.g., '{{range .Object.endpoints}}{{.targetRef.name}}.svc.example.com:{{index .addresses 0}},{{end}}'). Accepts comma separated list for multiple pairs. |
| `--fqdn-template=FQDN-TEMPLATE` | A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Specify multiple times for multiple templates. |
| `--target-template=TARGET-TEMPLATE` | A templated string used to generate DNS targets (IP or hostname) from sources that support it (optional). Specify multiple times for multiple targets. |
| `--fqdn-target-template=FQDN-TARGET-TEMPLATE` | A template that returns host:target pairs (e.g., '{{range .Object.endpoints}}{{.targetRef.name}}.svc.example.com:{{index .addresses 0}},{{end}}'). Specify multiple times for multiple pairs. |
| `--kubeconfig=""` | Retrieve target cluster configuration from a Kubernetes configuration file (default: auto-detect) |
| `--request-timeout=30s` | [DEPRECATED: use --kube-api-request-timeout] Request timeout when calling Kubernetes APIs. 0s means no timeout |
| `--kube-api-request-timeout=30s` | Request timeout when calling Kubernetes APIs. 0s means no timeout |
Expand Down
26 changes: 15 additions & 11 deletions docs/sources/unstructured.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ spec:

## Configuration

| Flag | Description |
|-----------------------------|--------------------------------------------------------------------|
| `--unstructured-resource` | Resources to watch in `resource.version.group` format (repeatable) |
| `--fqdn-template` | Go template for DNS names |
| `--target-template` | Go template for DNS targets |
| `--fqdn-target-template` | Go template returning `host:target` pairs |
| `--label-filter` | Filter resources by labels |
| `--annotation-filter` | Filter resources by annotations |
| `--combine-fqdn-annotation` | Combine FQDN template and Annotations instead of overwriting |
| Flag | Description |
|-----------------------------|-----------------------------------------------------------------------------------|
| `--unstructured-resource` | Resources to watch in `resource.version.group` format (repeatable) |
| `--fqdn-template` | Go template for DNS names (repeatable; comma-separated values within one flag also accepted) |
| `--target-template` | Go template for DNS targets (repeatable; comma-separated values within one flag also accepted) |
| `--fqdn-target-template` | Go template returning `host:target` pairs (repeatable; comma-separated values within one flag also accepted) |
| `--label-filter` | Filter resources by labels |
| `--annotation-filter` | Filter resources by annotations |
| `--combine-fqdn-annotation` | Combine FQDN template and Annotations instead of overwriting |

## Template Syntax

Expand Down Expand Up @@ -403,10 +403,14 @@ external-dns \
# Result:
# app-abc12.pod.com -> 10.244.1.2 (A)
# app-def34.pod.com -> 10.244.2.3, 10.244.2.4 (A)
# test-abc12.example.com -> 10.244.1.2, 10.244.2.3, 10.244.2.4 (A)
# test-headless.example.com -> 10.244.1.2, 10.244.2.3, 10.244.2.4 (A)
```

The `--fqdn-target-template` flag returns `host:target` pairs, enabling 1:1 mapping between hostnames and targets. Useful when a Kubernetes resource contains arrays where each element should produce its own DNS record (e.g., EndpointSlice endpoints, multi-host configurations).
Specifying `--fqdn-target-template` multiple times applies each template independently against the same object and merges all results. This lets you produce per-pod records from one template and a service-level aggregate record from another — in a single ExternalDNS pass.

The `--fqdn-target-template` flag returns `host:target` pairs, enabling 1:1 mapping between hostnames and targets.
It is most useful when a resource contains arrays where each element produces its own record (e.g., EndpointSlice endpoints).
The same repeatable behaviour applies to `--fqdn-template` and `--target-template`.

## RBAC

Expand Down
18 changes: 9 additions & 9 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ type Config struct {
AnnotationPrefix string
LabelFilter string
IngressClassNames []string
FQDNTemplate string
TargetTemplate string
FQDNTargetTemplate string
FQDNTemplate []string
TargetTemplate []string
FQDNTargetTemplate []string
CombineFQDNAndAnnotation bool
IgnoreHostnameAnnotation bool
IgnoreNonHostNetworkPods bool
Expand Down Expand Up @@ -296,9 +296,9 @@ var defaultConfig = &Config{
ExoscaleAPIZone: "ch-gva-2",
ExoscaleZoneCacheDuration: 0 * time.Second,
ExposeInternalIPV6: false,
FQDNTemplate: "",
TargetTemplate: "",
FQDNTargetTemplate: "",
FQDNTemplate: nil,
TargetTemplate: nil,
FQDNTargetTemplate: nil,
GatewayLabelFilter: "",
GatewayName: "",
GatewayNamespace: "",
Expand Down Expand Up @@ -736,9 +736,9 @@ func bindFlags(b flags.FlagBinder, cfg *Config) {

// FQDN Templating
b.BoolVar("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting (default: false)", false, &cfg.CombineFQDNAndAnnotation)
b.StringVar("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN.", defaultConfig.FQDNTemplate, &cfg.FQDNTemplate)
b.StringVar("target-template", "A templated string used to generate DNS targets (IP or hostname) from sources that support it (optional). Accepts comma separated list for multiple targets.", defaultConfig.TargetTemplate, &cfg.TargetTemplate)
b.StringVar("fqdn-target-template", "A template that returns host:target pairs (e.g., '{{range .Object.endpoints}}{{.targetRef.name}}.svc.example.com:{{index .addresses 0}},{{end}}'). Accepts comma separated list for multiple pairs.", defaultConfig.FQDNTargetTemplate, &cfg.FQDNTargetTemplate)
b.StringsVar("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Specify multiple times for multiple templates.", defaultConfig.FQDNTemplate, &cfg.FQDNTemplate)
b.StringsVar("target-template", "A templated string used to generate DNS targets (IP or hostname) from sources that support it (optional). Specify multiple times for multiple targets.", defaultConfig.TargetTemplate, &cfg.TargetTemplate)
b.StringsVar("fqdn-target-template", "A template that returns host:target pairs (e.g., '{{range .Object.endpoints}}{{.targetRef.name}}.svc.example.com:{{index .addresses 0}},{{end}}'). Specify multiple times for multiple pairs.", defaultConfig.FQDNTargetTemplate, &cfg.FQDNTargetTemplate)

// kube client config flags
b.StringVar("kubeconfig", "Retrieve target cluster configuration from a Kubernetes configuration file (default: auto-detect)", defaultConfig.KubeConfig, &cfg.KubeConfig)
Expand Down
4 changes: 2 additions & 2 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ var (
Sources: []string{"service"},
Namespace: "",
AnnotationPrefix: "external-dns.kubernetes.io/",
FQDNTemplate: "",
FQDNTemplate: nil,
Compatibility: "",
Provider: ProviderGoogle,
GoogleProject: "",
Expand Down Expand Up @@ -155,7 +155,7 @@ var (
IgnoreNonHostNetworkPods: true,
IgnoreIngressTLSSpec: true,
IgnoreIngressRulesSpec: true,
FQDNTemplate: "{{.Name}}.service.example.com",
FQDNTemplate: []string{"{{.Name}}.service.example.com"},
Compatibility: "mate",
Provider: ProviderGoogle,
GoogleProject: "project",
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/externaldns/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func ValidateConfig(cfg *externaldns.Config) error {
return err
}

if cfg.IgnoreHostnameAnnotation && cfg.FQDNTemplate == "" {
if cfg.IgnoreHostnameAnnotation && len(cfg.FQDNTemplate) == 0 {
return errors.New("FQDN Template must be set if ignoring annotations")
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/apis/externaldns/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func TestValidateFlags(t *testing.T) {

cfg = newValidConfig(t)
cfg.IgnoreHostnameAnnotation = true
cfg.FQDNTemplate = ""
cfg.FQDNTemplate = []string{}
require.Error(t, ValidateConfig(cfg))

cfg = newValidConfig(t)
Expand Down Expand Up @@ -142,7 +142,7 @@ func newValidConfig(t *testing.T) *externaldns.Config {
func TestValidateBadIgnoreHostnameAnnotationsConfig(t *testing.T) {
cfg := externaldns.NewConfig()
cfg.IgnoreHostnameAnnotation = true
cfg.FQDNTemplate = ""
cfg.FQDNTemplate = []string{}

assert.Error(t, ValidateConfig(cfg))
}
Expand Down
Loading