Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
54 changes: 27 additions & 27 deletions docs/advanced/fqdn-templating.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,33 @@ The template uses the following data from the source object (e.g., a `Service` o

<!-- TODO: generate from code -->

| Source | Description | FQDN Supported |
|:-----------------------|:----------------------------------------------------------------|:--------------:|
| `ambassador-host` | Queries Ambassador Host resources for endpoints. | ❌ |
| `cloudfoundry` | Queries Cloud Foundry resources for endpoints. | ❌ |
| `connector` | Queries a custom connector source for endpoints. | ❌ |
| `contour-httpproxy` | Queries Contour HTTPProxy resources for endpoints. | ✅ |
| `crd` | Queries Custom Resource Definitions (CRDs) for endpoints. | ❌ |
| `empty` | Uses an empty source, typically for testing or no-op scenarios. | ❌ |
| `f5-transportserver` | Queries F5 TransportServer resources for endpoints. | ❌ |
| `f5-virtualserver` | Queries F5 VirtualServer resources for endpoints. | ❌ |
| `fake` | Uses a fake source for testing purposes. | ❌ |
| `gateway-grpcroute` | Queries GRPCRoute resources from the Gateway API. | ✅ |
| `gateway-httproute` | Queries HTTPRoute resources from the Gateway API. | ✅ |
| `gateway-tcproute` | Queries TCPRoute resources from the Gateway API. | ✅ |
| `gateway-tlsroute` | Queries TLSRoute resources from the Gateway API. | ❌ |
| `gateway-udproute` | Queries UDPRoute resources from the Gateway API. | ❌ |
| `gloo-proxy` | Queries Gloo Proxy resources for endpoints. | ❌ |
| `ingress` | Queries Kubernetes Ingress resources for endpoints. | ✅ |
| `istio-gateway` | Queries Istio Gateway resources for endpoints. | ✅ |
| `istio-virtualservice` | Queries Istio VirtualService resources for endpoints. | ✅ |
| `kong-tcpingress` | Queries Kong TCPIngress resources for endpoints. | ❌ |
| `node` | Queries Kubernetes Node resources for endpoints. | ✅ |
| `openshift-route` | Queries OpenShift Route resources for endpoints. | ✅ |
| `pod` | Queries Kubernetes Pod resources for endpoints. | |
| `service` | Queries Kubernetes Service resources for endpoints. | ✅ |
| `skipper-routegroup` | Queries Skipper RouteGroup resources for endpoints. | ✅ |
| `traefik-proxy` | Queries Traefik Proxy resources for endpoints. | ❌ |
| Source | Description | FQDN Supported | FQDN Combine |
|:-----------------------|:----------------------------------------------------------------|:--------------:|:------------:|
| `ambassador-host` | Queries Ambassador Host resources for endpoints. | ❌ | ❌ |
| `cloudfoundry` | Queries Cloud Foundry resources for endpoints. | ❌ | ❌ |
| `connector` | Queries a custom connector source for endpoints. | ❌ | ❌ |
| `contour-httpproxy` | Queries Contour HTTPProxy resources for endpoints. | ✅ | ✅ |
| `crd` | Queries Custom Resource Definitions (CRDs) for endpoints. | ❌ | ❌ |
| `empty` | Uses an empty source, typically for testing or no-op scenarios. | ❌ | ❌ |
| `f5-transportserver` | Queries F5 TransportServer resources for endpoints. | ❌ | ❌ |
| `f5-virtualserver` | Queries F5 VirtualServer resources for endpoints. | ❌ | ❌ |
| `fake` | Uses a fake source for testing purposes. | ❌ | ❌ |
| `gateway-grpcroute` | Queries GRPCRoute resources from the Gateway API. | ✅ | ❌ |
| `gateway-httproute` | Queries HTTPRoute resources from the Gateway API. | ✅ | ❌ |
| `gateway-tcproute` | Queries TCPRoute resources from the Gateway API. | ✅ | ❌ |
| `gateway-tlsroute` | Queries TLSRoute resources from the Gateway API. | ❌ | ❌ |
| `gateway-udproute` | Queries UDPRoute resources from the Gateway API. | ❌ | ❌ |
| `gloo-proxy` | Queries Gloo Proxy resources for endpoints. | ❌ | ❌ |
| `ingress` | Queries Kubernetes Ingress resources for endpoints. | ✅ | ✅ |
| `istio-gateway` | Queries Istio Gateway resources for endpoints. | ✅ | ✅ |
| `istio-virtualservice` | Queries Istio VirtualService resources for endpoints. | ✅ | ✅ |
| `kong-tcpingress` | Queries Kong TCPIngress resources for endpoints. | ❌ | ❌ |
| `node` | Queries Kubernetes Node resources for endpoints. | ✅ | ❌ |
| `openshift-route` | Queries OpenShift Route resources for endpoints. | ✅ | ✅ |
| `pod` | Queries Kubernetes Pod resources for endpoints. | ✅ | ✅ |
| `service` | Queries Kubernetes Service resources for endpoints. | ✅ | ✅ |
| `skipper-routegroup` | Queries Skipper RouteGroup resources for endpoints. | ✅ | ✅ |
| `traefik-proxy` | Queries Traefik Proxy resources for endpoints. | ❌ | ❌ |

## Custom Functions

Expand Down
79 changes: 71 additions & 8 deletions source/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ package source

import (
"context"
"fmt"
"maps"
"text/template"

log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
Expand All @@ -27,14 +30,19 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"

"sigs.k8s.io/external-dns/source/fqdn"

"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/source/annotations"
"sigs.k8s.io/external-dns/source/informers"
)

type podSource struct {
client kubernetes.Interface
namespace string
client kubernetes.Interface
namespace string
fqdnTemplate *template.Template
combineFQDNAnnotation bool

podInformer coreinformers.PodInformer
nodeInformer coreinformers.NodeInformer
compatibility string
Expand All @@ -43,18 +51,27 @@ type podSource struct {
}

// NewPodSource creates a new podSource with the given config.
func NewPodSource(ctx context.Context, kubeClient kubernetes.Interface, namespace string, compatibility string, ignoreNonHostNetworkPods bool, podSourceDomain string) (Source, error) {
func NewPodSource(
ctx context.Context,
kubeClient kubernetes.Interface,
namespace string,
compatibility string,
ignoreNonHostNetworkPods bool,
podSourceDomain string,
fqdnTemplate string,
combineFqdnAnnotation bool,
) (Source, error) {
informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0, kubeinformers.WithNamespace(namespace))
podInformer := informerFactory.Core().V1().Pods()
nodeInformer := informerFactory.Core().V1().Nodes()

podInformer.Informer().AddEventHandler(
_, _ = podInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
},
},
)
nodeInformer.Informer().AddEventHandler(
_, _ = nodeInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
},
Expand All @@ -68,6 +85,11 @@ func NewPodSource(ctx context.Context, kubeClient kubernetes.Interface, namespac
return nil, err
}

tmpl, err := fqdn.ParseTemplate(fqdnTemplate)
if err != nil {
return nil, err
}

return &podSource{
client: kubeClient,
podInformer: podInformer,
Expand All @@ -76,22 +98,39 @@ func NewPodSource(ctx context.Context, kubeClient kubernetes.Interface, namespac
compatibility: compatibility,
ignoreNonHostNetworkPods: ignoreNonHostNetworkPods,
podSourceDomain: podSourceDomain,
fqdnTemplate: tmpl,
combineFQDNAnnotation: combineFqdnAnnotation,
}, nil
}

func (*podSource) AddEventHandler(ctx context.Context, handler func()) {
func (*podSource) AddEventHandler(_ context.Context, _ func()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, do you know why this function does nothing? (Other sources often add the handler to informers)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. We need to fix that as well. Related PR #5274

We need to review and fix all the handlers. It's a useful feature. I'm not too sure how it differ from adding handlers in constructur, that the only gotcha

}

func (ps *podSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
func (ps *podSource) Endpoints(_ context.Context) ([]*endpoint.Endpoint, error) {
pods, err := ps.podInformer.Lister().Pods(ps.namespace).List(labels.Everything())
if err != nil {
return nil, err
}

endpointMap := make(map[endpoint.EndpointKey][]string)
fqdnEndpointMap := make(map[endpoint.EndpointKey][]string)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this map there to ensure the fqdn endpoints get priority over pod endpoints by adding them to endpointMap at the end? Or we could copy directly into endpointMap without it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can try to copy. The code could be slightly more complex. As currently we could merge them at the end. Do you want me try to re-use single map?

The main challange I experience, is that ps.addPodEndpointsToEndpointMap(endpointMap, pod) actually modify same map. But still possible

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. removed fqdn map, and we are using single map

for _, pod := range pods {
ps.addPodEndpointsToEndpointMap(endpointMap, pod)
if ps.fqdnTemplate == nil || ps.combineFQDNAnnotation {
ps.addPodEndpointsToEndpointMap(endpointMap, pod)
}

if ps.fqdnTemplate != nil {
fqdnHosts, err := ps.hostsFromTemplate(pod)
if err != nil {
log.Debug(err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be a warning at least? Or this is expected to "silently" ignore errors?

Copy link
Member Author

@ivankatliarchuk ivankatliarchuk Jun 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someone may decide to use FQDN with {{ .PodSpec.PodDNSConfig }}, one pod could have it, the other pod not necessary. We may have too many warnings. No prference, shell I change it to warn?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've looked at other Sources and they return the error directly.
I'm not sure what is the best thing to do here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sense. I'll do the same then

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to return error directly as in others.

continue
}
maps.Copy(fqdnEndpointMap, fqdnHosts)
}
}

maps.Copy(endpointMap, fqdnEndpointMap)

var endpoints []*endpoint.Endpoint
for key, targets := range endpointMap {
endpoints = append(endpoints, endpoint.NewEndpoint(key.DNSName, key.RecordType, targets...))
Expand Down Expand Up @@ -180,6 +219,30 @@ func (ps *podSource) addPodNodeEndpointsToEndpointMap(endpointMap map[endpoint.E
}
}

func (ps *podSource) hostsFromTemplate(pod *corev1.Pod) (map[endpoint.EndpointKey][]string, error) {
hosts, err := fqdn.ExecTemplate(ps.fqdnTemplate, pod)
if err != nil {
return nil, fmt.Errorf("skipping generating endpoints from template for pod %s: %w", pod.Name, err)
}

result := make(map[endpoint.EndpointKey][]string)
for _, target := range hosts {
for _, address := range pod.Status.PodIPs {
if address.IP == "" {
log.Debugf("skipping pod %q. PodIP is empty with phasae is %q", pod.Name, pod.Status.Phase)
continue
}
key := endpoint.EndpointKey{
DNSName: target,
RecordType: suitableType(address.IP),
}
result[key] = append(result[key], address.IP)
}
}

return result, nil
}

func addTargetsToEndpointMap(endpointMap map[endpoint.EndpointKey][]string, targets []string, domainList ...string) {
for _, domain := range domainList {
for _, target := range targets {
Expand Down
Loading
Loading