github.com/go-graphite/carbonapi@v0.17.0/cmd/carbonapi/main.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"expvar"
     6  	"flag"
     7  	"log"
     8  	"net"
     9  	"net/http"
    10  	"net/http/pprof"
    11  	"os"
    12  	"os/signal"
    13  	"sync"
    14  	"syscall"
    15  	"time"
    16  
    17  	"github.com/gorilla/handlers"
    18  	"github.com/lomik/zapwriter"
    19  	"go.uber.org/zap"
    20  
    21  	"github.com/go-graphite/carbonapi/pkg/tlsconfig"
    22  
    23  	"github.com/go-graphite/carbonapi/cmd/carbonapi/config"
    24  	"github.com/go-graphite/carbonapi/cmd/carbonapi/helper"
    25  	carbonapiHttp "github.com/go-graphite/carbonapi/cmd/carbonapi/http"
    26  	"github.com/go-graphite/carbonapi/internal/dns"
    27  )
    28  
    29  // BuildVersion is provided to be overridden at build time. Eg. go build -ldflags -X 'main.BuildVersion=...'
    30  var BuildVersion = "(development build)"
    31  
    32  func main() {
    33  	err := zapwriter.ApplyConfig([]zapwriter.Config{config.DefaultLoggerConfig})
    34  	if err != nil {
    35  		log.Fatal("Failed to initialize logger with default configuration")
    36  	}
    37  	logger := zapwriter.Logger("main")
    38  
    39  	configPath := flag.String("config", "", "Path to the `config file`.")
    40  	checkConfig := flag.Bool("check-config", false, "Check config file and exit.")
    41  	exactConfig := flag.Bool("exact-config", false, "Ensure that all config params are contained in the target struct.")
    42  	envPrefix := flag.String("envprefix", "CARBONAPI", "Prefix for environment variables override")
    43  	if *envPrefix == "(empty)" {
    44  		*envPrefix = ""
    45  	}
    46  	if *envPrefix == "" {
    47  		logger.Warn("empty prefix is not recommended due to possible collisions with OS environment variables")
    48  	}
    49  	flag.Parse()
    50  	config.SetUpViper(logger, configPath, *exactConfig, *envPrefix)
    51  	if *checkConfig {
    52  		os.Exit(0)
    53  	}
    54  	config.SetUpConfigUpstreams(logger)
    55  	config.SetUpConfig(logger, BuildVersion)
    56  	carbonapiHttp.SetupMetrics(logger)
    57  	setupGraphiteMetrics(logger)
    58  
    59  	if config.Config.UseCachingDNSResolver {
    60  		logger.Info("will use custom caching dns resolver")
    61  		dns.UseDNSCache(config.Config.CachingDNSRefreshTime)
    62  	}
    63  
    64  	if err := config.Config.SetZipper(newZipper(carbonapiHttp.ZipperStats, &config.Config.Upstreams, config.Config.IgnoreClientTimeout, zapwriter.Logger("zipper"))); err != nil {
    65  		logger.Fatal("failed to setup zipper",
    66  			zap.Error(err),
    67  		)
    68  	}
    69  
    70  	wg := sync.WaitGroup{}
    71  	serve := func(listen config.Listener, handler http.Handler) {
    72  		l := &net.ListenConfig{Control: helper.ReusePort}
    73  		h, p, err := net.SplitHostPort(listen.Address)
    74  		if err != nil {
    75  			logger.Fatal("failed to split address",
    76  				zap.String("address", listen.Address),
    77  				zap.Error(err),
    78  			)
    79  		}
    80  		any := false
    81  		if h == "" {
    82  			h = "[::]"
    83  			any = true
    84  		}
    85  		ips, err := net.LookupIP(h)
    86  		if err != nil {
    87  			// Fallback for a case where machine doesn't have ipv6 at all
    88  			if any {
    89  				h = "0.0.0.0"
    90  				ips, err = net.LookupIP(h)
    91  			}
    92  			if err != nil {
    93  				logger.Fatal("failed to resolve address",
    94  					zap.String("address", h),
    95  					zap.String("port", p),
    96  					zap.Error(err),
    97  				)
    98  			}
    99  		}
   100  		// Resolve named ports
   101  		port, err := net.LookupPort("tcp", p)
   102  		if err != nil {
   103  			logger.Fatal("failed to resolve port",
   104  				zap.String("address", h),
   105  				zap.String("port", p),
   106  				zap.Error(err),
   107  			)
   108  		}
   109  		httpLogger, err := zap.NewStdLogAt(zapwriter.Logger("http"), zap.WarnLevel)
   110  		if err != nil {
   111  			logger.Fatal("failed to set up http server logger",
   112  				zap.Error(err),
   113  			)
   114  		}
   115  
   116  		servers := make([]*http.Server, 0)
   117  
   118  		for _, ip := range ips {
   119  			address := (&net.TCPAddr{IP: ip, Port: port}).String()
   120  			s := &http.Server{
   121  				Addr:     address,
   122  				Handler:  handler,
   123  				ErrorLog: httpLogger,
   124  			}
   125  			servers = append(servers, s)
   126  			isTLS := false
   127  			if len(listen.ServerTLSConfig.CACertFiles) > 0 {
   128  				tlsConfig, warns, err := tlsconfig.ParseServerTLSConfig(&listen.ServerTLSConfig, &listen.ClientTLSConfig)
   129  				if err != nil {
   130  					logger.Fatal("failed to initialize TLS",
   131  						zap.Error(err),
   132  					)
   133  				}
   134  				if len(warns) != 0 {
   135  					logger.Warn("insecure ciphers are in-use",
   136  						zap.Strings("insecure_ciphers", warns),
   137  					)
   138  				}
   139  				s.TLSConfig = tlsConfig
   140  				isTLS = true
   141  			}
   142  
   143  			listener, err := l.Listen(context.Background(), "tcp", address)
   144  			if err != nil {
   145  				logger.Fatal("failed to start http server",
   146  					zap.Error(err),
   147  				)
   148  			}
   149  			wg.Add(1)
   150  			go func(listener net.Listener, isTLS bool) {
   151  				if isTLS {
   152  					err = s.ServeTLS(listener, "", "")
   153  				} else {
   154  					err = s.Serve(listener)
   155  				}
   156  
   157  				if err != nil && err != http.ErrServerClosed {
   158  					logger.Error("failed to start http server",
   159  						zap.Error(err),
   160  					)
   161  				}
   162  
   163  				wg.Done()
   164  			}(listener, isTLS)
   165  		}
   166  
   167  		go func() {
   168  			stop := make(chan os.Signal, 1)
   169  			signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)
   170  			<-stop
   171  			logger.Info("stoping carbonapi")
   172  			// initiating the shutdown
   173  			ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
   174  			for _, s := range servers {
   175  				s.Shutdown(ctx)
   176  			}
   177  			cancel()
   178  		}()
   179  
   180  	}
   181  
   182  	if config.Config.Expvar.Enabled {
   183  		if config.Config.Expvar.Listen != "" && config.Config.Expvar.Listen != config.Config.Listeners[0].Address {
   184  			r := http.NewServeMux()
   185  			r.HandleFunc(config.Config.Prefix+"/debug/vars", expvar.Handler().ServeHTTP)
   186  			if config.Config.Expvar.PProfEnabled {
   187  				r.HandleFunc(config.Config.Prefix+"/debug/pprof/", pprof.Index)
   188  				r.HandleFunc(config.Config.Prefix+"/debug/pprof/cmdline", pprof.Cmdline)
   189  				r.HandleFunc(config.Config.Prefix+"/debug/pprof/profile", pprof.Profile)
   190  				r.HandleFunc(config.Config.Prefix+"/debug/pprof/symbol", pprof.Symbol)
   191  				r.HandleFunc(config.Config.Prefix+"/debug/pprof/trace", pprof.Trace)
   192  			}
   193  
   194  			handler := handlers.CompressHandler(r)
   195  			handler = handlers.CORS()(handler)
   196  			handler = handlers.ProxyHeaders(handler)
   197  
   198  			logger.Info("expvar handler will listen on a separate address/port",
   199  				zap.String("expvar_listen", config.Config.Expvar.Listen),
   200  				zap.Bool("pprof_enabled", config.Config.Expvar.PProfEnabled),
   201  			)
   202  
   203  			listener := config.Listener{
   204  				Address: config.Config.Expvar.Listen,
   205  			}
   206  			serve(listener, handler)
   207  		}
   208  	}
   209  
   210  	r := carbonapiHttp.InitHandlers(config.Config.HeadersToPass, config.Config.HeadersToLog)
   211  	handler := handlers.CompressHandler(r)
   212  	handler = handlers.CORS()(handler)
   213  	handler = handlers.ProxyHeaders(handler)
   214  
   215  	for _, listener := range config.Config.Listeners {
   216  		serve(listener, handler)
   217  	}
   218  
   219  	wg.Wait()
   220  
   221  	if g != nil {
   222  		g.Stop()
   223  	}
   224  	if carbonapiHttp.Gstatsd != nil {
   225  		carbonapiHttp.Gstatsd.Close()
   226  	}
   227  }