github.com/observiq/carbon@v0.9.11-0.20200820160507-1b872e368a5e/operator/builtin/input/k8s_events.go (about) 1 package input 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 backoff "github.com/cenkalti/backoff/v4" 10 "github.com/observiq/carbon/errors" 11 "github.com/observiq/carbon/operator" 12 "github.com/observiq/carbon/operator/helper" 13 "go.uber.org/zap" 14 apiv1 "k8s.io/api/core/v1" 15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 "k8s.io/apimachinery/pkg/runtime" 17 watch "k8s.io/apimachinery/pkg/watch" 18 corev1 "k8s.io/client-go/kubernetes/typed/core/v1" 19 "k8s.io/client-go/rest" 20 ) 21 22 func init() { 23 operator.Register("k8s_event_input", func() operator.Builder { return NewK8sEventsConfig("") }) 24 } 25 26 // NewK8sEventsConfig creates a default K8sEventsConfig 27 func NewK8sEventsConfig(operatorID string) *K8sEventsConfig { 28 return &K8sEventsConfig{ 29 InputConfig: helper.NewInputConfig(operatorID, "k8s_event_input"), 30 } 31 } 32 33 // K8sEventsConfig is the configuration of K8sEvents operator 34 type K8sEventsConfig struct { 35 helper.InputConfig `yaml:",inline"` 36 Namespaces []string 37 } 38 39 // Build will build a k8s_event_input operator from the supplied configuration 40 func (c K8sEventsConfig) Build(context operator.BuildContext) (operator.Operator, error) { 41 input, err := c.InputConfig.Build(context) 42 if err != nil { 43 return nil, errors.Wrap(err, "build transformer") 44 } 45 46 return &K8sEvents{ 47 InputOperator: input, 48 namespaces: c.Namespaces, 49 }, nil 50 } 51 52 // K8sEvents is an operator for generating logs from k8s events 53 type K8sEvents struct { 54 helper.InputOperator 55 client corev1.CoreV1Interface 56 namespaces []string 57 58 cancel func() 59 wg sync.WaitGroup 60 } 61 62 // Start implements the operator.Operator interface 63 func (k *K8sEvents) Start() error { 64 ctx, cancel := context.WithCancel(context.Background()) 65 k.cancel = cancel 66 67 // Currently, we only support running in the cluster. In contrast to the 68 // k8s_metadata_decorator, it may make sense to relax this restriction 69 // by exposing client config options. 70 config, err := rest.InClusterConfig() 71 if err != nil { 72 return errors.NewError( 73 "agent not in kubernetes cluster", 74 "the k8s_event_input operator only supports running in a pod inside a kubernetes cluster", 75 ) 76 } 77 78 k.client, err = corev1.NewForConfig(config) 79 if err != nil { 80 return errors.Wrap(err, "build client") 81 } 82 83 // Get all namespaces if left empty 84 if len(k.namespaces) == 0 { 85 k.namespaces, err = listNamespaces(ctx, k.client) 86 if err != nil { 87 return errors.Wrap(err, "list namespaces") 88 } 89 } 90 91 // Test connection 92 testWatcher, err := k.client.Events(k.namespaces[0]).Watch(ctx, metav1.ListOptions{}) 93 if err != nil { 94 return fmt.Errorf("test connection: list events for namespace '%s': %s", k.namespaces[0], err) 95 } 96 testWatcher.Stop() 97 98 for _, ns := range k.namespaces { 99 k.startWatchingNamespace(ctx, ns) 100 } 101 102 return nil 103 } 104 105 // Stop implements operator.Operator 106 func (k *K8sEvents) Stop() error { 107 k.cancel() 108 k.wg.Wait() 109 return nil 110 } 111 112 // listNamespaces gets a full list of namespaces from the client 113 func listNamespaces(ctx context.Context, client corev1.CoreV1Interface) ([]string, error) { 114 ctx, cancel := context.WithTimeout(ctx, 10*time.Second) 115 defer cancel() 116 res, err := client.Namespaces().List(ctx, metav1.ListOptions{}) 117 if err != nil { 118 return nil, err 119 } 120 namespaces := make([]string, 0, 10) 121 for _, ns := range res.Items { 122 namespaces = append(namespaces, ns.Name) 123 } 124 return namespaces, nil 125 } 126 127 // startWatchingNamespace creates a goroutine that watches the events for a 128 // specific namespace 129 func (k *K8sEvents) startWatchingNamespace(ctx context.Context, ns string) { 130 k.wg.Add(1) 131 go func() { 132 defer k.wg.Done() 133 134 b := backoff.NewExponentialBackOff() 135 for { 136 select { 137 case <-ctx.Done(): 138 return 139 case <-time.After(b.NextBackOff()): 140 } 141 142 watcher, err := k.client.Events(ns).Watch(ctx, metav1.ListOptions{}) 143 if err != nil { 144 k.Errorw("Failed to start watcher", zap.Error(err)) 145 continue 146 } 147 b.Reset() 148 149 k.consumeWatchEvents(ctx, watcher.ResultChan()) 150 } 151 }() 152 } 153 154 // consumeWatchEvents will read events from the watcher channel until the channel is closed 155 // or the context is canceled 156 func (k *K8sEvents) consumeWatchEvents(ctx context.Context, events <-chan watch.Event) { 157 for { 158 select { 159 case event, ok := <-events: 160 if !ok { 161 k.Error("Watcher channel closed") 162 return 163 } 164 165 typedEvent := event.Object.(*apiv1.Event) 166 record, err := runtime.DefaultUnstructuredConverter.ToUnstructured(event.Object) 167 if err != nil { 168 k.Error("Failed to convert event to map", zap.Error(err)) 169 continue 170 } 171 172 entry, err := k.NewEntry(record) 173 if err != nil { 174 k.Error("Failed to create new entry from record", zap.Error(err)) 175 continue 176 } 177 178 entry.Timestamp = typedEvent.LastTimestamp.Time 179 entry.AddLabel("event_type", string(event.Type)) 180 k.Write(ctx, entry) 181 case <-ctx.Done(): 182 return 183 } 184 } 185 }