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  }