github.phpd.cn/cilium/cilium@v1.6.12/pkg/k8s/service_cache.go (about) 1 // Copyright 2018-2019 Authors of Cilium 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 k8s 16 17 import ( 18 "net" 19 20 "github.com/cilium/cilium/pkg/k8s/types" 21 "github.com/cilium/cilium/pkg/loadbalancer" 22 "github.com/cilium/cilium/pkg/lock" 23 "github.com/cilium/cilium/pkg/logging/logfields" 24 "github.com/cilium/cilium/pkg/option" 25 "github.com/cilium/cilium/pkg/service" 26 27 "github.com/davecgh/go-spew/spew" 28 "github.com/sirupsen/logrus" 29 ) 30 31 // CacheAction is the type of action that was performed on the cache 32 type CacheAction int 33 34 const ( 35 // UpdateService reflects that the service was updated or added 36 UpdateService CacheAction = iota 37 38 // DeleteService reflects that the service was deleted 39 DeleteService 40 41 // UpdateIngress reflects that the ingress was updated or added 42 UpdateIngress 43 44 // DeleteIngress reflects that the ingress was deleted 45 DeleteIngress 46 ) 47 48 // String returns the cache action as a string 49 func (c CacheAction) String() string { 50 switch c { 51 case UpdateService: 52 return "service-updated" 53 case DeleteService: 54 return "service-deleted" 55 case UpdateIngress: 56 return "ingress-updated" 57 case DeleteIngress: 58 return "ingress-deleted" 59 default: 60 return "unknown" 61 } 62 } 63 64 // ServiceEvent is emitted via the Events channel of ServiceCache and describes 65 // the change that occurred in the cache 66 type ServiceEvent struct { 67 // Action is the action that was performed in the cache 68 Action CacheAction 69 70 // ID is the identified of the service 71 ID ServiceID 72 73 // Service is the service structure 74 Service *Service 75 76 // OldService is the service structure 77 OldService *Service 78 79 // Endpoints is the endpoints structured correlated with the service 80 Endpoints *Endpoints 81 } 82 83 // ServiceCache is a list of services and ingresses correlated with the 84 // matching endpoints. The Events member will receive events as services and 85 // ingresses 86 type ServiceCache struct { 87 Events chan ServiceEvent 88 89 // mutex protects the maps below including the concurrent access of each 90 // value. 91 mutex lock.RWMutex 92 services map[ServiceID]*Service 93 endpoints map[ServiceID]*Endpoints 94 ingresses map[ServiceID]*Service 95 96 // externalEndpoints is a list of additional service backends derived from source other than the local cluster 97 externalEndpoints map[ServiceID]externalEndpoints 98 } 99 100 // NewServiceCache returns a new ServiceCache 101 func NewServiceCache() ServiceCache { 102 return ServiceCache{ 103 services: map[ServiceID]*Service{}, 104 endpoints: map[ServiceID]*Endpoints{}, 105 ingresses: map[ServiceID]*Service{}, 106 externalEndpoints: map[ServiceID]externalEndpoints{}, 107 Events: make(chan ServiceEvent, option.Config.K8sServiceCacheSize), 108 } 109 } 110 111 // GetServiceIP returns a random L3n4Addr that is backing the given Service ID. 112 func (s *ServiceCache) GetServiceIP(svcID ServiceID) *loadbalancer.L3n4Addr { 113 s.mutex.RLock() 114 defer s.mutex.RUnlock() 115 svc := s.services[svcID] 116 if svc == nil { 117 return nil 118 } 119 for _, port := range svc.Ports { 120 return loadbalancer.NewL3n4Addr(port.Protocol, svc.FrontendIP, port.Port) 121 } 122 return nil 123 } 124 125 // UpdateService parses a Kubernetes service and adds or updates it in the 126 // ServiceCache. Returns the ServiceID unless the Kubernetes service could not 127 // be parsed and a bool to indicate whether the service was changed in the 128 // cache or not. 129 func (s *ServiceCache) UpdateService(k8sSvc *types.Service) ServiceID { 130 svcID, newService := ParseService(k8sSvc) 131 if newService == nil { 132 return svcID 133 } 134 135 s.mutex.Lock() 136 defer s.mutex.Unlock() 137 138 oldService, ok := s.services[svcID] 139 if ok { 140 if oldService.DeepEquals(newService) { 141 return svcID 142 } 143 } 144 145 s.services[svcID] = newService 146 147 // Check if the corresponding Endpoints resource is already available 148 endpoints, serviceReady := s.correlateEndpoints(svcID) 149 if serviceReady { 150 s.Events <- ServiceEvent{ 151 Action: UpdateService, 152 ID: svcID, 153 Service: newService, 154 OldService: oldService, 155 Endpoints: endpoints, 156 } 157 } 158 159 return svcID 160 } 161 162 // DeleteService parses a Kubernetes service and removes it from the 163 // ServiceCache 164 func (s *ServiceCache) DeleteService(k8sSvc *types.Service) { 165 svcID := ParseServiceID(k8sSvc) 166 167 s.mutex.Lock() 168 defer s.mutex.Unlock() 169 170 oldService, serviceOK := s.services[svcID] 171 endpoints, _ := s.correlateEndpoints(svcID) 172 delete(s.services, svcID) 173 174 if serviceOK { 175 s.Events <- ServiceEvent{ 176 Action: DeleteService, 177 ID: svcID, 178 Service: oldService, 179 Endpoints: endpoints, 180 } 181 } 182 } 183 184 // UpdateEndpoints parses a Kubernetes endpoints and adds or updates it in the 185 // ServiceCache. Returns the ServiceID unless the Kubernetes endpoints could not 186 // be parsed and a bool to indicate whether the endpoints was changed in the 187 // cache or not. 188 func (s *ServiceCache) UpdateEndpoints(k8sEndpoints *types.Endpoints) (ServiceID, *Endpoints) { 189 svcID, newEndpoints := ParseEndpoints(k8sEndpoints) 190 191 s.mutex.Lock() 192 defer s.mutex.Unlock() 193 194 if oldEndpoints, ok := s.endpoints[svcID]; ok { 195 if oldEndpoints.DeepEquals(newEndpoints) { 196 return svcID, newEndpoints 197 } 198 } 199 200 s.endpoints[svcID] = newEndpoints 201 202 // Check if the corresponding Endpoints resource is already available 203 service, ok := s.services[svcID] 204 endpoints, serviceReady := s.correlateEndpoints(svcID) 205 if ok && serviceReady { 206 s.Events <- ServiceEvent{ 207 Action: UpdateService, 208 ID: svcID, 209 Service: service, 210 Endpoints: endpoints, 211 } 212 } 213 214 return svcID, newEndpoints 215 } 216 217 // DeleteEndpoints parses a Kubernetes endpoints and removes it from the 218 // ServiceCache 219 func (s *ServiceCache) DeleteEndpoints(k8sEndpoints *types.Endpoints) ServiceID { 220 svcID := ParseEndpointsID(k8sEndpoints) 221 222 s.mutex.Lock() 223 defer s.mutex.Unlock() 224 225 service, serviceOK := s.services[svcID] 226 delete(s.endpoints, svcID) 227 endpoints, _ := s.correlateEndpoints(svcID) 228 229 if serviceOK { 230 event := ServiceEvent{ 231 Action: UpdateService, 232 ID: svcID, 233 Service: service, 234 Endpoints: endpoints, 235 } 236 237 s.Events <- event 238 } 239 240 return svcID 241 } 242 243 // UpdateIngress parses a Kubernetes ingress and adds or updates it in the 244 // ServiceCache. 245 func (s *ServiceCache) UpdateIngress(ingress *types.Ingress, host net.IP) (ServiceID, error) { 246 svcID, newService, err := ParseIngress(ingress, host) 247 if err != nil { 248 return svcID, err 249 } 250 251 s.mutex.Lock() 252 defer s.mutex.Unlock() 253 254 if oldService, ok := s.ingresses[svcID]; ok { 255 if oldService.DeepEquals(newService) { 256 return svcID, nil 257 } 258 } 259 260 s.ingresses[svcID] = newService 261 262 s.Events <- ServiceEvent{ 263 Action: UpdateIngress, 264 ID: svcID, 265 Service: newService, 266 } 267 268 return svcID, nil 269 } 270 271 // DeleteIngress parses a Kubernetes ingress and removes it from the 272 // ServiceCache 273 func (s *ServiceCache) DeleteIngress(ingress *types.Ingress) { 274 svcID := ParseIngressID(ingress) 275 276 s.mutex.Lock() 277 defer s.mutex.Unlock() 278 279 oldService, ok := s.ingresses[svcID] 280 endpoints := s.endpoints[svcID] 281 delete(s.ingresses, svcID) 282 283 if ok { 284 s.Events <- ServiceEvent{ 285 Action: DeleteIngress, 286 ID: svcID, 287 Service: oldService, 288 Endpoints: endpoints, 289 } 290 } 291 } 292 293 // FrontendList is the list of all k8s service frontends 294 type FrontendList map[string]struct{} 295 296 // LooseMatch returns true if the provided frontend is found in the 297 // FrontendList. If the frontend has a protocol value set, it only matches a 298 // k8s service with a matching protocol. If no protocol is set, any k8s service 299 // matching frontend IP and port is considered a match, regardless of protocol. 300 func (l FrontendList) LooseMatch(frontend loadbalancer.L3n4Addr) (exists bool) { 301 switch frontend.Protocol { 302 case loadbalancer.NONE: 303 for _, protocol := range loadbalancer.AllProtocols { 304 frontend.Protocol = protocol 305 _, exists = l[frontend.StringWithProtocol()] 306 if exists { 307 return 308 } 309 } 310 311 // If the protocol is set, perform an exact match 312 default: 313 _, exists = l[frontend.StringWithProtocol()] 314 } 315 return 316 } 317 318 // UniqueServiceFrontends returns all services known to the service cache as a 319 // map, indexed by the string representation of a loadbalancer.L3n4Addr 320 func (s *ServiceCache) UniqueServiceFrontends() FrontendList { 321 uniqueFrontends := FrontendList{} 322 323 s.mutex.RLock() 324 defer s.mutex.RUnlock() 325 326 for _, svc := range s.services { 327 for _, p := range svc.Ports { 328 address := loadbalancer.L3n4Addr{ 329 IP: svc.FrontendIP, 330 L4Addr: *p.L4Addr, 331 } 332 333 uniqueFrontends[address.StringWithProtocol()] = struct{}{} 334 } 335 for _, nodePortFEs := range svc.NodePorts { 336 for _, fe := range nodePortFEs { 337 uniqueFrontends[fe.StringWithProtocol()] = struct{}{} 338 } 339 } 340 } 341 342 return uniqueFrontends 343 } 344 345 // correlateEndpoints builds a combined Endpoints of the local endpoints and 346 // all external endpoints if the service is marked as a global service. Also 347 // returns a boolean that indicates whether the service is ready to be plumbed, 348 // this is true if: 349 // IF If ta local endpoints resource is present. Regardless whether the 350 // endpoints resource contains actual backends or not. 351 // OR Remote endpoints exist which correlate to the service. 352 func (s *ServiceCache) correlateEndpoints(id ServiceID) (*Endpoints, bool) { 353 endpoints := newEndpoints() 354 355 localEndpoints, hasLocalEndpoints := s.endpoints[id] 356 if hasLocalEndpoints { 357 for ip, e := range localEndpoints.Backends { 358 endpoints.Backends[ip] = e 359 } 360 } 361 362 svc, hasExternalService := s.services[id] 363 if hasExternalService && svc.IncludeExternal { 364 externalEndpoints, hasExternalEndpoints := s.externalEndpoints[id] 365 if hasExternalEndpoints { 366 for clusterName, remoteClusterEndpoints := range externalEndpoints.endpoints { 367 if clusterName == option.Config.ClusterName { 368 continue 369 } 370 371 for ip, e := range remoteClusterEndpoints.Backends { 372 if _, ok := endpoints.Backends[ip]; ok { 373 log.WithFields(logrus.Fields{ 374 logfields.K8sSvcName: id.Name, 375 logfields.K8sNamespace: id.Namespace, 376 logfields.IPAddr: ip, 377 "cluster": clusterName, 378 }).Warning("Conflicting service backend IP") 379 } else { 380 endpoints.Backends[ip] = e 381 } 382 } 383 } 384 } 385 } 386 387 // Report the service as ready if a local endpoints object exists or if 388 // external endpoints have have been identified 389 return endpoints, hasLocalEndpoints || len(endpoints.Backends) > 0 390 } 391 392 // MergeExternalServiceUpdate merges a cluster service of a remote cluster into 393 // the local service cache. The service endpoints are stored as external endpoints 394 // and are correlated on demand with local services via correlateEndpoints(). 395 func (s *ServiceCache) MergeExternalServiceUpdate(service *service.ClusterService) { 396 id := ServiceID{Name: service.Name, Namespace: service.Namespace} 397 scopedLog := log.WithFields(logrus.Fields{logfields.ServiceName: service.String()}) 398 399 // Ignore updates of own cluster 400 if service.Cluster == option.Config.ClusterName { 401 scopedLog.Debug("Not merging external service. Own cluster") 402 return 403 } 404 405 s.mutex.Lock() 406 defer s.mutex.Unlock() 407 408 externalEndpoints, ok := s.externalEndpoints[id] 409 if !ok { 410 externalEndpoints = newExternalEndpoints() 411 s.externalEndpoints[id] = externalEndpoints 412 } 413 414 scopedLog.Debugf("Updating backends to %+v", service.Backends) 415 externalEndpoints.endpoints[service.Cluster] = &Endpoints{ 416 Backends: service.Backends, 417 } 418 419 svc, ok := s.services[id] 420 421 endpoints, serviceReady := s.correlateEndpoints(id) 422 423 // Only send event notification if service is shared and ready. 424 // External endpoints are still tracked but correlation will not happen 425 // until the service is marked as shared. 426 if ok && svc.Shared && serviceReady { 427 s.Events <- ServiceEvent{ 428 Action: UpdateService, 429 ID: id, 430 Service: svc, 431 Endpoints: endpoints, 432 } 433 } 434 } 435 436 // MergeExternalServiceDelete merges the deletion of a cluster service in a 437 // remote cluster into the local service cache. The service endpoints are 438 // stored as external endpoints and are correlated on demand with local 439 // services via correlateEndpoints(). 440 func (s *ServiceCache) MergeExternalServiceDelete(service *service.ClusterService) { 441 scopedLog := log.WithFields(logrus.Fields{logfields.ServiceName: service.String()}) 442 id := ServiceID{Name: service.Name, Namespace: service.Namespace} 443 444 // Ignore updates of own cluster 445 if service.Cluster == option.Config.ClusterName { 446 scopedLog.Debug("Not merging external service. Own cluster") 447 return 448 } 449 450 s.mutex.Lock() 451 defer s.mutex.Unlock() 452 453 externalEndpoints, ok := s.externalEndpoints[id] 454 if ok { 455 scopedLog.Debug("Deleting external endpoints") 456 457 delete(externalEndpoints.endpoints, service.Cluster) 458 459 svc, ok := s.services[id] 460 461 endpoints, serviceReady := s.correlateEndpoints(id) 462 463 // Only send event notification if service is shared. External 464 // endpoints are still tracked but correlation will not happen 465 // until the service is marked as shared. 466 if ok && svc.Shared { 467 event := ServiceEvent{ 468 Action: UpdateService, 469 ID: id, 470 Service: svc, 471 Endpoints: endpoints, 472 } 473 474 if !serviceReady { 475 event.Action = DeleteService 476 } 477 478 s.Events <- event 479 } 480 } else { 481 scopedLog.Debug("Received delete event for non-existing endpoints") 482 } 483 } 484 485 // DebugStatus implements debug.StatusObject to provide debug status collection 486 // ability 487 func (s *ServiceCache) DebugStatus() string { 488 s.mutex.RLock() 489 str := spew.Sdump(s) 490 s.mutex.RUnlock() 491 return str 492 }