traefik/cmd/traefik/traefik.go

574 lines
17 KiB
Go
Raw Normal View History

2015-09-11 14:37:13 +00:00
package main
import (
2018-03-14 12:14:03 +00:00
"context"
2020-12-18 17:44:03 +00:00
"crypto/x509"
2016-05-03 14:52:14 +00:00
"encoding/json"
"fmt"
stdlog "log"
2017-08-21 21:18:02 +00:00
"net/http"
"os"
2021-06-18 16:43:10 +00:00
"os/signal"
2017-04-27 15:36:04 +00:00
"path/filepath"
2020-03-09 10:12:05 +00:00
"sort"
"strings"
2021-06-18 16:43:10 +00:00
"syscall"
"time"
"github.com/coreos/go-systemd/daemon"
"github.com/go-acme/lego/v4/challenge"
2020-12-18 17:44:03 +00:00
gokitmetrics "github.com/go-kit/kit/metrics"
2018-01-22 11:16:03 +00:00
"github.com/sirupsen/logrus"
"github.com/traefik/paerser/cli"
"github.com/traefik/traefik/v2/cmd"
"github.com/traefik/traefik/v2/cmd/healthcheck"
cmdVersion "github.com/traefik/traefik/v2/cmd/version"
tcli "github.com/traefik/traefik/v2/pkg/cli"
"github.com/traefik/traefik/v2/pkg/collector"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/config/runtime"
"github.com/traefik/traefik/v2/pkg/config/static"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/metrics"
"github.com/traefik/traefik/v2/pkg/middlewares/accesslog"
"github.com/traefik/traefik/v2/pkg/pilot"
"github.com/traefik/traefik/v2/pkg/provider/acme"
"github.com/traefik/traefik/v2/pkg/provider/aggregator"
"github.com/traefik/traefik/v2/pkg/provider/traefik"
"github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/server"
"github.com/traefik/traefik/v2/pkg/server/middleware"
"github.com/traefik/traefik/v2/pkg/server/service"
traefiktls "github.com/traefik/traefik/v2/pkg/tls"
"github.com/traefik/traefik/v2/pkg/types"
"github.com/traefik/traefik/v2/pkg/version"
2018-04-11 14:30:04 +00:00
"github.com/vulcand/oxy/roundrobin"
2015-09-11 23:55:10 +00:00
)
2015-09-11 14:37:13 +00:00
func main() {
2018-02-19 00:04:45 +00:00
// traefik config inits
tConfig := cmd.NewTraefikConfiguration()
loaders := []cli.ResourceLoader{&tcli.FileLoader{}, &tcli.FlagLoader{}, &tcli.EnvLoader{}}
2018-02-19 00:04:45 +00:00
cmdTraefik := &cli.Command{
2016-05-03 14:52:14 +00:00
Name: "traefik",
2019-04-05 10:44:03 +00:00
Description: `Traefik is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.
2016-05-03 14:52:14 +00:00
Complete documentation is available at https://traefik.io`,
Configuration: tConfig,
Resources: loaders,
Run: func(_ []string) error {
return runCmd(&tConfig.Configuration)
2016-05-03 14:52:14 +00:00
},
}
err := cmdTraefik.AddCommand(healthcheck.NewCmd(&tConfig.Configuration, loaders))
2016-07-29 15:36:53 +00:00
if err != nil {
stdlog.Println(err)
2018-02-19 00:05:39 +00:00
os.Exit(1)
2016-07-29 15:36:53 +00:00
}
err = cmdTraefik.AddCommand(cmdVersion.NewCmd())
2016-06-24 07:58:42 +00:00
if err != nil {
stdlog.Println(err)
2018-02-19 00:05:39 +00:00
os.Exit(1)
2016-06-24 07:58:42 +00:00
}
err = cli.Execute(cmdTraefik)
if err != nil {
stdlog.Println(err)
logrus.Exit(1)
2015-11-23 14:41:16 +00:00
}
2016-05-03 14:52:14 +00:00
logrus.Exit(0)
2015-09-11 14:37:13 +00:00
}
2016-05-03 14:52:14 +00:00
func runCmd(staticConfiguration *static.Configuration) error {
configureLogging(staticConfiguration)
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment
2018-08-06 18:00:03 +00:00
if err := roundrobin.SetDefaultWeight(0); err != nil {
log.WithoutContext().Errorf("Could not set round robin default weight: %v", err)
2018-08-06 18:00:03 +00:00
}
2018-04-11 14:30:04 +00:00
staticConfiguration.SetEffectiveConfiguration()
if err := staticConfiguration.ValidateConfiguration(); err != nil {
return err
}
log.WithoutContext().Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
2018-10-01 17:18:03 +00:00
jsonConf, err := json.Marshal(staticConfiguration)
2018-10-01 17:18:03 +00:00
if err != nil {
log.WithoutContext().Errorf("Could not marshal static configuration: %v", err)
log.WithoutContext().Debugf("Static configuration loaded [struct] %#v", staticConfiguration)
2018-10-01 17:18:03 +00:00
} else {
log.WithoutContext().Debugf("Static configuration loaded %s", string(jsonConf))
2018-10-01 17:18:03 +00:00
}
if staticConfiguration.Global.CheckNewVersion {
checkNewVersion()
}
stats(staticConfiguration)
2017-11-25 12:36:03 +00:00
svr, err := setupServer(staticConfiguration)
if err != nil {
return err
}
2021-06-18 16:43:10 +00:00
ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
if staticConfiguration.Ping != nil {
staticConfiguration.Ping.WithContext(ctx)
}
svr.Start(ctx)
defer svr.Close()
2017-11-23 15:10:04 +00:00
sent, err := daemon.SdNotify(false, "READY=1")
if !sent && err != nil {
log.WithoutContext().Errorf("Failed to notify: %v", err)
}
2017-11-23 15:10:04 +00:00
t, err := daemon.SdWatchdogEnabled(false)
if err != nil {
log.WithoutContext().Errorf("Could not enable Watchdog: %v", err)
} else if t != 0 {
// Send a ping each half time given
2019-02-05 16:10:03 +00:00
t /= 2
log.WithoutContext().Infof("Watchdog activated with timer duration %s", t)
safe.Go(func() {
tick := time.Tick(t)
for range tick {
resp, errHealthCheck := healthcheck.Do(*staticConfiguration)
if resp != nil {
_ = resp.Body.Close()
}
if staticConfiguration.Ping == nil || errHealthCheck == nil {
2017-11-23 15:10:04 +00:00
if ok, _ := daemon.SdNotify(false, "WATCHDOG=1"); !ok {
log.WithoutContext().Error("Fail to tick watchdog")
2017-11-23 15:10:04 +00:00
}
} else {
log.WithoutContext().Error(errHealthCheck)
}
}
})
}
2017-11-23 15:10:04 +00:00
svr.Wait()
log.WithoutContext().Info("Shutting down")
return nil
}
func setupServer(staticConfiguration *static.Configuration) (*server.Server, error) {
providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers)
ctx := context.Background()
routinesPool := safe.NewPool(ctx)
// adds internal provider
err := providerAggregator.AddProvider(traefik.New(*staticConfiguration))
if err != nil {
return nil, err
}
// ACME
tlsManager := traefiktls.NewManager()
httpChallengeProvider := acme.NewChallengeHTTP()
// we need to wait at least 2 times the ProvidersThrottleDuration to be sure to handle the challenge.
tlsChallengeProvider := acme.NewChallengeTLSALPN(time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration) * 2)
err = providerAggregator.AddProvider(tlsChallengeProvider)
if err != nil {
return nil, err
}
acmeProviders := initACMEProvider(staticConfiguration, &providerAggregator, tlsManager, httpChallengeProvider, tlsChallengeProvider)
// Entrypoints
serverEntryPointsTCP, err := server.NewTCPEntryPoints(staticConfiguration.EntryPoints)
if err != nil {
return nil, err
}
serverEntryPointsUDP, err := server.NewUDPEntryPoints(staticConfiguration.EntryPoints)
if err != nil {
return nil, err
}
// Pilot
var aviator *pilot.Pilot
var pilotRegistry *metrics.PilotRegistry
if isPilotEnabled(staticConfiguration) {
pilotRegistry = metrics.RegisterPilot()
aviator = pilot.New(staticConfiguration.Pilot.Token, pilotRegistry, routinesPool)
routinesPool.GoCtx(func(ctx context.Context) {
aviator.Tick(ctx)
})
}
if staticConfiguration.Pilot != nil {
version.PilotEnabled = staticConfiguration.Pilot.Dashboard
}
// Plugins
pluginBuilder, err := createPluginBuilder(staticConfiguration)
2020-04-20 16:36:34 +00:00
if err != nil {
return nil, err
}
// Providers plugins
for name, conf := range staticConfiguration.Providers.Plugin {
p, err := pluginBuilder.BuildProvider(name, conf)
if err != nil {
return nil, fmt.Errorf("plugin: failed to build provider: %w", err)
}
err = providerAggregator.AddProvider(p)
if err != nil {
return nil, fmt.Errorf("plugin: failed to add provider: %w", err)
}
}
// Metrics
metricRegistries := registerMetricClients(staticConfiguration.Metrics)
if pilotRegistry != nil {
metricRegistries = append(metricRegistries, pilotRegistry)
2020-04-20 16:36:34 +00:00
}
metricsRegistry := metrics.NewMultiRegistry(metricRegistries)
2020-04-20 16:36:34 +00:00
// Service manager factory
roundTripperManager := service.NewRoundTripperManager()
acmeHTTPHandler := getHTTPChallengeHandler(acmeProviders, httpChallengeProvider)
managerFactory := service.NewManagerFactory(*staticConfiguration, routinesPool, metricsRegistry, roundTripperManager, acmeHTTPHandler)
// Router factory
accessLog := setupAccessLog(staticConfiguration.AccessLog)
chainBuilder := middleware.NewChainBuilder(*staticConfiguration, metricsRegistry, accessLog)
2021-04-30 08:22:04 +00:00
routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, chainBuilder, pluginBuilder, metricsRegistry)
// Watcher
2020-03-09 10:12:05 +00:00
watcher := server.NewConfigurationWatcher(
routinesPool,
providerAggregator,
time.Duration(staticConfiguration.Providers.ProvidersThrottleDuration),
getDefaultsEntrypoints(staticConfiguration),
"internal",
)
// TLS
watcher.AddListener(func(conf dynamic.Configuration) {
ctx := context.Background()
tlsManager.UpdateConfigs(ctx, conf.TLS.Stores, conf.TLS.Options, conf.TLS.Certificates)
2020-12-18 17:44:03 +00:00
gauge := metricsRegistry.TLSCertsNotAfterTimestampGauge()
for _, certificate := range tlsManager.GetCertificates() {
appendCertMetric(gauge, certificate)
}
})
// Metrics
watcher.AddListener(func(_ dynamic.Configuration) {
metricsRegistry.ConfigReloadsCounter().Add(1)
metricsRegistry.LastConfigReloadSuccessGauge().Set(float64(time.Now().Unix()))
})
// Server Transports
2020-09-11 13:40:03 +00:00
watcher.AddListener(func(conf dynamic.Configuration) {
roundTripperManager.Update(conf.HTTP.ServersTransports)
})
// Switch router
watcher.AddListener(switchRouter(routerFactory, serverEntryPointsTCP, serverEntryPointsUDP, aviator))
// Metrics
if metricsRegistry.IsEpEnabled() || metricsRegistry.IsSvcEnabled() {
var eps []string
for key := range serverEntryPointsTCP {
eps = append(eps, key)
}
watcher.AddListener(func(conf dynamic.Configuration) {
metrics.OnConfigurationUpdate(conf, eps)
})
}
// TLS challenge
watcher.AddListener(tlsChallengeProvider.ListenConfiguration)
// ACME
resolverNames := map[string]struct{}{}
for _, p := range acmeProviders {
resolverNames[p.ResolverName] = struct{}{}
watcher.AddListener(p.ListenConfiguration)
}
// Certificate resolver logs
watcher.AddListener(func(config dynamic.Configuration) {
for rtName, rt := range config.HTTP.Routers {
if rt.TLS == nil || rt.TLS.CertResolver == "" {
continue
}
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok {
log.WithoutContext().Errorf("the router %s uses a non-existent resolver: %s", rtName, rt.TLS.CertResolver)
}
}
})
return server.NewServer(routinesPool, serverEntryPointsTCP, serverEntryPointsUDP, watcher, chainBuilder, accessLog), nil
}
func getHTTPChallengeHandler(acmeProviders []*acme.Provider, httpChallengeProvider http.Handler) http.Handler {
var acmeHTTPHandler http.Handler
for _, p := range acmeProviders {
if p != nil && p.HTTPChallenge != nil {
acmeHTTPHandler = httpChallengeProvider
break
}
}
return acmeHTTPHandler
}
func getDefaultsEntrypoints(staticConfiguration *static.Configuration) []string {
var defaultEntryPoints []string
for name, cfg := range staticConfiguration.EntryPoints {
protocol, err := cfg.GetProtocol()
if err != nil {
// Should never happen because Traefik should not start if protocol is invalid.
log.WithoutContext().Errorf("Invalid protocol: %v", err)
}
if protocol != "udp" && name != static.DefaultInternalEntryPointName {
defaultEntryPoints = append(defaultEntryPoints, name)
}
}
sort.Strings(defaultEntryPoints)
return defaultEntryPoints
}
func switchRouter(routerFactory *server.RouterFactory, serverEntryPointsTCP server.TCPEntryPoints, serverEntryPointsUDP server.UDPEntryPoints, aviator *pilot.Pilot) func(conf dynamic.Configuration) {
return func(conf dynamic.Configuration) {
rtConf := runtime.NewConfig(conf)
routers, udpRouters := routerFactory.CreateRouters(rtConf)
if aviator != nil {
aviator.SetDynamicConfiguration(conf)
}
serverEntryPointsTCP.Switch(routers)
serverEntryPointsUDP.Switch(udpRouters)
}
}
2020-05-11 10:06:07 +00:00
// initACMEProvider creates an acme provider from the ACME part of globalConfiguration.
func initACMEProvider(c *static.Configuration, providerAggregator *aggregator.ProviderAggregator, tlsManager *traefiktls.Manager, httpChallengeProvider, tlsChallengeProvider challenge.Provider) []*acme.Provider {
localStores := map[string]*acme.LocalStore{}
var resolvers []*acme.Provider
for name, resolver := range c.CertificatesResolvers {
2021-03-04 08:02:03 +00:00
if resolver.ACME == nil {
continue
}
2021-03-04 08:02:03 +00:00
if localStores[resolver.ACME.Storage] == nil {
localStores[resolver.ACME.Storage] = acme.NewLocalStore(resolver.ACME.Storage)
}
2021-03-04 08:02:03 +00:00
p := &acme.Provider{
Configuration: resolver.ACME,
Store: localStores[resolver.ACME.Storage],
ResolverName: name,
HTTPChallengeProvider: httpChallengeProvider,
TLSChallengeProvider: tlsChallengeProvider,
}
2021-03-04 08:02:03 +00:00
if err := providerAggregator.AddProvider(p); err != nil {
log.WithoutContext().Errorf("The ACME resolver %q is skipped from the resolvers list because: %v", name, err)
continue
}
2021-03-04 08:02:03 +00:00
p.SetTLSManager(tlsManager)
2021-03-04 08:02:03 +00:00
p.SetConfigListenerChan(make(chan dynamic.Configuration))
resolvers = append(resolvers, p)
}
return resolvers
}
func registerMetricClients(metricsConfig *types.Metrics) []metrics.Registry {
if metricsConfig == nil {
return nil
}
var registries []metrics.Registry
if metricsConfig.Prometheus != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "prometheus"))
prometheusRegister := metrics.RegisterPrometheus(ctx, metricsConfig.Prometheus)
if prometheusRegister != nil {
registries = append(registries, prometheusRegister)
log.FromContext(ctx).Debug("Configured Prometheus metrics")
}
}
if metricsConfig.Datadog != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "datadog"))
registries = append(registries, metrics.RegisterDatadog(ctx, metricsConfig.Datadog))
log.FromContext(ctx).Debugf("Configured Datadog metrics: pushing to %s once every %s",
metricsConfig.Datadog.Address, metricsConfig.Datadog.PushInterval)
}
if metricsConfig.StatsD != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "statsd"))
registries = append(registries, metrics.RegisterStatsd(ctx, metricsConfig.StatsD))
log.FromContext(ctx).Debugf("Configured StatsD metrics: pushing to %s once every %s",
metricsConfig.StatsD.Address, metricsConfig.StatsD.PushInterval)
}
if metricsConfig.InfluxDB != nil {
ctx := log.With(context.Background(), log.Str(log.MetricsProviderName, "influxdb"))
registries = append(registries, metrics.RegisterInfluxDB(ctx, metricsConfig.InfluxDB))
log.FromContext(ctx).Debugf("Configured InfluxDB metrics: pushing to %s once every %s",
metricsConfig.InfluxDB.Address, metricsConfig.InfluxDB.PushInterval)
}
return registries
}
2020-12-18 17:44:03 +00:00
func appendCertMetric(gauge gokitmetrics.Gauge, certificate *x509.Certificate) {
sort.Strings(certificate.DNSNames)
labels := []string{
"cn", certificate.Subject.CommonName,
"serial", certificate.SerialNumber.String(),
"sans", strings.Join(certificate.DNSNames, ","),
}
notAfter := float64(certificate.NotAfter.Unix())
gauge.With(labels...).Set(notAfter)
}
func setupAccessLog(conf *types.AccessLog) *accesslog.Handler {
if conf == nil {
return nil
}
accessLoggerMiddleware, err := accesslog.NewHandler(conf)
if err != nil {
log.WithoutContext().Warnf("Unable to create access logger : %v", err)
return nil
}
return accessLoggerMiddleware
}
func configureLogging(staticConfiguration *static.Configuration) {
// configure default log flags
stdlog.SetFlags(stdlog.Lshortfile | stdlog.LstdFlags)
// configure log level
// an explicitly defined log level always has precedence. if none is
// given and debug mode is disabled, the default is ERROR, and DEBUG
// otherwise.
levelStr := "error"
if staticConfiguration.Log != nil && staticConfiguration.Log.Level != "" {
levelStr = strings.ToLower(staticConfiguration.Log.Level)
}
level, err := logrus.ParseLevel(levelStr)
if err != nil {
log.WithoutContext().Errorf("Error getting level: %v", err)
}
log.SetLevel(level)
2018-07-31 17:28:03 +00:00
var logFile string
if staticConfiguration.Log != nil && len(staticConfiguration.Log.FilePath) > 0 {
logFile = staticConfiguration.Log.FilePath
}
// configure log format
var formatter logrus.Formatter
if staticConfiguration.Log != nil && staticConfiguration.Log.Format == "json" {
formatter = &logrus.JSONFormatter{}
} else {
2018-02-19 00:04:45 +00:00
disableColors := len(logFile) > 0
formatter = &logrus.TextFormatter{DisableColors: disableColors, FullTimestamp: true, DisableSorting: true}
}
log.SetFormatter(formatter)
if len(logFile) > 0 {
dir := filepath.Dir(logFile)
2017-04-27 15:36:04 +00:00
2020-07-07 12:42:03 +00:00
if err := os.MkdirAll(dir, 0o755); err != nil {
log.WithoutContext().Errorf("Failed to create log path %s: %s", dir, err)
2017-04-27 15:36:04 +00:00
}
err = log.OpenFile(logFile)
logrus.RegisterExitHandler(func() {
if err := log.CloseFile(); err != nil {
log.WithoutContext().Errorf("Error while closing log: %v", err)
2016-05-03 14:52:14 +00:00
}
})
2016-05-03 14:52:14 +00:00
if err != nil {
log.WithoutContext().Errorf("Error while opening log file %s: %v", logFile, err)
2016-05-03 14:52:14 +00:00
}
}
}
2017-10-02 08:32:02 +00:00
func checkNewVersion() {
2017-11-25 12:36:03 +00:00
ticker := time.Tick(24 * time.Hour)
safe.Go(func() {
for time.Sleep(10 * time.Minute); ; <-ticker {
version.CheckNewVersion()
}
})
}
func stats(staticConfiguration *static.Configuration) {
2019-09-16 15:26:06 +00:00
logger := log.WithoutContext()
2019-03-14 18:32:03 +00:00
2019-09-16 15:26:06 +00:00
if staticConfiguration.Global.SendAnonymousUsage {
logger.Info(`Stats collection is enabled.`)
logger.Info(`Many thanks for contributing to Traefik's improvement by allowing us to receive anonymous information from your configuration.`)
logger.Info(`Help us improve Traefik by leaving this feature on :)`)
logger.Info(`More details on: https://doc.traefik.io/traefik/contributing/data-collection/`)
collect(staticConfiguration)
2017-11-25 12:36:03 +00:00
} else {
2019-09-16 15:26:06 +00:00
logger.Info(`
2017-11-25 12:36:03 +00:00
Stats collection is disabled.
Help us improve Traefik by turning this feature on :)
More details on: https://doc.traefik.io/traefik/contributing/data-collection/
2017-11-25 12:36:03 +00:00
`)
}
}
func collect(staticConfiguration *static.Configuration) {
2017-11-25 12:36:03 +00:00
ticker := time.Tick(24 * time.Hour)
2017-10-02 08:32:02 +00:00
safe.Go(func() {
2017-11-25 12:36:03 +00:00
for time.Sleep(10 * time.Minute); ; <-ticker {
if err := collector.Collect(staticConfiguration); err != nil {
log.WithoutContext().Debug(err)
2017-10-02 08:32:02 +00:00
}
}
})
}