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 }