github.com/cilium/cilium@v1.16.2/pkg/k8s/watchers/cilium_endpoint.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package watchers 5 6 import ( 7 "context" 8 "net" 9 "sync" 10 "sync/atomic" 11 12 "github.com/cilium/hive/cell" 13 "github.com/sirupsen/logrus" 14 15 agentK8s "github.com/cilium/cilium/daemon/k8s" 16 "github.com/cilium/cilium/pkg/endpointmanager" 17 hubblemetrics "github.com/cilium/cilium/pkg/hubble/metrics" 18 "github.com/cilium/cilium/pkg/identity" 19 "github.com/cilium/cilium/pkg/ipcache" 20 "github.com/cilium/cilium/pkg/k8s/resource" 21 k8sSynced "github.com/cilium/cilium/pkg/k8s/synced" 22 "github.com/cilium/cilium/pkg/k8s/types" 23 "github.com/cilium/cilium/pkg/kvstore" 24 "github.com/cilium/cilium/pkg/logging/logfields" 25 "github.com/cilium/cilium/pkg/node" 26 "github.com/cilium/cilium/pkg/option" 27 "github.com/cilium/cilium/pkg/policy" 28 "github.com/cilium/cilium/pkg/source" 29 ciliumTypes "github.com/cilium/cilium/pkg/types" 30 "github.com/cilium/cilium/pkg/u8proto" 31 ) 32 33 type k8sCiliumEndpointsWatcherParams struct { 34 cell.In 35 36 Resources agentK8s.Resources 37 K8sResourceSynced *k8sSynced.Resources 38 K8sAPIGroups *k8sSynced.APIGroups 39 40 EndpointManager endpointmanager.EndpointManager 41 PolicyUpdater *policy.Updater 42 IPCache *ipcache.IPCache 43 } 44 45 func newK8sCiliumEndpointsWatcher(params k8sCiliumEndpointsWatcherParams) *K8sCiliumEndpointsWatcher { 46 return &K8sCiliumEndpointsWatcher{ 47 k8sResourceSynced: params.K8sResourceSynced, 48 k8sAPIGroups: params.K8sAPIGroups, 49 resources: params.Resources, 50 endpointManager: params.EndpointManager, 51 policyManager: params.PolicyUpdater, 52 ipcache: params.IPCache, 53 } 54 } 55 56 type K8sCiliumEndpointsWatcher struct { 57 // k8sResourceSynced maps a resource name to a channel. Once the given 58 // resource name is synchronized with k8s, the channel for which that 59 // resource name maps to is closed. 60 k8sResourceSynced *k8sSynced.Resources 61 62 // k8sAPIGroups is a set of k8s API in use. They are setup in watchers, 63 // and may be disabled while the agent runs. 64 k8sAPIGroups *k8sSynced.APIGroups 65 66 endpointManager endpointManager 67 policyManager policyManager 68 ipcache ipcacheManager 69 70 resources agentK8s.Resources 71 } 72 73 // initCiliumEndpointOrSlices initializes the ciliumEndpoints or ciliumEndpointSlice 74 func (k *K8sCiliumEndpointsWatcher) initCiliumEndpointOrSlices(ctx context.Context, asyncControllers *sync.WaitGroup) { 75 // If CiliumEndpointSlice feature is enabled, Cilium-agent watches CiliumEndpointSlice 76 // objects instead of CiliumEndpoints. Hence, skip watching CiliumEndpoints if CiliumEndpointSlice 77 // feature is enabled. 78 asyncControllers.Add(1) 79 if option.Config.EnableCiliumEndpointSlice { 80 go k.ciliumEndpointSliceInit(ctx, asyncControllers) 81 } else { 82 go k.ciliumEndpointsInit(ctx, asyncControllers) 83 } 84 } 85 86 func (k *K8sCiliumEndpointsWatcher) ciliumEndpointsInit(ctx context.Context, asyncControllers *sync.WaitGroup) { 87 // CiliumEndpoint objects are used for ipcache discovery until the 88 // key-value store is connected 89 var once sync.Once 90 apiGroup := k8sAPIGroupCiliumEndpointV2 91 92 for { 93 var synced atomic.Bool 94 stop := make(chan struct{}) 95 96 k.k8sResourceSynced.BlockWaitGroupToSyncResources( 97 stop, 98 nil, 99 func() bool { return synced.Load() }, 100 apiGroup, 101 ) 102 k.k8sAPIGroups.AddAPI(apiGroup) 103 104 // Signalize that we have put node controller in the wait group to sync resources. 105 once.Do(asyncControllers.Done) 106 107 // derive another context to signal Events() in case of kvstore connection 108 eventsCtx, cancel := context.WithCancel(ctx) 109 110 go func() { 111 defer close(stop) 112 113 events := k.resources.CiliumSlimEndpoint.Events(eventsCtx) 114 cache := make(map[resource.Key]*types.CiliumEndpoint) 115 for event := range events { 116 var err error 117 switch event.Kind { 118 case resource.Sync: 119 synced.Store(true) 120 case resource.Upsert: 121 oldObj, ok := cache[event.Key] 122 if !ok || !oldObj.DeepEqual(event.Object) { 123 k.endpointUpdated(oldObj, event.Object) 124 cache[event.Key] = event.Object 125 } 126 case resource.Delete: 127 k.endpointDeleted(event.Object) 128 delete(cache, event.Key) 129 } 130 event.Done(err) 131 } 132 }() 133 134 select { 135 case <-kvstore.Connected(): 136 log.Info("Connected to key-value store, stopping CiliumEndpoint watcher") 137 cancel() 138 k.k8sResourceSynced.CancelWaitGroupToSyncResources(apiGroup) 139 k.k8sAPIGroups.RemoveAPI(apiGroup) 140 <-stop 141 case <-ctx.Done(): 142 cancel() 143 <-stop 144 return 145 } 146 147 select { 148 case <-ctx.Done(): 149 return 150 case <-kvstore.Client().Disconnected(): 151 log.Info("Disconnected from key-value store, restarting CiliumEndpoint watcher") 152 } 153 } 154 } 155 156 func (k *K8sCiliumEndpointsWatcher) endpointUpdated(oldEndpoint, endpoint *types.CiliumEndpoint) { 157 var namedPortsChanged bool 158 defer func() { 159 if namedPortsChanged { 160 k.policyManager.TriggerPolicyUpdates(true, "Named ports added or updated") 161 } 162 }() 163 var ipsAdded []string 164 if oldEndpoint != nil && oldEndpoint.Networking != nil { 165 // Delete the old IP addresses from the IP cache 166 defer func() { 167 for _, oldPair := range oldEndpoint.Networking.Addressing { 168 v4Added, v6Added := false, false 169 for _, ipAdded := range ipsAdded { 170 if ipAdded == oldPair.IPV4 { 171 v4Added = true 172 } 173 if ipAdded == oldPair.IPV6 { 174 v6Added = true 175 } 176 } 177 if !v4Added { 178 portsChanged := k.ipcache.DeleteOnMetadataMatch(oldPair.IPV4, source.CustomResource, endpoint.Namespace, endpoint.Name) 179 if portsChanged { 180 namedPortsChanged = true 181 } 182 } 183 if !v6Added { 184 portsChanged := k.ipcache.DeleteOnMetadataMatch(oldPair.IPV6, source.CustomResource, endpoint.Namespace, endpoint.Name) 185 if portsChanged { 186 namedPortsChanged = true 187 } 188 } 189 } 190 }() 191 } 192 193 // default to the standard key 194 encryptionKey := node.GetEndpointEncryptKeyIndex() 195 196 id := identity.ReservedIdentityUnmanaged 197 if endpoint.Identity != nil { 198 id = identity.NumericIdentity(endpoint.Identity.ID) 199 } 200 201 if endpoint.Encryption != nil { 202 encryptionKey = uint8(endpoint.Encryption.Key) 203 } 204 205 if endpoint.Networking == nil || endpoint.Networking.NodeIP == "" { 206 // When upgrading from an older version, the nodeIP may 207 // not be available yet in the CiliumEndpoint and we 208 // have to wait for it to be propagated 209 return 210 } 211 212 nodeIP := net.ParseIP(endpoint.Networking.NodeIP) 213 if nodeIP == nil { 214 log.WithField("nodeIP", endpoint.Networking.NodeIP).Warning("Unable to parse node IP while processing CiliumEndpoint update") 215 return 216 } 217 218 if option.Config.EnableHighScaleIPcache && 219 !identity.IsWellKnownIdentity(id) { 220 // Well-known identities are kept in the high-scale ipcache because we 221 // need to be able to connect to the DNS pods to resolve FQDN policies. 222 scopedLog := log.WithFields(logrus.Fields{ 223 logfields.Identity: id, 224 }) 225 scopedLog.Debug("Endpoint is not well-known; skipping ipcache upsert") 226 return 227 } 228 229 k8sMeta := &ipcache.K8sMetadata{ 230 Namespace: endpoint.Namespace, 231 PodName: endpoint.Name, 232 NamedPorts: make(ciliumTypes.NamedPortMap, len(endpoint.NamedPorts)), 233 } 234 for _, port := range endpoint.NamedPorts { 235 p, err := u8proto.ParseProtocol(port.Protocol) 236 if err != nil { 237 continue 238 } 239 k8sMeta.NamedPorts[port.Name] = ciliumTypes.PortProto{ 240 Port: port.Port, 241 Proto: uint8(p), 242 } 243 } 244 245 for _, pair := range endpoint.Networking.Addressing { 246 if pair.IPV4 != "" { 247 ipsAdded = append(ipsAdded, pair.IPV4) 248 portsChanged, _ := k.ipcache.Upsert(pair.IPV4, nodeIP, encryptionKey, k8sMeta, 249 ipcache.Identity{ID: id, Source: source.CustomResource}) 250 if portsChanged { 251 namedPortsChanged = true 252 } 253 } 254 255 if pair.IPV6 != "" { 256 ipsAdded = append(ipsAdded, pair.IPV6) 257 portsChanged, _ := k.ipcache.Upsert(pair.IPV6, nodeIP, encryptionKey, k8sMeta, 258 ipcache.Identity{ID: id, Source: source.CustomResource}) 259 if portsChanged { 260 namedPortsChanged = true 261 } 262 } 263 } 264 } 265 266 func (k *K8sCiliumEndpointsWatcher) endpointDeleted(endpoint *types.CiliumEndpoint) { 267 if endpoint.Networking != nil { 268 namedPortsChanged := false 269 for _, pair := range endpoint.Networking.Addressing { 270 if pair.IPV4 != "" { 271 portsChanged := k.ipcache.DeleteOnMetadataMatch(pair.IPV4, source.CustomResource, endpoint.Namespace, endpoint.Name) 272 if portsChanged { 273 namedPortsChanged = true 274 } 275 } 276 277 if pair.IPV6 != "" { 278 portsChanged := k.ipcache.DeleteOnMetadataMatch(pair.IPV6, source.CustomResource, endpoint.Namespace, endpoint.Name) 279 if portsChanged { 280 namedPortsChanged = true 281 } 282 } 283 } 284 if namedPortsChanged { 285 k.policyManager.TriggerPolicyUpdates(true, "Named ports deleted") 286 } 287 } 288 hubblemetrics.ProcessCiliumEndpointDeletion(endpoint) 289 }