github.phpd.cn/cilium/cilium@v1.6.12/pkg/identity/cache/allocator.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 cache
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"path"
    21  
    22  	"github.com/cilium/cilium/pkg/allocator"
    23  	"github.com/cilium/cilium/pkg/identity"
    24  	"github.com/cilium/cilium/pkg/idpool"
    25  	clientset "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned"
    26  	"github.com/cilium/cilium/pkg/k8s/identitybackend"
    27  	"github.com/cilium/cilium/pkg/kvstore"
    28  	kvstoreallocator "github.com/cilium/cilium/pkg/kvstore/allocator"
    29  	"github.com/cilium/cilium/pkg/labels"
    30  	"github.com/cilium/cilium/pkg/lock"
    31  	"github.com/cilium/cilium/pkg/logging/logfields"
    32  	"github.com/cilium/cilium/pkg/metrics"
    33  	"github.com/cilium/cilium/pkg/option"
    34  
    35  	"github.com/sirupsen/logrus"
    36  	"k8s.io/client-go/tools/cache"
    37  )
    38  
    39  // GlobalIdentity is the structure used to store an identity
    40  type GlobalIdentity struct {
    41  	labels.LabelArray
    42  }
    43  
    44  // GetKey encodes an Identity as string
    45  func (gi GlobalIdentity) GetKey() (str string) {
    46  	for _, l := range gi.LabelArray {
    47  		str += l.FormatForKVStore()
    48  	}
    49  	return
    50  }
    51  
    52  // GetAsMap encodes a GlobalIdentity a map of keys to values. The keys will
    53  // include a source delimted by a ':'. This output is pareable by PutKeyFromMap.
    54  func (gi GlobalIdentity) GetAsMap() map[string]string {
    55  	return gi.StringMap()
    56  }
    57  
    58  // PutKey decodes an Identity from its string representation
    59  func (gi GlobalIdentity) PutKey(v string) allocator.AllocatorKey {
    60  	return GlobalIdentity{labels.NewLabelArrayFromSortedList(v)}
    61  }
    62  
    63  // PutKeyFromMap decodes an Identity from a map of key to value. Output
    64  // from GetAsMap can be parsed.
    65  // Note: NewLabelArrayFromMap will parse the ':' separated label source from
    66  // the keys because the source parameter is ""
    67  func (gi GlobalIdentity) PutKeyFromMap(v map[string]string) allocator.AllocatorKey {
    68  	return GlobalIdentity{labels.Map2Labels(v, "").LabelArray()}
    69  }
    70  
    71  var (
    72  	// IdentityAllocator is an allocator for security identities from the
    73  	// kvstore.
    74  	IdentityAllocator *allocator.Allocator
    75  
    76  	// GlobalIdentityAllocatorInitialized is closed whenever the global identity
    77  	// allocator is initialized.
    78  	GlobalIdentityAllocatorInitialized = make(chan struct{})
    79  
    80  	// localIdentityAllocatorInitialized is closed whenever the local identity
    81  	// allocator is initialized.
    82  	localIdentityAllocatorInitialized = make(chan struct{})
    83  
    84  	localIdentities *localIdentityCache
    85  
    86  	// IdentitiesPath is the path to where identities are stored in the key-value
    87  	// store.
    88  	IdentitiesPath = path.Join(kvstore.BaseKeyPrefix, "state", "identities", "v1")
    89  
    90  	// setupMutex synchronizes InitIdentityAllocator() and Close()
    91  	setupMutex lock.Mutex
    92  
    93  	watcher identityWatcher
    94  
    95  	identityAllocatorOwner IdentityAllocatorOwner
    96  )
    97  
    98  // IdentityAllocatorOwner is the interface the owner of an identity allocator
    99  // must implement
   100  type IdentityAllocatorOwner interface {
   101  	// UpdateIdentities will be called when identities have changed
   102  	//
   103  	// The caller is responsible for making sure the same identity
   104  	// is not present in both 'added' and 'deleted', so that they
   105  	// can be processed in either order.
   106  	UpdateIdentities(added, deleted IdentityCache)
   107  
   108  	// GetSuffix must return the node specific suffix to use
   109  	GetNodeSuffix() string
   110  }
   111  
   112  // InitIdentityAllocator creates the the identity allocator. Only the first
   113  // invocation of this function will have an effect. The Caller must have
   114  // initialized well known identities before calling this (by calling
   115  // identity.InitWellKnownIdentities()).
   116  // client and identityStore are only used by the CRD identity allocator,
   117  // currently, and identityStore may be nil.
   118  // Returns a channel which is closed when initialization of the allocator is
   119  // completed.
   120  // TODO: identity backends are initialized directly in this function, pulling
   121  // in dependencies on kvstore and k8s. It would be better to decouple this,
   122  // since the backends are an interface.
   123  func InitIdentityAllocator(owner IdentityAllocatorOwner, client clientset.Interface, identityStore cache.Store) <-chan struct{} {
   124  	setupMutex.Lock()
   125  	defer setupMutex.Unlock()
   126  
   127  	if IdentityAllocator != nil {
   128  		log.Panic("InitIdentityAllocator() in succession without calling Close()")
   129  	}
   130  
   131  	log.Info("Initializing identity allocator")
   132  
   133  	// Local identity cache can be created synchronously since it doesn't
   134  	// rely upon any external resources (e.g., external kvstore).
   135  	events := make(allocator.AllocatorEventChan, 1024)
   136  	localIdentities = newLocalIdentityCache(1, 0xFFFFFF, events)
   137  	close(localIdentityAllocatorInitialized)
   138  
   139  	minID := idpool.ID(identity.MinimalAllocationIdentity)
   140  	maxID := idpool.ID(identity.MaximumAllocationIdentity)
   141  
   142  	// It is important to start listening for events before calling
   143  	// NewAllocator() as it will emit events while filling the
   144  	// initial cache
   145  	watcher.watch(owner, events)
   146  
   147  	identityAllocatorOwner = owner
   148  
   149  	// Asynchronously set up the global identity allocator since it connects
   150  	// to the kvstore.
   151  	go func(owner IdentityAllocatorOwner, evs allocator.AllocatorEventChan, minID, maxID idpool.ID) {
   152  		setupMutex.Lock()
   153  		defer setupMutex.Unlock()
   154  
   155  		var (
   156  			backend allocator.Backend
   157  			err     error
   158  		)
   159  
   160  		switch option.Config.IdentityAllocationMode {
   161  		case option.IdentityAllocationModeKVstore:
   162  			log.Debug("Identity allocation backed by KVStore")
   163  			backend, err = kvstoreallocator.NewKVStoreBackend(IdentitiesPath, owner.GetNodeSuffix(), GlobalIdentity{}, kvstore.Client())
   164  			if err != nil {
   165  				log.WithError(err).Fatal("Unable to initialize kvstore backend for identity allocation")
   166  			}
   167  
   168  		case option.IdentityAllocationModeCRD:
   169  			log.Debug("Identity allocation backed by CRD")
   170  			backend, err = identitybackend.NewCRDBackend(identitybackend.CRDBackendConfiguration{
   171  				NodeName: owner.GetNodeSuffix(),
   172  				Store:    identityStore,
   173  				Client:   client,
   174  				KeyType:  GlobalIdentity{},
   175  			})
   176  			if err != nil {
   177  				log.WithError(err).Fatal("Unable to initialize Kubernetes CRD backend for identity allocation")
   178  			}
   179  
   180  		default:
   181  			log.Fatalf("Unsupported identity allocation mode %s", option.Config.IdentityAllocationMode)
   182  		}
   183  
   184  		a, err := allocator.NewAllocator(GlobalIdentity{}, backend,
   185  			allocator.WithMax(maxID), allocator.WithMin(minID),
   186  			allocator.WithEvents(events),
   187  			allocator.WithMasterKeyProtection(),
   188  			allocator.WithPrefixMask(idpool.ID(option.Config.ClusterID<<identity.ClusterIDShift)))
   189  		if err != nil {
   190  			log.WithError(err).Fatalf("Unable to initialize Identity Allocator with backend %s", option.Config.IdentityAllocationMode)
   191  		}
   192  
   193  		IdentityAllocator = a
   194  		close(GlobalIdentityAllocatorInitialized)
   195  	}(owner, events, minID, maxID)
   196  
   197  	return GlobalIdentityAllocatorInitialized
   198  }
   199  
   200  // Close closes the identity allocator and allows to call
   201  // InitIdentityAllocator() again
   202  func Close() {
   203  	setupMutex.Lock()
   204  	defer setupMutex.Unlock()
   205  
   206  	select {
   207  	case <-GlobalIdentityAllocatorInitialized:
   208  		// This means the channel was closed and therefore the IdentityAllocator == nil will never be true
   209  	default:
   210  		if IdentityAllocator == nil {
   211  			log.Panic("Close() called without calling InitIdentityAllocator() first")
   212  		}
   213  	}
   214  
   215  	select {
   216  	case <-localIdentityAllocatorInitialized:
   217  		// This means the channel was closed and therefore the IdentityAllocator == nil will never be true
   218  	default:
   219  		if IdentityAllocator == nil {
   220  			log.Panic("Close() called without calling InitIdentityAllocator() first")
   221  		}
   222  	}
   223  
   224  	IdentityAllocator.Delete()
   225  	watcher.stop()
   226  	IdentityAllocator = nil
   227  	GlobalIdentityAllocatorInitialized = make(chan struct{})
   228  	localIdentityAllocatorInitialized = make(chan struct{})
   229  	localIdentities = nil
   230  }
   231  
   232  // WaitForInitialGlobalIdentities waits for the initial set of global security
   233  // identities to have been received and populated into the allocator cache.
   234  func WaitForInitialGlobalIdentities(ctx context.Context) error {
   235  	select {
   236  	case <-GlobalIdentityAllocatorInitialized:
   237  	case <-ctx.Done():
   238  		return fmt.Errorf("initial global identity sync was cancelled: %s", ctx.Err())
   239  	}
   240  
   241  	return IdentityAllocator.WaitForInitialSync(ctx)
   242  }
   243  
   244  // IdentityAllocationIsLocal returns true if a call to AllocateIdentity with
   245  // the given labels would not require accessing the KV store to allocate the
   246  // identity.
   247  // Currently, this function returns true only if the labels are those of a
   248  // reserved identity, i.e. if the slice contains a single reserved
   249  // "reserved:*" label.
   250  func IdentityAllocationIsLocal(lbls labels.Labels) bool {
   251  	// If there is only one label with the "reserved" source and a well-known
   252  	// key, the well-known identity for it can be allocated locally.
   253  	return LookupReservedIdentityByLabels(lbls) != nil
   254  }
   255  
   256  // AllocateIdentity allocates an identity described by the specified labels. If
   257  // an identity for the specified set of labels already exist, the identity is
   258  // re-used and reference counting is performed, otherwise a new identity is
   259  // allocated via the kvstore.
   260  func AllocateIdentity(ctx context.Context, owner IdentityAllocatorOwner, lbls labels.Labels) (id *identity.Identity, allocated bool, err error) {
   261  	// Notify the owner of the newly added identities so that the
   262  	// cached identities can be updated ASAP, rather than just
   263  	// relying on the kv-store update events.
   264  	defer func() {
   265  		if err == nil && allocated {
   266  			metrics.IdentityCount.Inc()
   267  			if owner != nil {
   268  				added := IdentityCache{
   269  					id.ID: id.LabelArray,
   270  				}
   271  				owner.UpdateIdentities(added, nil)
   272  			}
   273  		}
   274  	}()
   275  	if option.Config.Debug {
   276  		log.WithFields(logrus.Fields{
   277  			logfields.IdentityLabels: lbls.String(),
   278  		}).Debug("Resolving identity")
   279  	}
   280  
   281  	// If there is only one label with the "reserved" source and a well-known
   282  	// key, use the well-known identity for that key.
   283  	if reservedIdentity := LookupReservedIdentityByLabels(lbls); reservedIdentity != nil {
   284  		if option.Config.Debug {
   285  			log.WithFields(logrus.Fields{
   286  				logfields.Identity:       reservedIdentity.ID,
   287  				logfields.IdentityLabels: lbls.String(),
   288  				"isNew":                  false,
   289  			}).Debug("Resolved reserved identity")
   290  		}
   291  		return reservedIdentity, false, nil
   292  	}
   293  
   294  	if !identity.RequiresGlobalIdentity(lbls) {
   295  		<-localIdentityAllocatorInitialized
   296  		return localIdentities.lookupOrCreate(lbls)
   297  	}
   298  
   299  	// This will block until the kvstore can be accessed and all identities
   300  	// were successfully synced
   301  	err = WaitForInitialGlobalIdentities(ctx)
   302  	if err != nil {
   303  		return nil, false, err
   304  	}
   305  
   306  	if IdentityAllocator == nil {
   307  		return nil, false, fmt.Errorf("allocator not initialized")
   308  	}
   309  
   310  	idp, isNew, err := IdentityAllocator.Allocate(ctx, GlobalIdentity{lbls.LabelArray()})
   311  	if err != nil {
   312  		return nil, false, err
   313  	}
   314  
   315  	if option.Config.Debug {
   316  		log.WithFields(logrus.Fields{
   317  			logfields.Identity:       idp,
   318  			logfields.IdentityLabels: lbls.String(),
   319  			"isNew":                  isNew,
   320  		}).Debug("Resolved identity")
   321  	}
   322  
   323  	return identity.NewIdentity(identity.NumericIdentity(idp), lbls), isNew, nil
   324  }
   325  
   326  // Release is the reverse operation of AllocateIdentity() and releases the
   327  // identity again. This function may result in kvstore operations.
   328  // After the last user has released the ID, the returned lastUse value is true.
   329  func Release(ctx context.Context, owner IdentityAllocatorOwner, id *identity.Identity) (released bool, err error) {
   330  	defer func() {
   331  		if released {
   332  			metrics.IdentityCount.Dec()
   333  		}
   334  	}()
   335  
   336  	// Ignore reserved identities.
   337  	if id.IsReserved() {
   338  		return false, nil
   339  	}
   340  
   341  	if !identity.RequiresGlobalIdentity(id.Labels) {
   342  		<-localIdentityAllocatorInitialized
   343  		lastUse := localIdentities.release(id)
   344  		// Notify release of locally managed identities on last use
   345  		if owner != nil && lastUse {
   346  			deleted := IdentityCache{
   347  				id.ID: id.LabelArray,
   348  			}
   349  			owner.UpdateIdentities(nil, deleted)
   350  		}
   351  		return lastUse, nil
   352  	}
   353  
   354  	// This will block until the kvstore can be accessed and all identities
   355  	// were successfully synced
   356  	err = WaitForInitialGlobalIdentities(ctx)
   357  	if err != nil {
   358  		return false, err
   359  	}
   360  
   361  	if IdentityAllocator == nil {
   362  		return false, fmt.Errorf("allocator not initialized")
   363  	}
   364  
   365  	// Rely on the eventual Kv-Store events for delete
   366  	// notifications of kv-store allocated identities. Even if an
   367  	// ID is no longer used locally, it may still be used by
   368  	// remote nodes, so we can't rely on the locally computed
   369  	// "lastUse".
   370  	return IdentityAllocator.Release(ctx, GlobalIdentity{id.LabelArray})
   371  }
   372  
   373  // ReleaseSlice attempts to release a set of identities. It is a helper
   374  // function that may be useful for cleaning up multiple identities in paths
   375  // where several identities may be allocated and another error means that they
   376  // should all be released.
   377  func ReleaseSlice(ctx context.Context, owner IdentityAllocatorOwner, identities []*identity.Identity) error {
   378  	var err error
   379  	for _, id := range identities {
   380  		if id == nil {
   381  			continue
   382  		}
   383  		_, err2 := Release(ctx, owner, id)
   384  		if err2 != nil {
   385  			log.WithError(err2).WithFields(logrus.Fields{
   386  				logfields.Identity: id,
   387  			}).Error("Failed to release identity")
   388  			err = err2
   389  		}
   390  	}
   391  	return err
   392  }
   393  
   394  // WatchRemoteIdentities starts watching for identities in another kvstore and
   395  // syncs all identities to the local identity cache.
   396  func WatchRemoteIdentities(backend kvstore.BackendOperations) (*allocator.RemoteCache, error) {
   397  	<-GlobalIdentityAllocatorInitialized
   398  
   399  	remoteAllocatorBackend, err := kvstoreallocator.NewKVStoreBackend(IdentitiesPath, identityAllocatorOwner.GetNodeSuffix(), GlobalIdentity{}, backend)
   400  	if err != nil {
   401  		return nil, fmt.Errorf("Error setting up remote allocator backend: %s", err)
   402  	}
   403  
   404  	remoteAlloc, err := allocator.NewAllocator(GlobalIdentity{}, remoteAllocatorBackend, allocator.WithEvents(IdentityAllocator.GetEvents()))
   405  	if err != nil {
   406  		return nil, fmt.Errorf("Unable to initialize remote Identity Allocator: %s", err)
   407  	}
   408  	return IdentityAllocator.WatchRemoteKVStore(remoteAlloc), nil
   409  }