-
Notifications
You must be signed in to change notification settings - Fork 2.7k
feat(source)!: introduce optional force-default-targets #5316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(source)!: introduce optional force-default-targets #5316
Conversation
Welcome @alen-z! |
Hi @alen-z. Thanks for your PR. I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
As far as I know, that's not in our current plans, but things might have shifted. A breaking CRD change usually means a new API version, and we're not there yet. To explore this further, could you help us understand the underlying issue by providing example manifests and any alternative solutions you've considered? Breaking changes are generally something we try to avoid, so I'm not going to review that. |
I can switch the script to keep current behavior and allow new flag (Default: With set apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: demo
labels:
demo: foo
annotations:
external-dns.demo.io/access: public
spec:
endpoints:
- dnsName: demo.example.com
recordTTL: 300
recordType: CNAME
targets:
- placeholder I'd like to avoid placeholders while allowing to override Expected CRD that should be allowed with apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: demo
labels:
demo: foo
annotations:
external-dns.demo.io/access: public
spec:
endpoints:
- dnsName: demo.example.com
recordTTL: 300
recordType: CNAME |
I'm working on path to beta at the moment #5243 |
For this case
Which provider you using, and what the result DNS is expected |
It's Cloudflare:
Result DNS with
In PR, you can see if we do set |
Not sure about practicality, may need some time to re-think. How this DNSEndpoint(s) created? I'm using helm, example relevant part {{- range $hostNames }}
{{- if .name }}
{{- $target := printf "eks-cluster-%s-ingress-%s.%s" $.Values.cluster.env $accessType $defaultDomainName }}
{{- $subdomain := printf "%s-%s-eks" .name $accessType }}
{{- if and (eq (include "this.allPortsAreGrpc" $) "false") (ne .protocol "gRPC") }}
- dnsName: {{ $subdomain }}.{{ $defaultDomainName }}
recordTTL: 60
recordType: CNAME
targets:
- {{ $target }}
{{- end }}
{{- if and (eq $accessType "internal") (eq (include "this.http2Enabled" $) "true") }}
- dnsName: {{ $subdomain }}.http2.{{ $defaultDomainName }}
recordTTL: 60
recordType: CNAME
targets:
- {{ $target }}
{{- end }}
{{- end }}
{{- end }}
or
- dnsName: {{ .Values.dns.host }}
recordTTL: 60
recordType: CNAME
targets:
- {{ include "common.dns.target" . }} # target or default target I may missing some other use case Or similar with kustomize or kyverno admission webhooks. |
It's simple:
This is PR to improve 4th step. Another approach I'm using is different starting with 3rd step, which is setting annotations on Service of type Both approaches have product teams using DNSEndpoint resources, while load balancer DNS records and ExternalDNS instances are maintained by operations team. Product team just creates proper DNSEndpoint with annotation for public or private (again, bummer is it needs to contain |
If I understand correctly, we have exactly same cases in environments I look after. For the use case you shared, I'm not sure how you setting annotations, but if annotations on ALB are set with helm, you could add a template for DNSEndpoint as well and set all required values, use kustomize and any other tools. The targets are static, so it works. If teams using Crossplane, it could manage DNSEndpoint targets as well at runtime. Regardless of how DNSEndpoint manifests are generated (Helm, Kustomize, Admission WebHooks, ArgoCD, Jsonnet, etc.), any DNSEndpoint with the I apologise, but I don't understand the proposed approach and don't see any clear benefits. However, other contributors or maintainers might have a different perspective and see value in this feature. |
Again, it's simple proposition: No need for Additional comment on your suggestion that it's easy to add |
We have differing opinions on this. In my view, the number of DNSEndpoints shouldn't matter – automation tools exist to handle scaling so 1, 100, 10000 should not be matter. If other maintainers or contributors believe external-dns should support this specific scenario, I don't see a fundamental conflict as consensus is not needed. However, from my perspective, this problem seems too niche and is better addressed with dedicated tools. This is just my personal opinion, and others may have different views. |
Maybe CRD should support FQDN templating |
All good. I'm not attached to this, only offered a fix to make it better for our use case. I can close the PR. |
@alen-z do you wanna try FQDN approach? @mloiseleur wdyt? |
@ivankatliarchuk To me, this use case is quite straightforward and valid. We are using the same pattern on some clusters: a single target (the LB) with as many CNAME as we need. So, setting it once in the CLI arg looks better to me than set the same value in all the CR. It's also consistent: that's how other sources works. Since there is no CEL validation ATM in the CRD, I'm not sure if this is really a breaking change. With current version, it will fail without target so, to me, a warning in release notes should be enough. But maybe I missed something, I need to double check that. |
@mloiseleur, in terms of looking at breaking change, depends on the approach (current PR or one other possible non-breaking). Challenge is, to make current ExternalDNS work, many have put placeholders in CRD To me, it's best to be able to upgrade to new version, clean DNSEndpoint under the flag, remove the flag. This actually requires this PR to be a bit more improved to allow empty If you folks align on the approach, I can prepare PR. |
@alen-z We took the time to discuss it with @szuecs @Raffo and @ivankatliarchuk . We are aligned on this approach. You can prepare the PR. |
Feel free to re-open this one or open a new one. |
Hey @mloiseleur and all involved, sounds good. Appreciate you folks spending time on this one. I'll re-open the PR.
I'll follow through with this approach and ask for review. ⚾ is in my court now. Will keep you posted. |
Let us know when ready. What else is required, share results of a smoke tests with flag enabled/disabled, my understanding this are the maniests #5316 (comment). This should help to speed up a review. |
Hijacking a bit this thread. Currently (unless I am mistaken) endpoints generated from annotations on ingress object behave in a completely opposite way where anything you set on the ingress is overriden if defaultTarget is set which is counter intuitive. Is there a way to have all of this uniformed? As things are right now, its not possible to have an override for an individual ingress. |
This fix is realed to CRD, not an ingress. CRDs do not support annotations. The best way is to create an issue and provide kubernetes manifests alongside with current and desired behaviour. |
Hi @alen-z we are planning to do a release soonish. Do you have a capacity to rebase and address this comment #5316 (comment) |
Let's see what I can do today, you can then plan around it. |
Hey @alekc, I think your comment is valid. I can look into it if decision makers decide to expand current scope. This should flow to new multisource logic, which would see Code example...diff --git a/source/ingress.go b/source/ingress.go
index d3b8f8d0..ac7314da 100644
--- a/source/ingress.go
+++ b/source/ingress.go
@@ -291,51 +291,47 @@ func endpointsFromIngress(ing *networkv1.Ingress, ignoreHostnameAnnotation bool,
providerSpecific, setIdentifier := annotations.ProviderSpecificAnnotations(ing.Annotations)
- // Gather endpoints defined on hosts sections of the ingress
- var definedHostsEndpoints []*endpoint.Endpoint
- // Skip endpoints if we do not want entries from Rules section
+ var definedHosts, annotationHosts []string
+
+ // Gather hostnames from rules section
if !ignoreIngressRulesSpec {
for _, rule := range ing.Spec.Rules {
- if rule.Host == "" {
- continue
+ if rule.Host != "" {
+ definedHosts = append(definedHosts, rule.Host)
}
- definedHostsEndpoints = append(definedHostsEndpoints, endpointsForHostname(rule.Host, targets, ttl, providerSpecific, setIdentifier, resource)...)
}
}
- // Skip endpoints if we do not want entries from tls spec section
+ // Gather hostnames from TLS section
if !ignoreIngressTLSSpec {
for _, tls := range ing.Spec.TLS {
for _, host := range tls.Hosts {
- if host == "" {
- continue
+ if host != "" {
+ definedHosts = append(definedHosts, host)
}
- definedHostsEndpoints = append(definedHostsEndpoints, endpointsForHostname(host, targets, ttl, providerSpecific, setIdentifier, resource)...)
}
}
}
- // Gather endpoints defined on annotations in the ingress
- var annotationEndpoints []*endpoint.Endpoint
+ // Gather hostnames from annotation
if !ignoreHostnameAnnotation {
- for _, hostname := range annotations.HostnamesFromAnnotations(ing.Annotations) {
- annotationEndpoints = append(annotationEndpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier, resource)...)
- }
+ annotationHosts = annotations.HostnamesFromAnnotations(ing.Annotations)
}
- // Determine which hostnames to consider in our final list
+ // Determine which hostnames to consider
+ var hostnames []string
hostnameSourceAnnotation, hostnameSourceAnnotationExists := ing.Annotations[ingressHostnameSourceKey]
if !hostnameSourceAnnotationExists {
- return append(definedHostsEndpoints, annotationEndpoints...)
+ hostnames = append(definedHosts, annotationHosts...)
+ } else if strings.ToLower(hostnameSourceAnnotation) == IngressHostnameSourceDefinedHostsOnlyValue {
+ hostnames = definedHosts
+ } else if strings.ToLower(hostnameSourceAnnotation) == IngressHostnameSourceAnnotationOnlyValue {
+ hostnames = annotationHosts
}
- // Include endpoints according to the hostname source annotation in our final list
var endpoints []*endpoint.Endpoint
- if strings.ToLower(hostnameSourceAnnotation) == IngressHostnameSourceDefinedHostsOnlyValue {
- endpoints = append(endpoints, definedHostsEndpoints...)
- }
- if strings.ToLower(hostnameSourceAnnotation) == IngressHostnameSourceAnnotationOnlyValue {
- endpoints = append(endpoints, annotationEndpoints...)
+ for _, hostname := range hostnames {
+ endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier, resource)...)
}
return endpoints
} Smoke test Note: It'd be nice to provide smoke test infrastructure since it's required in PRs. Let me know if you would like me to create one. I've pushed staging image to my infrastructure ( ---
# Working as expected, w/o force-default-targets, w/ default-targets = creates records, adds CRD targets since not empty, new behavior
# Working as expected, w/ force-default-targets, w/ default-targets = creates records, sets only default targets, legacy behavior
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: targets
namespace: default
labels:
demo: foo
annotations:
external-dns-smoke.relaymonkey.io/access: public
spec:
endpoints:
- dnsName: smoke-t.relaymonkey.com
recordTTL: 300
recordType: CNAME
targets:
- placeholder
---
# Working as expected, w/o force-default-targets, w/ default-targets = creates records, sets only default targets, new behavior (not failing on empty targets), debug log
# Working as expected, w/ force-default-targets, w/ default-targets = creates records, sets only default targets, new behavior (not failing on empty targets), debug log
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: no-targets
namespace: default
labels:
demo: foo
annotations:
external-dns-smoke.relaymonkey.io/access: public
spec:
endpoints:
- dnsName: smoke-nt.relaymonkey.com
recordTTL: 300
recordType: CNAME Debug log for 2nd manifest: {"level":"debug","msg":"Endpoint no-targets with DNSName smoke-nt.relaymonkey.com has an empty list of targets, allowing it to pass through for default-targets processing","time":"2025-06-17T19:55:05Z"} 1st manifest w/o All seems as expected ✅ |
/lgtm |
To keep things manageble, the preffered approach is to scope PR to a single problem. If there is follow-up pull request with the fix, we could discuss it there. cc @mloiseleur for final review |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: mloiseleur The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Description
Improved
default-targets
behavior to allow empty CRDtargets
section in case default targets are set. Default targets are overridden if it's set anywhere else. Since it introduces breaking change in situations where targets are set, we offerforce-default-targets
flag to keep current behavior (hopefully easing migration).Breaking change should be stated in release documentation.
Fixes #3163
Checklist