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 }