feat: add readIdleTimeout and pingTimeout config options to ServersTransport
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
8e32d1913b
commit
1f17731369
14 changed files with 204 additions and 38 deletions
|
@ -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:
|
||||
|
|
|
@ -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_
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -147,6 +147,8 @@ func TestDo_dynamicConfiguration(t *testing.T) {
|
|||
DialTimeout: 42,
|
||||
ResponseHeaderTimeout: 42,
|
||||
IdleConnTimeout: 42,
|
||||
ReadIdleTimeout: 42,
|
||||
PingTimeout: 42,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -355,7 +355,9 @@
|
|||
"forwardingTimeouts": {
|
||||
"dialTimeout": "42ns",
|
||||
"responseHeaderTimeout": "42ns",
|
||||
"idleConnTimeout": "42ns"
|
||||
"idleConnTimeout": "42ns",
|
||||
"readIdleTimeout": "42ns",
|
||||
"pingTimeout": "42ns"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -474,4 +476,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -110,6 +110,8 @@ spec:
|
|||
dialTimeout: 42
|
||||
responseHeaderTimeout: 42s
|
||||
idleConnTimeout: 42ms
|
||||
readIdleTimeout: 42s
|
||||
pingTimeout: 42s
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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") {
|
||||
|
|
Loading…
Reference in a new issue