Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add configurable idle timeout #2632

Merged
merged 3 commits into from
Jul 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions cmd/contour/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,18 @@ func doServe(log logrus.FieldLogger, ctx *serveContext) error {
}

listenerConfig := contour.ListenerVisitorConfig{
UseProxyProto: ctx.useProxyProto,
HTTPAddress: ctx.httpAddr,
HTTPPort: ctx.httpPort,
HTTPAccessLog: ctx.httpAccessLog,
HTTPSAddress: ctx.httpsAddr,
HTTPSPort: ctx.httpsPort,
HTTPSAccessLog: ctx.httpsAccessLog,
AccessLogType: ctx.AccessLogFormat,
AccessLogFields: ctx.AccessLogFields,
MinimumTLSVersion: annotation.MinTLSVersion(ctx.TLSConfig.MinimumProtocolVersion),
RequestTimeout: ctx.RequestTimeout,
UseProxyProto: ctx.useProxyProto,
HTTPAddress: ctx.httpAddr,
HTTPPort: ctx.httpPort,
HTTPAccessLog: ctx.httpAccessLog,
HTTPSAddress: ctx.httpsAddr,
HTTPSPort: ctx.httpsPort,
HTTPSAccessLog: ctx.httpsAccessLog,
AccessLogType: ctx.AccessLogFormat,
AccessLogFields: ctx.AccessLogFields,
MinimumTLSVersion: annotation.MinTLSVersion(ctx.TLSConfig.MinimumProtocolVersion),
RequestTimeout: ctx.RequestTimeout,
ConnectionIdleTimeout: ctx.ConnectionIdleTimeout,
}

defaultHTTPVersions, err := parseDefaultHTTPVersions(ctx.DefaultHTTPVersions)
Expand Down
17 changes: 17 additions & 0 deletions cmd/contour/servecontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ type serveContext struct {
// LeaderElectionConfig can be set in the config file.
LeaderElectionConfig `yaml:"leaderelection,omitempty"`

// TimeoutConfig holds various configurable timeouts that can
// be set in the config file.
TimeoutConfig `yaml:"timeouts,omitempty"`

// RequestTimeout sets the client request timeout globally for Contour.
RequestTimeout time.Duration `yaml:"request-timeout,omitempty"`

Expand Down Expand Up @@ -177,6 +181,11 @@ func newServeContext() *serveContext {
UseExperimentalServiceAPITypes: false,
EnvoyServiceName: "envoy",
EnvoyServiceNamespace: getEnv("CONTOUR_NAMESPACE", "projectcontour"),
TimeoutConfig: TimeoutConfig{
// This is chosen as a rough default to stop idle connections wasting resources,
// without stopping slow connections from being terminated too quickly.
ConnectionIdleTimeout: 60 * time.Second,
},
}
}

Expand Down Expand Up @@ -227,6 +236,14 @@ type LeaderElectionConfig struct {
Name string `yaml:"configmap-name,omitempty"`
}

// TimeoutConfig holds various configurable proxy timeout values.
type TimeoutConfig struct {
// ConnectionIdleTimeout defines how long the proxy should wait while there
// are no active requests before terminating an HTTP connection. Set
// to 0 to disable the timeout.
ConnectionIdleTimeout time.Duration `yaml:"connection-idle-timeout,omitempty"`
}

// grpcOptions returns a slice of grpc.ServerOptions.
// if ctx.PermitInsecureGRPC is false, the option set will
// include TLS configuration.
Expand Down
4 changes: 4 additions & 0 deletions examples/contour/01-contour-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@ data:
# default-http-versions:
# - "HTTP/2"
# - "HTTP/1.1"
#
# The following shows the default proxy timeout settings.
# timeouts:
# connection-idle-timeout: 60s
4 changes: 4 additions & 0 deletions examples/render/contour.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ data:
# default-http-versions:
# - "HTTP/2"
# - "HTTP/1.1"
#
# The following shows the default proxy timeout settings.
# timeouts:
# connection-idle-timeout: 60s

---
apiVersion: apiextensions.k8s.io/v1beta1
Expand Down
20 changes: 19 additions & 1 deletion internal/contour/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ type ListenerVisitorConfig struct {

// RequestTimeout configures the request_timeout for all Connection Managers.
RequestTimeout time.Duration

// ConnectionIdleTimeout configures the common_http_protocol_options.idle_timeout for all
// Connection Managers.
ConnectionIdleTimeout time.Duration
}

