Add configurable timeouts and curate default timeout settings
This commit is contained in:
parent
d84ccbc52a
commit
14a0d66410
10 changed files with 460 additions and 76 deletions
|
@ -28,8 +28,15 @@ import (
|
|||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
// DefaultHealthCheckInterval is the default health check interval.
|
||||
const DefaultHealthCheckInterval = 30 * time.Second
|
||||
const (
|
||||
// DefaultHealthCheckInterval is the default health check interval.
|
||||
DefaultHealthCheckInterval = 30 * time.Second
|
||||
|
||||
// DefaultDialTimeout when connecting to a backend server.
|
||||
DefaultDialTimeout = 30 * time.Second
|
||||
// DefaultIdleTimeout before closing an idle connection.
|
||||
DefaultIdleTimeout = 180 * time.Second
|
||||
)
|
||||
|
||||
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||
type TraefikConfiguration struct {
|
||||
|
@ -54,11 +61,13 @@ type GlobalConfiguration struct {
|
|||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
|
||||
ProvidersThrottleDuration flaeg.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
|
||||
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
|
||||
IdleTimeout flaeg.Duration `description:"maximum amount of time an idle (keep-alive) connection will remain idle before closing itself."`
|
||||
IdleTimeout flaeg.Duration `description:"(Deprecated) maximum amount of time an idle (keep-alive) connection will remain idle before closing itself."` // Deprecated
|
||||
InsecureSkipVerify bool `description:"Disable SSL certificate verification"`
|
||||
RootCAs RootCAs `description:"Add cert file for self-signed certicate"`
|
||||
Retry *Retry `description:"Enable retry sending request if network error"`
|
||||
HealthCheck *HealthCheckConfig `description:"Health check parameters"`
|
||||
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance"`
|
||||
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers"`
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings"`
|
||||
File *file.Provider `description:"Enable File backend with default settings"`
|
||||
Web *WebProvider `description:"Enable Web backend with default settings"`
|
||||
|
@ -411,6 +420,19 @@ type HealthCheckConfig struct {
|
|||
Interval flaeg.Duration `description:"Default periodicity of enabled health checks"`
|
||||
}
|
||||
|
||||
// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
|
||||
type RespondingTimeouts struct {
|
||||
ReadTimeout flaeg.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set"`
|
||||
WriteTimeout flaeg.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set"`
|
||||
IdleTimeout flaeg.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. Defaults to 180 seconds. If zero, no timeout is set"`
|
||||
}
|
||||
|
||||
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
|
||||
type ForwardingTimeouts struct {
|
||||
DialTimeout flaeg.Duration `description:"The amount of time to wait until a connection to a backend server can be established. Defaults to 30 seconds. If zero, no timeout exists"`
|
||||
ResponseHeaderTimeout flaeg.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists"`
|
||||
}
|
||||
|
||||
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
|
||||
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
//default Docker
|
||||
|
@ -573,10 +595,16 @@ func NewTraefikConfiguration() *TraefikConfiguration {
|
|||
DefaultEntryPoints: []string{},
|
||||
ProvidersThrottleDuration: flaeg.Duration(2 * time.Second),
|
||||
MaxIdleConnsPerHost: 200,
|
||||
IdleTimeout: flaeg.Duration(180 * time.Second),
|
||||
IdleTimeout: flaeg.Duration(0),
|
||||
HealthCheck: &HealthCheckConfig{
|
||||
Interval: flaeg.Duration(DefaultHealthCheckInterval),
|
||||
},
|
||||
RespondingTimeouts: &RespondingTimeouts{
|
||||
IdleTimeout: flaeg.Duration(DefaultIdleTimeout),
|
||||
},
|
||||
ForwardingTimeouts: &ForwardingTimeouts{
|
||||
DialTimeout: flaeg.Duration(DefaultDialTimeout),
|
||||
},
|
||||
CheckNewVersion: true,
|
||||
},
|
||||
ConfigFile: "",
|
||||
|
|
175
server/server.go
175
server/server.go
|
@ -7,6 +7,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -33,23 +34,25 @@ import (
|
|||
"github.com/vulcand/oxy/forward"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var oxyLogger = &OxyLogger{}
|
||||
|
||||
// Server is the reverse-proxy/load-balancer engine
|
||||
type Server struct {
|
||||
serverEntryPoints serverEntryPoints
|
||||
configurationChan chan types.ConfigMessage
|
||||
configurationValidatedChan chan types.ConfigMessage
|
||||
signals chan os.Signal
|
||||
stopChan chan bool
|
||||
providers []provider.Provider
|
||||
currentConfigurations safe.Safe
|
||||
globalConfiguration GlobalConfiguration
|
||||
accessLoggerMiddleware *accesslog.LogHandler
|
||||
routinesPool *safe.Pool
|
||||
leadership *cluster.Leadership
|
||||
serverEntryPoints serverEntryPoints
|
||||
configurationChan chan types.ConfigMessage
|
||||
configurationValidatedChan chan types.ConfigMessage
|
||||
signals chan os.Signal
|
||||
stopChan chan bool
|
||||
providers []provider.Provider
|
||||
currentConfigurations safe.Safe
|
||||
globalConfiguration GlobalConfiguration
|
||||
accessLoggerMiddleware *accesslog.LogHandler
|
||||
routinesPool *safe.Pool
|
||||
leadership *cluster.Leadership
|
||||
defaultForwardingRoundTripper http.RoundTripper
|
||||
}
|
||||
|
||||
type serverEntryPoints map[string]*serverEntryPoint
|
||||
|
@ -82,6 +85,8 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
|||
server.currentConfigurations.Set(currentConfigurations)
|
||||
server.globalConfiguration = globalConfiguration
|
||||
server.routinesPool = safe.NewPool(context.Background())
|
||||
server.defaultForwardingRoundTripper = createHTTPTransport(globalConfiguration)
|
||||
|
||||
if globalConfiguration.Cluster != nil {
|
||||
// leadership creation if cluster mode
|
||||
server.leadership = cluster.NewLeadership(server.routinesPool.Ctx(), globalConfiguration.Cluster)
|
||||
|
@ -101,6 +106,60 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
|||
return server
|
||||
}
|
||||
|
||||
// createHTTPTransport creates an http.Transport configured with the GlobalConfiguration settings.
|
||||
// For the settings that can't be configured in Traefik it uses the default http.Transport settings.
|
||||
// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost
|
||||
// in Traefik at this point in time. Setting this value to the default of 100 could lead to confusing
|
||||
// behaviour and backwards compatibility issues.
|
||||
func createHTTPTransport(globalConfiguration GlobalConfiguration) *http.Transport {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}
|
||||
if globalConfiguration.ForwardingTimeouts != nil {
|
||||
dialer.Timeout = time.Duration(globalConfiguration.ForwardingTimeouts.DialTimeout)
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: dialer.DialContext,
|
||||
MaxIdleConnsPerHost: globalConfiguration.MaxIdleConnsPerHost,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
if globalConfiguration.ForwardingTimeouts != nil {
|
||||
transport.ResponseHeaderTimeout = time.Duration(globalConfiguration.ForwardingTimeouts.ResponseHeaderTimeout)
|
||||
}
|
||||
if globalConfiguration.InsecureSkipVerify {
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
if len(globalConfiguration.RootCAs) > 0 {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
RootCAs: createRootCACertPool(globalConfiguration.RootCAs),
|
||||
}
|
||||
http2.ConfigureTransport(transport)
|
||||
}
|
||||
|
||||
return transport
|
||||
}
|
||||
|
||||
func createRootCACertPool(rootCAs RootCAs) *x509.CertPool {
|
||||
roots := x509.NewCertPool()
|
||||
|
||||
for _, cert := range rootCAs {
|
||||
certContent, err := cert.Read()
|
||||
if err != nil {
|
||||
log.Error("Error while read RootCAs", err)
|
||||
continue
|
||||
}
|
||||
roots.AppendCertsFromPEM(certContent)
|
||||
}
|
||||
|
||||
return roots
|
||||
}
|
||||
|
||||
// Start starts the server.
|
||||
func (server *Server) Start() {
|
||||
server.startHTTPServers()
|
||||
|
@ -224,7 +283,7 @@ func (server *Server) setupServerEntryPoint(newServerEntryPointName string, newS
|
|||
}
|
||||
serverMiddlewares = append(serverMiddlewares, ipWhitelistMiddleware)
|
||||
}
|
||||
newsrv, err := server.prepareServer(newServerEntryPointName, newServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], serverMiddlewares...)
|
||||
newsrv, err := server.prepareServer(newServerEntryPointName, server.globalConfiguration.EntryPoints[newServerEntryPointName], newServerEntryPoint.httpRouter, serverMiddlewares...)
|
||||
if err != nil {
|
||||
log.Fatal("Error preparing server: ", err)
|
||||
}
|
||||
|
@ -551,14 +610,17 @@ func (server *Server) startServer(srv *http.Server, globalConfiguration GlobalCo
|
|||
}
|
||||
}
|
||||
|
||||
func (server *Server) prepareServer(entryPointName string, router *middlewares.HandlerSwitcher, entryPoint *EntryPoint, middlewares ...negroni.Handler) (*http.Server, error) {
|
||||
log.Infof("Preparing server %s %+v", entryPointName, entryPoint)
|
||||
func (server *Server) prepareServer(entryPointName string, entryPoint *EntryPoint, router *middlewares.HandlerSwitcher, middlewares ...negroni.Handler) (*http.Server, error) {
|
||||
readTimeout, writeTimeout, idleTimeout := buildServerTimeouts(server.globalConfiguration)
|
||||
log.Infof("Preparing server %s %+v with readTimeout=%s writeTimeout=%s idleTimeout=%s", entryPointName, entryPoint, readTimeout, writeTimeout, idleTimeout)
|
||||
|
||||
// middlewares
|
||||
var negroni = negroni.New()
|
||||
n := negroni.New()
|
||||
for _, middleware := range middlewares {
|
||||
negroni.Use(middleware)
|
||||
n.Use(middleware)
|
||||
}
|
||||
negroni.UseHandler(router)
|
||||
n.UseHandler(router)
|
||||
|
||||
tlsConfig, err := server.createTLSConfig(entryPointName, entryPoint.TLS, router)
|
||||
if err != nil {
|
||||
log.Errorf("Error creating TLS config: %s", err)
|
||||
|
@ -566,13 +628,37 @@ func (server *Server) prepareServer(entryPointName string, router *middlewares.H
|
|||
}
|
||||
|
||||
return &http.Server{
|
||||
Addr: entryPoint.Address,
|
||||
Handler: negroni,
|
||||
TLSConfig: tlsConfig,
|
||||
IdleTimeout: time.Duration(server.globalConfiguration.IdleTimeout),
|
||||
Addr: entryPoint.Address,
|
||||
Handler: n,
|
||||
TLSConfig: tlsConfig,
|
||||
ReadTimeout: readTimeout,
|
||||
WriteTimeout: writeTimeout,
|
||||
IdleTimeout: idleTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildServerTimeouts(globalConfig GlobalConfiguration) (readTimeout, writeTimeout, idleTimeout time.Duration) {
|
||||
readTimeout = time.Duration(0)
|
||||
writeTimeout = time.Duration(0)
|
||||
if globalConfig.RespondingTimeouts != nil {
|
||||
readTimeout = time.Duration(globalConfig.RespondingTimeouts.ReadTimeout)
|
||||
writeTimeout = time.Duration(globalConfig.RespondingTimeouts.WriteTimeout)
|
||||
}
|
||||
|
||||
// When RespondingTimeouts.IdleTimout is configured, always use that setting
|
||||
if globalConfig.RespondingTimeouts != nil {
|
||||
idleTimeout = time.Duration(globalConfig.RespondingTimeouts.IdleTimeout)
|
||||
} else if globalConfig.IdleTimeout != 0 {
|
||||
// Backwards compatibility for deprecated IdleTimeout
|
||||
idleTimeout = time.Duration(globalConfig.IdleTimeout)
|
||||
} else {
|
||||
// Default value if neither the deprecated IdleTimeout nor the new RespondingTimeouts.IdleTimout are configured
|
||||
idleTimeout = time.Duration(DefaultIdleTimeout)
|
||||
}
|
||||
|
||||
return readTimeout, writeTimeout, idleTimeout
|
||||
}
|
||||
|
||||
func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) map[string]*serverEntryPoint {
|
||||
serverEntryPoints := make(map[string]*serverEntryPoint)
|
||||
for entryPointName := range globalConfiguration.EntryPoints {
|
||||
|
@ -584,15 +670,22 @@ func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration)
|
|||
return serverEntryPoints
|
||||
}
|
||||
|
||||
// clientTLSRoundTripper is used for forwarding client authentication to
|
||||
// backend server
|
||||
func clientTLSRoundTripper(config *tls.Config) http.RoundTripper {
|
||||
if config == nil {
|
||||
return http.DefaultTransport
|
||||
}
|
||||
return &http.Transport{
|
||||
TLSClientConfig: config,
|
||||
// getRoundTripper will either use server.defaultForwardingRoundTripper or create a new one
|
||||
// given a custom TLS configuration is passed and the passTLSCert option is set to true.
|
||||
func (server *Server) getRoundTripper(globalConfiguration GlobalConfiguration, passTLSCert bool, tls *TLS) (http.RoundTripper, error) {
|
||||
if passTLSCert {
|
||||
tlsConfig, err := createClientTLSConfig(tls)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create TLSClientConfig: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transport := createHTTPTransport(globalConfiguration)
|
||||
transport.TLSClientConfig = tlsConfig
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
return server.defaultForwardingRoundTripper, nil
|
||||
}
|
||||
|
||||
// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic
|
||||
|
@ -660,29 +753,20 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
|||
if backends[entryPointName+frontend.Backend] == nil {
|
||||
log.Debugf("Creating backend %s", frontend.Backend)
|
||||
|
||||
var (
|
||||
tlsConfig *tls.Config
|
||||
err error
|
||||
lb http.Handler
|
||||
)
|
||||
|
||||
if frontend.PassTLSCert {
|
||||
tlsConfig, err = createClientTLSConfig(entryPoint.TLS)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create TLS config for frontend %s: %v", frontendName, err)
|
||||
continue frontend
|
||||
}
|
||||
roundTripper, err := server.getRoundTripper(globalConfiguration, frontend.PassTLSCert, entryPoint.TLS)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create RoundTripper for frontend %s: %v", frontendName, err)
|
||||
log.Errorf("Skipping frontend %s...", frontendName)
|
||||
continue frontend
|
||||
}
|
||||
|
||||
// passing nil will use the roundtripper http.DefaultTransport
|
||||
rt := clientTLSRoundTripper(tlsConfig)
|
||||
|
||||
fwd, err := forward.New(
|
||||
forward.Logger(oxyLogger),
|
||||
forward.PassHostHeader(frontend.PassHostHeader),
|
||||
forward.RoundTripper(rt),
|
||||
forward.RoundTripper(roundTripper),
|
||||
forward.ErrorHandler(errorHandler),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error creating forwarder for frontend %s: %v", frontendName, err)
|
||||
log.Errorf("Skipping frontend %s...", frontendName)
|
||||
|
@ -720,6 +804,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
|||
sticky = roundrobin.NewStickySession(cookiename)
|
||||
}
|
||||
|
||||
var lb http.Handler
|
||||
switch lbMethod {
|
||||
case types.Drr:
|
||||
log.Debugf("Creating load-balancer drr")
|
||||
|
|
|
@ -36,6 +36,86 @@ func (lb *testLoadBalancer) Servers() []*url.URL {
|
|||
return []*url.URL{}
|
||||
}
|
||||
|
||||
func TestPrepareServerTimeouts(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
globalConfig GlobalConfiguration
|
||||
wantIdleTimeout time.Duration
|
||||
wantReadTimeout time.Duration
|
||||
wantWriteTimeout time.Duration
|
||||
}{
|
||||
{
|
||||
desc: "full configuration",
|
||||
globalConfig: GlobalConfiguration{
|
||||
RespondingTimeouts: &RespondingTimeouts{
|
||||
IdleTimeout: flaeg.Duration(10 * time.Second),
|
||||
ReadTimeout: flaeg.Duration(12 * time.Second),
|
||||
WriteTimeout: flaeg.Duration(14 * time.Second),
|
||||
},
|
||||
},
|
||||
wantIdleTimeout: time.Duration(10 * time.Second),
|
||||
wantReadTimeout: time.Duration(12 * time.Second),
|
||||
wantWriteTimeout: time.Duration(14 * time.Second),
|
||||
},
|
||||
{
|
||||
desc: "using defaults",
|
||||
globalConfig: GlobalConfiguration{},
|
||||
wantIdleTimeout: time.Duration(180 * time.Second),
|
||||
wantReadTimeout: time.Duration(0 * time.Second),
|
||||
wantWriteTimeout: time.Duration(0 * time.Second),
|
||||
},
|
||||
{
|
||||
desc: "deprecated IdleTimeout configured",
|
||||
globalConfig: GlobalConfiguration{
|
||||
IdleTimeout: flaeg.Duration(45 * time.Second),
|
||||
},
|
||||
wantIdleTimeout: time.Duration(45 * time.Second),
|
||||
wantReadTimeout: time.Duration(0 * time.Second),
|
||||
wantWriteTimeout: time.Duration(0 * time.Second),
|
||||
},
|
||||
{
|
||||
desc: "deprecated and new IdleTimeout configured",
|
||||
globalConfig: GlobalConfiguration{
|
||||
IdleTimeout: flaeg.Duration(45 * time.Second),
|
||||
RespondingTimeouts: &RespondingTimeouts{
|
||||
IdleTimeout: flaeg.Duration(80 * time.Second),
|
||||
},
|
||||
},
|
||||
wantIdleTimeout: time.Duration(80 * time.Second),
|
||||
wantReadTimeout: time.Duration(0 * time.Second),
|
||||
wantWriteTimeout: time.Duration(0 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
entryPointName := "http"
|
||||
entryPoint := &EntryPoint{Address: "localhost:8080"}
|
||||
router := middlewares.NewHandlerSwitcher(mux.NewRouter())
|
||||
|
||||
srv := NewServer(test.globalConfig)
|
||||
httpServer, err := srv.prepareServer(entryPointName, entryPoint, router)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error when preparing srv: %s", err)
|
||||
}
|
||||
|
||||
if httpServer.IdleTimeout != test.wantIdleTimeout {
|
||||
t.Errorf("Got %s as IdleTimeout, want %s", httpServer.IdleTimeout, test.wantIdleTimeout)
|
||||
}
|
||||
if httpServer.ReadTimeout != test.wantReadTimeout {
|
||||
t.Errorf("Got %s as ReadTimeout, want %s", httpServer.ReadTimeout, test.wantReadTimeout)
|
||||
}
|
||||
if httpServer.WriteTimeout != test.wantWriteTimeout {
|
||||
t.Errorf("Got %s as WriteTimeout, want %s", httpServer.WriteTimeout, test.wantWriteTimeout)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerMultipleFrontendRules(t *testing.T) {
|
||||
cases := []struct {
|
||||
expression string
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue