Skip to content

Gateway api resources#865

Open
Azahorscak wants to merge 1 commit into
rstudio:mainfrom
Azahorscak:main
Open

Gateway api resources#865
Azahorscak wants to merge 1 commit into
rstudio:mainfrom
Azahorscak:main

Conversation

@Azahorscak
Copy link
Copy Markdown

@Azahorscak Azahorscak commented May 11, 2026

  • feat: add consistent, off-by-default, gatewayAPI configuration options to connect, workbench and package-manager

* feat: add consistent, off-by-default, gatewayAPI configuration options

* fix: versions and docs
@Azahorscak
Copy link
Copy Markdown
Author

Hi folks, I'm happy to iterate as requested.
I'm selfishly interested in keeping the httpRoute configuration consistent across all three components.

Copy link
Copy Markdown

@jaygalvin jaygalvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Adam, thanks for this PR! I came here to add a kgateway example, but you beat me to it, AND added support for Gateway API to the chart.

Tested the kgateway track end-to-end on EKS (k8s 1.35, kgateway v2.2.4, AWS LB Controller v2.11) against a real Workbench release installed from this branch. The chart-native gatewayApi flow works well: the rendered HTTPRoute attached cross-namespace to the shared posit-shared Gateway in kgateway-system (Accepted + ResolvedRefs, listener attachedRoutes=1), kgateway's defaults provisioned an internal NLB, and HTTP routing to Workbench worked (GET / → 302 → sign-in page).

One gap worth addressing for the Posit-product use case: kgateway rejects WebSocket upgrades by default, and nothing in the HTTPRoute the chart renders can change that. Workbench and Connect interactive sessions (RStudio, Jupyter, VS Code, Shiny, Dash, Streamlit) all rely on websockets, so the example as-is will route the UI but break sessions.

Reproduced cleanly with an echo backend behind a bare HTTPRoute identical in shape to the chart's output (same gateway, same upgrade headers):

Config Plain HTTP WebSocket upgrade
Bare HTTPRoute, no policy 200 403 Forbidden
+ HTTPListenerPolicy{upgradeConfig: [websocket]} on the Gateway 200 101 Switching Protocols

Adding the policy flipped the same route from 403 → 101, so it's specifically the upgrade that's gated.

Since the policy targets the Gateway, a single one covers every product attached to the shared gateway. Suggest adding it to examples/gateway-api/kgateway/ (e.g. a httplistenerpolicy.yaml) and referencing it from the README:

apiVersion: gateway.kgateway.dev/v1alpha1
kind: HTTPListenerPolicy
metadata:
  name: posit-shared-ws
  namespace: kgateway-system
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: posit-shared
  upgradeConfig:
    enabledUpgrades:
      - websocket
  # Optional: don't drop idle interactive sessions.
  streamIdleTimeout: "0s"

Beyond WebSockets, three more Envoy behaviors that the bare HTTPRoute doesn't cover matter for these products. Each is a kgateway policy CRD that targets the Gateway, HTTPRoute, or Service — no change to the rendered route. Applicability:

Knob Resource → target Workbench Connect Package Manager
WebSocket upgrades HTTPListenerPolicy → Gateway Required Required
Request / stream-idle timeout TrafficPolicy → HTTPRoute Required Required Recommended
Buffer disable (large uploads) TrafficPolicy → HTTPRoute Recommended Required Required
Session-affinity cookie BackendConfigPolicy → Service multi-replica multi-replica

Timeouts + large uploads — Envoy's defaults (15s request, 5m stream-idle, and the request-body buffer cap) cut off long sessions/transfers and return 413 on big Connect bundles or PM packages. Gateway API has HTTPRoute.rules[].timeouts, but the gatewayApi values block doesn't render it — so the no-chart-change path is a TrafficPolicy targeting the chart's route by its generated name (<fullname>-gw-<index>). Timeouts and buffer can share one policy:

apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
  name: posit-connect-traffic
  namespace: <release-namespace>
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      name: posit-connect-rstudio-connect-gw-0   # kubectl get httproute -n <ns>
  timeouts:
    request: "0s"        # disable per-request timeout (or set a finite bound, e.g. "3600s")
    streamIdle: "0s"
  buffer:
    disable: {}          # stream large bodies instead of buffering (or buffer.maxRequestSize)

Session affinity (only when replicas > 1) — Workbench/Connect keep per-user state on the serving pod, so users must stick to one backend. A BackendConfigPolicy on the Service does consistent-hash LB on a cookie kgateway sets:

apiVersion: gateway.kgateway.dev/v1alpha1
kind: BackendConfigPolicy
metadata:
  name: posit-workbench-affinity
  namespace: <release-namespace>
spec:
  targetRefs:
    - group: ""
      kind: Service
      name: posit-workbench-rstudio-workbench
  loadBalancer:
    ringHash:
      hashPolicies:
        - cookie: { name: posit-workbench-affinity, path: /, ttl: 86400s }
          terminal: true

Verification notes (all on kgateway v2.2.4): WebSocket 403→101 and the affinity-cookie Set-Cookie … HttpOnly confirmed end-to-end; the combined TrafficPolicy (timeouts + buffer in one resource) reported Accepted/Attached, and the request timeout was functionally enforced against a delaying backend — request: "3s" returned 504 at 3.01s while a fast request passed, and request: "0s" let an 8s request complete (200). BackendConfigPolicy reported Accepted/Attached.

Happy to open a follow-up PR adding the HTTPListenerPolicy to examples/gateway-api/kgateway/ plus a "tuning for Posit products" section covering the above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants