github.com/fafucoder/cilium@v1.6.11/cilium/cmd/preflight_identity_crd_migrate.go (about)

     1  // Copyright 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 cmd
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"path"
    21  	"time"
    22  
    23  	"github.com/cilium/cilium/pkg/allocator"
    24  	"github.com/cilium/cilium/pkg/identity"
    25  	"github.com/cilium/cilium/pkg/identity/cache"
    26  	"github.com/cilium/cilium/pkg/idpool"
    27  	"github.com/cilium/cilium/pkg/k8s"
    28  	"github.com/cilium/cilium/pkg/k8s/identitybackend"
    29  	"github.com/cilium/cilium/pkg/kvstore"
    30  	kvstoreallocator "github.com/cilium/cilium/pkg/kvstore/allocator"
    31  	"github.com/cilium/cilium/pkg/logging"
    32  	"github.com/cilium/cilium/pkg/logging/logfields"
    33  	"github.com/cilium/cilium/pkg/option"
    34  
    35  	"github.com/sirupsen/logrus"
    36  	"github.com/spf13/cobra"
    37  	"github.com/spf13/viper"
    38  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    39  )
    40  
    41  // opTimeout is the time allowed for each operation to complete. This includes
    42  // listing, allocating and getting identities.
    43  const opTimeout = 30 * time.Second
    44  
    45  var migrateIdentityCmd = &cobra.Command{
    46  	Use:   "migrate-identity",
    47  	Short: "Migrate KVStore-backed identities to kubernetes CRD-backed identities",
    48  	Long: `migrate-identity allows migrating to CRD-backed identities while
    49  	minimizing connection interruptions. It will allocate a CRD-backed identity,
    50  	with the same numeric security identity, for each cilium security identity
    51  	defined in the kvstore. When cilium-agents are restarted with
    52  	identity-allocation-mode set to CRD the numeric identities will then be
    53  	equivalent between new instances and not-upgraded ones. In cases where the
    54  	numeric identity is already in-use by a different set of labels, a new
    55  	numeric identity is created.`,
    56  	Run: func(cmd *cobra.Command, args []string) {
    57  		migrateIdentities()
    58  	},
    59  }
    60  
    61  // migrateIdentities attempts to mirror the security identities in the kvstore
    62  // into k8s CRD-backed identities. The identities are snapshotted on startup
    63  // and new identities created during migrations will not be seen.
    64  // It is a little odd because it violates the cilium-agent assumption that only
    65  // 1 Backend is active at a time.
    66  // The steps are:
    67  // 1- Connect to the kvstore via a pkg/allocatore.Backend
    68  // 2- Connect to k8s
    69  //   a- Create the ciliumidentity CRD if it is missing.
    70  // 3- Iterate over each identity in the kvstore
    71  //   a- Attempt to allocate the same numeric ID to this key
    72  //   b- Already allocated identies that match ID->key are skipped
    73  //   c- kvstore IDs with conflicting CRDs are allocated with a different ID
    74  //
    75  // NOTE: It is assumed that the migration is from k8s to k8s installations. The
    76  // key labels different when running in non-k8s mode.
    77  func migrateIdentities() {
    78  	// The internal packages log things. Make sure they follow the setup of of
    79  	// the CLI tool.
    80  	logging.DefaultLogger.SetFormatter(log.Formatter)
    81  
    82  	// Setup global configuration
    83  	// These are defined in cilium/cmd/kvstore.go
    84  	option.Config.KVStore = kvStore
    85  	option.Config.KVStoreOpt = kvStoreOpts
    86  
    87  	// This allows us to initialize a CRD allocator
    88  	option.Config.IdentityAllocationMode = option.IdentityAllocationModeCRD // force CRD mode to make ciliumid
    89  
    90  	// Init Identity backends
    91  	initCtx, initCancel := context.WithTimeout(context.Background(), opTimeout)
    92  	kvstoreBackend := initKVStore()
    93  
    94  	crdBackend, crdAllocator := initK8s(initCtx)
    95  	initCancel()
    96  
    97  	log.Info("Listing identities in kvstore")
    98  	listCtx, listCancel := context.WithTimeout(context.Background(), opTimeout)
    99  	kvstoreIDs, err := getKVStoreIdentities(listCtx, kvstoreBackend)
   100  	if err != nil {
   101  		log.WithError(err).Fatal("Unable to initialize Identity Allocator with CRD backend to allocate identities with already allocated IDs")
   102  	}
   103  	listCancel()
   104  
   105  	log.Info("Migrating identities to CRD")
   106  	badKeys := make([]allocator.AllocatorKey, 0)                       // keys that have real errors
   107  	alreadyAllocatedKeys := make(map[idpool.ID]allocator.AllocatorKey) // IDs that are already allocated, maybe with different labels
   108  
   109  	for id, key := range kvstoreIDs {
   110  		scopedLog := log.WithFields(logrus.Fields{
   111  			logfields.Identity:       id,
   112  			logfields.IdentityLabels: key.GetKey(),
   113  		})
   114  
   115  		ctx, cancel := context.WithTimeout(context.Background(), opTimeout)
   116  		err := crdBackend.AllocateID(ctx, id, key)
   117  		switch {
   118  		case err != nil && k8serrors.IsAlreadyExists(err):
   119  			alreadyAllocatedKeys[id] = key
   120  
   121  		case err != nil:
   122  			scopedLog.WithError(err).Error("Cannot allocate CRD ID. This key will be allocated with a new numeric identity")
   123  			badKeys = append(badKeys, key)
   124  
   125  		default:
   126  			scopedLog.Info("Migrated identity")
   127  		}
   128  		cancel()
   129  	}
   130  
   131  	// Handle IDs that have conflicts. These can be:
   132  	// 1- The same ID -> key (from a previous run). This is a no-op
   133  	// 2- The same ID but with different labels. This is not ideal. A new ID is
   134  	// allocated as a fallback.
   135  	for id, key := range alreadyAllocatedKeys {
   136  		scopedLog := log.WithFields(logrus.Fields{
   137  			logfields.Identity:       id,
   138  			logfields.IdentityLabels: key.GetKey(),
   139  		})
   140  
   141  		upstreamKey, err := crdBackend.GetByID(id)
   142  		scopedLog.Debugf("Looking at upstream key with this ID: %+v", upstreamKey)
   143  		switch {
   144  		case err != nil:
   145  			log.WithError(err).Error("ID already allocated but we cannot verify whether it is the same key. It may not be migrated")
   146  			continue
   147  
   148  		// nil returns mean the key doesn't exist. This shouldn't happen, but treat
   149  		// it like a mismatch and allocate it. The allocator will find it if it has
   150  		// been re-allocated via master key protection.
   151  		case upstreamKey == nil && err == nil:
   152  			// fallthrough
   153  
   154  		case key.GetKey() == upstreamKey.GetKey():
   155  			scopedLog.Info("ID was already allocated to this key. It is already migrated")
   156  			continue
   157  		}
   158  
   159  		scopedLog = log.WithFields(logrus.Fields{
   160  			logfields.OldIdentity:    id,
   161  			logfields.IdentityLabels: key.GetKey(),
   162  		})
   163  		scopedLog.Warn("ID is allocated to a different key in CRD. A new ID will be allocated for the this key")
   164  
   165  		ctx, cancel := context.WithTimeout(context.Background(), opTimeout)
   166  		defer cancel()
   167  		newID, actuallyAllocated, err := crdAllocator.Allocate(ctx, key)
   168  		switch {
   169  		case err != nil:
   170  			log.WithError(err).Errorf("Cannot allocate new CRD ID for %v", key)
   171  			continue
   172  
   173  		case !actuallyAllocated:
   174  			scopedLog.Debug("Expected to allocate ID but this ID->key mapping re-existed")
   175  		}
   176  
   177  		log.WithFields(logrus.Fields{
   178  			logfields.OldIdentity:    id,
   179  			logfields.Identity:       newID,
   180  			logfields.IdentityLabels: key.GetKey(),
   181  		}).Info("New ID allocated for key in CRD")
   182  	}
   183  }
   184  
   185  // initK8s connects to k8s with a allocator.Backend and an initialized
   186  // allocator.Allocator, using the k8s config passed into the command.
   187  func initK8s(ctx context.Context) (crdBackend allocator.Backend, crdAllocator *allocator.Allocator) {
   188  	log.Info("Setting up kubernetes client")
   189  
   190  	k8sClientQPSLimit := viper.GetFloat64(option.K8sClientQPSLimit)
   191  	k8sClientBurst := viper.GetInt(option.K8sClientBurst)
   192  
   193  	k8s.Configure(k8sAPIServer, k8sKubeConfigPath, float32(k8sClientQPSLimit), k8sClientBurst)
   194  
   195  	if err := k8s.Init(); err != nil {
   196  		log.WithError(err).Fatal("Unable to connect to Kubernetes apiserver")
   197  	}
   198  
   199  	// Update CRDs to ensure ciliumIdentity is present
   200  	k8s.RegisterCRDs()
   201  
   202  	// Create a CRD Backend
   203  	crdBackend, err := identitybackend.NewCRDBackend(identitybackend.CRDBackendConfiguration{
   204  		NodeName: "cilium-preflight",
   205  		Store:    nil,
   206  		Client:   k8s.CiliumClient(),
   207  		KeyType:  cache.GlobalIdentity{},
   208  	})
   209  	if err != nil {
   210  		log.WithError(err).Fatal("Cannot create CRD identity backend")
   211  	}
   212  
   213  	// Create a real allocator with CRD as the backend. This mimics the setup in
   214  	// pkg/allocator/cache
   215  	//
   216  	// FIXME: add options to handle clustermesh with this constructor parameter:
   217  	//    allocator.WithPrefixMask(idpool.ID(option.Config.ClusterID<<identity.ClusterIDShift)))
   218  	minID := idpool.ID(identity.MinimalAllocationIdentity)
   219  	maxID := idpool.ID(identity.MaximumAllocationIdentity)
   220  	crdAllocator, err = allocator.NewAllocator(cache.GlobalIdentity{}, crdBackend,
   221  		allocator.WithMax(maxID), allocator.WithMin(minID))
   222  	if err != nil {
   223  		log.WithError(err).Fatal("Unable to initialize Identity Allocator with CRD backend to allocate identities with already allocated IDs")
   224  	}
   225  
   226  	// Wait for the initial sync to complete
   227  	if err := crdAllocator.WaitForInitialSync(ctx); err != nil {
   228  		log.WithError(err).Fatal("Error waiting for k8s identity allocator to sync. No identities have been migrated.")
   229  	}
   230  
   231  	return crdBackend, crdAllocator
   232  }
   233  
   234  // initKVStore connects to the kvstore with a allocator.Backend, initialised to
   235  // find identities at the default cilium paths.
   236  func initKVStore() (kvstoreBackend allocator.Backend) {
   237  	log.Info("Setting up kvstore client")
   238  	setupKvstore()
   239  
   240  	idPath := path.Join(cache.IdentitiesPath, "id")
   241  	kvstoreBackend, err := kvstoreallocator.NewKVStoreBackend(cache.IdentitiesPath, idPath, cache.GlobalIdentity{}, kvstore.Client())
   242  	if err != nil {
   243  		log.WithError(err).Fatal("Cannot create kvstore identity backend")
   244  	}
   245  
   246  	return kvstoreBackend
   247  }
   248  
   249  // getKVStoreIdentities lists all identities in the kvstore. It will wait for
   250  // the listing to complete.
   251  func getKVStoreIdentities(ctx context.Context, kvstoreBackend allocator.Backend) (identities map[idpool.ID]allocator.AllocatorKey, err error) {
   252  	identities = make(map[idpool.ID]allocator.AllocatorKey)
   253  	stopChan := make(chan struct{})
   254  
   255  	go kvstoreBackend.ListAndWatch(kvstoreListHandler{
   256  		onAdd: func(id idpool.ID, key allocator.AllocatorKey) {
   257  			log.Debugf("kvstore listed ID: %+v -> %+v", id, key)
   258  			identities[id] = key
   259  		},
   260  		onListDone: func() {
   261  			close(stopChan)
   262  		},
   263  	}, stopChan)
   264  	// This makes the ListAndWatch exit after the initial listing or on a timeout
   265  	// that exits this function
   266  
   267  	// Wait for the listing to complete
   268  	select {
   269  	case <-stopChan:
   270  		log.Debug("kvstore ID list complete")
   271  
   272  	case <-ctx.Done():
   273  		return nil, errors.New("Timeout while listing identities")
   274  	}
   275  
   276  	return identities, nil
   277  }
   278  
   279  // kvstoreListHandler is a dummy type to receive callbacks from the kvstore subsystem
   280  type kvstoreListHandler struct {
   281  	onAdd      func(id idpool.ID, key allocator.AllocatorKey)
   282  	onListDone func()
   283  }
   284  
   285  func (h kvstoreListHandler) OnListDone()                                       { h.onListDone() }
   286  func (h kvstoreListHandler) OnAdd(id idpool.ID, key allocator.AllocatorKey)    { h.onAdd(id, key) }
   287  func (h kvstoreListHandler) OnModify(id idpool.ID, key allocator.AllocatorKey) {}
   288  func (h kvstoreListHandler) OnDelete(id idpool.ID, key allocator.AllocatorKey) {}