2018-11-14 09:18:03 +00:00
package service
import (
"context"
2019-08-26 08:30:05 +00:00
"errors"
2018-11-14 09:18:03 +00:00
"fmt"
"net/http"
"net/http/httputil"
"net/url"
2019-08-26 17:00:04 +00:00
"reflect"
2018-11-14 09:18:03 +00:00
"time"
2019-01-18 14:18:04 +00:00
"github.com/containous/alice"
2020-09-16 13:46:04 +00:00
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/config/runtime"
"github.com/traefik/traefik/v2/pkg/healthcheck"
"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/middlewares/emptybackendhandler"
metricsMiddle "github.com/traefik/traefik/v2/pkg/middlewares/metrics"
"github.com/traefik/traefik/v2/pkg/middlewares/pipelining"
"github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/server/cookie"
"github.com/traefik/traefik/v2/pkg/server/provider"
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror"
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr"
2018-11-14 09:18:03 +00:00
"github.com/vulcand/oxy/roundrobin"
2021-04-29 15:56:03 +00:00
"github.com/vulcand/oxy/roundrobin/stickycookie"
2018-11-14 09:18:03 +00:00
)
const (
defaultHealthCheckInterval = 30 * time . Second
defaultHealthCheckTimeout = 5 * time . Second
)
2020-03-05 17:03:08 +00:00
const defaultMaxBodySize int64 = - 1
2020-09-11 13:40:03 +00:00
// RoundTripperGetter is a roundtripper getter interface.
type RoundTripperGetter interface {
Get ( name string ) ( http . RoundTripper , error )
}
2020-05-11 10:06:07 +00:00
// NewManager creates a new Manager.
2020-09-11 13:40:03 +00:00
func NewManager ( configs map [ string ] * runtime . ServiceInfo , metricsRegistry metrics . Registry , routinePool * safe . Pool , roundTripperManager RoundTripperGetter ) * Manager {
2018-11-14 09:18:03 +00:00
return & Manager {
2019-08-26 17:00:04 +00:00
routinePool : routinePool ,
2019-07-18 19:36:05 +00:00
metricsRegistry : metricsRegistry ,
2018-11-14 09:18:03 +00:00
bufferPool : newBufferPool ( ) ,
2020-09-11 13:40:03 +00:00
roundTripperManager : roundTripperManager ,
2019-11-29 11:40:05 +00:00
balancers : make ( map [ string ] healthcheck . Balancers ) ,
2018-11-14 09:18:03 +00:00
configs : configs ,
}
}
2020-05-11 10:06:07 +00:00
// Manager The service manager.
2018-11-14 09:18:03 +00:00
type Manager struct {
2019-08-26 17:00:04 +00:00
routinePool * safe . Pool
2019-07-18 19:36:05 +00:00
metricsRegistry metrics . Registry
2018-11-14 09:18:03 +00:00
bufferPool httputil . BufferPool
2020-09-11 13:40:03 +00:00
roundTripperManager RoundTripperGetter
2019-11-29 11:40:05 +00:00
// balancers is the map of all Balancers, keyed by service name.
// There is one Balancer per service handler, and there is one service handler per reference to a service
// (e.g. if 2 routers refer to the same service name, 2 service handlers are created),
// which is why there is not just one Balancer per service name.
balancers map [ string ] healthcheck . Balancers
configs map [ string ] * runtime . ServiceInfo
2018-11-14 09:18:03 +00:00
}
2019-03-14 08:30:04 +00:00
// BuildHTTP Creates a http.Handler for a service configuration.
2020-09-01 16:16:04 +00:00
func ( m * Manager ) BuildHTTP ( rootCtx context . Context , serviceName string ) ( http . Handler , error ) {
2018-11-14 09:18:03 +00:00
ctx := log . With ( rootCtx , log . Str ( log . ServiceName , serviceName ) )
2020-01-27 09:40:05 +00:00
serviceName = provider . GetQualifiedName ( ctx , serviceName )
ctx = provider . AddInContext ( ctx , serviceName )
2019-01-15 13:28:04 +00:00
2019-05-16 08:58:06 +00:00
conf , ok := m . configs [ serviceName ]
if ! ok {
return nil , fmt . Errorf ( "the service %q does not exist" , serviceName )
}
2019-08-26 17:00:04 +00:00
value := reflect . ValueOf ( * conf . Service )
var count int
for i := 0 ; i < value . NumField ( ) ; i ++ {
if ! value . Field ( i ) . IsNil ( ) {
count ++
}
}
if count > 1 {
2019-09-13 18:00:06 +00:00
err := errors . New ( "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead" )
conf . AddError ( err , true )
return nil , err
2019-08-26 08:30:05 +00:00
}
var lb http . Handler
switch {
case conf . LoadBalancer != nil :
var err error
2020-09-01 16:16:04 +00:00
lb , err = m . getLoadBalancerServiceHandler ( ctx , serviceName , conf . LoadBalancer )
2019-08-26 08:30:05 +00:00
if err != nil {
conf . AddError ( err , true )
return nil , err
}
case conf . Weighted != nil :
var err error
2020-09-01 16:16:04 +00:00
lb , err = m . getWRRServiceHandler ( ctx , serviceName , conf . Weighted )
2019-08-26 08:30:05 +00:00
if err != nil {
conf . AddError ( err , true )
return nil , err
}
2019-08-26 17:00:04 +00:00
case conf . Mirroring != nil :
var err error
2020-09-01 16:16:04 +00:00
lb , err = m . getMirrorServiceHandler ( ctx , conf . Mirroring )
2019-08-26 17:00:04 +00:00
if err != nil {
conf . AddError ( err , true )
return nil , err
}
2019-08-26 08:30:05 +00:00
default :
sErr := fmt . Errorf ( "the service %q does not have any type defined" , serviceName )
2019-07-15 15:04:04 +00:00
conf . AddError ( sErr , true )
return nil , sErr
2018-11-14 09:18:03 +00:00
}
2019-05-16 08:58:06 +00:00
2019-08-26 08:30:05 +00:00
return lb , nil
}
2020-09-01 16:16:04 +00:00
func ( m * Manager ) getMirrorServiceHandler ( ctx context . Context , config * dynamic . Mirroring ) ( http . Handler , error ) {
serviceHandler , err := m . BuildHTTP ( ctx , config . Service )
2019-08-26 17:00:04 +00:00
if err != nil {
return nil , err
}
2020-03-05 17:03:08 +00:00
maxBodySize := defaultMaxBodySize
if config . MaxBodySize != nil {
maxBodySize = * config . MaxBodySize
}
handler := mirror . New ( serviceHandler , m . routinePool , maxBodySize )
2019-08-26 17:00:04 +00:00
for _ , mirrorConfig := range config . Mirrors {
2020-09-01 16:16:04 +00:00
mirrorHandler , err := m . BuildHTTP ( ctx , mirrorConfig . Name )
2019-08-26 17:00:04 +00:00
if err != nil {
return nil , err
}
err = handler . AddMirror ( mirrorHandler , mirrorConfig . Percent )
if err != nil {
return nil , err
}
}
return handler , nil
}
2020-09-01 16:16:04 +00:00
func ( m * Manager ) getWRRServiceHandler ( ctx context . Context , serviceName string , config * dynamic . WeightedRoundRobin ) ( http . Handler , error ) {
2019-08-26 08:30:05 +00:00
// TODO Handle accesslog and metrics with multiple service name
if config . Sticky != nil && config . Sticky . Cookie != nil {
config . Sticky . Cookie . Name = cookie . GetName ( config . Sticky . Cookie . Name , serviceName )
2019-05-16 08:58:06 +00:00
}
2019-08-26 08:30:05 +00:00
balancer := wrr . New ( config . Sticky )
for _ , service := range config . Services {
2020-09-01 16:16:04 +00:00
serviceHandler , err := m . BuildHTTP ( ctx , service . Name )
2019-08-26 08:30:05 +00:00
if err != nil {
return nil , err
}
balancer . AddService ( service . Name , serviceHandler , service . Weight )
}
return balancer , nil
2018-11-14 09:18:03 +00:00
}
2020-09-01 16:16:04 +00:00
func ( m * Manager ) getLoadBalancerServiceHandler ( ctx context . Context , serviceName string , service * dynamic . ServersLoadBalancer ) ( http . Handler , error ) {
2019-09-30 16:12:04 +00:00
if service . PassHostHeader == nil {
defaultPassHostHeader := true
service . PassHostHeader = & defaultPassHostHeader
}
2020-09-11 13:40:03 +00:00
if len ( service . ServersTransport ) > 0 {
service . ServersTransport = provider . GetQualifiedName ( ctx , service . ServersTransport )
}
roundTripper , err := m . roundTripperManager . Get ( service . ServersTransport )
if err != nil {
return nil , err
}
fwd , err := buildProxy ( service . PassHostHeader , service . ResponseForwarding , roundTripper , m . bufferPool )
2018-11-14 09:18:03 +00:00
if err != nil {
return nil , err
}
2019-01-18 14:18:04 +00:00
alHandler := func ( next http . Handler ) ( http . Handler , error ) {
return accesslog . NewFieldHandler ( next , accesslog . ServiceName , serviceName , accesslog . AddServiceFields ) , nil
}
2019-07-18 19:36:05 +00:00
chain := alice . New ( )
if m . metricsRegistry != nil && m . metricsRegistry . IsSvcEnabled ( ) {
chain = chain . Append ( metricsMiddle . WrapServiceHandler ( ctx , m . metricsRegistry , serviceName ) )
}
2018-11-14 09:18:03 +00:00
2019-07-18 19:36:05 +00:00
handler , err := chain . Append ( alHandler ) . Then ( pipelining . New ( ctx , fwd , "pipelining" ) )
2018-11-14 09:18:03 +00:00
if err != nil {
return nil , err
}
2019-01-18 14:18:04 +00:00
balancer , err := m . getLoadBalancer ( ctx , serviceName , service , handler )
2018-11-14 09:18:03 +00:00
if err != nil {
return nil , err
}
// TODO rename and checks
m . balancers [ serviceName ] = append ( m . balancers [ serviceName ] , balancer )
// Empty (backend with no servers)
return emptybackendhandler . New ( balancer ) , nil
}
// LaunchHealthCheck Launches the health checks.
func ( m * Manager ) LaunchHealthCheck ( ) {
backendConfigs := make ( map [ string ] * healthcheck . BackendConfig )
for serviceName , balancers := range m . balancers {
ctx := log . With ( context . Background ( ) , log . Str ( log . ServiceName , serviceName ) )
2019-03-18 10:30:07 +00:00
// TODO Should all the services handle healthcheck? Handle different types
2018-11-14 09:18:03 +00:00
service := m . configs [ serviceName ] . LoadBalancer
// Health Check
var backendHealthCheck * healthcheck . BackendConfig
2019-11-29 11:40:05 +00:00
if hcOpts := buildHealthCheckOptions ( ctx , balancers , serviceName , service . HealthCheck ) ; hcOpts != nil {
2018-11-14 09:18:03 +00:00
log . FromContext ( ctx ) . Debugf ( "Setting up healthcheck for service %s with %s" , serviceName , * hcOpts )
2020-09-11 13:40:03 +00:00
hcOpts . Transport , _ = m . roundTripperManager . Get ( service . ServersTransport )
2018-11-14 09:18:03 +00:00
backendHealthCheck = healthcheck . NewBackendConfig ( * hcOpts , serviceName )
}
if backendHealthCheck != nil {
backendConfigs [ serviceName ] = backendHealthCheck
}
}
2020-09-26 11:30:03 +00:00
healthcheck . GetHealthCheck ( m . metricsRegistry ) . SetBackendsConfiguration ( context . Background ( ) , backendConfigs )
2018-11-14 09:18:03 +00:00
}
2019-11-29 11:40:05 +00:00
func buildHealthCheckOptions ( ctx context . Context , lb healthcheck . Balancer , backend string , hc * dynamic . HealthCheck ) * healthcheck . Options {
2018-11-14 09:18:03 +00:00
if hc == nil || hc . Path == "" {
return nil
}
logger := log . FromContext ( ctx )
interval := defaultHealthCheckInterval
if hc . Interval != "" {
intervalOverride , err := time . ParseDuration ( hc . Interval )
2019-02-05 16:10:03 +00:00
switch {
case err != nil :
2018-11-14 09:18:03 +00:00
logger . Errorf ( "Illegal health check interval for '%s': %s" , backend , err )
2019-02-05 16:10:03 +00:00
case intervalOverride <= 0 :
2018-11-14 09:18:03 +00:00
logger . Errorf ( "Health check interval smaller than zero for service '%s'" , backend )
2019-02-05 16:10:03 +00:00
default :
2018-11-14 09:18:03 +00:00
interval = intervalOverride
}
}
timeout := defaultHealthCheckTimeout
if hc . Timeout != "" {
timeoutOverride , err := time . ParseDuration ( hc . Timeout )
2019-02-05 16:10:03 +00:00
switch {
case err != nil :
2018-11-14 09:18:03 +00:00
logger . Errorf ( "Illegal health check timeout for backend '%s': %s" , backend , err )
2019-02-05 16:10:03 +00:00
case timeoutOverride <= 0 :
2018-11-14 09:18:03 +00:00
logger . Errorf ( "Health check timeout smaller than zero for backend '%s', backend" , backend )
2019-02-05 16:10:03 +00:00
default :
2018-11-14 09:18:03 +00:00
timeout = timeoutOverride
}
}
if timeout >= interval {
2019-05-16 08:58:06 +00:00
logger . Warnf ( "Health check timeout for backend '%s' should be lower than the health check interval. Interval set to timeout + 1 second (%s)." , backend , interval )
2018-11-14 09:18:03 +00:00
}
2020-02-26 16:28:04 +00:00
followRedirects := true
if hc . FollowRedirects != nil {
followRedirects = * hc . FollowRedirects
}
2018-11-14 09:18:03 +00:00
return & healthcheck . Options {
2020-02-26 16:28:04 +00:00
Scheme : hc . Scheme ,
Path : hc . Path ,
Port : hc . Port ,
Interval : interval ,
Timeout : timeout ,
LB : lb ,
Hostname : hc . Hostname ,
Headers : hc . Headers ,
FollowRedirects : followRedirects ,
2018-11-14 09:18:03 +00:00
}
}
2019-08-26 08:30:05 +00:00
func ( m * Manager ) getLoadBalancer ( ctx context . Context , serviceName string , service * dynamic . ServersLoadBalancer , fwd http . Handler ) ( healthcheck . BalancerHandler , error ) {
2018-11-14 09:18:03 +00:00
logger := log . FromContext ( ctx )
2019-06-05 20:18:06 +00:00
logger . Debug ( "Creating load-balancer" )
var options [ ] roundrobin . LBOption
2018-11-14 09:18:03 +00:00
var cookieName string
2019-08-26 08:30:05 +00:00
if service . Sticky != nil && service . Sticky . Cookie != nil {
cookieName = cookie . GetName ( service . Sticky . Cookie . Name , serviceName )
2020-03-23 10:24:05 +00:00
opts := roundrobin . CookieOptions {
HTTPOnly : service . Sticky . Cookie . HTTPOnly ,
Secure : service . Sticky . Cookie . Secure ,
SameSite : convertSameSite ( service . Sticky . Cookie . SameSite ) ,
}
2021-04-29 15:56:03 +00:00
// Sticky Cookie Value
cv , err := stickycookie . NewFallbackValue ( & stickycookie . RawValue { } , & stickycookie . HashValue { } )
if err != nil {
return nil , err
}
options = append ( options , roundrobin . EnableStickySession ( roundrobin . NewStickySessionWithOptions ( cookieName , opts ) . SetCookieValue ( cv ) ) )
2020-03-23 10:24:05 +00:00
2019-06-05 20:18:06 +00:00
logger . Debugf ( "Sticky session cookie name: %v" , cookieName )
2018-11-14 09:18:03 +00:00
}
2019-06-05 20:18:06 +00:00
lb , err := roundrobin . New ( fwd , options ... )
if err != nil {
return nil , err
2018-11-14 09:18:03 +00:00
}
2019-05-16 08:58:06 +00:00
lbsu := healthcheck . NewLBStatusUpdater ( lb , m . configs [ serviceName ] )
if err := m . upsertServers ( ctx , lbsu , service . Servers ) ; err != nil {
2020-05-11 10:06:07 +00:00
return nil , fmt . Errorf ( "error configuring load balancer for service %s: %w" , serviceName , err )
2018-11-14 09:18:03 +00:00
}
2019-09-18 15:56:05 +00:00
return lbsu , nil
2018-11-14 09:18:03 +00:00
}
2019-07-10 07:26:04 +00:00
func ( m * Manager ) upsertServers ( ctx context . Context , lb healthcheck . BalancerHandler , servers [ ] dynamic . Server ) error {
2018-11-14 09:18:03 +00:00
logger := log . FromContext ( ctx )
for name , srv := range servers {
u , err := url . Parse ( srv . URL )
if err != nil {
2020-05-11 10:06:07 +00:00
return fmt . Errorf ( "error parsing server URL %s: %w" , srv . URL , err )
2018-11-14 09:18:03 +00:00
}
2019-06-05 20:18:06 +00:00
logger . WithField ( log . ServerName , name ) . Debugf ( "Creating server %d %s" , name , u )
2018-11-14 09:18:03 +00:00
2019-06-05 20:18:06 +00:00
if err := lb . UpsertServer ( u , roundrobin . Weight ( 1 ) ) ; err != nil {
2020-05-11 10:06:07 +00:00
return fmt . Errorf ( "error adding server %s to load balancer: %w" , srv . URL , err )
2018-11-14 09:18:03 +00:00
}
// FIXME Handle Metrics
}
return nil
}
2020-03-23 10:24:05 +00:00
func convertSameSite ( sameSite string ) http . SameSite {
switch sameSite {
case "none" :
return http . SameSiteNoneMode
case "lax" :
return http . SameSiteLaxMode
case "strict" :
return http . SameSiteStrictMode
default :
return 0
}
}