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 @@
connectTimeout
+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.
+