1package main 2 3import ( 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 29var ( 30 Version string 31 Branch string 32 Revision string 33) 34 35// configHash exposes information about the loaded config 36var 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 44func 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 52const ( 53 configFileOption = "config.file" 54 configExpandENV = "config.expand-env" 55) 56 57var testMode = false 58 59func 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. 202func 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. 223func 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 247func 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}. 259func 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} 271