// httpAddress returns the port for the HTTP (non TLS)
Expand Down Expand Up @@ -195,13 +199,24 @@ func (lvc *ListenerVisitorConfig) newSecureAccessLog() []*envoy_api_v2_accesslog
// >0 duration - the timeout.
// The value may be unset, but we always set it to 0.
func (lvc *ListenerVisitorConfig) requestTimeout() time.Duration {

if lvc.RequestTimeout < 0 {
return 0
}
return lvc.RequestTimeout
}

// connectionIdleTimeout sets any durations in lvc.ConnectionIdleTimeout <0 to 0 so that Envoy ends up with a positive duration.
// for the idle_timeout value we are passing, there are only two valid values:
// 0 - disabled
// >0 duration - the timeout.
// The value may be unset, but we always set it to 0.
func (lvc *ListenerVisitorConfig) connectionIdleTimeout() time.Duration {
if lvc.ConnectionIdleTimeout < 0 {
return 0
}
return lvc.ConnectionIdleTimeout
}

// minTLSVersion returns the requested minimum TLS protocol
// version or envoy_api_v2_auth.TlsParameters_TLSv1_1 if not configured.
func (lvc *ListenerVisitorConfig) minTLSVersion() envoy_api_v2_auth.TlsParameters_TlsProtocol {
Expand Down Expand Up @@ -311,6 +326,7 @@ func visitListeners(root dag.Vertex, lvc *ListenerVisitorConfig) map[string]*v2.
MetricsPrefix(ENVOY_HTTP_LISTENER).
AccessLoggers(lvc.newInsecureAccessLog()).
RequestTimeout(lvc.requestTimeout()).
ConnectionIdleTimeout(lvc.connectionIdleTimeout()).
Get()

lv.listeners[ENVOY_HTTP_LISTENER] = envoy.Listener(
Expand Down Expand Up @@ -382,6 +398,7 @@ func (v *listenerVisitor) visit(vertex dag.Vertex) {
MetricsPrefix(ENVOY_HTTPS_LISTENER).
AccessLoggers(v.ListenerVisitorConfig.newSecureAccessLog()).
RequestTimeout(v.ListenerVisitorConfig.requestTimeout()).
ConnectionIdleTimeout(v.ListenerVisitorConfig.connectionIdleTimeout()).
Get(),
)

Expand Down Expand Up @@ -436,6 +453,7 @@ func (v *listenerVisitor) visit(vertex dag.Vertex) {
MetricsPrefix(ENVOY_HTTPS_LISTENER).
AccessLoggers(v.ListenerVisitorConfig.newSecureAccessLog()).
RequestTimeout(v.ListenerVisitorConfig.requestTimeout()).
ConnectionIdleTimeout(v.ListenerVisitorConfig.connectionIdleTimeout()).
Get(),
)

Expand Down
134 changes: 134 additions & 0 deletions internal/contour/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package contour
import (
"path"
"testing"
"time"

"github.com/projectcontour/contour/internal/k8s"

Expand Down Expand Up @@ -1409,6 +1410,139 @@ func TestListenerVisit(t *testing.T) {
),
}),
},
"httpproxy with connection idle timeout set in visitor config": {
ListenerVisitorConfig: ListenerVisitorConfig{
ConnectionIdleTimeout: 90 * time.Second,
},
objs: []interface{}{
&projcontour.HTTPProxy{
ObjectMeta: metav1.ObjectMeta{
Name: "simple",
Namespace: "default",
},
Spec: projcontour.HTTPProxySpec{
VirtualHost: &projcontour.VirtualHost{
Fqdn: "www.example.com",
},
Routes: []projcontour.Route{{
Conditions: []projcontour.Condition{{
Prefix: "/",
}},
Services: []projcontour.Service{{
Name: "backend",
Port: 80,
}},
}},
},
},
&v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "backend",
Namespace: "default",
},
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{{
Name: "http",
Protocol: "TCP",
Port: 80,
}},
},
},
},
want: listenermap(&v2.Listener{
Name: ENVOY_HTTP_LISTENER,
Address: envoy.SocketAddress("0.0.0.0", 8080),
FilterChains: envoy.FilterChains(
envoy.HTTPConnectionManagerBuilder().
RouteConfigName(ENVOY_HTTP_LISTENER).
MetricsPrefix(ENVOY_HTTP_LISTENER).
AccessLoggers(envoy.FileAccessLogEnvoy(DEFAULT_HTTP_ACCESS_LOG)).
DefaultFilters().
ConnectionIdleTimeout(90 * time.Second).
Get(),
),
}),
},
"httpsproxy with secret with connection idle timeout set in visitor config": {
ListenerVisitorConfig: ListenerVisitorConfig{
ConnectionIdleTimeout: 90 * time.Second,
},
objs: []interface{}{
&projcontour.HTTPProxy{
ObjectMeta: metav1.ObjectMeta{
Name: "simple",
Namespace: "default",
},
Spec: projcontour.HTTPProxySpec{
VirtualHost: &projcontour.VirtualHost{
Fqdn: "www.example.com",
TLS: &projcontour.TLS{
SecretName: "secret",
},
},
Routes: []projcontour.Route{{
Services: []projcontour.Service{{
Name: "backend",
Port: 80,
}},
}},
},
},
&v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "secret",
Namespace: "default",
},
Type: "kubernetes.io/tls",
Data: secretdata(CERTIFICATE, RSA_PRIVATE_KEY),
},
&v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "backend",
Namespace: "default",
},
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{{
Name: "http",
Protocol: "TCP",
Port: 80,
}},
},
},
},
want: listenermap(&v2.Listener{
Name: ENVOY_HTTP_LISTENER,
Address: envoy.SocketAddress("0.0.0.0", 8080),
FilterChains: envoy.FilterChains(envoy.HTTPConnectionManagerBuilder().
RouteConfigName(ENVOY_HTTP_LISTENER).
MetricsPrefix(ENVOY_HTTP_LISTENER).
AccessLoggers(envoy.FileAccessLogEnvoy(DEFAULT_HTTP_ACCESS_LOG)).
DefaultFilters().
ConnectionIdleTimeout(90 * time.Second).
Get(),
),
}, &v2.Listener{
Name: ENVOY_HTTPS_LISTENER,
Address: envoy.SocketAddress("0.0.0.0", 8443),
FilterChains: []*envoy_api_v2_listener.FilterChain{{
FilterChainMatch: &envoy_api_v2_listener.FilterChainMatch{
ServerNames: []string{"www.example.com"},
},
TransportSocket: transportSocket("secret", envoy_api_v2_auth.TlsParameters_TLSv1_1, "h2", "http/1.1"),
Filters: envoy.Filters(envoy.HTTPConnectionManagerBuilder().
AddFilter(envoy.FilterMisdirectedRequests("www.example.com")).
DefaultFilters().
MetricsPrefix(ENVOY_HTTPS_LISTENER).
RouteConfigName(path.Join("https", "www.example.com")).
AccessLoggers(envoy.FileAccessLogEnvoy(DEFAULT_HTTP_ACCESS_LOG)).
ConnectionIdleTimeout(90 * time.Second).
Get()),
}},
ListenerFilters: envoy.ListenerFilters(
envoy.TLSInspector(),
),
}),
},
}

