istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/config/kube/ingress/status.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ingress
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  	"strings"
    21  
    22  	corev1 "k8s.io/api/core/v1"
    23  	knetworking "k8s.io/api/networking/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/labels"
    26  	"k8s.io/apimachinery/pkg/types"
    27  
    28  	istiolabels "istio.io/istio/pkg/config/labels"
    29  	"istio.io/istio/pkg/config/mesh"
    30  	kubelib "istio.io/istio/pkg/kube"
    31  	"istio.io/istio/pkg/kube/controllers"
    32  	"istio.io/istio/pkg/kube/kclient"
    33  	"istio.io/istio/pkg/log"
    34  	netutil "istio.io/istio/pkg/util/net"
    35  )
    36  
    37  var statusLog = log.RegisterScope("ingress status", "")
    38  
    39  // StatusSyncer keeps the status IP in each Ingress resource updated
    40  type StatusSyncer struct {
    41  	meshConfig mesh.Watcher
    42  
    43  	queue          controllers.Queue
    44  	ingresses      kclient.Client[*knetworking.Ingress]
    45  	ingressClasses kclient.Client[*knetworking.IngressClass]
    46  	pods           kclient.Client[*corev1.Pod]
    47  	services       kclient.Client[*corev1.Service]
    48  	nodes          kclient.Client[*corev1.Node]
    49  }
    50  
    51  // Run the syncer until stopCh is closed
    52  func (s *StatusSyncer) Run(stopCh <-chan struct{}) {
    53  	s.queue.Run(stopCh)
    54  	controllers.ShutdownAll(s.services, s.nodes, s.pods, s.ingressClasses, s.ingresses)
    55  }
    56  
    57  // NewStatusSyncer creates a new instance
    58  func NewStatusSyncer(meshHolder mesh.Watcher, kc kubelib.Client) *StatusSyncer {
    59  	c := &StatusSyncer{
    60  		meshConfig:     meshHolder,
    61  		ingresses:      kclient.NewFiltered[*knetworking.Ingress](kc, kclient.Filter{ObjectFilter: kc.ObjectFilter()}),
    62  		ingressClasses: kclient.New[*knetworking.IngressClass](kc),
    63  		pods: kclient.NewFiltered[*corev1.Pod](kc, kclient.Filter{
    64  			ObjectFilter:    kc.ObjectFilter(),
    65  			ObjectTransform: kubelib.StripPodUnusedFields,
    66  		}),
    67  		services: kclient.NewFiltered[*corev1.Service](kc, kclient.Filter{ObjectFilter: kc.ObjectFilter()}),
    68  		nodes: kclient.NewFiltered[*corev1.Node](kc, kclient.Filter{
    69  			ObjectTransform: kubelib.StripNodeUnusedFields,
    70  		}),
    71  	}
    72  	c.queue = controllers.NewQueue("ingress status",
    73  		controllers.WithReconciler(c.Reconcile),
    74  		controllers.WithMaxAttempts(5))
    75  
    76  	// For any ingress change, enqueue it - we may need to update the status.
    77  	c.ingresses.AddEventHandler(controllers.ObjectHandler(c.queue.AddObject))
    78  	// For any class change, sync all ingress; the handler will filter non-matching ones already
    79  	c.ingressClasses.AddEventHandler(controllers.ObjectHandler(func(o controllers.Object) {
    80  		// Just sync them all
    81  		c.enqueueAll()
    82  	}))
    83  	// For services, we queue all Ingress if its the ingress service
    84  	c.services.AddEventHandler(controllers.ObjectHandler(func(o controllers.Object) {
    85  		if o.GetName() == c.meshConfig.Mesh().IngressService && o.GetNamespace() == IngressNamespace {
    86  			c.enqueueAll()
    87  		}
    88  	}))
    89  	// For pods, we enqueue all Ingress if its part of the ingress service
    90  	c.pods.AddEventHandler(controllers.ObjectHandler(func(o controllers.Object) {
    91  		if c.meshConfig.Mesh().IngressService != "" {
    92  			// Ingress Service takes precedence
    93  			return
    94  		}
    95  		ingressSelector := c.meshConfig.Mesh().IngressSelector
    96  
    97  		// get all pods acting as ingress gateways
    98  		igSelector := getIngressGatewaySelector(ingressSelector, "")
    99  		if istiolabels.Instance(igSelector).SubsetOf(o.GetLabels()) {
   100  			// Ingress selector matches this pod, enqueue everything
   101  			c.enqueueAll()
   102  		}
   103  	}))
   104  	// Mesh may have changed ingress fields, enqueue everything
   105  	c.meshConfig.AddMeshHandler(c.enqueueAll)
   106  	return c
   107  }
   108  
   109  // runningAddresses returns a list of IP addresses and/or FQDN in the namespace
   110  // where the ingress controller is currently running
   111  func (s *StatusSyncer) runningAddresses() []string {
   112  	addrs := make([]string, 0)
   113  	ingressService := s.meshConfig.Mesh().IngressService
   114  	ingressSelector := s.meshConfig.Mesh().IngressSelector
   115  
   116  	if ingressService != "" {
   117  		svc := s.services.Get(ingressService, IngressNamespace)
   118  		if svc == nil {
   119  			return nil
   120  		}
   121  
   122  		if svc.Spec.Type == corev1.ServiceTypeExternalName {
   123  			addrs = append(addrs, svc.Spec.ExternalName)
   124  
   125  			return addrs
   126  		}
   127  
   128  		for _, ip := range svc.Status.LoadBalancer.Ingress {
   129  			if ip.IP == "" {
   130  				addrs = append(addrs, ip.Hostname)
   131  			} else {
   132  				addrs = append(addrs, ip.IP)
   133  			}
   134  		}
   135  
   136  		addrs = append(addrs, svc.Spec.ExternalIPs...)
   137  		return addrs
   138  	}
   139  
   140  	// get all pods acting as ingress gateways
   141  	igSelector := getIngressGatewaySelector(ingressSelector, ingressService)
   142  	igPods := s.pods.List(IngressNamespace, labels.SelectorFromSet(igSelector))
   143  
   144  	for _, pod := range igPods {
   145  		// only Running pods are valid
   146  		if pod.Status.Phase != corev1.PodRunning {
   147  			continue
   148  		}
   149  
   150  		// Find node external IP
   151  		node := s.nodes.Get(pod.Spec.NodeName, "")
   152  		if node == nil {
   153  			continue
   154  		}
   155  
   156  		for _, address := range node.Status.Addresses {
   157  			if address.Type == corev1.NodeExternalIP {
   158  				if address.Address != "" && !addressInSlice(address.Address, addrs) {
   159  					addrs = append(addrs, address.Address)
   160  				}
   161  			}
   162  		}
   163  	}
   164  
   165  	return addrs
   166  }
   167  
   168  func addressInSlice(addr string, list []string) bool {
   169  	for _, v := range list {
   170  		if v == addr {
   171  			return true
   172  		}
   173  	}
   174  
   175  	return false
   176  }
   177  
   178  // sliceToStatus converts a slice of IP and/or hostnames to LoadBalancerIngress
   179  func sliceToStatus(endpoints []string) []knetworking.IngressLoadBalancerIngress {
   180  	lbi := make([]knetworking.IngressLoadBalancerIngress, 0, len(endpoints))
   181  	for _, ep := range endpoints {
   182  		if !netutil.IsValidIPAddress(ep) {
   183  			lbi = append(lbi, knetworking.IngressLoadBalancerIngress{Hostname: ep})
   184  		} else {
   185  			lbi = append(lbi, knetworking.IngressLoadBalancerIngress{IP: ep})
   186  		}
   187  	}
   188  
   189  	sort.SliceStable(lbi, lessLoadBalancerIngress(lbi))
   190  	return lbi
   191  }
   192  
   193  func lessLoadBalancerIngress(addrs []knetworking.IngressLoadBalancerIngress) func(int, int) bool {
   194  	return func(a, b int) bool {
   195  		switch strings.Compare(addrs[a].Hostname, addrs[b].Hostname) {
   196  		case -1:
   197  			return true
   198  		case 1:
   199  			return false
   200  		}
   201  		return addrs[a].IP < addrs[b].IP
   202  	}
   203  }
   204  
   205  func ingressSliceEqual(lhs, rhs []knetworking.IngressLoadBalancerIngress) bool {
   206  	if len(lhs) != len(rhs) {
   207  		return false
   208  	}
   209  
   210  	for i := range lhs {
   211  		if lhs[i].IP != rhs[i].IP {
   212  			return false
   213  		}
   214  		if lhs[i].Hostname != rhs[i].Hostname {
   215  			return false
   216  		}
   217  	}
   218  	return true
   219  }
   220  
   221  // shouldTargetIngress determines whether the status watcher should target a given ingress resource
   222  func (s *StatusSyncer) shouldTargetIngress(ingress *knetworking.Ingress) bool {
   223  	var ingressClass *knetworking.IngressClass
   224  	if ingress.Spec.IngressClassName != nil {
   225  		ingressClass = s.ingressClasses.Get(*ingress.Spec.IngressClassName, "")
   226  	}
   227  	return shouldProcessIngressWithClass(s.meshConfig.Mesh(), ingress, ingressClass)
   228  }
   229  
   230  func (s *StatusSyncer) enqueueAll() {
   231  	for _, ing := range s.ingresses.List(metav1.NamespaceAll, labels.Everything()) {
   232  		s.queue.AddObject(ing)
   233  	}
   234  }
   235  
   236  func (s *StatusSyncer) Reconcile(key types.NamespacedName) error {
   237  	log := statusLog.WithLabels("ingress", key)
   238  	ing := s.ingresses.Get(key.Name, key.Namespace)
   239  	if ing == nil {
   240  		log.Debugf("ingress removed, no action")
   241  		return nil
   242  	}
   243  	shouldTarget := s.shouldTargetIngress(ing)
   244  	if !shouldTarget {
   245  		log.Debugf("ingress not selected, no action")
   246  		return nil
   247  	}
   248  
   249  	curIPs := ing.Status.LoadBalancer.Ingress
   250  	sort.SliceStable(curIPs, lessLoadBalancerIngress(curIPs))
   251  
   252  	wantIPs := sliceToStatus(s.runningAddresses())
   253  
   254  	if ingressSliceEqual(wantIPs, curIPs) {
   255  		log.Debugf("ingress has no change, no action")
   256  		return nil
   257  	}
   258  
   259  	log.Infof("updating IPs (%v)", wantIPs)
   260  	ing = ing.DeepCopy()
   261  	ing.Status.LoadBalancer.Ingress = wantIPs
   262  	_, err := s.ingresses.UpdateStatus(ing)
   263  	if err != nil {
   264  		return fmt.Errorf("error updating ingress status: %v", err)
   265  	}
   266  	return nil
   267  }