github.com/cilium/cilium@v1.16.2/pkg/k8s/resource/example/main.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package main
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"log/slog"
    11  	"math/rand/v2"
    12  
    13  	"github.com/cilium/hive/cell"
    14  	"github.com/cilium/workerpool"
    15  	"github.com/spf13/pflag"
    16  	corev1 "k8s.io/api/core/v1"
    17  
    18  	"github.com/cilium/cilium/pkg/hive"
    19  	"github.com/cilium/cilium/pkg/k8s/client"
    20  	"github.com/cilium/cilium/pkg/k8s/resource"
    21  	"github.com/cilium/cilium/pkg/k8s/utils"
    22  	"github.com/cilium/cilium/pkg/labels"
    23  	"github.com/cilium/cilium/pkg/logging"
    24  	"github.com/cilium/cilium/pkg/logging/logfields"
    25  	"github.com/cilium/cilium/pkg/time"
    26  )
    27  
    28  // PrintServices for the pkg/k8s/resource which observers pods and services and once a second prints the list of
    29  // services with the pods associated with each service.
    30  //
    31  // Run with:
    32  //
    33  //  go run . --k8s-kubeconfig-path ~/.kube/config
    34  //
    35  // To test, try running:
    36  //
    37  //  kubectl run -it --rm --image=nginx  --port=80 --expose nginx
    38  
    39  var (
    40  	log = logging.DefaultLogger.WithField(logfields.LogSubsys, "example")
    41  )
    42  
    43  func main() {
    44  	hive := hive.New(
    45  		client.Cell,
    46  		resourcesCell,
    47  		printServicesCell,
    48  
    49  		cell.Invoke(func(*PrintServices) {}),
    50  	)
    51  	hive.RegisterFlags(pflag.CommandLine)
    52  	pflag.Parse()
    53  	hive.Run(slog.Default())
    54  }
    55  
    56  var resourcesCell = cell.Module(
    57  	"resources",
    58  	"Kubernetes Pod and Service resources",
    59  
    60  	cell.Provide(
    61  		func(lc cell.Lifecycle, c client.Clientset) resource.Resource[*corev1.Pod] {
    62  			if !c.IsEnabled() {
    63  				return nil
    64  			}
    65  			lw := utils.ListerWatcherFromTyped[*corev1.PodList](c.CoreV1().Pods(""))
    66  			return resource.New[*corev1.Pod](lc, lw, resource.WithMetric("Pod"))
    67  		},
    68  		func(lc cell.Lifecycle, c client.Clientset) resource.Resource[*corev1.Service] {
    69  			if !c.IsEnabled() {
    70  				return nil
    71  			}
    72  			lw := utils.ListerWatcherFromTyped[*corev1.ServiceList](c.CoreV1().Services(""))
    73  			return resource.New[*corev1.Service](lc, lw, resource.WithMetric("Service"))
    74  		},
    75  	),
    76  )
    77  
    78  var printServicesCell = cell.Module(
    79  	"print-services",
    80  	"Prints Kubernetes Services",
    81  
    82  	cell.Provide(newPrintServices),
    83  )
    84  
    85  type PrintServices struct {
    86  	wp *workerpool.WorkerPool
    87  
    88  	pods     resource.Resource[*corev1.Pod]
    89  	services resource.Resource[*corev1.Service]
    90  }
    91  
    92  type printServicesParams struct {
    93  	cell.In
    94  
    95  	Lifecycle cell.Lifecycle
    96  	Pods      resource.Resource[*corev1.Pod]
    97  	Services  resource.Resource[*corev1.Service]
    98  }
    99  
   100  func newPrintServices(p printServicesParams) (*PrintServices, error) {
   101  	if p.Pods == nil || p.Services == nil {
   102  		return nil, fmt.Errorf("Resources not available. Missing --k8s-kubeconfig-path?")
   103  	}
   104  	ps := &PrintServices{
   105  		pods:     p.Pods,
   106  		services: p.Services,
   107  	}
   108  	p.Lifecycle.Append(ps)
   109  	return ps, nil
   110  }
   111  
   112  func (ps *PrintServices) Start(startCtx cell.HookContext) error {
   113  	ps.wp = workerpool.New(1)
   114  	ps.wp.Submit("processLoop", ps.processLoop)
   115  
   116  	// Using the start context, do a blocking dump of all
   117  	// services. Using the start context here makes sure that
   118  	// this operation is aborted if it blocks too long.
   119  	ps.printServices(startCtx)
   120  
   121  	return nil
   122  }
   123  
   124  func (ps *PrintServices) Stop(cell.HookContext) error {
   125  	ps.wp.Close()
   126  	return nil
   127  }
   128  
   129  // printServices prints services at start to show how Store() can be used.
   130  func (ps *PrintServices) printServices(ctx context.Context) {
   131  
   132  	// Retrieve a handle to the store. Blocks until the store has synced.
   133  	// Can fail if the context is cancelled (e.g. PrintServices is being stopped).
   134  	store, err := ps.services.Store(ctx)
   135  	if err != nil {
   136  		log.Errorf("Failed to retrieve store: %s, aborting", err)
   137  		return
   138  	}
   139  
   140  	log.Info("Services:")
   141  	for _, svc := range store.List() {
   142  		labels := labels.Map2Labels(svc.Spec.Selector, labels.LabelSourceK8s)
   143  		log.Infof("  - %s/%s\ttype=%s\tselector=%s", svc.Namespace, svc.Name, svc.Spec.Type, labels)
   144  	}
   145  
   146  }
   147  
   148  // processLoop observes changes to pods and services and periodically prints the
   149  // services and the pods that each service selects.
   150  func (ps *PrintServices) processLoop(ctx context.Context) error {
   151  	ticker := time.NewTicker(time.Second)
   152  	defer ticker.Stop()
   153  
   154  	// Subscribe to pods and services.
   155  	pods := ps.pods.Events(ctx)
   156  	services := ps.services.Events(ctx)
   157  
   158  	// State:
   159  	podLabels := make(map[resource.Key]labels.Labels)
   160  	serviceSelectors := make(map[resource.Key]labels.Labels)
   161  
   162  	// Process the pod and service events and periodically print the services.
   163  	// Loop until the pods and services have completed. We need to process
   164  	// both streams to the end to make sure we're not blocking the resource even
   165  	// if we're stopping (e.g. context cancelled).
   166  	for pods != nil || services != nil {
   167  		select {
   168  		case <-ticker.C:
   169  			for key, selectors := range serviceSelectors {
   170  				log.Infof("%s (%s)", key, selectors)
   171  				for podName, lbls := range podLabels {
   172  					match := true
   173  					for _, sel := range selectors {
   174  						match = match && lbls.Has(sel)
   175  					}
   176  					if match {
   177  						log.Infof("  - %s", podName)
   178  					}
   179  				}
   180  			}
   181  			log.Println("----------------------------------------------------------")
   182  
   183  		case ev, ok := <-pods:
   184  			if !ok {
   185  				pods = nil
   186  				continue
   187  			}
   188  
   189  			switch ev.Kind {
   190  			case resource.Sync:
   191  				// Pods have now been synced and the set of Upsert events
   192  				// received thus far forms a coherent snapshot of the pods
   193  				// at a specific point in time. This is usually used in the context
   194  				// of garbage collection at startup: we now know what is the set of pods that
   195  				// existed at the api-server brief moment ago and can remove persisted
   196  				// data of pods that are not part of this set.
   197  			case resource.Upsert:
   198  				log.Infof("Pod %s updated", ev.Key)
   199  				podLabels[ev.Key] = labels.Map2Labels(ev.Object.Labels, labels.LabelSourceK8s)
   200  			case resource.Delete:
   201  				log.Infof("Pod %s deleted", ev.Key)
   202  				delete(podLabels, ev.Key)
   203  			}
   204  
   205  			// Always mark the event as processed. This tells the resource that more
   206  			// events can be now emitted for this key and if error is nil it clears
   207  			// any rate limiting state related to failed attempts.
   208  			ev.Done(nil)
   209  
   210  		case ev, ok := <-services:
   211  			if !ok {
   212  				services = nil
   213  				continue
   214  			}
   215  
   216  			// Simulate a fault 10% of the time. This will cause this event to be retried
   217  			// later.
   218  			if rand.IntN(10) == 1 {
   219  				log.Info("Injecting a fault!")
   220  				ev.Done(errors.New("injected fault"))
   221  				continue
   222  			}
   223  
   224  			switch ev.Kind {
   225  			case resource.Sync:
   226  				log.Info("Services synced")
   227  			case resource.Upsert:
   228  				log.Infof("Service %s updated", ev.Key)
   229  				if len(ev.Object.Spec.Selector) > 0 {
   230  					serviceSelectors[ev.Key] = labels.Map2Labels(ev.Object.Spec.Selector, labels.LabelSourceK8s)
   231  				}
   232  			case resource.Delete:
   233  				log.Infof("Service %s deleted", ev.Key)
   234  				delete(serviceSelectors, ev.Key)
   235  			}
   236  			ev.Done(nil)
   237  		}
   238  	}
   239  
   240  	return nil
   241  }