for name, tc := range tests {
Expand Down
25 changes: 15 additions & 10 deletions internal/envoy/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,13 @@ func Listener(name, address string, port int, lf []*envoy_api_v2_listener.Listen
}

type httpConnectionManagerBuilder struct {
routeConfigName string
metricsPrefix string
accessLoggers []*accesslog.AccessLog
requestTimeout time.Duration
filters []*http.HttpFilter
codec HTTPVersionType // Note the zero value is AUTO, which is the default we want.
routeConfigName string
metricsPrefix string
accessLoggers []*accesslog.AccessLog
requestTimeout time.Duration
connectionIdleTimeout time.Duration
filters []*http.HttpFilter
codec HTTPVersionType // Note the zero value is AUTO, which is the default we want.
}

// RouteConfigName sets the name of the RDS element that contains
Expand Down Expand Up @@ -169,6 +170,13 @@ func (b *httpConnectionManagerBuilder) RequestTimeout(timeout time.Duration) *ht
return b
}

// ConnectionIdleTimeout sets the idle timeout on the connection
// manager. If not specified or set to 0, this timeout is disabled.
func (b *httpConnectionManagerBuilder) ConnectionIdleTimeout(timeout time.Duration) *httpConnectionManagerBuilder {
b.connectionIdleTimeout = timeout
return b
}

func (b *httpConnectionManagerBuilder) DefaultFilters() *httpConnectionManagerBuilder {
b.filters = append(b.filters,
&http.HttpFilter{
Expand Down Expand Up @@ -205,10 +213,7 @@ func (b *httpConnectionManagerBuilder) Get() *envoy_api_v2_listener.Filter {
},
HttpFilters: b.filters,
CommonHttpProtocolOptions: &envoy_api_v2_core.HttpProtocolOptions{
// Sets the idle timeout for HTTP connections to 60 seconds.
// This is chosen as a rough default to stop idle connections wasting resources,
// without stopping slow connections from being terminated too quickly.
IdleTimeout: protobuf.Duration(60 * time.Second),
IdleTimeout: protobuf.Duration(b.connectionIdleTimeout),
},
HttpProtocolOptions: &envoy_api_v2_core.Http1ProtocolOptions{
// Enable support for HTTP/1.0 requests that carry
Expand Down
Loading