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 }