Skip to content
Open
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
15 changes: 15 additions & 0 deletions api/v1alpha/httpproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,23 @@ type HTTPProxyRule struct {
// +kubebuilder:validation:MinItems=0
// +kubebuilder:validation:MaxItems=16
Backends []HTTPProxyRuleBackend `json:"backends,omitempty"`

// Timeouts bound how long a request matching this rule may take.
//
// The request timeout runs from the request reaching the edge until the
// last byte of the response is sent, so a streaming response or a large
// download must finish within it. It defaults to one hour, and the operator
// caps it at a configured maximum. It cannot be disabled.
//
// See documentation for the `timeouts` field in the `HTTPRouteRule` type at
// https://gateway-api.sigs.k8s.io/reference/spec/#httprouterule
//
// +kubebuilder:default={request: "1h"}
Timeouts *gatewayv1.HTTPRouteTimeouts `json:"timeouts,omitempty"`
}

const HTTPProxyDefaultRequestTimeout = "1h"

// +kubebuilder:validation:XValidation:message="endpoint is required unless instance or networkService is set; instance and networkService are mutually exclusive with each other and with endpoint and connector",rule="has(self.instance) ? (!has(self.endpoint) && !has(self.connector) && !has(self.networkService)) : (has(self.networkService) ? (!has(self.endpoint) && !has(self.connector)) : has(self.endpoint))"
// +kubebuilder:validation:XValidation:message="backend TLS is not supported for networkService backends",rule="has(self.networkService) ? !has(self.tls) : true"
type HTTPProxyRuleBackend struct {
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 19 additions & 26 deletions claude-plugin/skills/alb/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ An Application Load Balancer is a **product built from several objects**, not a
- Add routes and the origins behind them, by URL or by network service
- Split a route's traffic across several origins by weight, choose the load balancing algorithm, and turn on passive health checks
- Configure traffic protection (WAF), request headers and basic authentication
- Raise the 15-second response limit for streams and large downloads
- Set how long a response may run, up to an hour, per route
- Read access logs for one load balancer
- Preview every change before applying it
- Attach advanced Envoy Gateway policies the plugin does not cover
Expand Down Expand Up @@ -233,43 +233,36 @@ Datum attaches a `BackendTrafficPolicy` of its own to the load balancer's routin

A Gateway takes one `BackendTrafficPolicy`. Put rate limiting, circuit breaking and the response limit below in the same policy.

### Responses that run longer than 15 seconds
### Responses that run longer than an hour

A response must finish within about 15 seconds of the request reaching the edge. After that the edge ends it:
Every route rule carries `timeouts.request`: the time from the request reaching the edge to the last byte of the response. It defaults to `1h`, so streams, large downloads and slow APIs run for up to an hour before the edge ends them:

- If the origin has not started answering, the client gets a `504`.
- If the origin has started, the response is cut off mid-transfer with whatever status it began with. Large downloads stop partway, and Server-Sent Events, LLM token streams, log tails and long polls close.
- If the origin has started, the response is cut off mid-transfer with whatever status it began with.

WebSockets are not cut. In `alb logs`, a cut response shows a `DURATION` of about 15000 ms.
WebSockets are not cut. In `alb logs`, a cut response shows a `DURATION` equal to the rule's timeout.

`HTTPProxy` has no timeout field. Raise the limit, up to `1h`, with `timeout.http.requestTimeout` on the load balancer's Gateway:
The plugin does not set the timeout. Set it per rule in the `HTTPProxy` manifest, between `1ns` and `1h`. A lower value makes a hung origin fail fast; `0s` is refused, since it would also switch off the idle timeout and leave abandoned streams open:

```yaml
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
apiVersion: networking.datumapis.com/v1alpha
kind: HTTPProxy
metadata:
name: my-app
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: my-app
timeout:
http:
requestTimeout: 1h
rules:
- matches:
- path:
type: PathPrefix
value: /
backends:
- endpoint: https://origin.example.com
timeouts:
request: 10m
```

**Target the Gateway, never the HTTPRoute.** An HTTPRoute takes one `BackendTrafficPolicy`, and Datum attaches its own to the HTTPRoute when the load balancer has a network service origin, an algorithm or health checks. The older of the two wins and the other is rejected as `Conflicted`:

- If Datum's is older, yours does nothing and responses are still cut at 15 seconds.
- If yours is older, Datum's is rejected and the load balancer silently loses its algorithm and health checks, while still reporting `Programmed`.

A Gateway-level policy merges with Datum's, so both apply. Editing `timeouts` on the generated HTTPRoute does not work either: Datum rewrites that route from the `HTTPProxy` and the edit is reverted.

`requestTimeout` covers everything from the request arriving to the last byte sent, so set it longer than the longest stream or download. A value above `1h` is rejected with `duration must be less than or equal to 1h0m0s`.

To confirm it worked, check that `datumctl describe backendtrafficpolicy my-app` shows `Accepted` and not `Conflicted`, then make a request that runs past 15 seconds and check that it completes. To restore the default, delete the policy, or remove the `timeout` block if the policy carries other settings.
A value above `1h` is rejected with `duration must be less than or equal to 1h0m0s`. A `BackendTrafficPolicy` `requestTimeout` does not change a load balancer's limit any more: the rule's timeout takes precedence. Remove one you attached for that purpose.

### Authentication beyond basic auth

Expand Down Expand Up @@ -302,7 +295,7 @@ Failures print a named exit code — `exit status 6 # ALB_INVALID`. The names ar
- The portal manages one route: the pool of origins on `/`, with weights, the algorithm and health checks. Those are the same fields `datumctl alb` writes, so either can change them. Once a load balancer has a second route, a path match or a per-origin filter, the portal locks its origins, weights, algorithm, health checks, TLS, redirect and Host override and points to `datumctl`; hostnames, protection and auth stay editable there. Tell the user to expect that lock after you add a route
- Traffic protection takes paranoia 1 to 4 here; the portal offers only 1 and 2
- Attach a `BackendTrafficPolicy` to the load balancer's Gateway, never its HTTPRoute. A route-level policy collides with the one Datum attaches for health checks and load balancing
- Responses end after about 15 seconds unless a `BackendTrafficPolicy` raises the limit, up to 1 hour
- Responses end after 1 hour by default. Lower it per rule with `timeouts.request` in the manifest; it cannot go above 1 hour or be switched off
- Run `datumctl diff -f` before `apply`, and validate with `--dry-run=server`
- `delete` has no confirmation prompt — verify the name first. Deleting a load balancer also removes its traffic protection policy and basic auth
- Start traffic protection in `mode: Observe` on a live site, watch real traffic, then move to `Enforce`. Raise paranoia one level at a time; above 2 the false-positive rate climbs sharply
Expand Down
2 changes: 1 addition & 1 deletion claude-plugin/skills/httproute/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ datumctl apply -f header-injection.yaml --project my-project
- Use `sectionName` in `spec.parentRefs[]` to bind a route to a specific listener when a Gateway has both HTTP and HTTPS listeners; binding only to `http` is the standard pattern for redirect routes
- `gateway.networking.k8s.io/v1` is stable GA — unlike the `gateway.envoyproxy.io` policy CRDs it is not subject to breaking alpha changes
- Policy attachments from the alb skill (`SecurityPolicy`, `BackendTrafficPolicy`, `TrafficProtectionPolicy`) reference `HTTPRoute` resources by name via `targetRefs` — create the HTTPRoute first before attaching policies
- This skill is for HTTPRoutes you write yourself. An Application Load Balancer's HTTPRoute is generated from its `HTTPProxy` and rewritten on every change, so edits to it, `timeouts` included, are reverted. To let a load balancer's responses run longer than 15 seconds, attach a `BackendTrafficPolicy` to its Gateway as the [alb skill](../alb/SKILL.md) describes
- This skill is for HTTPRoutes you write yourself. An Application Load Balancer's HTTPRoute is generated from its `HTTPProxy` and rewritten on every change, so edits to it, `timeouts` included, are reverted. Set a load balancer's response limit with `timeouts.request` on the `HTTPProxy` rule instead, as the [alb skill](../alb/SKILL.md) describes
- Run `datumctl diff -f` before `apply` for any changes
- `--dry-run=server` validates the manifest against the API before committing
- `delete` has no confirmation prompt — always verify the resource name first
Expand Down
70 changes: 70 additions & 0 deletions config/crd/bases/networking.datumapis.com_httpproxies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3458,6 +3458,76 @@ spec:
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
timeouts:
default:
request: 1h
description: |-
Timeouts bound how long a request matching this rule may take.

The request timeout runs from the request reaching the edge until the
last byte of the response is sent, so a streaming response or a large
download must finish within it. It defaults to one hour, and the operator
caps it at a configured maximum. It cannot be disabled.

See documentation for the `timeouts` field in the `HTTPRouteRule` type at
https://gateway-api.sigs.k8s.io/reference/spec/#httprouterule
properties:
backendRequest:
description: |-
BackendRequest specifies a timeout for an individual request from the gateway
to a backend. This covers the time from when the request first starts being
sent from the gateway to when the full response has been received from the backend.

Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout
completely. Implementations that cannot completely disable the timeout MUST
instead interpret the zero duration as the longest possible value to which
the timeout can be set.

An entire client HTTP transaction with a gateway, covered by the Request timeout,
may result in more than one call from the gateway to the destination backend,
for example, if automatic retries are supported.

The value of BackendRequest must be a Gateway API Duration string as defined by
GEP-2257. When this field is unspecified, its behavior is implementation-specific;
when specified, the value of BackendRequest must be no more than the value of the
Request timeout (since the Request timeout encompasses the BackendRequest timeout).

Support: Extended
pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$
type: string
request:
description: |-
Request specifies the maximum duration for a gateway to respond to an HTTP request.
If the gateway has not been able to respond before this deadline is met, the gateway
MUST return a timeout error.

For example, setting the `rules.timeouts.request` field to the value `10s` in an
`HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds
to complete.

Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout
completely. Implementations that cannot completely disable the timeout MUST
instead interpret the zero duration as the longest possible value to which
the timeout can be set.

This timeout is intended to cover as close to the whole request-response transaction
as possible although an implementation MAY choose to start the timeout after the entire
request stream has been received instead of immediately after the transaction is
initiated by the client.

The value of Request is a Gateway API Duration string as defined by GEP-2257. When this
field is unspecified, request timeout behavior is implementation-specific.

Support: Extended
pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$
type: string
type: object
x-kubernetes-validations:
- message: backendRequest timeout cannot be longer than request
timeout
rule: '!(has(self.request) && has(self.backendRequest) &&
duration(self.request) != duration(''0s'') && duration(self.backendRequest)
> duration(self.request))'
type: object
x-kubernetes-validations:
- message: RequestRedirect filter must not be used together with
Expand Down
11 changes: 6 additions & 5 deletions docs/agent/skills/access-log-triage.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ Then widen the window, or send a request yourself and look again.
is down, refusing connections, or too slow.
- `429`: rate limited.

**Durations near 15 seconds** mean the response limit, not a slow origin. A
response must finish within about 15 seconds of the request reaching the edge.
A request whose origin had not started answering by then comes back `504`. One
whose origin had started keeps its status, `200` included, and is cut off
mid-transfer. So a `200` at about 15000 ms is a large download or a stream that
**Durations at the route's timeout** mean the response limit, not a slow
origin. A response must finish within the rule's `timeouts.request`, one hour
by default, of the request reaching the edge. A request whose origin had not
started answering by then comes back `504`. One whose origin had started keeps
its status, `200` included, and is cut off mid-transfer. So a `200` at about
3600000 ms, or at whatever the rule sets, is a large download or a stream that
was cut short, not a success. WebSockets are not cut.

The limit can be raised to at most one hour. `HTTPProxy` has no field for it.
Expand Down
99 changes: 99 additions & 0 deletions docs/api/httpproxies.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,24 @@ https://gateway-api.sigs.k8s.io/reference/spec/#httprouterule<br/>
if it is set.<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#httpproxyspecrulesindextimeouts">timeouts</a></b></td>
<td>object</td>
<td>
Timeouts bound how long a request matching this rule may take.

The request timeout runs from the request reaching the edge until the
last byte of the response is sent, so a streaming response or a large
download must finish within it. It defaults to one hour, and the operator
caps it at a configured maximum. It cannot be disabled.

See documentation for the `timeouts` field in the `HTTPRouteRule` type at
https://gateway-api.sigs.k8s.io/reference/spec/#httprouterule<br/>
<br/>
<i>Validations</i>:<li>!(has(self.request) && has(self.backendRequest) && duration(self.request) != duration('0s') && duration(self.backendRequest) > duration(self.request)): backendRequest timeout cannot be longer than request timeout</li>
<i>Default</i>: map[request:1h]<br/>
</td>
<td>false</td>
</tr></tbody>
</table>

Expand Down Expand Up @@ -4324,6 +4342,87 @@ documentation to determine the supported dialect.<br/>
</table>


### HTTPProxy.spec.rules[index].timeouts
<sup><sup>[↩ Parent](#httpproxyspecrulesindex)</sup></sup>



Timeouts bound how long a request matching this rule may take.

The request timeout runs from the request reaching the edge until the
last byte of the response is sent, so a streaming response or a large
download must finish within it. It defaults to one hour, and the operator
caps it at a configured maximum. It cannot be disabled.

See documentation for the `timeouts` field in the `HTTPRouteRule` type at
https://gateway-api.sigs.k8s.io/reference/spec/#httprouterule

<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Required</th>
</tr>
</thead>
<tbody><tr>
<td><b>backendRequest</b></td>
<td>string</td>
<td>
BackendRequest specifies a timeout for an individual request from the gateway
to a backend. This covers the time from when the request first starts being
sent from the gateway to when the full response has been received from the backend.

Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout
completely. Implementations that cannot completely disable the timeout MUST
instead interpret the zero duration as the longest possible value to which
the timeout can be set.

An entire client HTTP transaction with a gateway, covered by the Request timeout,
may result in more than one call from the gateway to the destination backend,
for example, if automatic retries are supported.

The value of BackendRequest must be a Gateway API Duration string as defined by
GEP-2257. When this field is unspecified, its behavior is implementation-specific;
when specified, the value of BackendRequest must be no more than the value of the
Request timeout (since the Request timeout encompasses the BackendRequest timeout).

Support: Extended<br/>
</td>
<td>false</td>
</tr><tr>
<td><b>request</b></td>
<td>string</td>
<td>
Request specifies the maximum duration for a gateway to respond to an HTTP request.
If the gateway has not been able to respond before this deadline is met, the gateway
MUST return a timeout error.

For example, setting the `rules.timeouts.request` field to the value `10s` in an
`HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds
to complete.

Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout
completely. Implementations that cannot completely disable the timeout MUST
instead interpret the zero duration as the longest possible value to which
the timeout can be set.

This timeout is intended to cover as close to the whole request-response transaction
as possible although an implementation MAY choose to start the timeout after the entire
request stream has been received instead of immediately after the transaction is
initiated by the client.

The value of Request is a Gateway API Duration string as defined by GEP-2257. When this
field is unspecified, request timeout behavior is implementation-specific.

Support: Extended<br/>
</td>
<td>false</td>
</tr></tbody>
</table>


### HTTPProxy.spec.healthCheck
<sup><sup>[↩ Parent](#httpproxyspec)</sup></sup>

Expand Down
Loading
Loading