istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/config/kube/ingress/controller.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 provides a read-only view of Kubernetes ingress resources 16 // as an ingress rule configuration type store 17 package ingress 18 19 import ( 20 "errors" 21 "fmt" 22 "sort" 23 "sync" 24 25 corev1 "k8s.io/api/core/v1" 26 knetworking "k8s.io/api/networking/v1" 27 klabels "k8s.io/apimachinery/pkg/labels" 28 "k8s.io/apimachinery/pkg/types" 29 30 meshconfig "istio.io/api/mesh/v1alpha1" 31 "istio.io/istio/pilot/pkg/model" 32 kubecontroller "istio.io/istio/pilot/pkg/serviceregistry/kube/controller" 33 "istio.io/istio/pkg/config" 34 "istio.io/istio/pkg/config/constants" 35 "istio.io/istio/pkg/config/mesh" 36 "istio.io/istio/pkg/config/schema/collection" 37 "istio.io/istio/pkg/config/schema/collections" 38 "istio.io/istio/pkg/config/schema/gvk" 39 "istio.io/istio/pkg/env" 40 "istio.io/istio/pkg/kube" 41 "istio.io/istio/pkg/kube/controllers" 42 "istio.io/istio/pkg/kube/kclient" 43 "istio.io/istio/pkg/util/sets" 44 ) 45 46 // In 1.0, the Gateway is defined in the namespace where the actual controller runs, and needs to be managed by 47 // user. 48 // The gateway is named by appending "-istio-autogenerated-k8s-ingress" to the name of the ingress. 49 // 50 // Currently the gateway namespace is hardcoded to istio-system (model.IstioIngressNamespace) 51 // 52 // VirtualServices are also auto-generated in the model.IstioIngressNamespace. 53 // 54 // The sync of Ingress objects to IP is done by status.go 55 // the 'ingress service' name is used to get the IP of the Service 56 // If ingress service is empty, it falls back to NodeExternalIP list, selected using the labels. 57 // This is using 'namespace' of pilot - but seems to be broken (never worked), since it uses Pilot's pod labels 58 // instead of the ingress labels. 59 60 // Follows mesh.IngressControllerMode setting to enable - OFF|STRICT|DEFAULT. 61 // STRICT requires "kubernetes.io/ingress.class" == mesh.IngressClass 62 // DEFAULT allows Ingress without explicit class. 63 64 // In 1.1: 65 // - K8S_INGRESS_NS - namespace of the Gateway that will act as ingress. 66 // - labels of the gateway set to "app=ingressgateway" for node_port, service set to 'ingressgateway' (matching default install) 67 // If we need more flexibility - we can add it (but likely we'll deprecate ingress support first) 68 // - 69 70 var schemas = collection.SchemasFor( 71 collections.VirtualService, 72 collections.Gateway) 73 74 // Control needs RBAC permissions to write to Pods. 75 76 type controller struct { 77 meshWatcher mesh.Holder 78 domainSuffix string 79 80 queue controllers.Queue 81 virtualServiceHandlers []model.EventHandler 82 gatewayHandlers []model.EventHandler 83 84 mutex sync.RWMutex 85 // processed ingresses 86 ingresses map[types.NamespacedName]*knetworking.Ingress 87 88 classes kclient.Client[*knetworking.IngressClass] 89 ingress kclient.Client[*knetworking.Ingress] 90 services kclient.Client[*corev1.Service] 91 } 92 93 var IngressNamespace = env.Register("K8S_INGRESS_NS", constants.IstioSystemNamespace, 94 "The namespace where ingress controller runs, by default it is istio-system").Get() 95 96 var errUnsupportedOp = errors.New("unsupported operation: the ingress config store is a read-only view") 97 98 // NewController creates a new Kubernetes controller 99 func NewController(client kube.Client, meshWatcher mesh.Holder, 100 options kubecontroller.Options, 101 ) model.ConfigStoreController { 102 ingress := kclient.NewFiltered[*knetworking.Ingress](client, kclient.Filter{ObjectFilter: client.ObjectFilter()}) 103 classes := kclient.New[*knetworking.IngressClass](client) 104 services := kclient.NewFiltered[*corev1.Service](client, kclient.Filter{ObjectFilter: client.ObjectFilter()}) 105 106 c := &controller{ 107 meshWatcher: meshWatcher, 108 domainSuffix: options.DomainSuffix, 109 ingresses: make(map[types.NamespacedName]*knetworking.Ingress), 110 ingress: ingress, 111 classes: classes, 112 services: services, 113 } 114 c.queue = controllers.NewQueue("ingress", 115 controllers.WithReconciler(c.onEvent), 116 controllers.WithMaxAttempts(5)) 117 c.ingress.AddEventHandler(controllers.ObjectHandler(c.queue.AddObject)) 118 119 // We watch service changes to detect service port number change to trigger 120 // re-convert ingress to new-vs. 121 c.services.AddEventHandler(controllers.FromEventHandler(func(o controllers.Event) { 122 c.onServiceEvent(o) 123 })) 124 125 return c 126 } 127 128 func (c *controller) Run(stop <-chan struct{}) { 129 kube.WaitForCacheSync("ingress", stop, c.ingress.HasSynced, c.services.HasSynced, c.classes.HasSynced) 130 c.queue.Run(stop) 131 controllers.ShutdownAll(c.ingress, c.services, c.classes) 132 } 133 134 func (c *controller) shouldProcessIngress(mesh *meshconfig.MeshConfig, i *knetworking.Ingress) bool { 135 var class *knetworking.IngressClass 136 if i.Spec.IngressClassName != nil { 137 c := c.classes.Get(*i.Spec.IngressClassName, "") 138 if c == nil { 139 return false 140 } 141 class = c 142 } 143 return shouldProcessIngressWithClass(mesh, i, class) 144 } 145 146 // shouldProcessIngressUpdate checks whether we should renotify registered handlers about an update event 147 func (c *controller) shouldProcessIngressUpdate(ing *knetworking.Ingress) bool { 148 // ingress add/update 149 shouldProcess := c.shouldProcessIngress(c.meshWatcher.Mesh(), ing) 150 item := config.NamespacedName(ing) 151 if shouldProcess { 152 // record processed ingress 153 c.mutex.Lock() 154 c.ingresses[item] = ing 155 c.mutex.Unlock() 156 return true 157 } 158 159 c.mutex.Lock() 160 _, preProcessed := c.ingresses[item] 161 // previous processed but should not currently, delete it 162 if preProcessed && !shouldProcess { 163 delete(c.ingresses, item) 164 } else { 165 c.ingresses[item] = ing 166 } 167 c.mutex.Unlock() 168 169 return preProcessed 170 } 171 172 func (c *controller) onEvent(item types.NamespacedName) error { 173 event := model.EventUpdate 174 ing := c.ingress.Get(item.Name, item.Namespace) 175 if ing == nil { 176 event = model.EventDelete 177 c.mutex.Lock() 178 ing = c.ingresses[item] 179 delete(c.ingresses, item) 180 c.mutex.Unlock() 181 if ing == nil { 182 // It was a delete and we didn't have an existing known ingress, no action 183 return nil 184 } 185 } 186 187 // we should check need process only when event is not delete, 188 // if it is delete event, and previously processed, we need to process too. 189 if event != model.EventDelete { 190 shouldProcess := c.shouldProcessIngressUpdate(ing) 191 if !shouldProcess { 192 return nil 193 } 194 } 195 196 vsmetadata := config.Meta{ 197 Name: item.Name + "-" + "virtualservice", 198 Namespace: item.Namespace, 199 GroupVersionKind: gvk.VirtualService, 200 // Set this label so that we do not compare configs and just push. 201 Labels: map[string]string{constants.AlwaysPushLabel: "true"}, 202 } 203 gatewaymetadata := config.Meta{ 204 Name: item.Name + "-" + "gateway", 205 Namespace: item.Namespace, 206 GroupVersionKind: gvk.Gateway, 207 // Set this label so that we do not compare configs and just push. 208 Labels: map[string]string{constants.AlwaysPushLabel: "true"}, 209 } 210 211 // Trigger updates for Gateway and VirtualService 212 // TODO: we could be smarter here and only trigger when real changes were found 213 for _, f := range c.virtualServiceHandlers { 214 f(config.Config{Meta: vsmetadata}, config.Config{Meta: vsmetadata}, event) 215 } 216 for _, f := range c.gatewayHandlers { 217 f(config.Config{Meta: gatewaymetadata}, config.Config{Meta: gatewaymetadata}, event) 218 } 219 220 return nil 221 } 222 223 func (c *controller) onServiceEvent(input any) { 224 event := input.(controllers.Event) 225 curSvc := event.Latest().(*corev1.Service) 226 227 // This is shortcut. We only care about the port number change if we receive service update event. 228 if event.Event == controllers.EventUpdate { 229 oldSvc := event.Old.(*corev1.Service) 230 oldPorts := extractPorts(oldSvc.Spec.Ports) 231 curPorts := extractPorts(curSvc.Spec.Ports) 232 // If the ports don't change, we do nothing. 233 if oldPorts.Equals(curPorts) { 234 return 235 } 236 } 237 238 // We care about add, delete and ports changed event of services that are referred 239 // by ingress using port name. 240 namespacedName := config.NamespacedName(curSvc).String() 241 for _, ingress := range c.ingress.List(curSvc.GetNamespace(), klabels.Everything()) { 242 referredSvcSet := extractServicesByPortNameType(ingress) 243 if referredSvcSet.Contains(namespacedName) { 244 c.queue.AddObject(ingress) 245 } 246 } 247 } 248 249 func (c *controller) RegisterEventHandler(kind config.GroupVersionKind, f model.EventHandler) { 250 switch kind { 251 case gvk.VirtualService: 252 c.virtualServiceHandlers = append(c.virtualServiceHandlers, f) 253 case gvk.Gateway: 254 c.gatewayHandlers = append(c.gatewayHandlers, f) 255 } 256 } 257 258 func (c *controller) HasSynced() bool { 259 return c.queue.HasSynced() 260 } 261 262 func (c *controller) Schemas() collection.Schemas { 263 // TODO: are these two config descriptors right? 264 return schemas 265 } 266 267 func (c *controller) Get(typ config.GroupVersionKind, name, namespace string) *config.Config { 268 return nil 269 } 270 271 // sortIngressByCreationTime sorts the list of config objects in ascending order by their creation time (if available). 272 func sortIngressByCreationTime(ingr []*knetworking.Ingress) []*knetworking.Ingress { 273 sort.Slice(ingr, func(i, j int) bool { 274 // If creation time is the same, then behavior is nondeterministic. In this case, we can 275 // pick an arbitrary but consistent ordering based on name and namespace, which is unique. 276 // CreationTimestamp is stored in seconds, so this is not uncommon. 277 if ingr[i].CreationTimestamp == ingr[j].CreationTimestamp { 278 in := ingr[i].Name + "." + ingr[i].Namespace 279 jn := ingr[j].Name + "." + ingr[j].Namespace 280 return in < jn 281 } 282 return ingr[i].CreationTimestamp.Before(&ingr[j].CreationTimestamp) 283 }) 284 return ingr 285 } 286 287 func (c *controller) List(typ config.GroupVersionKind, namespace string) []config.Config { 288 if typ != gvk.Gateway && 289 typ != gvk.VirtualService { 290 return nil 291 } 292 293 out := make([]config.Config, 0) 294 ingressByHost := map[string]*config.Config{} 295 for _, ingress := range sortIngressByCreationTime(c.ingress.List(namespace, klabels.Everything())) { 296 process := c.shouldProcessIngress(c.meshWatcher.Mesh(), ingress) 297 if !process { 298 continue 299 } 300 301 switch typ { 302 case gvk.VirtualService: 303 ConvertIngressVirtualService(*ingress, c.domainSuffix, ingressByHost, c.services) 304 case gvk.Gateway: 305 gateways := ConvertIngressV1alpha3(*ingress, c.meshWatcher.Mesh(), c.domainSuffix) 306 out = append(out, gateways) 307 } 308 } 309 310 if typ == gvk.VirtualService { 311 for _, obj := range ingressByHost { 312 out = append(out, *obj) 313 } 314 } 315 316 return out 317 } 318 319 // extractServicesByPortNameType extract services that are of port name type in the specified ingress resource. 320 func extractServicesByPortNameType(ingress *knetworking.Ingress) sets.String { 321 services := sets.String{} 322 for _, rule := range ingress.Spec.Rules { 323 if rule.HTTP == nil { 324 continue 325 } 326 327 for _, route := range rule.HTTP.Paths { 328 if route.Backend.Service == nil { 329 continue 330 } 331 332 if route.Backend.Service.Port.Name != "" { 333 services.Insert(types.NamespacedName{ 334 Namespace: ingress.GetNamespace(), 335 Name: route.Backend.Service.Name, 336 }.String()) 337 } 338 } 339 } 340 return services 341 } 342 343 func extractPorts(ports []corev1.ServicePort) sets.String { 344 result := sets.String{} 345 for _, port := range ports { 346 // the format is port number|port name. 347 result.Insert(fmt.Sprintf("%d|%s", port.Port, port.Name)) 348 } 349 return result 350 } 351 352 func (c *controller) Create(_ config.Config) (string, error) { 353 return "", errUnsupportedOp 354 } 355 356 func (c *controller) Update(_ config.Config) (string, error) { 357 return "", errUnsupportedOp 358 } 359 360 func (c *controller) UpdateStatus(config.Config) (string, error) { 361 return "", errUnsupportedOp 362 } 363 364 func (c *controller) Patch(_ config.Config, _ config.PatchFunc) (string, error) { 365 return "", errUnsupportedOp 366 } 367 368 func (c *controller) Delete(_ config.GroupVersionKind, _, _ string, _ *string) error { 369 return errUnsupportedOp 370 }