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  }