github.com/cilium/cilium@v1.16.2/pkg/k8s/watchers/cilium_node.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package watchers 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "maps" 11 "sync" 12 "sync/atomic" 13 14 "github.com/cilium/hive/cell" 15 k8sErrors "k8s.io/apimachinery/pkg/api/errors" 16 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/runtime/schema" 18 19 agentK8s "github.com/cilium/cilium/daemon/k8s" 20 "github.com/cilium/cilium/pkg/k8s" 21 cilium_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 22 k8sClient "github.com/cilium/cilium/pkg/k8s/client" 23 "github.com/cilium/cilium/pkg/k8s/resource" 24 k8sSynced "github.com/cilium/cilium/pkg/k8s/synced" 25 "github.com/cilium/cilium/pkg/kvstore" 26 nm "github.com/cilium/cilium/pkg/node/manager" 27 "github.com/cilium/cilium/pkg/node/types" 28 ) 29 30 type k8sCiliumNodeWatcherParams struct { 31 cell.In 32 33 Clientset k8sClient.Clientset 34 Resources agentK8s.Resources 35 K8sResourceSynced *k8sSynced.Resources 36 K8sAPIGroups *k8sSynced.APIGroups 37 38 NodeManager nm.NodeManager 39 } 40 41 func newK8sCiliumNodeWatcher(params k8sCiliumNodeWatcherParams) *K8sCiliumNodeWatcher { 42 return &K8sCiliumNodeWatcher{ 43 clientset: params.Clientset, 44 k8sResourceSynced: params.K8sResourceSynced, 45 k8sAPIGroups: params.K8sAPIGroups, 46 resources: params.Resources, 47 nodeManager: params.NodeManager, 48 } 49 } 50 51 type K8sCiliumNodeWatcher struct { 52 clientset k8sClient.Clientset 53 54 // k8sResourceSynced maps a resource name to a channel. Once the given 55 // resource name is synchronized with k8s, the channel for which that 56 // resource name maps to is closed. 57 k8sResourceSynced *k8sSynced.Resources 58 // k8sAPIGroups is a set of k8s API in use. They are setup in watchers, 59 // and may be disabled while the agent runs. 60 k8sAPIGroups *k8sSynced.APIGroups 61 resources agentK8s.Resources 62 63 nodeManager nodeManager 64 65 ciliumNodeStore atomic.Pointer[resource.Store[*cilium_v2.CiliumNode]] 66 } 67 68 func (k *K8sCiliumNodeWatcher) ciliumNodeInit(ctx context.Context, asyncControllers *sync.WaitGroup) { 69 // CiliumNode objects are used for node discovery until the key-value 70 // store is connected 71 var once sync.Once 72 apiGroup := k8sAPIGroupCiliumNodeV2 73 74 for { 75 var synced atomic.Bool 76 stop := make(chan struct{}) 77 78 k.k8sResourceSynced.BlockWaitGroupToSyncResources( 79 stop, 80 nil, 81 func() bool { return synced.Load() }, 82 apiGroup, 83 ) 84 k.k8sAPIGroups.AddAPI(apiGroup) 85 86 // Signalize that we have put node controller in the wait group to sync resources. 87 once.Do(asyncControllers.Done) 88 89 // derive another context to signal Events() and Store() in case of kvstore connection 90 subCtx, cancel := context.WithCancel(ctx) 91 92 var wg sync.WaitGroup 93 94 wg.Add(1) 95 go func() { 96 defer wg.Done() 97 defer close(stop) 98 99 events := k.resources.CiliumNode.Events(subCtx) 100 cache := make(map[resource.Key]*cilium_v2.CiliumNode) 101 for event := range events { 102 var err error 103 switch event.Kind { 104 case resource.Sync: 105 synced.Store(true) 106 k.nodeManager.NodeSync() 107 case resource.Upsert: 108 var needUpdate bool 109 oldObj, ok := cache[event.Key] 110 if !ok { 111 needUpdate = k.onCiliumNodeInsert(event.Object) 112 } else { 113 needUpdate = k.onCiliumNodeUpdate(oldObj, event.Object) 114 } 115 if needUpdate { 116 cache[event.Key] = event.Object.DeepCopy() 117 } 118 case resource.Delete: 119 k.onCiliumNodeDelete(event.Object) 120 delete(cache, event.Key) 121 } 122 event.Done(err) 123 } 124 }() 125 126 wg.Add(1) 127 go func() { 128 defer wg.Done() 129 130 store, err := k.resources.CiliumNode.Store(subCtx) 131 if err != nil { 132 if !errors.Is(err, context.Canceled) { 133 log.WithError(err).Warning("unable to retrieve CiliumNode local store, going to query kube-apiserver directly") 134 } 135 return 136 } 137 138 k.ciliumNodeStore.Store(&store) 139 140 <-subCtx.Done() 141 142 store.Release() 143 k.ciliumNodeStore.Store(nil) 144 }() 145 146 select { 147 case <-kvstore.Connected(): 148 log.Info("Connected to key-value store, stopping CiliumNode watcher") 149 cancel() 150 k.k8sResourceSynced.CancelWaitGroupToSyncResources(apiGroup) 151 k.k8sAPIGroups.RemoveAPI(apiGroup) 152 wg.Wait() 153 case <-ctx.Done(): 154 cancel() 155 wg.Wait() 156 return 157 } 158 159 select { 160 case <-ctx.Done(): 161 return 162 case <-kvstore.Client().Disconnected(): 163 log.Info("Disconnected from key-value store, restarting CiliumNode watcher") 164 } 165 } 166 } 167 168 func (k *K8sCiliumNodeWatcher) onCiliumNodeInsert(ciliumNode *cilium_v2.CiliumNode) bool { 169 if k8s.IsLocalCiliumNode(ciliumNode) { 170 return false 171 } 172 n := types.ParseCiliumNode(ciliumNode) 173 k.nodeManager.NodeUpdated(n) 174 return true 175 } 176 177 func (k *K8sCiliumNodeWatcher) onCiliumNodeUpdate(oldNode, newNode *cilium_v2.CiliumNode) bool { 178 // Comparing Annotations here since wg-pub-key annotation is used to exchange rotated WireGuard keys. 179 if oldNode.DeepEqual(newNode) && 180 maps.Equal(oldNode.ObjectMeta.Labels, newNode.ObjectMeta.Labels) && 181 maps.Equal(oldNode.ObjectMeta.Annotations, newNode.ObjectMeta.Annotations) { 182 return false 183 } 184 return k.onCiliumNodeInsert(newNode) 185 } 186 187 func (k *K8sCiliumNodeWatcher) onCiliumNodeDelete(ciliumNode *cilium_v2.CiliumNode) { 188 if k8s.IsLocalCiliumNode(ciliumNode) { 189 return 190 } 191 n := types.ParseCiliumNode(ciliumNode) 192 k.nodeManager.NodeDeleted(n) 193 } 194 195 // GetCiliumNode returns the CiliumNode "nodeName" from the local Resource[T] store. If the 196 // local Resource[T] store is not initialized or the key value store is connected, then it will 197 // retrieve the node from kube-apiserver. 198 // Note that it may be possible (although rare) that the requested nodeName is not yet in the 199 // store if the local cache is falling behind due to the high amount of CiliumNode events 200 // received from the k8s API server. To mitigate this, the caller should retry GetCiliumNode 201 // for a given interval to be sure that a CiliumNode with that name has not actually been created. 202 func (k *K8sCiliumNodeWatcher) GetCiliumNode(ctx context.Context, nodeName string) (*cilium_v2.CiliumNode, error) { 203 store := k.ciliumNodeStore.Load() 204 if store == nil { 205 return k.clientset.CiliumV2().CiliumNodes().Get(ctx, nodeName, v1.GetOptions{}) 206 } 207 208 ciliumNode, exists, err := (*store).GetByKey(resource.Key{Name: nodeName}) 209 if err != nil { 210 return nil, fmt.Errorf("unable to get CiliumNode %s from local store: %w", nodeName, err) 211 } 212 if !exists { 213 return nil, k8sErrors.NewNotFound(schema.GroupResource{ 214 Group: "cilium", 215 Resource: "CiliumNode", 216 }, nodeName) 217 } 218 return ciliumNode.DeepCopy(), nil 219 }