github.com/cilium/cilium@v1.16.2/pkg/hubble/exporter/dynamic_exporter.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package exporter
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  
    10  	"github.com/sirupsen/logrus"
    11  
    12  	v1 "github.com/cilium/cilium/pkg/hubble/api/v1"
    13  	"github.com/cilium/cilium/pkg/hubble/exporter/exporteroption"
    14  	"github.com/cilium/cilium/pkg/lock"
    15  	"github.com/cilium/cilium/pkg/time"
    16  )
    17  
    18  // DynamicExporter represents instance of hubble exporter with dynamic
    19  // configuration reload.
    20  type DynamicExporter struct {
    21  	FlowLogExporter
    22  	logger           logrus.FieldLogger
    23  	watcher          *configWatcher
    24  	managedExporters map[string]*managedExporter
    25  	maxFileSizeMB    int
    26  	maxBackups       int
    27  	// mutex protects from concurrent modification of managedExporters by config
    28  	// reloader when hubble events are processed
    29  	mutex lock.RWMutex
    30  }
    31  
    32  // OnDecodedEvent distributes events across all managed exporters.
    33  func (d *DynamicExporter) OnDecodedEvent(ctx context.Context, event *v1.Event) (bool, error) {
    34  	select {
    35  	case <-ctx.Done():
    36  		return false, d.Stop()
    37  	default:
    38  	}
    39  
    40  	d.mutex.RLock()
    41  	defer d.mutex.RUnlock()
    42  
    43  	var errs error
    44  	for _, me := range d.managedExporters {
    45  		if me.config.End == nil || me.config.End.After(time.Now()) {
    46  			_, err := me.exporter.OnDecodedEvent(ctx, event)
    47  			errs = errors.Join(errs, err)
    48  		}
    49  	}
    50  	return false, errs
    51  }
    52  
    53  // Stop stops configuration watcher  and all managed flow log exporters.
    54  func (d *DynamicExporter) Stop() error {
    55  	d.watcher.Stop()
    56  
    57  	d.mutex.Lock()
    58  	defer d.mutex.Unlock()
    59  
    60  	var errs error
    61  	for _, me := range d.managedExporters {
    62  		errs = errors.Join(errs, me.exporter.Stop())
    63  	}
    64  
    65  	return errs
    66  }
    67  
    68  // NewDynamicExporter creates instance of dynamic hubble flow exporter.
    69  func NewDynamicExporter(logger logrus.FieldLogger, configFilePath string, maxFileSizeMB, maxBackups int) *DynamicExporter {
    70  	dynamicExporter := &DynamicExporter{
    71  		logger:           logger,
    72  		managedExporters: make(map[string]*managedExporter),
    73  		maxFileSizeMB:    maxFileSizeMB,
    74  		maxBackups:       maxBackups,
    75  	}
    76  
    77  	registerMetrics(dynamicExporter)
    78  
    79  	watcher := NewConfigWatcher(configFilePath, dynamicExporter.onConfigReload)
    80  	dynamicExporter.watcher = watcher
    81  	return dynamicExporter
    82  }
    83  
    84  func (d *DynamicExporter) onConfigReload(ctx context.Context, hash uint64, config DynamicExportersConfig) {
    85  	d.mutex.Lock()
    86  	defer d.mutex.Unlock()
    87  
    88  	configuredFlowLogNames := make(map[string]interface{})
    89  	for _, flowlog := range config.FlowLogs {
    90  		configuredFlowLogNames[flowlog.Name] = struct{}{}
    91  		if _, ok := d.managedExporters[flowlog.Name]; ok {
    92  			if d.applyUpdatedConfig(ctx, flowlog) {
    93  				DynamicExporterReconfigurations.WithLabelValues("update").Inc()
    94  			}
    95  		} else {
    96  			d.applyNewConfig(ctx, flowlog)
    97  			DynamicExporterReconfigurations.WithLabelValues("add").Inc()
    98  		}
    99  	}
   100  
   101  	for flowLogName := range d.managedExporters {
   102  		if _, ok := configuredFlowLogNames[flowLogName]; !ok {
   103  			d.applyRemovedConfig(flowLogName)
   104  			DynamicExporterReconfigurations.WithLabelValues("remove").Inc()
   105  		}
   106  	}
   107  
   108  	d.updateLastAppliedConfigGauges(hash)
   109  }
   110  
   111  func (d *DynamicExporter) applyNewConfig(ctx context.Context, flowlog *FlowLogConfig) {
   112  	exporterOpts := []exporteroption.Option{
   113  		exporteroption.WithPath(flowlog.FilePath),
   114  		exporteroption.WithMaxSizeMB(d.maxFileSizeMB),
   115  		exporteroption.WithMaxBackups(d.maxBackups),
   116  		exporteroption.WithAllowList(d.logger, flowlog.IncludeFilters),
   117  		exporteroption.WithDenyList(d.logger, flowlog.ExcludeFilters),
   118  		exporteroption.WithFieldMask(flowlog.FieldMask),
   119  	}
   120  
   121  	exporter, err := NewExporter(ctx, d.logger.WithField("flowLogName", flowlog.Name), exporterOpts...)
   122  	if err != nil {
   123  		d.logger.Errorf("Failed to apply flowlog for name %s: %v", flowlog.Name, err)
   124  	}
   125  
   126  	d.managedExporters[flowlog.Name] = &managedExporter{
   127  		config:   flowlog,
   128  		exporter: exporter,
   129  	}
   130  
   131  }
   132  
   133  func (d *DynamicExporter) applyUpdatedConfig(ctx context.Context, flowlog *FlowLogConfig) bool {
   134  	m, ok := d.managedExporters[flowlog.Name]
   135  	if ok && m.config.equals(flowlog) {
   136  		return false
   137  	}
   138  	d.applyRemovedConfig(flowlog.Name)
   139  	d.applyNewConfig(ctx, flowlog)
   140  	return true
   141  }
   142  
   143  func (d *DynamicExporter) applyRemovedConfig(name string) {
   144  	m, ok := d.managedExporters[name]
   145  	if !ok {
   146  		return
   147  	}
   148  	if err := m.exporter.Stop(); err != nil {
   149  		d.logger.Errorf("failed to stop exporter: %w", err)
   150  	}
   151  	delete(d.managedExporters, name)
   152  }
   153  
   154  func (d *DynamicExporter) updateLastAppliedConfigGauges(hash uint64) {
   155  	DynamicExporterConfigHash.WithLabelValues().Set(float64(hash))
   156  	DynamicExporterConfigLastApplied.WithLabelValues().SetToCurrentTime()
   157  }
   158  
   159  type managedExporter struct {
   160  	config   *FlowLogConfig
   161  	exporter FlowLogExporter
   162  }