feat: add readIdleTimeout and pingTimeout config options to ServersTransport

Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
Tom Moulard 2021-11-09 12:16:08 +01:00 committed by GitHub
parent 8e32d1913b
commit 1f17731369
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 204 additions and 38 deletions

View file

@ -52,23 +52,40 @@ spec:
anyOf:
- type: integer
- type: string
description: The amount of time to wait until a connection to
a backend server can be established. If zero, no timeout exists.
description: DialTimeout is the amount of time to wait until a
connection to a backend server can be established. If zero,
no timeout exists.
x-kubernetes-int-or-string: true
idleConnTimeout:
anyOf:
- type: integer
- type: string
description: The maximum period for which an idle HTTP keep-alive
connection will remain open before closing itself.
description: IdleConnTimeout is the maximum period for which an
idle HTTP keep-alive connection will remain open before closing
itself.
x-kubernetes-int-or-string: true
pingTimeout:
anyOf:
- type: integer
- type: string
description: PingTimeout is the timeout after which the HTTP/2
connection will be closed if a response to ping is not received.
x-kubernetes-int-or-string: true
readIdleTimeout:
anyOf:
- type: integer
- type: string
description: ReadIdleTimeout is the timeout after which a health
check using ping frame will be carried out if no frame is received
on the HTTP/2 connection. If zero, no health check is performed.
x-kubernetes-int-or-string: true
responseHeaderTimeout:
anyOf:
- type: integer
- type: string
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.
description: ResponseHeaderTimeout is 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.
x-kubernetes-int-or-string: true
type: object
insecureSkipVerify:

View file

@ -324,7 +324,7 @@ serversTransport:
`forwardingTimeouts` is about a number of timeouts relevant to when forwarding requests to the backend servers.
#### forwardingTimeouts.dialTimeout`
#### `forwardingTimeouts.dialTimeout`
_Optional, Default=30s_
@ -349,7 +349,7 @@ serversTransport:
--serversTransport.forwardingTimeouts.dialTimeout=1s
```
#### forwardingTimeouts.responseHeaderTimeout`
#### `forwardingTimeouts.responseHeaderTimeout`
_Optional, Default=0s_
@ -376,7 +376,7 @@ serversTransport:
--serversTransport.forwardingTimeouts.responseHeaderTimeout=1s
```
#### forwardingTimeouts.idleConnTimeout`
#### `forwardingTimeouts.idleConnTimeout`
_Optional, Default=90s_

View file

