github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/cmd/cortex/main.go (about)

     1  package main
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"flag"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"math/rand"
     9  	"os"
    10  	"runtime"
    11  	"sort"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/go-kit/log/level"
    16  	"github.com/grafana/dskit/flagext"
    17  	"github.com/pkg/errors"
    18  	"github.com/prometheus/client_golang/prometheus"
    19  	"github.com/prometheus/common/version"
    20  	"github.com/weaveworks/common/tracing"
    21  	"gopkg.in/yaml.v2"
    22  
    23  	"github.com/cortexproject/cortex/pkg/cortex"
    24  	"github.com/cortexproject/cortex/pkg/util"
    25  	util_log "github.com/cortexproject/cortex/pkg/util/log"
    26  )
    27  
    28  // Version is set via build flag -ldflags -X main.Version
    29  var (
    30  	Version  string
    31  	Branch   string
    32  	Revision string
    33  )
    34  
    35  // configHash exposes information about the loaded config
    36  var configHash *prometheus.GaugeVec = prometheus.NewGaugeVec(
    37  	prometheus.GaugeOpts{
    38  		Name: "cortex_config_hash",
    39  		Help: "Hash of the currently active config file.",
    40  	},
    41  	[]string{"sha256"},
    42  )
    43  
    44  func init() {
    45  	version.Version = Version
    46  	version.Branch = Branch
    47  	version.Revision = Revision
    48  	prometheus.MustRegister(version.NewCollector("cortex"))
    49  	prometheus.MustRegister(configHash)
    50  }
    51  
    52  const (
    53  	configFileOption = "config.file"
    54  	configExpandENV  = "config.expand-env"
    55  )
    56  
    57  var testMode = false
    58  
    59  func main() {
    60  	var (
    61  		cfg                  cortex.Config
    62  		eventSampleRate      int
    63  		ballastBytes         int
    64  		mutexProfileFraction int
    65  		blockProfileRate     int
    66  		printVersion         bool
    67  		printModules         bool
    68  	)
    69  
    70  	configFile, expandENV := parseConfigFileParameter(os.Args[1:])
    71  
    72  	// This sets default values from flags to the config.
    73  	// It needs to be called before parsing the config file!
    74  	flagext.RegisterFlags(&cfg)
    75  
    76  	if configFile != "" {
    77  		if err := LoadConfig(configFile, expandENV, &cfg); err != nil {
    78  			fmt.Fprintf(os.Stderr, "error loading config from %s: %v\n", configFile, err)
    79  			if testMode {
    80  				return
    81  			}
    82  			os.Exit(1)
    83  		}
    84  	}
    85  
    86  	// Ignore -config.file and -config.expand-env here, since it was already parsed, but it's still present on command line.
    87  	flagext.IgnoredFlag(flag.CommandLine, configFileOption, "Configuration file to load.")
    88  	_ = flag.CommandLine.Bool(configExpandENV, false, "Expands ${var} or $var in config according to the values of the environment variables.")
    89  
    90  	flag.IntVar(&eventSampleRate, "event.sample-rate", 0, "How often to sample observability events (0 = never).")
    91  	flag.IntVar(&ballastBytes, "mem-ballast-size-bytes", 0, "Size of memory ballast to allocate.")
    92  	flag.IntVar(&mutexProfileFraction, "debug.mutex-profile-fraction", 0, "Fraction of mutex contention events that are reported in the mutex profile. On average 1/rate events are reported. 0 to disable.")
    93  	flag.IntVar(&blockProfileRate, "debug.block-profile-rate", 0, "Fraction of goroutine blocking events that are reported in the blocking profile. 1 to include every blocking event in the profile, 0 to disable.")
    94  	flag.BoolVar(&printVersion, "version", false, "Print Cortex version and exit.")
    95  	flag.BoolVar(&printModules, "modules", false, "List available values that can be used as target.")
    96  
    97  	usage := flag.CommandLine.Usage
    98  	flag.CommandLine.Usage = func() { /* don't do anything by default, we will print usage ourselves, but only when requested. */ }
    99  	flag.CommandLine.Init(flag.CommandLine.Name(), flag.ContinueOnError)
   100  
   101  	err := flag.CommandLine.Parse(os.Args[1:])
   102  	if err == flag.ErrHelp {
   103  		// Print available parameters to stdout, so that users can grep/less it easily.
   104  		flag.CommandLine.SetOutput(os.Stdout)
   105  		usage()
   106  		if !testMode {
   107  			os.Exit(2)
   108  		}
   109  	} else if err != nil {
   110  		fmt.Fprintln(flag.CommandLine.Output(), "Run with -help to get list of available parameters")
   111  		if !testMode {
   112  			os.Exit(2)
   113  		}
   114  	}
   115  
   116  	if printVersion {
   117  		fmt.Fprintln(os.Stdout, version.Print("Cortex"))
   118  		return
   119  	}
   120  
   121  	// Validate the config once both the config file has been loaded
   122  	// and CLI flags parsed.
   123  	err = cfg.Validate(util_log.Logger)
   124  	if err != nil {
   125  		fmt.Fprintf(os.Stderr, "error validating config: %v\n", err)
   126  		if !testMode {
   127  			os.Exit(1)
   128  		}
   129  	}
   130  
   131  	// Continue on if -modules flag is given. Code handling the
   132  	// -modules flag will not start cortex.
   133  	if testMode && !printModules {
   134  		DumpYaml(&cfg)
   135  		return
   136  	}
   137  
   138  	if mutexProfileFraction > 0 {
   139  		runtime.SetMutexProfileFraction(mutexProfileFraction)
   140  	}
   141  	if blockProfileRate > 0 {
   142  		runtime.SetBlockProfileRate(blockProfileRate)
   143  	}
   144  
   145  	util_log.InitLogger(&cfg.Server)
   146  
   147  	// Allocate a block of memory to alter GC behaviour. See https://github.com/golang/go/issues/23044
   148  	ballast := make([]byte, ballastBytes)
   149  
   150  	util.InitEvents(eventSampleRate)
   151  
   152  	// In testing mode skip JAEGER setup to avoid panic due to
   153  	// "duplicate metrics collector registration attempted"
   154  	if !testMode {
   155  		name := "cortex"
   156  		if len(cfg.Target) == 1 {
   157  			name += "-" + cfg.Target[0]
   158  		}
   159  
   160  		// Setting the environment variable JAEGER_AGENT_HOST enables tracing.
   161  		if trace, err := tracing.NewFromEnv(name); err != nil {
   162  			level.Error(util_log.Logger).Log("msg", "Failed to setup tracing", "err", err.Error())
   163  		} else {
   164  			defer trace.Close()
   165  		}
   166  	}
   167  
   168  	// Initialise seed for randomness usage.
   169  	rand.Seed(time.Now().UnixNano())
   170  
   171  	t, err := cortex.New(cfg)
   172  	util_log.CheckFatal("initializing cortex", err)
   173  
   174  	if printModules {
   175  		allDeps := t.ModuleManager.DependenciesForModule(cortex.All)
   176  
   177  		for _, m := range t.ModuleManager.UserVisibleModuleNames() {
   178  			ix := sort.SearchStrings(allDeps, m)
   179  			included := ix < len(allDeps) && allDeps[ix] == m
   180  
   181  			if included {
   182  				fmt.Fprintln(os.Stdout, m, "*")
   183  			} else {
   184  				fmt.Fprintln(os.Stdout, m)
   185  			}
   186  		}
   187  
   188  		fmt.Fprintln(os.Stdout)
   189  		fmt.Fprintln(os.Stdout, "Modules marked with * are included in target All.")
   190  		return
   191  	}
   192  
   193  	level.Info(util_log.Logger).Log("msg", "Starting Cortex", "version", version.Info())
   194  
   195  	err = t.Run()
   196  
   197  	runtime.KeepAlive(ballast)
   198  	util_log.CheckFatal("running cortex", err)
   199  }
   200  
   201  // Parse -config.file and -config.expand-env option via separate flag set, to avoid polluting default one and calling flag.Parse on it twice.
   202  func parseConfigFileParameter(args []string) (configFile string, expandEnv bool) {
   203  	// ignore errors and any output here. Any flag errors will be reported by main flag.Parse() call.
   204  	fs := flag.NewFlagSet("", flag.ContinueOnError)
   205  	fs.SetOutput(ioutil.Discard)
   206  
   207  	// usage not used in these functions.
   208  	fs.StringVar(&configFile, configFileOption, "", "")
   209  	fs.BoolVar(&expandEnv, configExpandENV, false, "")
   210  
   211  	// Try to find -config.file and -config.expand-env option in the flags. As Parsing stops on the first error, eg. unknown flag, we simply
   212  	// try remaining parameters until we find config flag, or there are no params left.
   213  	// (ContinueOnError just means that flag.Parse doesn't call panic or os.Exit, but it returns error, which we ignore)
   214  	for len(args) > 0 {
   215  		_ = fs.Parse(args)
   216  		args = args[1:]
   217  	}
   218  
   219  	return
   220  }
   221  
   222  // LoadConfig read YAML-formatted config from filename into cfg.
   223  func LoadConfig(filename string, expandENV bool, cfg *cortex.Config) error {
   224  	buf, err := ioutil.ReadFile(filename)
   225  	if err != nil {
   226  		return errors.Wrap(err, "Error reading config file")
   227  	}
   228  
   229  	// create a sha256 hash of the config before expansion and expose it via
   230  	// the config_info metric
   231  	hash := sha256.Sum256(buf)
   232  	configHash.Reset()
   233  	configHash.WithLabelValues(fmt.Sprintf("%x", hash)).Set(1)
   234  
   235  	if expandENV {
   236  		buf = expandEnv(buf)
   237  	}
   238  
   239  	err = yaml.UnmarshalStrict(buf, cfg)
   240  	if err != nil {
   241  		return errors.Wrap(err, "Error parsing config file")
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  func DumpYaml(cfg *cortex.Config) {
   248  	out, err := yaml.Marshal(cfg)
   249  	if err != nil {
   250  		fmt.Fprintln(os.Stderr, err)
   251  	} else {
   252  		fmt.Printf("%s\n", out)
   253  	}
   254  }
   255  
   256  // expandEnv replaces ${var} or $var in config according to the values of the current environment variables.
   257  // The replacement is case-sensitive. References to undefined variables are replaced by the empty string.
   258  // A default value can be given by using the form ${var:default value}.
   259  func expandEnv(config []byte) []byte {
   260  	return []byte(os.Expand(string(config), func(key string) string {
   261  		keyAndDefault := strings.SplitN(key, ":", 2)
   262  		key = keyAndDefault[0]
   263  
   264  		v := os.Getenv(key)
   265  		if v == "" && len(keyAndDefault) == 2 {
   266  			v = keyAndDefault[1] // Set value to the default.
   267  		}
   268  		return v
   269  	}))
   270  }