diff --git a/artifacts/flagger/crd.yaml b/artifacts/flagger/crd.yaml index fdadb971..e02f5aa0 100644 --- a/artifacts/flagger/crd.yaml +++ b/artifacts/flagger/crd.yaml @@ -230,6 +230,9 @@ spec: timeout: description: HTTP or gRPC request timeout type: string + backendTimeout: + description: Backend request timeout (Gateway API only) + type: string meshName: description: AppMesh mesh name type: string diff --git a/charts/flagger/crds/crd.yaml b/charts/flagger/crds/crd.yaml index fdadb971..e02f5aa0 100644 --- a/charts/flagger/crds/crd.yaml +++ b/charts/flagger/crds/crd.yaml @@ -230,6 +230,9 @@ spec: timeout: description: HTTP or gRPC request timeout type: string + backendTimeout: + description: Backend request timeout (Gateway API only) + type: string meshName: description: AppMesh mesh name type: string diff --git a/docs/gitbook/tutorials/gatewayapi-progressive-delivery.md b/docs/gitbook/tutorials/gatewayapi-progressive-delivery.md index dc5bb2ff..d9f7cb51 100644 --- a/docs/gitbook/tutorials/gatewayapi-progressive-delivery.md +++ b/docs/gitbook/tutorials/gatewayapi-progressive-delivery.md @@ -720,6 +720,38 @@ The above procedures can be extended with [custom metrics](../usage/metrics.md) Besides the `hosts` and `gatewayRefs` fields, you can customize the generated HTTPRoute with various options exposed under the `spec.service` field of the Canary. +### Timeouts + +You can configure request timeouts on the generated HTTPRoute using the `spec.service.timeout` and +`spec.service.backendTimeout` fields of the Canary. Both values are +[Gateway API Duration](https://gateway-api.sigs.k8s.io/geps/gep-2257/) strings (e.g. `5s`, `500ms`). + +- `timeout` maps to `HTTPRouteTimeouts.Request` and bounds the total request duration from when the gateway + receives the request to when the response is sent back to the client. +- `backendTimeout` maps to `HTTPRouteTimeouts.BackendRequest` and bounds the duration of an individual + request from the gateway to a backend. When set together with `timeout`, `backendTimeout` must be + less than or equal to `timeout`. + +> **Note:** Timeouts require a Gateway API implementation that supports +> [`HTTPRouteTimeouts`](https://gateway-api.sigs.k8s.io/guides/user-guides/http-timeouts/). +> `backendTimeout` additionally requires support for the `BackendRequest` timeout field. + +Example configuration: + +```yaml +apiVersion: flagger.app/v1beta1 +kind: Canary +metadata: + name: podinfo + namespace: test +spec: + service: + # Total request timeout (gateway to client) + timeout: 5s + # Per-attempt backend request timeout (gateway to backend) + backendTimeout: 2s +``` + ### Header Manipulation You can configure request and response header manipulation using the `spec.service.headers` field of the Canary. diff --git a/docs/gitbook/usage/how-it-works.md b/docs/gitbook/usage/how-it-works.md index ef8dcea2..d8f2ed16 100644 --- a/docs/gitbook/usage/how-it-works.md +++ b/docs/gitbook/usage/how-it-works.md @@ -230,6 +230,9 @@ spec: attempts: 3 perTryTimeout: 1s timeout: 5s + # backendTimeout is only supported by the Gateway API provider and + # maps to HTTPRouteTimeouts.BackendRequest. + backendTimeout: 2s ``` When using **Istio** as the mesh provider, you can also specify HTTP header operations, diff --git a/kustomize/base/flagger/crd.yaml b/kustomize/base/flagger/crd.yaml index fdadb971..e02f5aa0 100644 --- a/kustomize/base/flagger/crd.yaml +++ b/kustomize/base/flagger/crd.yaml @@ -230,6 +230,9 @@ spec: timeout: description: HTTP or gRPC request timeout type: string + backendTimeout: + description: Backend request timeout (Gateway API only) + type: string meshName: description: AppMesh mesh name type: string diff --git a/pkg/apis/flagger/v1beta1/canary.go b/pkg/apis/flagger/v1beta1/canary.go index 950a6fe0..11f882c0 100644 --- a/pkg/apis/flagger/v1beta1/canary.go +++ b/pkg/apis/flagger/v1beta1/canary.go @@ -168,6 +168,11 @@ type CanaryService struct { // +optional Timeout string `json:"timeout,omitempty"` + // BackendTimeout specifies a timeout for an individual request from the gateway + // to a backend. Only supported by the Gateway API provider. + // +optional + BackendTimeout string `json:"backendTimeout,omitempty"` + // Gateways attached to the generated Istio virtual service // Defaults to the internal mesh gateway // +optional diff --git a/pkg/router/gateway_api.go b/pkg/router/gateway_api.go index 31b09b20..df325caa 100644 --- a/pkg/router/gateway_api.go +++ b/pkg/router/gateway_api.go @@ -108,11 +108,8 @@ func (gwr *GatewayAPIRouter) Reconcile(canary *flaggerv1.Canary) error { }, }, } - if canary.Spec.Service.Timeout != "" { - timeout := v1.Duration(canary.Spec.Service.Timeout) - httpRouteSpec.Rules[0].Timeouts = &v1.HTTPRouteTimeouts{ - Request: &timeout, - } + if canary.Spec.Service.Timeout != "" || canary.Spec.Service.BackendTimeout != "" { + httpRouteSpec.Rules[0].Timeouts = makeHTTPRouteTimeouts(canary) } // A/B testing @@ -129,11 +126,8 @@ func (gwr *GatewayAPIRouter) Reconcile(canary *flaggerv1.Canary) error { }, }, }) - if canary.Spec.Service.Timeout != "" { - timeout := v1.Duration(canary.Spec.Service.Timeout) - httpRouteSpec.Rules[1].Timeouts = &v1.HTTPRouteTimeouts{ - Request: &timeout, - } + if canary.Spec.Service.Timeout != "" || canary.Spec.Service.BackendTimeout != "" { + httpRouteSpec.Rules[1].Timeouts = makeHTTPRouteTimeouts(canary) } } @@ -382,11 +376,8 @@ func (gwr *GatewayAPIRouter) SetRoutes( }, }, } - if canary.Spec.Service.Timeout != "" { - timeout := v1.Duration(canary.Spec.Service.Timeout) - weightedRouteRule.Timeouts = &v1.HTTPRouteTimeouts{ - Request: &timeout, - } + if canary.Spec.Service.Timeout != "" || canary.Spec.Service.BackendTimeout != "" { + weightedRouteRule.Timeouts = makeHTTPRouteTimeouts(canary) } // If B/G mirroring is enabled, then add a route filter which mirrors the traffic @@ -439,11 +430,8 @@ func (gwr *GatewayAPIRouter) SetRoutes( }, }) - if canary.Spec.Service.Timeout != "" { - timeout := v1.Duration(canary.Spec.Service.Timeout) - hrClone.Spec.Rules[1].Timeouts = &v1.HTTPRouteTimeouts{ - Request: &timeout, - } + if canary.Spec.Service.Timeout != "" || canary.Spec.Service.BackendTimeout != "" { + hrClone.Spec.Rules[1].Timeouts = makeHTTPRouteTimeouts(canary) } } @@ -711,6 +699,19 @@ func (gwr *GatewayAPIRouter) mapRouteMatches(requestMatches []istiov1beta1.HTTPM return matches, nil } +func makeHTTPRouteTimeouts(canary *flaggerv1.Canary) *v1.HTTPRouteTimeouts { + timeouts := &v1.HTTPRouteTimeouts{} + if canary.Spec.Service.Timeout != "" { + timeout := v1.Duration(canary.Spec.Service.Timeout) + timeouts.Request = &timeout + } + if canary.Spec.Service.BackendTimeout != "" { + backendTimeout := v1.Duration(canary.Spec.Service.BackendTimeout) + timeouts.BackendRequest = &backendTimeout + } + return timeouts +} + func (gwr *GatewayAPIRouter) makeBackendRef(svcName string, weight, port int32) v1.BackendRef { return v1.BackendRef{ BackendObjectReference: v1.BackendObjectReference{ diff --git a/pkg/router/gateway_api_test.go b/pkg/router/gateway_api_test.go index 94d871c1..b9923f28 100644 --- a/pkg/router/gateway_api_test.go +++ b/pkg/router/gateway_api_test.go @@ -61,6 +61,7 @@ func TestGatewayAPIRouter_Reconcile(t *testing.T) { timeout := routeRules[0].Timeouts assert.Equal(t, string(*timeout.Request), canary.Spec.Service.Timeout) + assert.Equal(t, string(*timeout.BackendRequest), canary.Spec.Service.BackendTimeout) // assert that http route annotations injected by the networking controller is preserved. httpRoute.Annotations["foo"] = "bar" diff --git a/pkg/router/router_test.go b/pkg/router/router_test.go index e8f09561..1d2d869e 100644 --- a/pkg/router/router_test.go +++ b/pkg/router/router_test.go @@ -593,7 +593,8 @@ func newTestGatewayAPICanary() *flaggerv1.Canary { Name: "podinfo", }, }, - Timeout: "10s", + Timeout: "10s", + BackendTimeout: "5s", }, Analysis: &flaggerv1.CanaryAnalysis{ Threshold: 10,