@ -876,6 +876,78 @@ spec:
idleConnTimeout: "1s"
```
##### `forwardingTimeouts.readIdleTimeout`
_Optional, Default=0s_
`readIdleTimeout` is the timeout after which a health check using ping frame will be carried out
if no frame is received on the HTTP/2 connection.
Note that a ping response will be considered a received frame,
so if there is no other traffic on the connection,
the health check will be performed every `readIdleTimeout` interval.
If zero, no health check is performed.
```yaml tab="File (YAML)"
## Dynamic configuration
http:
serversTransports:
mytransport:
forwardingTimeouts:
readIdleTimeout: "1s"
```
```toml tab="File (TOML)"
## Dynamic configuration
[http.serversTransports.mytransport.forwardingTimeouts]
readIdleTimeout = "1s"
```
```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: ServersTransport
metadata:
name: mytransport
namespace: default
spec:
forwardingTimeouts:
readIdleTimeout: "1s"
```
##### `forwardingTimeouts.pingTimeout`
_Optional, Default=15s_
`pingTimeout` is the timeout after which the HTTP/2 connection will be closed
if a response to ping is not received.
```yaml tab="File (YAML)"
## Dynamic configuration
http:
serversTransports:
mytransport:
forwardingTimeouts:
pingTimeout: "1s"
```
```toml tab="File (TOML)"
## Dynamic configuration
[http.serversTransports.mytransport.forwardingTimeouts]
pingTimeout = "1s"
```
```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: ServersTransport
metadata:
name: mytransport
namespace: default
spec:
forwardingTimeouts:
pingTimeout: "1s"
```
### Weighted Round Robin (service)
The WRR is able to load balance the requests between multiple services based on weights.

View file

@ -1125,23 +1125,40 @@ spec:
anyOf:
- type: integer
- type: string
description: The amount of time to wait until a connection to
a backend server can be established. If zero, no timeout exists.
description: DialTimeout is the amount of time to wait until a
connection to a backend server can be established. If zero,
no timeout exists.
x-kubernetes-int-or-string: true
idleConnTimeout:
anyOf:
- type: integer
- type: string
description: The maximum period for which an idle HTTP keep-alive
connection will remain open before closing itself.
description: IdleConnTimeout is the maximum period for which an
idle HTTP keep-alive connection will remain open before closing
itself.
x-kubernetes-int-or-string: true
pingTimeout:
anyOf:
- type: integer
- type: string
description: PingTimeout is the timeout after which the HTTP/2
connection will be closed if a response to ping is not received.
x-kubernetes-int-or-string: true
readIdleTimeout:
anyOf:
- type: integer
- type: string
description: ReadIdleTimeout is the timeout after which a health
check using ping frame will be carried out if no frame is received
on the HTTP/2 connection. If zero, no health check is performed.
x-kubernetes-int-or-string: true
responseHeaderTimeout:
anyOf:
- type: integer
- type: string
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.
description: ResponseHeaderTimeout is 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.
x-kubernetes-int-or-string: true
type: object
insecureSkipVerify:

View file

@ -147,6 +147,8 @@ func TestDo_dynamicConfiguration(t *testing.T) {
DialTimeout: 42,
ResponseHeaderTimeout: 42,
IdleConnTimeout: 42,
ReadIdleTimeout: 42,
PingTimeout: 42,
},
},
},

View file

@ -355,7 +355,9 @@
"forwardingTimeouts": {
"dialTimeout": "42ns",
"responseHeaderTimeout": "42ns",
"idleConnTimeout": "42ns"
"idleConnTimeout": "42ns",
"readIdleTimeout": "42ns",
"pingTimeout": "42ns"
}
}
}

View file

@ -218,7 +218,7 @@ type HealthCheck struct{}
// ServersTransport options to configure communication between Traefik and the servers.
type ServersTransport struct {
ServerName string `description:"ServerName used to contact the server" json:"serverName,omitempty" toml:"serverName,omitempty" yaml:"serverName,omitempty"`
ServerName string `description:"ServerName used to contact the server." json:"serverName,omitempty" toml:"serverName,omitempty" yaml:"serverName,omitempty"`
InsecureSkipVerify bool `description:"Disable SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"`
RootCAs []traefiktls.FileOrContent `description:"Add cert file for self-signed certificate." json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"`
Certificates traefiktls.Certificates `description:"Certificates for mTLS." json:"certificates,omitempty" toml:"certificates,omitempty" yaml:"certificates,omitempty" export:"true"`
@ -234,11 +234,14 @@ type ServersTransport struct {
type ForwardingTimeouts struct {
DialTimeout ptypes.Duration `description:"The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists." json:"dialTimeout,omitempty" toml:"dialTimeout,omitempty" yaml:"dialTimeout,omitempty" export:"true"`
ResponseHeaderTimeout ptypes.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." json:"responseHeaderTimeout,omitempty" toml:"responseHeaderTimeout,omitempty" yaml:"responseHeaderTimeout,omitempty" export:"true"`
IdleConnTimeout ptypes.Duration `description:"The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself" json:"idleConnTimeout,omitempty" toml:"idleConnTimeout,omitempty" yaml:"idleConnTimeout,omitempty" export:"true"`
IdleConnTimeout ptypes.Duration `description:"The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself." json:"idleConnTimeout,omitempty" toml:"idleConnTimeout,omitempty" yaml:"idleConnTimeout,omitempty" export:"true"`
ReadIdleTimeout ptypes.Duration `description:"The timeout after which a health check using ping frame will be carried out if no frame is received on the HTTP/2 connection. If zero, no health check is performed." json:"readIdleTimeout,omitempty" toml:"readIdleTimeout,omitempty" yaml:"readIdleTimeout,omitempty" export:"true"`
PingTimeout ptypes.Duration `description:"The timeout after which the HTTP/2 connection will be closed if a response to ping is not received." json:"pingTimeout,omitempty" toml:"pingTimeout,omitempty" yaml:"pingTimeout,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (f *ForwardingTimeouts) SetDefaults() {
f.DialTimeout = ptypes.Duration(30 * time.Second)
f.IdleConnTimeout = ptypes.Duration(90 * time.Second)
f.PingTimeout = ptypes.Duration(15 * time.Second)
}

View file

@ -110,6 +110,8 @@ spec:
dialTimeout: 42
responseHeaderTimeout: 42s
idleConnTimeout: 42ms
readIdleTimeout: 42s
pingTimeout: 42s
---
apiVersion: traefik.containo.us/v1alpha1

View file

@ -340,6 +340,20 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
logger.Errorf("Error while reading IdleConnTimeout: %v", err)
}
}
if serversTransport.Spec.ForwardingTimeouts.ReadIdleTimeout != nil {
err := forwardingTimeout.ReadIdleTimeout.Set(serversTransport.Spec.ForwardingTimeouts.ReadIdleTimeout.String())
if err != nil {
logger.Errorf("Error while reading ReadIdleTimeout: %v", err)
}
}
if serversTransport.Spec.ForwardingTimeouts.PingTimeout != nil {
err := forwardingTimeout.PingTimeout.Set(serversTransport.Spec.ForwardingTimeouts.PingTimeout.String())
if err != nil {
logger.Errorf("Error while reading PingTimeout: %v", err)
}
}
}
id := provider.Normalize(makeID(serversTransport.Namespace, serversTransport.Name))

View file

@ -3618,6 +3618,8 @@ func TestLoadIngressRoutes(t *testing.T) {
DialTimeout: ptypes.Duration(42 * time.Second),
ResponseHeaderTimeout: ptypes.Duration(42 * time.Second),
IdleConnTimeout: ptypes.Duration(42 * time.Millisecond),
ReadIdleTimeout: ptypes.Duration(42 * time.Second),
PingTimeout: ptypes.Duration(42 * time.Second),
},
PeerCertURI: "foo://bar",
},
@ -3626,6 +3628,7 @@ func TestLoadIngressRoutes(t *testing.T) {
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
DialTimeout: ptypes.Duration(30 * time.Second),
IdleConnTimeout: ptypes.Duration(90 * time.Second),
PingTimeout: ptypes.Duration(15 * time.Second),
},
},
},
@ -4873,6 +4876,8 @@ func TestCrossNamespace(t *testing.T) {
DialTimeout: 30000000000,
ResponseHeaderTimeout: 0,
IdleConnTimeout: 90000000000,
ReadIdleTimeout: 0,
PingTimeout: 15000000000,
},
DisableHTTP2: true,
},
@ -4904,6 +4909,8 @@ func TestCrossNamespace(t *testing.T) {
DialTimeout: 30000000000,
ResponseHeaderTimeout: 0,
IdleConnTimeout: 90000000000,
ReadIdleTimeout: 0,
PingTimeout: 15000000000,
},
DisableHTTP2: true,
},

View file

@ -43,13 +43,17 @@ type ServersTransportSpec struct {
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
type ForwardingTimeouts struct {
// The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists.
// DialTimeout is the amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists.
DialTimeout *intstr.IntOrString `json:"dialTimeout,omitempty"`
// The amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
// ResponseHeaderTimeout is 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.
ResponseHeaderTimeout *intstr.IntOrString `json:"responseHeaderTimeout,omitempty"`
// The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself.
// IdleConnTimeout is the maximum period for which an idle HTTP keep-alive connection will remain open before closing itself.
IdleConnTimeout *intstr.IntOrString `json:"idleConnTimeout,omitempty"`
// ReadIdleTimeout is the timeout after which a health check using ping frame will be carried out if no frame is received on the HTTP/2 connection. If zero, no health check is performed.
ReadIdleTimeout *intstr.IntOrString `json:"readIdleTimeout,omitempty"`
// PingTimeout is the timeout after which the HTTP/2 connection will be closed if a response to ping is not received.
PingTimeout *intstr.IntOrString `json:"pingTimeout,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View file

@ -214,6 +214,16 @@ func (in *ForwardingTimeouts) DeepCopyInto(out *ForwardingTimeouts) {
*out = new(intstr.IntOrString)
**out = **in
}
if in.ReadIdleTimeout != nil {
in, out := &in.ReadIdleTimeout, &out.ReadIdleTimeout
*out = new(intstr.IntOrString)
**out = **in
}
if in.PingTimeout != nil {
in, out := &in.PingTimeout, &out.PingTimeout
*out = new(intstr.IntOrString)
**out = **in
}
return
}

View file

@ -152,16 +152,7 @@ func createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error
return transport, nil
}
transport.RegisterProtocol("h2c", &h2cTransportWrapper{
Transport: &http2.Transport{
DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(netw, addr)
},
AllowHTTP: true,
},
})
return newSmartRoundTripper(transport)
return newSmartRoundTripper(transport, cfg.ForwardingTimeouts)
}
func createRootCACertPool(rootCAs []traefiktls.FileOrContent) *x509.CertPool {

View file

@ -1,33 +1,58 @@
package service
import (
"crypto/tls"
"net"
"net/http"
"time"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2"
)
func newSmartRoundTripper(transport *http.Transport) (http.RoundTripper, error) {
func newSmartRoundTripper(transport *http.Transport, forwardingTimeouts *dynamic.ForwardingTimeouts) (http.RoundTripper, error) {
transportHTTP1 := transport.Clone()
err := http2.ConfigureTransport(transport)
transportHTTP2, err := http2.ConfigureTransports(transport)
if err != nil {
return nil, err
}
if forwardingTimeouts != nil {
transportHTTP2.ReadIdleTimeout = time.Duration(forwardingTimeouts.ReadIdleTimeout)
transportHTTP2.PingTimeout = time.Duration(forwardingTimeouts.PingTimeout)
}
transportH2C := &h2cTransportWrapper{
Transport: &http2.Transport{
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
AllowHTTP: true,
},
}
if forwardingTimeouts != nil {
transportH2C.ReadIdleTimeout = time.Duration(forwardingTimeouts.ReadIdleTimeout)
transportH2C.PingTimeout = time.Duration(forwardingTimeouts.PingTimeout)
}
transport.RegisterProtocol("h2c", transportH2C)
return &smartRoundTripper{
http2: transport,
http: transportHTTP1,
}, nil
}
// smartRoundTripper implements RoundTrip while making sure that HTTP/2 is not used
// with protocols that start with a Connection Upgrade, such as SPDY or Websocket.
type smartRoundTripper struct {
http2 *http.Transport
http *http.Transport
}
// smartRoundTripper implements RoundTrip while making sure that HTTP/2 is not used
// with protocols that start with a Connection Upgrade, such as SPDY or Websocket.
func (m *smartRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// If we have a connection upgrade, we don't use HTTP/2
if httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") {