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) {}