github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/monitor/internal/pod/monitor.go (about)

     1  // +build linux !windows
     2  
     3  package podmonitor
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"fmt"
     9  	"time"
    10  
    11  	"go.aporeto.io/trireme-lib/monitor/config"
    12  	"go.aporeto.io/trireme-lib/monitor/extractors"
    13  	"go.aporeto.io/trireme-lib/monitor/registerer"
    14  
    15  	"k8s.io/client-go/rest"
    16  	"k8s.io/client-go/tools/clientcmd"
    17  
    18  	"sigs.k8s.io/controller-runtime/pkg/client"
    19  	"sigs.k8s.io/controller-runtime/pkg/event"
    20  	"sigs.k8s.io/controller-runtime/pkg/manager"
    21  
    22  	"go.uber.org/zap"
    23  )
    24  
    25  // PodMonitor implements a monitor that sends pod events upstream
    26  // It is implemented as a filter on the standard DockerMonitor.
    27  // It gets all the PU events from the DockerMonitor and if the container is the POD container from Kubernetes,
    28  // It connects to the Kubernetes API and adds the tags that are coming from Kuberntes that cannot be found
    29  type PodMonitor struct {
    30  	localNode                 string
    31  	handlers                  *config.ProcessorConfig
    32  	metadataExtractor         extractors.PodMetadataExtractor
    33  	netclsProgrammer          extractors.PodNetclsProgrammer
    34  	pidsSetMaxProcsProgrammer extractors.PodPidsSetMaxProcsProgrammer
    35  	resetNetcls               extractors.ResetNetclsKubepods
    36  	sandboxExtractor          extractors.PodSandboxExtractor
    37  	enableHostPods            bool
    38  	workers                   int
    39  	kubeCfg                   *rest.Config
    40  	kubeClient                client.Client
    41  	eventsCh                  chan event.GenericEvent
    42  	resyncInfo                *ResyncInfoChan
    43  }
    44  
    45  // New returns a new kubernetes monitor.
    46  func New() *PodMonitor {
    47  	podMonitor := &PodMonitor{
    48  		eventsCh:   make(chan event.GenericEvent),
    49  		resyncInfo: NewResyncInfoChan(),
    50  	}
    51  
    52  	return podMonitor
    53  }
    54  
    55  // SetupConfig provides a configuration to implmentations. Every implmentation
    56  // can have its own config type.
    57  func (m *PodMonitor) SetupConfig(_ registerer.Registerer, cfg interface{}) error {
    58  
    59  	defaultConfig := DefaultConfig()
    60  
    61  	if cfg == nil {
    62  		cfg = defaultConfig
    63  	}
    64  
    65  	kubernetesconfig, ok := cfg.(*Config)
    66  	if !ok {
    67  		return fmt.Errorf("Invalid configuration specified (type '%T')", cfg)
    68  	}
    69  
    70  	kubernetesconfig = SetupDefaultConfig(kubernetesconfig)
    71  
    72  	// build kubernetes config
    73  	var kubeCfg *rest.Config
    74  	if len(kubernetesconfig.Kubeconfig) > 0 {
    75  		var err error
    76  		kubeCfg, err = clientcmd.BuildConfigFromFlags("", kubernetesconfig.Kubeconfig)
    77  		if err != nil {
    78  			return err
    79  		}
    80  	} else {
    81  		var err error
    82  		kubeCfg, err = rest.InClusterConfig()
    83  		if err != nil {
    84  			return err
    85  		}
    86  	}
    87  
    88  	if kubernetesconfig.MetadataExtractor == nil {
    89  		return fmt.Errorf("missing metadata extractor")
    90  	}
    91  
    92  	if kubernetesconfig.NetclsProgrammer == nil {
    93  		return fmt.Errorf("missing net_cls programmer")
    94  	}
    95  
    96  	if kubernetesconfig.ResetNetcls == nil {
    97  		return fmt.Errorf("missing reset net_cls implementation")
    98  	}
    99  	if kubernetesconfig.SandboxExtractor == nil {
   100  		return fmt.Errorf("missing SandboxExtractor implementation")
   101  	}
   102  	if kubernetesconfig.Workers < 1 {
   103  		return fmt.Errorf("number of Kubernetes monitor workers must be at least 1")
   104  	}
   105  	// Setting up Kubernetes
   106  	m.kubeCfg = kubeCfg
   107  	m.localNode = kubernetesconfig.Nodename
   108  	m.enableHostPods = kubernetesconfig.EnableHostPods
   109  	m.metadataExtractor = kubernetesconfig.MetadataExtractor
   110  	m.netclsProgrammer = kubernetesconfig.NetclsProgrammer
   111  	m.pidsSetMaxProcsProgrammer = kubernetesconfig.PidsSetMaxProcsProgrammer
   112  	m.sandboxExtractor = kubernetesconfig.SandboxExtractor
   113  	m.resetNetcls = kubernetesconfig.ResetNetcls
   114  	m.workers = kubernetesconfig.Workers
   115  
   116  	return nil
   117  }
   118  
   119  // Run starts the monitor.
   120  func (m *PodMonitor) Run(ctx context.Context) error {
   121  	if m.kubeCfg == nil {
   122  		return errors.New("pod: missing kubeconfig")
   123  	}
   124  
   125  	if err := m.handlers.IsComplete(); err != nil {
   126  		return fmt.Errorf("pod: handlers are not complete: %s", err.Error())
   127  	}
   128  
   129  	// ensure to run the reset net_cls
   130  	// NOTE: we also call this during resync, however, that is not called at startup (we call ResyncWithAllPods instead before we return)
   131  	if m.resetNetcls == nil {
   132  		return errors.New("pod: missing net_cls reset implementation")
   133  	}
   134  	if err := m.resetNetcls(ctx); err != nil {
   135  		return fmt.Errorf("pod: failed to reset net_cls cgroups: %s", err.Error())
   136  	}
   137  
   138  	// starts the manager in the background and will return once it is running
   139  	// NOTE: This will block until the Kubernetes manager and all controllers are up. All errors are being handled within the function
   140  	m.startManager(ctx)
   141  
   142  	// call ResyncWithAllPods before we return from here
   143  	// this will block until every pod at this point in time has been seeing at least one `Reconcile` call
   144  	// we do this so that we build up our internal PU cache in the policy engine,
   145  	// so that when we remove stale pods on startup, we don't remove them and create them again
   146  	if err := ResyncWithAllPods(ctx, m.kubeClient, m.resyncInfo, m.eventsCh, m.localNode); err != nil {
   147  		zap.L().Warn("Pod resync failed", zap.Error(err))
   148  	}
   149  	return nil
   150  }
   151  
   152  // SetupHandlers sets up handlers for monitors to invoke for various events such as
   153  // processing unit events and synchronization events. This will be called before Start()
   154  // by the consumer of the monitor
   155  func (m *PodMonitor) SetupHandlers(c *config.ProcessorConfig) {
   156  	m.handlers = c
   157  }
   158  
   159  // Resync requests to the monitor to do a resync.
   160  func (m *PodMonitor) Resync(ctx context.Context) error {
   161  	if m.resetNetcls != nil {
   162  		if err := m.resetNetcls(ctx); err != nil {
   163  			return err
   164  		}
   165  	}
   166  
   167  	if m.kubeClient == nil {
   168  		return errors.New("pod: client has not been initialized yet")
   169  	}
   170  
   171  	return ResyncWithAllPods(ctx, m.kubeClient, m.resyncInfo, m.eventsCh, m.localNode)
   172  }
   173  
   174  const (
   175  	startupWarningMessage = "pod: the Kubernetes controller did not start within the last 5s. Waiting..."
   176  )
   177  
   178  var (
   179  	retrySleep          = time.Second * 3
   180  	warningMessageSleep = time.Second * 5
   181  	warningTimeout      = time.Second * 5
   182  	managerNew          = manager.New
   183  )
   184  
   185  func (m *PodMonitor) startManager(ctx context.Context) {
   186  	var mgr manager.Manager
   187  
   188  	startTimestamp := time.Now()
   189  	controllerStarted := make(chan struct{})
   190  
   191  	go func() {
   192  		// manager.New already contacts the Kubernetes API
   193  		for {
   194  			var err error
   195  			mgr, err = managerNew(m.kubeCfg, manager.Options{})
   196  			if err != nil {
   197  				zap.L().Error("pod: new manager instantiation failed. Retrying in 3s...", zap.Error(err))
   198  				time.Sleep(retrySleep)
   199  				continue
   200  			}
   201  			break
   202  		}
   203  
   204  		// Create the delete event controller first
   205  		dc := NewDeleteController(mgr.GetClient(), m.localNode, m.handlers, m.sandboxExtractor, m.eventsCh)
   206  		for {
   207  			if err := mgr.Add(dc); err != nil {
   208  				zap.L().Error("pod: adding delete controller failed. Retrying in 3s...", zap.Error(err))
   209  				time.Sleep(retrySleep)
   210  				continue
   211  			}
   212  			break
   213  		}
   214  
   215  		// Create the main controller for the monitor
   216  		for {
   217  			if err := addController(
   218  				mgr,
   219  				newReconciler(mgr, m.handlers, m.metadataExtractor, m.netclsProgrammer, m.sandboxExtractor, m.localNode, m.enableHostPods, dc.GetDeleteCh(), dc.GetReconcileCh(), m.resyncInfo),
   220  				m.workers,
   221  				m.eventsCh,
   222  			); err != nil {
   223  				zap.L().Error("pod: adding main monitor controller failed. Retrying in 3s...", zap.Error(err))
   224  				time.Sleep(retrySleep)
   225  				continue
   226  			}
   227  			break
   228  		}
   229  
   230  		for {
   231  			if err := mgr.Add(&runnable{ch: controllerStarted}); err != nil {
   232  				zap.L().Error("pod: adding side controller failed. Retrying in 3s...", zap.Error(err))
   233  				time.Sleep(retrySleep)
   234  				continue
   235  			}
   236  			break
   237  		}
   238  
   239  		// starting the manager is a bit awkward:
   240  		// - it does not use contexts
   241  		// - we pass in a fake signal handler channel
   242  		// - we start another go routine which waits for the context to be cancelled
   243  		//   and closes that channel if that is the case
   244  
   245  		for {
   246  			if err := mgr.Start(ctx.Done()); err != nil {
   247  				zap.L().Error("pod: manager start failed. Retrying in 3s...", zap.Error(err))
   248  				time.Sleep(retrySleep)
   249  				continue
   250  			}
   251  			break
   252  		}
   253  	}()
   254  
   255  waitLoop:
   256  	for {
   257  		select {
   258  		case <-ctx.Done():
   259  			break waitLoop
   260  		case <-time.After(warningMessageSleep):
   261  			// we give everything 5 seconds to report back before we issue a warning
   262  			zap.L().Warn(startupWarningMessage)
   263  		case <-controllerStarted:
   264  			m.kubeClient = mgr.GetClient()
   265  			t := time.Since(startTimestamp)
   266  			if t > warningTimeout {
   267  				zap.L().Warn("pod: controller startup finished, but took longer than expected", zap.Duration("duration", t))
   268  			} else {
   269  				zap.L().Debug("pod: controller startup finished", zap.Duration("duration", t))
   270  			}
   271  			break waitLoop
   272  		}
   273  	}
   274  }
   275  
   276  type runnable struct {
   277  	ch chan struct{}
   278  }
   279  
   280  func (r *runnable) Start(z <-chan struct{}) error {
   281  	// close the indicator channel which means that the manager has been started successfully
   282  	close(r.ch)
   283  
   284  	// stay up and running, the manager needs that
   285  	<-z
   286  	return nil
   287  }