package app import ( "context" "fmt" "net" "net/http" "os" "strconv" "time" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/spf13/cobra" "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" cliflag "k8s.io/component-base/cli/flag" "k8s.io/component-base/term" "k8s.io/klog/v2" ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics" "github.com/karmada-io/karmada/cmd/scheduler-estimator/app/options" "github.com/karmada-io/karmada/pkg/estimator/server" "github.com/karmada-io/karmada/pkg/sharedcli" "github.com/karmada-io/karmada/pkg/sharedcli/klogflag" "github.com/karmada-io/karmada/pkg/sharedcli/profileflag" "github.com/karmada-io/karmada/pkg/version" "github.com/karmada-io/karmada/pkg/version/sharedcommand" ) const ( // ReadHeaderTimeout is the amount of time allowed to read // request headers. // HTTP timeouts are necessary to expire inactive connections // and failing to do so might make the application vulnerable // to attacks like slowloris which work by sending data very slow, // which in case of no timeout will keep the connection active // eventually leading to a denial-of-service (DoS) attack. // References: // - https://en.wikipedia.org/wiki/Slowloris_(computer_security) ReadHeaderTimeout = 32 * time.Second ) // NewSchedulerEstimatorCommand creates a *cobra.Command object with default parameters func NewSchedulerEstimatorCommand(ctx context.Context) *cobra.Command { opts := options.NewOptions() cmd := &cobra.Command{ Use: "karmada-scheduler-estimator", Long: `The karmada-scheduler-estimator runs an accurate scheduler estimator of a cluster. It provides the scheduler with more accurate cluster resource information.`, RunE: func(cmd *cobra.Command, args []string) error { // validate options if errs := opts.Validate(); len(errs) != 0 { return errs.ToAggregate() } if err := run(ctx, opts); err != nil { return err } return nil }, } fss := cliflag.NamedFlagSets{} genericFlagSet := fss.FlagSet("generic") opts.AddFlags(genericFlagSet) // Set klog flags logsFlagSet := fss.FlagSet("logs") klogflag.Add(logsFlagSet) cmd.AddCommand(sharedcommand.NewCmdVersion("karmada-scheduler-estimator")) cmd.Flags().AddFlagSet(genericFlagSet) cmd.Flags().AddFlagSet(logsFlagSet) cols, _, _ := term.TerminalSize(cmd.OutOrStdout()) sharedcli.SetUsageAndHelpFunc(cmd, fss, cols) return cmd } func run(ctx context.Context, opts *options.Options) error { klog.Infof("karmada-scheduler-estimator version: %s", version.Get()) go serveHealthzAndMetrics(net.JoinHostPort(opts.BindAddress, strconv.Itoa(opts.SecurePort))) profileflag.ListenAndServe(opts.ProfileOpts) restConfig, err := clientcmd.BuildConfigFromFlags(opts.Master, opts.KubeConfig) if err != nil { return fmt.Errorf("error building kubeconfig: %s", err.Error()) } restConfig.QPS, restConfig.Burst = opts.ClusterAPIQPS, opts.ClusterAPIBurst kubeClient := kubernetes.NewForConfigOrDie(restConfig) dynamicClient := dynamic.NewForConfigOrDie(restConfig) discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(restConfig) e := server.NewEstimatorServer(kubeClient, dynamicClient, discoveryClient, opts, ctx.Done()) if err = e.Start(ctx); err != nil { klog.Errorf("estimator server exits unexpectedly: %v", err) return err } // never reach here return nil } func serveHealthzAndMetrics(address string) { mux := http.NewServeMux() mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok")) }) mux.Handle("/metrics", promhttp.HandlerFor(ctrlmetrics.Registry, promhttp.HandlerOpts{ ErrorHandling: promhttp.HTTPErrorOnError, })) httpServer := http.Server{ Addr: address, Handler: mux, ReadHeaderTimeout: ReadHeaderTimeout, } if err := httpServer.ListenAndServe(); err != nil { klog.Errorf("Failed to serve healthz and metrics: %v", err) os.Exit(1) } }