github.com/docker/compose-on-kubernetes@v0.5.0/cmd/compose-controller/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"flag"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"os"
    11  	"os/signal"
    12  	"syscall"
    13  	"time"
    14  
    15  	cliopts "github.com/docker/cli/opts"
    16  	"github.com/docker/compose-on-kubernetes/api/client/clientset"
    17  	"github.com/docker/compose-on-kubernetes/api/compose/latest"
    18  	"github.com/docker/compose-on-kubernetes/api/constants"
    19  	"github.com/docker/compose-on-kubernetes/internal"
    20  	"github.com/docker/compose-on-kubernetes/internal/check"
    21  	"github.com/docker/compose-on-kubernetes/internal/controller"
    22  	"github.com/docker/compose-on-kubernetes/internal/deduplication"
    23  	homedir "github.com/mitchellh/go-homedir"
    24  	log "github.com/sirupsen/logrus"
    25  	coretypes "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apiserver/pkg/server/healthz"
    28  	k8sclientset "k8s.io/client-go/kubernetes"
    29  	"k8s.io/client-go/rest"
    30  	"k8s.io/client-go/tools/clientcmd"
    31  )
    32  
    33  const (
    34  	// reconcileQueueLength is the size of the buffer for reconciliation deduplication (it means that up to 200 stacks can be updated
    35  	// concurrently before getting unnecessary duplicated events)
    36  	reconcileQueueLength = 200
    37  
    38  	// deletionChannelSize is the size of the chan in which delete events are queued.
    39  	// this means that the stackInformer won't block until we get more than 50 deletions messages in queue
    40  	deletionChannelSize = 50
    41  )
    42  
    43  type controllerOptions struct {
    44  	kubeconfig             string
    45  	reconciliationInterval cliopts.PositiveDurationOpt
    46  	logLevel               string
    47  	defaultServiceType     string
    48  	healthzCheckPort       int
    49  }
    50  
    51  func defaultOptions() controllerOptions {
    52  	defaultReconciliation := constants.DefaultFullSyncInterval
    53  	return controllerOptions{
    54  		reconciliationInterval: cliopts.PositiveDurationOpt{
    55  			DurationOpt: *cliopts.NewDurationOpt(&defaultReconciliation),
    56  		},
    57  	}
    58  }
    59  
    60  func main() {
    61  	opts := defaultOptions()
    62  
    63  	flag.StringVar(&opts.kubeconfig, "kubeconfig", "~/.kube/config", "Path to a kube config. Only required if out-of-cluster.")
    64  	flag.Var(&opts.reconciliationInterval, "reconciliation-interval", "Reconciliation interval of the stack controller (default: 12h)")
    65  	flag.StringVar(&opts.logLevel, "log-level", "info", `Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")`)
    66  	flag.StringVar(&opts.defaultServiceType, "default-service-type", "LoadBalancer", `Specify the default service type for published ports ("LoadBalancer"|"NodePort")`)
    67  	flag.IntVar(&opts.healthzCheckPort, "healthz-check-port", 8080, "defines the port used by healthz check server (0 to disable it)")
    68  
    69  	flag.Parse()
    70  
    71  	if err := start(&opts); err != nil {
    72  		log.Fatalln(err)
    73  	}
    74  }
    75  
    76  func start(opts *controllerOptions) error {
    77  	initLogger(opts.logLevel, os.Stdout)
    78  	fmt.Println(internal.FullVersion())
    79  
    80  	configFile, err := homedir.Expand(opts.kubeconfig)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	log.Debugf("Using config file: %s", configFile)
    85  
    86  	config, err := clientcmd.BuildConfigFromFlags("", configFile)
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	// Chances are we were started at the same time as the API server, so give
    92  	// it time to start
    93  	if err := checkAPIPresent(config); err != nil {
    94  		return err
    95  	}
    96  
    97  	clientSet, err := clientset.NewForConfig(config)
    98  	if err != nil {
    99  		return err
   100  	}
   101  	k8sClientSet, err := k8sclientset.NewForConfig(config)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	cache, err := controller.NewStackOwnerCache(config)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	timeoutctx, cancel := context.WithTimeout(context.Background(), time.Minute*10)
   110  	defer cancel()
   111  	for {
   112  		if wi, err := clientSet.ComposeV1beta2().Stacks(metav1.NamespaceAll).Watch(metav1.ListOptions{}); err == nil {
   113  			wi.Stop()
   114  			break
   115  		}
   116  		select {
   117  		case <-timeoutctx.Done():
   118  			return errors.New("cannot watch stacks")
   119  		default:
   120  			time.Sleep(time.Second)
   121  		}
   122  	}
   123  	stop := make(chan struct{})
   124  	sigs := make(chan os.Signal, 1)
   125  	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
   126  	go func() {
   127  		sig := <-sigs
   128  		log.Infof("Received signal: %v", sig)
   129  		close(stop)
   130  	}()
   131  	reconcileQueue := deduplication.NewStringChan(reconcileQueueLength)
   132  	deletionQueue := make(chan *latest.Stack, deletionChannelSize)
   133  	childrenStore, err := controller.NewChildrenListener(k8sClientSet, *opts.reconciliationInterval.Value(), reconcileQueue.In())
   134  	if err != nil {
   135  		return err
   136  	}
   137  	if !childrenStore.StartAndWaitForFullSync(stop) {
   138  		return errors.New("children store failed to sync")
   139  	}
   140  
   141  	stackStore := controller.NewStackListener(clientSet, *opts.reconciliationInterval.Value(), reconcileQueue.In(), deletionQueue, cache)
   142  	stackStore.Start(stop)
   143  	stackReconciler, err := controller.NewStackReconciler(
   144  		stackStore,
   145  		childrenStore,
   146  		coretypes.ServiceType(opts.defaultServiceType),
   147  		controller.NewImpersonatingResourceUpdaterProvider(*config, cache),
   148  		cache)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	stackReconciler.Start(reconcileQueue.Out(), deletionQueue, stop)
   153  	log.Infof("Controller ready")
   154  
   155  	if opts.healthzCheckPort > 0 {
   156  		m := http.NewServeMux()
   157  		healthz.InstallHandler(m)
   158  		srv := &http.Server{
   159  			Addr:    fmt.Sprintf(":%d", opts.healthzCheckPort),
   160  			Handler: m,
   161  		}
   162  		go srv.ListenAndServe()
   163  		go func() {
   164  			<-stop
   165  			srv.Close()
   166  		}()
   167  	}
   168  	<-stop
   169  	return nil
   170  }
   171  
   172  func checkAPIPresent(config *rest.Config) error {
   173  	var err error
   174  	for i := 0; i < 60; i++ {
   175  		if err = check.APIPresent(config); err == nil {
   176  			return nil
   177  		}
   178  		time.Sleep(1 * time.Second)
   179  	}
   180  	return err
   181  }
   182  
   183  func initLogger(level string, out io.Writer) {
   184  	log.SetOutput(out)
   185  	parseLogLevel(level)
   186  }
   187  
   188  func parseLogLevel(level string) {
   189  	lvl, err := log.ParseLevel(level)
   190  	if err != nil {
   191  		fmt.Fprintf(os.Stderr, "Unable to parse log level: %s\n", level)
   192  		os.Exit(1)
   193  	}
   194  	log.SetLevel(lvl)
   195  }