istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/autoserviceexportcontroller.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 controller
    16  
    17  import (
    18  	"context"
    19  
    20  	v1 "k8s.io/api/core/v1"
    21  	"k8s.io/apimachinery/pkg/api/errors"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	"k8s.io/apimachinery/pkg/types"
    26  	mcsapi "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
    27  
    28  	"istio.io/istio/pilot/pkg/model"
    29  	serviceRegistryKube "istio.io/istio/pilot/pkg/serviceregistry/kube"
    30  	"istio.io/istio/pkg/cluster"
    31  	"istio.io/istio/pkg/config/schema/gvk"
    32  	"istio.io/istio/pkg/kube"
    33  	"istio.io/istio/pkg/kube/controllers"
    34  	"istio.io/istio/pkg/kube/kclient"
    35  	"istio.io/istio/pkg/kube/kubetypes"
    36  	"istio.io/istio/pkg/kube/mcs"
    37  )
    38  
    39  type autoServiceExportController struct {
    40  	autoServiceExportOptions
    41  
    42  	client   kube.Client
    43  	queue    controllers.Queue
    44  	services kclient.Client[*v1.Service]
    45  
    46  	// We use this flag to short-circuit the logic and stop the controller
    47  	// if the CRD does not exist (or is deleted)
    48  	mcsSupported bool
    49  }
    50  
    51  // autoServiceExportOptions provide options for creating a autoServiceExportController.
    52  type autoServiceExportOptions struct {
    53  	Client       kube.Client
    54  	ClusterID    cluster.ID
    55  	DomainSuffix string
    56  	ClusterLocal model.ClusterLocalProvider
    57  }
    58  
    59  // newAutoServiceExportController creates a new autoServiceExportController.
    60  func newAutoServiceExportController(opts autoServiceExportOptions) *autoServiceExportController {
    61  	c := &autoServiceExportController{
    62  		autoServiceExportOptions: opts,
    63  		client:                   opts.Client,
    64  		mcsSupported:             true,
    65  	}
    66  	c.queue = controllers.NewQueue("auto export",
    67  		controllers.WithReconciler(c.Reconcile),
    68  		controllers.WithMaxAttempts(5))
    69  
    70  	c.services = kclient.NewFiltered[*v1.Service](opts.Client, kubetypes.Filter{ObjectFilter: opts.Client.ObjectFilter()})
    71  
    72  	// Only handle add. The controller only acts on parts of the service
    73  	// that are immutable (e.g. name). When we create ServiceExport, we bind its
    74  	// lifecycle to the Service so that when the Service is deleted,
    75  	// k8s automatically deletes the ServiceExport.
    76  	c.services.AddEventHandler(controllers.EventHandler[controllers.Object]{AddFunc: c.queue.AddObject})
    77  
    78  	return c
    79  }
    80  
    81  func (c *autoServiceExportController) Run(stopCh <-chan struct{}) {
    82  	kube.WaitForCacheSync("auto service export", stopCh, c.services.HasSynced)
    83  	c.queue.Run(stopCh)
    84  	c.services.ShutdownHandlers()
    85  }
    86  
    87  func (c *autoServiceExportController) logPrefix() string {
    88  	return "AutoServiceExport (cluster=" + c.ClusterID.String() + ") "
    89  }
    90  
    91  // func (c *autoServiceExportController) createServiceExportIfNotPresent(svc *v1.Service) error {
    92  func (c *autoServiceExportController) Reconcile(key types.NamespacedName) error {
    93  	if !c.mcsSupported {
    94  		// Don't create ServiceExport if MCS is not supported on the cluster.
    95  		log.Debugf("%s ignoring added Service, since !mcsSupported", c.logPrefix())
    96  		return nil
    97  	}
    98  
    99  	svc := c.services.Get(key.Name, key.Namespace)
   100  	if svc == nil {
   101  		// Service no longer exists, no action needed
   102  		return nil
   103  	}
   104  
   105  	if c.isClusterLocalService(svc) {
   106  		// Don't create ServiceExport if the service is configured to be
   107  		// local to the cluster (i.e. non-exported).
   108  		log.Debugf("%s ignoring cluster-local service %s/%s", c.logPrefix(), svc.Namespace, svc.Name)
   109  		return nil
   110  	}
   111  	serviceExport := mcsapi.ServiceExport{
   112  		TypeMeta: metav1.TypeMeta{
   113  			Kind:       "ServiceExport",
   114  			APIVersion: mcs.MCSSchemeGroupVersion.String(),
   115  		},
   116  		ObjectMeta: metav1.ObjectMeta{
   117  			Namespace: svc.Namespace,
   118  			Name:      svc.Name,
   119  
   120  			// Bind the lifecycle of the ServiceExport to the Service. We do this by making the Service
   121  			// the "owner" of the ServiceExport resource.
   122  			OwnerReferences: []metav1.OwnerReference{
   123  				{
   124  					APIVersion: v1.SchemeGroupVersion.String(),
   125  					Kind:       gvk.Service.Kind,
   126  					Name:       svc.Name,
   127  					UID:        svc.UID,
   128  				},
   129  			},
   130  		},
   131  	}
   132  
   133  	// Convert to unstructured.
   134  	u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&serviceExport)
   135  	if err != nil {
   136  		log.Warnf("%s failed converting ServiceExport %s/%s to Unstructured: %v", c.logPrefix(),
   137  			svc.Namespace, svc.Name, err)
   138  		return err
   139  	}
   140  
   141  	if _, err = c.client.Dynamic().Resource(mcs.ServiceExportGVR).Namespace(serviceExport.Namespace).Create(
   142  		context.TODO(), &unstructured.Unstructured{Object: u}, metav1.CreateOptions{}); err != nil {
   143  		switch {
   144  		case errors.IsAlreadyExists(err):
   145  			// The ServiceExport already exists. Nothing to do.
   146  			return nil
   147  		case errors.IsNotFound(err):
   148  			log.Warnf("%s ServiceExport CRD Not found. Shutting down MCS ServiceExport sync. "+
   149  				"Please add the CRD then restart the istiod deployment", c.logPrefix())
   150  			c.mcsSupported = false
   151  
   152  			// Do not return the error, so that the queue does not attempt a retry.
   153  			return nil
   154  		}
   155  	}
   156  
   157  	if err != nil {
   158  		log.Warnf("%s failed creating ServiceExport %s/%s: %v", c.logPrefix(), svc.Namespace, svc.Name, err)
   159  		return err
   160  	}
   161  
   162  	log.Debugf("%s created ServiceExport %s/%s", c.logPrefix(), svc.Namespace, svc.Name)
   163  	return nil
   164  }
   165  
   166  func (c *autoServiceExportController) isClusterLocalService(svc *v1.Service) bool {
   167  	hostname := serviceRegistryKube.ServiceHostname(svc.Name, svc.Namespace, c.DomainSuffix)
   168  	return c.ClusterLocal.GetClusterLocalHosts().IsClusterLocal(hostname)
   169  }