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  }