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 }