diff --git a/apis/projectcontour/v1alpha1/contourconfig.go b/apis/projectcontour/v1alpha1/contourconfig.go index 69493e8b51f..a61d429f15c 100644 --- a/apis/projectcontour/v1alpha1/contourconfig.go +++ b/apis/projectcontour/v1alpha1/contourconfig.go @@ -412,6 +412,14 @@ type TimeoutParameters struct { // for more information. // +optional ConnectionShutdownGracePeriod *string `json:"connectionShutdownGracePeriod,omitempty"` + + // ConnectTimeout defines how long the proxy should wait when establishing connection to upstream service. + // If not set, a default value of 2 seconds will be used. + // + // See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout + // for more information. + // +optional + ConnectTimeout *string `json:"connectTimeout"` } // ClusterDNSFamilyType is the Ip family to use for resolving DNS diff --git a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go index 492727786b7..04164afa048 100644 --- a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go +++ b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go @@ -806,6 +806,11 @@ func (in *TimeoutParameters) DeepCopyInto(out *TimeoutParameters) { *out = new(string) **out = **in } + if in.ConnectTimeout != nil { + in, out := &in.ConnectTimeout, &out.ConnectTimeout + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TimeoutParameters. diff --git a/changelogs/unreleased/4326-tsaarni-small.md b/changelogs/unreleased/4326-tsaarni-small.md new file mode 100644 index 00000000000..e221d7a549a --- /dev/null +++ b/changelogs/unreleased/4326-tsaarni-small.md @@ -0,0 +1 @@ +Upstream TCP connection timeout is now configurable in [configuration file](https://projectcontour.io/docs/main/configuration/#timeout-configuration) and in [`ContourConfiguration`](https://projectcontour.io/docs/main/config/api/#projectcontour.io/v1alpha1.TimeoutParameters). diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index e4456574486..a6d29d80cb3 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -399,6 +399,7 @@ func (s *Server) doServe() error { headersPolicy: contourConfiguration.Policy, clientCert: clientCert, fallbackCert: fallbackCert, + connectTimeout: timeouts.ConnectTimeout, }) // Build the core Kubernetes event handler. @@ -778,6 +779,7 @@ type dagBuilderConfig struct { headersPolicy *contour_api_v1alpha1.PolicyConfig clientCert *types.NamespacedName fallbackCert *types.NamespacedName + connectTimeout time.Duration } func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder { @@ -836,12 +838,14 @@ func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder { ClientCertificate: dbc.clientCert, RequestHeadersPolicy: &requestHeadersPolicyIngress, ResponseHeadersPolicy: &responseHeadersPolicyIngress, + ConnectTimeout: dbc.connectTimeout, }, &dag.ExtensionServiceProcessor{ // Note that ExtensionService does not support ExternalName, if it does get added, // need to bring EnableExternalNameService in here too. FieldLogger: s.log.WithField("context", "ExtensionServiceProcessor"), ClientCertificate: dbc.clientCert, + ConnectTimeout: dbc.connectTimeout, }, &dag.HTTPProxyProcessor{ EnableExternalNameService: dbc.enableExternalNameService, @@ -851,6 +855,7 @@ func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder { ClientCertificate: dbc.clientCert, RequestHeadersPolicy: &requestHeadersPolicy, ResponseHeadersPolicy: &responseHeadersPolicy, + ConnectTimeout: dbc.connectTimeout, }, } @@ -858,6 +863,7 @@ func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder { dagProcessors = append(dagProcessors, &dag.GatewayAPIProcessor{ EnableExternalNameService: dbc.enableExternalNameService, FieldLogger: s.log.WithField("context", "GatewayAPIProcessor"), + ConnectTimeout: dbc.connectTimeout, }) } diff --git a/cmd/contour/servecontext.go b/cmd/contour/servecontext.go index 3a26c65103e..792853a4afb 100644 --- a/cmd/contour/servecontext.go +++ b/cmd/contour/servecontext.go @@ -339,6 +339,9 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_api_v1alpha if len(ctx.Config.Timeouts.ConnectionShutdownGracePeriod) > 0 { timeoutParams.ConnectionShutdownGracePeriod = pointer.StringPtr(ctx.Config.Timeouts.ConnectionShutdownGracePeriod) } + if len(ctx.Config.Timeouts.ConnectTimeout) > 0 { + timeoutParams.ConnectTimeout = pointer.StringPtr(ctx.Config.Timeouts.ConnectTimeout) + } var dnsLookupFamily contour_api_v1alpha1.ClusterDNSFamilyType switch ctx.Config.Cluster.DNSLookupFamily { diff --git a/cmd/contour/servecontext_test.go b/cmd/contour/servecontext_test.go index fa5bcf705ea..bad80c80157 100644 --- a/cmd/contour/servecontext_test.go +++ b/cmd/contour/servecontext_test.go @@ -512,6 +512,7 @@ func TestConvertServeContext(t *testing.T) { DefaultHTTPVersions: nil, Timeouts: &contour_api_v1alpha1.TimeoutParameters{ ConnectionIdleTimeout: pointer.StringPtr("60s"), + ConnectTimeout: pointer.StringPtr("2s"), }, Cluster: contour_api_v1alpha1.ClusterParameters{ DNSLookupFamily: contour_api_v1alpha1.AutoClusterDNSFamily, @@ -617,6 +618,7 @@ func TestConvertServeContext(t *testing.T) { DefaultHTTPVersions: nil, Timeouts: &contour_api_v1alpha1.TimeoutParameters{ ConnectionIdleTimeout: pointer.StringPtr("60s"), + ConnectTimeout: pointer.StringPtr("2s"), }, Cluster: contour_api_v1alpha1.ClusterParameters{ DNSLookupFamily: contour_api_v1alpha1.AutoClusterDNSFamily, @@ -728,6 +730,7 @@ func TestConvertServeContext(t *testing.T) { DefaultHTTPVersions: nil, Timeouts: &contour_api_v1alpha1.TimeoutParameters{ ConnectionIdleTimeout: pointer.StringPtr("60s"), + ConnectTimeout: pointer.StringPtr("2s"), }, Cluster: contour_api_v1alpha1.ClusterParameters{ DNSLookupFamily: contour_api_v1alpha1.AutoClusterDNSFamily, @@ -833,6 +836,7 @@ func TestConvertServeContext(t *testing.T) { DefaultHTTPVersions: nil, Timeouts: &contour_api_v1alpha1.TimeoutParameters{ ConnectionIdleTimeout: pointer.StringPtr("60s"), + ConnectTimeout: pointer.StringPtr("2s"), }, Cluster: contour_api_v1alpha1.ClusterParameters{ DNSLookupFamily: contour_api_v1alpha1.AutoClusterDNSFamily, @@ -943,6 +947,7 @@ func TestConvertServeContext(t *testing.T) { DefaultHTTPVersions: nil, Timeouts: &contour_api_v1alpha1.TimeoutParameters{ ConnectionIdleTimeout: pointer.StringPtr("60s"), + ConnectTimeout: pointer.StringPtr("2s"), }, Cluster: contour_api_v1alpha1.ClusterParameters{ DNSLookupFamily: contour_api_v1alpha1.AutoClusterDNSFamily, @@ -1048,6 +1053,7 @@ func TestConvertServeContext(t *testing.T) { DefaultHTTPVersions: nil, Timeouts: &contour_api_v1alpha1.TimeoutParameters{ ConnectionIdleTimeout: pointer.StringPtr("60s"), + ConnectTimeout: pointer.StringPtr("2s"), }, Cluster: contour_api_v1alpha1.ClusterParameters{ DNSLookupFamily: contour_api_v1alpha1.AutoClusterDNSFamily, @@ -1156,6 +1162,7 @@ func TestConvertServeContext(t *testing.T) { DefaultHTTPVersions: nil, Timeouts: &contour_api_v1alpha1.TimeoutParameters{ ConnectionIdleTimeout: pointer.StringPtr("60s"), + ConnectTimeout: pointer.StringPtr("2s"), }, Cluster: contour_api_v1alpha1.ClusterParameters{ DNSLookupFamily: contour_api_v1alpha1.AutoClusterDNSFamily, @@ -1271,6 +1278,7 @@ func TestConvertServeContext(t *testing.T) { }, Timeouts: &contour_api_v1alpha1.TimeoutParameters{ ConnectionIdleTimeout: pointer.StringPtr("60s"), + ConnectTimeout: pointer.StringPtr("2s"), }, Cluster: contour_api_v1alpha1.ClusterParameters{ DNSLookupFamily: contour_api_v1alpha1.AutoClusterDNSFamily, @@ -1356,6 +1364,7 @@ func TestConvertServeContext(t *testing.T) { DefaultHTTPVersions: nil, Timeouts: &contour_api_v1alpha1.TimeoutParameters{ ConnectionIdleTimeout: pointer.StringPtr("60s"), + ConnectTimeout: pointer.StringPtr("2s"), }, Cluster: contour_api_v1alpha1.ClusterParameters{ DNSLookupFamily: contour_api_v1alpha1.AutoClusterDNSFamily, diff --git a/examples/contour/01-contour-config.yaml b/examples/contour/01-contour-config.yaml index fc8583f90f4..abe4e9c6aa0 100644 --- a/examples/contour/01-contour-config.yaml +++ b/examples/contour/01-contour-config.yaml @@ -102,6 +102,7 @@ data: # max-connection-duration: infinity # delayed-close-timeout: 1s # connection-shutdown-grace-period: 5s + # connect-timeout: 2s # # Envoy cluster settings. # cluster: diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml index b92793bd989..643b6e4f88e 100644 --- a/examples/contour/01-crds.yaml +++ b/examples/contour/01-crds.yaml @@ -402,6 +402,13 @@ spec: description: Timeouts holds various configurable timeouts that can be set in the config file. properties: + connectTimeout: + description: "ConnectTimeout defines how long the proxy should + wait when establishing connection to upstream service. If + not set, a default value of 2 seconds will be used. \n See + https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout + for more information." + type: string connectionIdleTimeout: description: "ConnectionIdleTimeout defines how long the proxy should wait while there are no active requests (for HTTP/1.1) @@ -1331,6 +1338,13 @@ spec: description: Timeouts holds various configurable timeouts that can be set in the config file. properties: + connectTimeout: + description: "ConnectTimeout defines how long the proxy + should wait when establishing connection to upstream + service. If not set, a default value of 2 seconds will + be used. \n See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout + for more information." + type: string connectionIdleTimeout: description: "ConnectionIdleTimeout defines how long the proxy should wait while there are no active requests diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml index bd6e67e2c9b..c5531728154 100644 --- a/examples/render/contour-deployment.yaml +++ b/examples/render/contour-deployment.yaml @@ -135,6 +135,7 @@ data: # max-connection-duration: infinity # delayed-close-timeout: 1s # connection-shutdown-grace-period: 5s + # connect-timeout: 2s # # Envoy cluster settings. # cluster: @@ -590,6 +591,13 @@ spec: description: Timeouts holds various configurable timeouts that can be set in the config file. properties: + connectTimeout: + description: "ConnectTimeout defines how long the proxy should + wait when establishing connection to upstream service. If + not set, a default value of 2 seconds will be used. \n See + https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout + for more information." + type: string connectionIdleTimeout: description: "ConnectionIdleTimeout defines how long the proxy should wait while there are no active requests (for HTTP/1.1) @@ -1519,6 +1527,13 @@ spec: description: Timeouts holds various configurable timeouts that can be set in the config file. properties: + connectTimeout: + description: "ConnectTimeout defines how long the proxy + should wait when establishing connection to upstream + service. If not set, a default value of 2 seconds will + be used. \n See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout + for more information." + type: string connectionIdleTimeout: description: "ConnectionIdleTimeout defines how long the proxy should wait while there are no active requests diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml index 24b2f735ead..307f6137085 100644 --- a/examples/render/contour-gateway.yaml +++ b/examples/render/contour-gateway.yaml @@ -138,6 +138,7 @@ data: # max-connection-duration: infinity # delayed-close-timeout: 1s # connection-shutdown-grace-period: 5s + # connect-timeout: 2s # # Envoy cluster settings. # cluster: @@ -593,6 +594,13 @@ spec: description: Timeouts holds various configurable timeouts that can be set in the config file. properties: + connectTimeout: + description: "ConnectTimeout defines how long the proxy should + wait when establishing connection to upstream service. If + not set, a default value of 2 seconds will be used. \n See + https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout + for more information." + type: string connectionIdleTimeout: description: "ConnectionIdleTimeout defines how long the proxy should wait while there are no active requests (for HTTP/1.1) @@ -1522,6 +1530,13 @@ spec: description: Timeouts holds various configurable timeouts that can be set in the config file. properties: + connectTimeout: + description: "ConnectTimeout defines how long the proxy + should wait when establishing connection to upstream + service. If not set, a default value of 2 seconds will + be used. \n See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout + for more information." + type: string connectionIdleTimeout: description: "ConnectionIdleTimeout defines how long the proxy should wait while there are no active requests diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index 23c25a1ba28..3c3b1eb942b 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -135,6 +135,7 @@ data: # max-connection-duration: infinity # delayed-close-timeout: 1s # connection-shutdown-grace-period: 5s + # connect-timeout: 2s # # Envoy cluster settings. # cluster: @@ -590,6 +591,13 @@ spec: description: Timeouts holds various configurable timeouts that can be set in the config file. properties: + connectTimeout: + description: "ConnectTimeout defines how long the proxy should + wait when establishing connection to upstream service. If + not set, a default value of 2 seconds will be used. \n See + https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout + for more information." + type: string connectionIdleTimeout: description: "ConnectionIdleTimeout defines how long the proxy should wait while there are no active requests (for HTTP/1.1) @@ -1519,6 +1527,13 @@ spec: description: Timeouts holds various configurable timeouts that can be set in the config file. properties: + connectTimeout: + description: "ConnectTimeout defines how long the proxy + should wait when establishing connection to upstream + service. If not set, a default value of 2 seconds will + be used. \n See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout + for more information." + type: string connectionIdleTimeout: description: "ConnectionIdleTimeout defines how long the proxy should wait while there are no active requests diff --git a/internal/contourconfig/contourconfiguration.go b/internal/contourconfig/contourconfiguration.go index 3fb2983d8f5..60856c1c823 100644 --- a/internal/contourconfig/contourconfiguration.go +++ b/internal/contourconfig/contourconfiguration.go @@ -15,6 +15,7 @@ package contourconfig import ( "fmt" + "time" contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" "github.com/projectcontour/contour/internal/timeout" @@ -27,6 +28,7 @@ type Timeouts struct { MaxConnectionDuration timeout.Setting DelayedClose timeout.Setting ConnectionShutdownGracePeriod timeout.Setting + ConnectTimeout time.Duration // Since "infinite" is not valid ConnectTimeout value, use time.Duration instead of timeout.Setting. } func ParseTimeoutPolicy(timeoutParameters *contour_api_v1alpha1.TimeoutParameters) (Timeouts, error) { @@ -72,6 +74,12 @@ func ParseTimeoutPolicy(timeoutParameters *contour_api_v1alpha1.TimeoutParameter return Timeouts{}, fmt.Errorf("failed to parse connection shutdown grace period: %s", err) } } + if timeoutParameters.ConnectTimeout != nil { + timeouts.ConnectTimeout, err = time.ParseDuration(*timeoutParameters.ConnectTimeout) + if err != nil { + return Timeouts{}, fmt.Errorf("failed to parse connect timeout: %s", err) + } + } } return timeouts, nil } diff --git a/internal/contourconfig/contourconfiguration_test.go b/internal/contourconfig/contourconfiguration_test.go index 92cbca8b696..148519226f4 100644 --- a/internal/contourconfig/contourconfiguration_test.go +++ b/internal/contourconfig/contourconfiguration_test.go @@ -39,6 +39,7 @@ func TestParseTimeoutPolicy(t *testing.T) { MaxConnectionDuration: timeout.DefaultSetting(), DelayedClose: timeout.DefaultSetting(), ConnectionShutdownGracePeriod: timeout.DefaultSetting(), + ConnectTimeout: 0, }, }, "timeouts not set": { @@ -50,6 +51,7 @@ func TestParseTimeoutPolicy(t *testing.T) { MaxConnectionDuration: timeout.DefaultSetting(), DelayedClose: timeout.DefaultSetting(), ConnectionShutdownGracePeriod: timeout.DefaultSetting(), + ConnectTimeout: 0, }, }, "timeouts all set": { @@ -60,6 +62,7 @@ func TestParseTimeoutPolicy(t *testing.T) { MaxConnectionDuration: pointer.String("infinity"), DelayedCloseTimeout: pointer.String("5s"), ConnectionShutdownGracePeriod: pointer.String("6s"), + ConnectTimeout: pointer.String("8s"), }, expected: contourconfig.Timeouts{ Request: timeout.DurationSetting(time.Second), @@ -68,6 +71,7 @@ func TestParseTimeoutPolicy(t *testing.T) { MaxConnectionDuration: timeout.DisabledSetting(), DelayedClose: timeout.DurationSetting(time.Second * 5), ConnectionShutdownGracePeriod: timeout.DurationSetting(time.Second * 6), + ConnectTimeout: 8 * time.Second, }, }, "request timeout invalid": { @@ -106,6 +110,12 @@ func TestParseTimeoutPolicy(t *testing.T) { }, errorMsg: "failed to parse connection shutdown grace period", }, + "connect timeout invalid": { + config: &contour_api_v1alpha1.TimeoutParameters{ + ConnectTimeout: pointer.String("infinite"), + }, + errorMsg: "failed to parse connect timeout", + }, } for name, tc := range testCases { diff --git a/internal/dag/dag.go b/internal/dag/dag.go index 9c517ef9dbd..7aa8401259f 100644 --- a/internal/dag/dag.go +++ b/internal/dag/dag.go @@ -685,6 +685,9 @@ type Cluster struct { // ClientCertificate is the optional identifier of the TLS secret containing client certificate and // private key to be used when establishing TLS connection to upstream cluster. ClientCertificate *Secret + + // ConnectTimeout defines how long the proxy should wait when establishing connection to upstream service. + ConnectTimeout time.Duration } // WeightedService represents the load balancing weight of a @@ -856,6 +859,9 @@ type ExtensionCluster struct { // ClientCertificate is the optional identifier of the TLS secret containing client certificate and // private key to be used when establishing TLS connection to upstream cluster. ClientCertificate *Secret + + // ConnectTimeout defines how long the proxy should wait when establishing connection to upstream service. + ConnectTimeout time.Duration } func wildcardDomainHeaderMatch(fqdn string) HeaderMatchCondition { diff --git a/internal/dag/extension_processor.go b/internal/dag/extension_processor.go index aed98eccf37..cd3d4585df4 100644 --- a/internal/dag/extension_processor.go +++ b/internal/dag/extension_processor.go @@ -16,6 +16,7 @@ package dag import ( "path" "strings" + "time" contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" @@ -35,6 +36,9 @@ type ExtensionServiceProcessor struct { // secret containing client certificate and private key to be // used when establishing TLS connection to upstream cluster. ClientCertificate *types.NamespacedName + + // ConnectTimeout defines how long the proxy should wait when establishing connection to upstream service. + ConnectTimeout time.Duration } var _ Processor = &ExtensionServiceProcessor{} @@ -108,6 +112,7 @@ func (p *ExtensionServiceProcessor) buildExtensionService( TimeoutPolicy: tp, SNI: "", ClientCertificate: clientCertSecret, + ConnectTimeout: p.ConnectTimeout, } lbPolicy := loadBalancerPolicy(ext.Spec.LoadBalancerPolicy) diff --git a/internal/dag/gatewayapi_processor.go b/internal/dag/gatewayapi_processor.go index 3996bf39acb..37723834dcc 100644 --- a/internal/dag/gatewayapi_processor.go +++ b/internal/dag/gatewayapi_processor.go @@ -18,6 +18,7 @@ import ( "net" "net/http" "strings" + "time" "github.com/projectcontour/contour/internal/errors" "github.com/projectcontour/contour/internal/k8s" @@ -52,6 +53,9 @@ type GatewayAPIProcessor struct { // This is normally disabled for security reasons. // See https://github.com/projectcontour/contour/security/advisories/GHSA-5ph6-qq5x-7jwc for details. EnableExternalNameService bool + + // ConnectTimeout defines how long the proxy should wait when establishing connection to upstream service. + ConnectTimeout time.Duration } // matchConditions holds match rules. @@ -720,9 +724,10 @@ func (p *GatewayAPIProcessor) computeTLSRoute(route *gatewayapi_v1alpha2.TLSRout // https://github.com/projectcontour/contour/issues/3593 service.Weighted.Weight = routeWeight proxy.Clusters = append(proxy.Clusters, &Cluster{ - Upstream: service, - SNI: service.ExternalName, - Weight: routeWeight, + Upstream: service, + SNI: service.ExternalName, + Weight: routeWeight, + ConnectTimeout: p.ConnectTimeout, }) } @@ -1072,6 +1077,7 @@ func (p *GatewayAPIProcessor) clusterRoutes(routeNamespace string, matchConditio Weight: routeWeight, Protocol: service.Protocol, RequestHeadersPolicy: headerPolicy, + ConnectTimeout: p.ConnectTimeout, }) } diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go index 9770eec67f8..499b510dabd 100644 --- a/internal/dag/httpproxy_processor.go +++ b/internal/dag/httpproxy_processor.go @@ -19,6 +19,7 @@ import ( "sort" "strconv" "strings" + "time" contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" @@ -84,6 +85,9 @@ type HTTPProxyProcessor struct { // Response headers that will be set on all routes (optional). ResponseHeadersPolicy *HeadersPolicy + + // ConnectTimeout defines how long the proxy should wait when establishing connection to upstream service. + ConnectTimeout time.Duration } // Run translates HTTPProxies into DAG objects and @@ -694,6 +698,7 @@ func (p *HTTPProxyProcessor) computeRoutes( SNI: determineSNI(r.RequestHeadersPolicy, reqHP, s), DNSLookupFamily: string(p.DNSLookupFamily), ClientCertificate: clientCertSecret, + ConnectTimeout: p.ConnectTimeout, } if service.Mirror && r.MirrorPolicy != nil { validCond.AddError(contour_api_v1.ConditionTypeServiceError, "OnlyOneMirror", @@ -789,6 +794,7 @@ func (p *HTTPProxyProcessor) processHTTPProxyTCPProxy(validCond *contour_api_v1. LoadBalancerPolicy: lbPolicy, TCPHealthCheckPolicy: tcpHealthCheckPolicy(tcpproxy.HealthCheckPolicy), SNI: s.ExternalName, + ConnectTimeout: p.ConnectTimeout, }) } secure := p.dag.EnsureSecureVirtualHost(host) diff --git a/internal/dag/ingress_processor.go b/internal/dag/ingress_processor.go index 6e5ab4f612b..de686ddfd83 100644 --- a/internal/dag/ingress_processor.go +++ b/internal/dag/ingress_processor.go @@ -17,6 +17,7 @@ import ( "regexp" "strconv" "strings" + "time" "k8s.io/apimachinery/pkg/util/intstr" @@ -49,6 +50,9 @@ type IngressProcessor struct { // Response headers that will be set on all routes (optional). ResponseHeadersPolicy *HeadersPolicy + + // ConnectTimeout defines how long the proxy should wait when establishing connection to upstream service. + ConnectTimeout time.Duration } // Run translates Ingresses into DAG objects and @@ -229,6 +233,7 @@ func (p *IngressProcessor) route(ingress *networking_v1.Ingress, host string, pa ClientCertificate: clientCertSecret, RequestHeadersPolicy: reqHP, ResponseHeadersPolicy: respHP, + ConnectTimeout: p.ConnectTimeout, }}, } diff --git a/internal/envoy/v3/cluster.go b/internal/envoy/v3/cluster.go index 9fee91a850f..2e748a9e6df 100644 --- a/internal/envoy/v3/cluster.go +++ b/internal/envoy/v3/cluster.go @@ -98,6 +98,10 @@ func Cluster(c *dag.Cluster) *envoy_cluster_v3.Cluster { cluster.TypedExtensionProtocolOptions = http2ProtocolOptions() } + if c.ConnectTimeout > time.Duration(0) { + cluster.ConnectTimeout = protobuf.Duration(c.ConnectTimeout) + } + return cluster } @@ -143,6 +147,10 @@ func ExtensionCluster(ext *dag.ExtensionCluster) *envoy_cluster_v3.Cluster { cluster.TypedExtensionProtocolOptions = http2ProtocolOptions() } + if ext.ConnectTimeout > time.Duration(0) { + cluster.ConnectTimeout = protobuf.Duration(ext.ConnectTimeout) + } + return cluster } diff --git a/internal/envoy/v3/cluster_test.go b/internal/envoy/v3/cluster_test.go index 24a2eeea2c3..54337e026ef 100644 --- a/internal/envoy/v3/cluster_test.go +++ b/internal/envoy/v3/cluster_test.go @@ -508,6 +508,22 @@ func TestCluster(t *testing.T) { ), }, }, + "cluster with connect timeout set": { + cluster: &dag.Cluster{ + Upstream: service(s1), + ConnectTimeout: 10 * time.Second, + }, + want: &envoy_cluster_v3.Cluster{ + Name: "default/kuard/443/da39a3ee5e", + AltStatName: "default_kuard_443", + ClusterDiscoveryType: ClusterDiscoveryType(envoy_cluster_v3.Cluster_EDS), + EdsClusterConfig: &envoy_cluster_v3.Cluster_EdsClusterConfig{ + EdsConfig: ConfigSource("contour"), + ServiceName: "default/kuard/http", + }, + ConnectTimeout: protobuf.Duration(10 * time.Second), + }, + }, } for name, tc := range tests { diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go index 11ee92c9628..d698a7db64d 100644 --- a/pkg/config/parameters.go +++ b/pkg/config/parameters.go @@ -412,6 +412,14 @@ type TimeoutParameters struct { // See https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-drain-timeout // for more information. ConnectionShutdownGracePeriod string `yaml:"connection-shutdown-grace-period,omitempty"` + + // ConnectTimeout defines how long the proxy should wait when establishing connection to upstream service. + // If not set, a default value of 2 seconds will be used. + // + // See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout + // for more information. + // +optional + ConnectTimeout string `yaml:"connect-timeout,omitempty"` } // Validate the timeout parameters. @@ -453,6 +461,14 @@ func (t TimeoutParameters) Validate() error { return fmt.Errorf("connection shutdown grace period %q: %w", t.ConnectionShutdownGracePeriod, err) } + // ConnectTimeout is normally implicitly set to 2s in Defaults(). + // ConnectTimeout cannot be "infinite" so use time.ParseDuration() directly instead of v(). + if t.ConnectTimeout != "" { + if _, err := time.ParseDuration(t.ConnectTimeout); err != nil { + return fmt.Errorf("connect timeout %q: %w", t.ConnectTimeout, err) + } + } + return nil } @@ -791,6 +807,7 @@ func Defaults() Parameters { // This is chosen as a rough default to stop idle connections wasting resources, // without stopping slow connections from being terminated too quickly. ConnectionIdleTimeout: "60s", + ConnectTimeout: "2s", }, Policy: PolicyParameters{ RequestHeadersPolicy: HeadersPolicy{}, diff --git a/pkg/config/parameters_test.go b/pkg/config/parameters_test.go index 45420cc7f7e..58e31461f42 100644 --- a/pkg/config/parameters_test.go +++ b/pkg/config/parameters_test.go @@ -73,6 +73,7 @@ json-fields: - x_forwarded_for timeouts: connection-idle-timeout: 60s + connect-timeout: 2s envoy-service-namespace: projectcontour envoy-service-name: envoy default-http-versions: [] @@ -252,6 +253,7 @@ func TestValidateTimeoutParams(t *testing.T) { MaxConnectionDuration: "infinite", DelayedCloseTimeout: "infinite", ConnectionShutdownGracePeriod: "infinite", + ConnectTimeout: "2s", }.Validate()) assert.NoError(t, TimeoutParameters{ RequestTimeout: "infinity", @@ -260,6 +262,7 @@ func TestValidateTimeoutParams(t *testing.T) { MaxConnectionDuration: "infinity", DelayedCloseTimeout: "infinity", ConnectionShutdownGracePeriod: "infinity", + ConnectTimeout: "2s", }.Validate()) assert.Error(t, TimeoutParameters{RequestTimeout: "foo"}.Validate()) @@ -268,6 +271,7 @@ func TestValidateTimeoutParams(t *testing.T) { assert.Error(t, TimeoutParameters{MaxConnectionDuration: "boop"}.Validate()) assert.Error(t, TimeoutParameters{DelayedCloseTimeout: "bebop"}.Validate()) assert.Error(t, TimeoutParameters{ConnectionShutdownGracePeriod: "bong"}.Validate()) + assert.Error(t, TimeoutParameters{ConnectTimeout: "infinite"}.Validate()) } diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html index 99191f82f7e..8e478bbf16d 100644 --- a/site/content/docs/main/config/api-reference.html +++ b/site/content/docs/main/config/api-reference.html @@ -6078,6 +6078,22 @@

TimeoutParameters for more information.

+ + +connectTimeout +
+ +string + + + +(Optional) +

ConnectTimeout defines how long the proxy should wait when establishing connection to upstream service. +If not set, a default value of 2 seconds will be used.

+

See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#envoy-v3-api-field-config-cluster-v3-cluster-connect-timeout +for more information.

+ +

XDSServerConfig diff --git a/site/content/docs/main/configuration.md b/site/content/docs/main/configuration.md index debaa5493a9..10281043a6b 100644 --- a/site/content/docs/main/configuration.md +++ b/site/content/docs/main/configuration.md @@ -149,6 +149,7 @@ The timeout configuration block can be used to configure various timeouts for th | max-connection-duration | string | none* | This field defines the maximum period of time after an HTTP connection has been established from the client to the proxy before it is closed by the proxy, regardless of whether there has been activity or not. Must be a [valid Go duration string][4], or omitted or set to `infinity` for no max duration. See [the Envoy documentation][10] for more information. | | delayed-close-timeout | string | `1s`* | *Note: this is an advanced setting that should not normally need to be tuned.*

This field defines how long envoy will wait, once connection close processing has been initiated, for the downstream peer to close the connection before Envoy closes the socket associated with the connection. Setting this timeout to 'infinity' will disable it. See [the Envoy documentation][13] for more information. | | connection-shutdown-grace-period | string | `5s`* | This field defines how long the proxy will wait between sending an initial GOAWAY frame and a second, final GOAWAY frame when terminating an HTTP/2 connection. During this grace period, the proxy will continue to respond to new streams. After the final GOAWAY frame has been sent, the proxy will refuse new streams. Must be a [valid Go duration string][4]. See [the Envoy documentation][11] for more information. | +| connect-timeout | string | `2s` | This field defines how long the proxy will wait for the upstream connection to be established. _This is Envoy's default setting value and is not explicitly configured by Contour._