sigs.k8s.io/external-dns@v0.14.1/source/node.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package source
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"text/template"
    23  
    24  	log "github.com/sirupsen/logrus"
    25  	v1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/labels"
    28  	kubeinformers "k8s.io/client-go/informers"
    29  	coreinformers "k8s.io/client-go/informers/core/v1"
    30  	"k8s.io/client-go/kubernetes"
    31  	"k8s.io/client-go/tools/cache"
    32  
    33  	"sigs.k8s.io/external-dns/endpoint"
    34  )
    35  
    36  type nodeSource struct {
    37  	client           kubernetes.Interface
    38  	annotationFilter string
    39  	fqdnTemplate     *template.Template
    40  	nodeInformer     coreinformers.NodeInformer
    41  	labelSelector    labels.Selector
    42  }
    43  
    44  // NewNodeSource creates a new nodeSource with the given config.
    45  func NewNodeSource(ctx context.Context, kubeClient kubernetes.Interface, annotationFilter, fqdnTemplate string, labelSelector labels.Selector) (Source, error) {
    46  	tmpl, err := parseTemplate(fqdnTemplate)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	// Use shared informers to listen for add/update/delete of nodes.
    52  	// Set resync period to 0, to prevent processing when nothing has changed
    53  	informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0)
    54  	nodeInformer := informerFactory.Core().V1().Nodes()
    55  
    56  	// Add default resource event handler to properly initialize informer.
    57  	nodeInformer.Informer().AddEventHandler(
    58  		cache.ResourceEventHandlerFuncs{
    59  			AddFunc: func(obj interface{}) {
    60  				log.Debug("node added")
    61  			},
    62  		},
    63  	)
    64  
    65  	informerFactory.Start(ctx.Done())
    66  
    67  	// wait for the local cache to be populated.
    68  	if err := waitForCacheSync(context.Background(), informerFactory); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	return &nodeSource{
    73  		client:           kubeClient,
    74  		annotationFilter: annotationFilter,
    75  		fqdnTemplate:     tmpl,
    76  		nodeInformer:     nodeInformer,
    77  		labelSelector:    labelSelector,
    78  	}, nil
    79  }
    80  
    81  // Endpoints returns endpoint objects for each service that should be processed.
    82  func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
    83  	nodes, err := ns.nodeInformer.Lister().List(ns.labelSelector)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	nodes, err = ns.filterByAnnotations(nodes)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	endpoints := map[endpoint.EndpointKey]*endpoint.Endpoint{}
    94  
    95  	// create endpoints for all nodes
    96  	for _, node := range nodes {
    97  		// Check controller annotation to see if we are responsible.
    98  		controller, ok := node.Annotations[controllerAnnotationKey]
    99  		if ok && controller != controllerAnnotationValue {
   100  			log.Debugf("Skipping node %s because controller value does not match, found: %s, required: %s",
   101  				node.Name, controller, controllerAnnotationValue)
   102  			continue
   103  		}
   104  
   105  		log.Debugf("creating endpoint for node %s", node.Name)
   106  
   107  		ttl := getTTLFromAnnotations(node.Annotations, fmt.Sprintf("node/%s", node.Name))
   108  
   109  		// create new endpoint with the information we already have
   110  		ep := &endpoint.Endpoint{
   111  			RecordTTL: ttl,
   112  		}
   113  
   114  		if ns.fqdnTemplate != nil {
   115  			hostnames, err := execTemplate(ns.fqdnTemplate, node)
   116  			if err != nil {
   117  				return nil, err
   118  			}
   119  			hostname := ""
   120  			if len(hostnames) > 0 {
   121  				hostname = hostnames[0]
   122  			}
   123  			ep.DNSName = hostname
   124  			log.Debugf("applied template for %s, converting to %s", node.Name, ep.DNSName)
   125  		} else {
   126  			ep.DNSName = node.Name
   127  			log.Debugf("not applying template for %s", node.Name)
   128  		}
   129  
   130  		addrs := getTargetsFromTargetAnnotation(node.Annotations)
   131  		if len(addrs) == 0 {
   132  			addrs, err = ns.nodeAddresses(node)
   133  			if err != nil {
   134  				return nil, fmt.Errorf("failed to get node address from %s: %w", node.Name, err)
   135  			}
   136  		}
   137  
   138  		ep.Labels = endpoint.NewLabels()
   139  		for _, addr := range addrs {
   140  			log.Debugf("adding endpoint %s target %s", ep, addr)
   141  			key := endpoint.EndpointKey{
   142  				DNSName:    ep.DNSName,
   143  				RecordType: suitableType(addr),
   144  			}
   145  			if _, ok := endpoints[key]; !ok {
   146  				epCopy := *ep
   147  				epCopy.RecordType = key.RecordType
   148  				endpoints[key] = &epCopy
   149  			}
   150  			endpoints[key].Targets = append(endpoints[key].Targets, addr)
   151  		}
   152  	}
   153  
   154  	endpointsSlice := []*endpoint.Endpoint{}
   155  	for _, ep := range endpoints {
   156  		endpointsSlice = append(endpointsSlice, ep)
   157  	}
   158  
   159  	return endpointsSlice, nil
   160  }
   161  
   162  func (ns *nodeSource) AddEventHandler(ctx context.Context, handler func()) {
   163  }
   164  
   165  // nodeAddress returns node's externalIP and if that's not found, node's internalIP
   166  // basically what k8s.io/kubernetes/pkg/util/node.GetPreferredNodeAddress does
   167  func (ns *nodeSource) nodeAddresses(node *v1.Node) ([]string, error) {
   168  	addresses := map[v1.NodeAddressType][]string{
   169  		v1.NodeExternalIP: {},
   170  		v1.NodeInternalIP: {},
   171  	}
   172  	var ipv6Addresses []string
   173  
   174  	for _, addr := range node.Status.Addresses {
   175  		addresses[addr.Type] = append(addresses[addr.Type], addr.Address)
   176  		// IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well.
   177  		if addr.Type == v1.NodeInternalIP && suitableType(addr.Address) == endpoint.RecordTypeAAAA {
   178  			ipv6Addresses = append(ipv6Addresses, addr.Address)
   179  		}
   180  	}
   181  
   182  	if len(addresses[v1.NodeExternalIP]) > 0 {
   183  		return append(addresses[v1.NodeExternalIP], ipv6Addresses...), nil
   184  	}
   185  
   186  	if len(addresses[v1.NodeInternalIP]) > 0 {
   187  		return addresses[v1.NodeInternalIP], nil
   188  	}
   189  
   190  	return nil, fmt.Errorf("could not find node address for %s", node.Name)
   191  }
   192  
   193  // filterByAnnotations filters a list of nodes by a given annotation selector.
   194  func (ns *nodeSource) filterByAnnotations(nodes []*v1.Node) ([]*v1.Node, error) {
   195  	labelSelector, err := metav1.ParseToLabelSelector(ns.annotationFilter)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	selector, err := metav1.LabelSelectorAsSelector(labelSelector)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	// empty filter returns original list
   205  	if selector.Empty() {
   206  		return nodes, nil
   207  	}
   208  
   209  	filteredList := []*v1.Node{}
   210  
   211  	for _, node := range nodes {
   212  		// convert the node's annotations to an equivalent label selector
   213  		annotations := labels.Set(node.Annotations)
   214  
   215  		// include node if its annotations match the selector
   216  		if selector.Matches(annotations) {
   217  			filteredList = append(filteredList, node)
   218  		}
   219  	}
   220  
   221  	return filteredList, nil
   222  }