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 }