Skip to content

feat(gateway): advertise HTTP/2 via ALPN on Gateway API listeners#3398

Merged
Timofei Larkin (lllamnyp) merged 1 commit into
mainfrom
feat/gateway-http2-alpn
Jul 23, 2026
Merged

feat(gateway): advertise HTTP/2 via ALPN on Gateway API listeners#3398
Timofei Larkin (lllamnyp) merged 1 commit into
mainfrom
feat/gateway-http2-alpn

Conversation

@lllamnyp

@lllamnyp Timofei Larkin (lllamnyp) commented Jul 21, 2026

Copy link
Copy Markdown
Member

What this PR does

Browsers negotiate HTTP/2 exclusively through TLS ALPN: if the terminating listener does not advertise h2, every client silently falls back to HTTP/1.1, with no error surfaced anywhere. Cozystack's Gateway API path is served by Cilium's embedded Envoy, and Cilium ships with enable-gateway-api-alpn=false — so every Gateway listener advertised no ALPN protocols and all browser traffic through a Gateway ran over HTTP/1.1 (observable in browser dev tools as http/1.1 on every request). The ingress-nginx path this replaces advertised h2 out of the box, so the migration to Gateway API silently downgraded clients: head-of-line blocking and the ~6-connections-per-origin limit are back for anything served through a Gateway.

This PR enables ALPN (h2, then http/1.1) on Gateway API listeners, default-on, with an opt-out at each management layer:

  • packages/system/cilium/values.yaml now defaults gatewayAPI.enableAlpn: true, which renders enable-gateway-api-alpn: "true" into the cilium-config ConfigMap.
  • The platform chart gains gateway.http2 (default true) next to the existing gateway.enabled/gateway.attachedNamespaces. The isp-full and isp-full-generic bundles thread it as inline component values into the cozystack.networking Package CR (components.cilium.values.cilium.gatewayAPI.enableAlpn); inline HelmRelease values override the package valuesFiles, so an explicit gateway.http2: false actually reaches the dataplane rather than being shadowed by the new chart default. On isp-hosted the hosting-side CNI owns this setting and the flag is a no-op.
  • Tenant Kubernetes clusters (packages/apps/kubernetes) get the same fix: when the gatewayAPI addon is enabled, the tenant cilium HelmRelease now sets enableAlpn: true; individual clusters opt out via addons.cilium.valuesOverride.cilium.gatewayAPI.enableAlpn=false.

Why default-on rather than opt-in

HTTP/2 for browsers was the platform's behavior before the Gateway API migration, so keeping ALPN off would preserve a regression, not a status quo. Defaulting on is safe because ALPN is a negotiation: clients that cannot speak HTTP/2 simply keep negotiating http/1.1, exactly as before. Operators who need the old wire behavior can set gateway.http2: false platform-wide.

Frontend vs backend hop

This change affects the client↔gateway hop only. Gateway↔backend connections stay HTTP/1.1 unless a Service explicitly opts in per GEP-1911 by declaring appProtocol: kubernetes.io/h2c on its port. Cilium couples that backend-protocol support to the ALPN flag (enable-gateway-api-app-protocol flips to true together with it), but it only acts on Services that declare a recognized appProtocol value — and no cozystack-shipped Service does today (only plain http/https values exist in-tree, which Cilium ignores for protocol selection). So upstreams see no change from this PR alone; h2c/gRPC backends become possible to configure, not configured.

The setting is cluster-wide Cilium agent config, so it covers the root Gateway and all tenant Gateways at once — Cilium offers no per-Gateway granularity here. Flipping it re-rolls the cilium DaemonSet on upgrade (rollOutCiliumPods: true), the same disruption profile as any other cilium config change.

How it was verified

  • New helm-unittest suites: packages/core/platform/tests/bundles_gateway_http2_wiring_test.yaml asserts the Package CR carries enableAlpn true by default and false with gateway.http2: false for both cilium bundles; packages/apps/kubernetes/tests/cilium_gateway_alpn_test.yaml asserts the tenant HelmRelease default, the addon-off case, and the valuesOverride opt-out. Full suites for both charts pass (93 and 184 tests).
  • helm template of packages/system/cilium confirms the value is consumed, not just rendered: default produces enable-gateway-api-alpn: "true" (and enable-gateway-api-app-protocol: "true") in cilium-config; the threaded false produces "false" for both.

Screenshots

Not a UI change.

Downstream repositories

The website's hand-written platform values table (content/en/docs/next/operations/configuration/platform-package.md) needs a row for gateway.http2 — filed as cozystack/website#625. No other downstream repository restates anything in this diff (no app values.schema.json, ansible-consumed platform key, or hack/ tooling changed).

Release note

feat(gateway): Gateway API listeners now advertise HTTP/2 via TLS ALPN (h2, then http/1.1) by default, restoring the browser HTTP/2 support lost in the migration from ingress-nginx. Opt out platform-wide with gateway.http2=false, or per tenant Kubernetes cluster via addons.cilium.valuesOverride.cilium.gatewayAPI.enableAlpn=false. Backend (gateway-to-upstream) connections are unchanged and stay HTTP/1.1 unless a Service declares appProtocol kubernetes.io/h2c.

Browsers negotiate HTTP/2 exclusively through TLS ALPN "h2". Cilium's
Gateway API implementation ships with enable-gateway-api-alpn=false, so
every Gateway listener advertised no ALPN protocols and every browser
silently fell back to HTTP/1.1 — a regression against the ingress-nginx
path, which advertised h2 out of the box.

Enable ALPN (h2, then http/1.1) by default on the client-facing hop:

- packages/system/cilium: default gatewayAPI.enableAlpn=true.
- platform chart: new gateway.http2 value (default true), threaded as
  inline component values into the cozystack.networking Package for the
  isp-full and isp-full-generic bundles, so an explicit false overrides
  the system/cilium valuesFiles default either way.
- apps/kubernetes: tenant-cluster cilium gets enableAlpn=true whenever
  the gatewayAPI addon is enabled; opt out per cluster via
  addons.cilium.valuesOverride.

The backend hop is unchanged: gateway-to-upstream stays HTTP/1.1 unless
a Service opts in per GEP-1911 via appProtocol kubernetes.io/h2c (that
support is switched on together with ALPN; no cozystack-shipped Service
declares such a value today).

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds a gateway.http2 platform setting, propagates it to Cilium Gateway API ALPN configuration for bundled deployments, enables ALPN in the Kubernetes Cilium addon, and adds tests for defaults, disabled states, and overrides.

Changes

Gateway HTTP/2 ALPN

Layer / File(s) Summary
Platform Gateway HTTP/2 configuration
packages/core/platform/values.yaml, packages/system/cilium/values.yaml
Adds the documented gateway.http2 setting and enables Cilium Envoy ALPN advertising with HTTP/1.1 fallback.
Bundle HTTP/2 wiring and validation
packages/core/platform/templates/bundles/system.yaml, packages/core/platform/tests/bundles_gateway_http2_wiring_test.yaml
Threads gateway.http2 into isp-full and isp-full-generic Cilium values while preserving existing endpoint and cgroup settings; tests enabled and disabled configurations.
Cilium addon ALPN defaults and overrides
packages/apps/kubernetes/templates/helmreleases/cilium.yaml, packages/apps/kubernetes/tests/cilium_gateway_alpn_test.yaml
Enables Gateway API ALPN by default and tests addon absence, enablement, and explicit enableAlpn: false overrides.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: area/testing

Suggested reviewers: kvaps

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: enabling HTTP/2 advertising via ALPN on Gateway API listeners.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gateway-http2-alpn

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added area/networking Issues or PRs related to networking (ingress, gateway, vpn, metallb, cilium, kube-ovn) kind/feature Categorizes issue or PR as related to a new feature size/L This PR changes 100-499 lines, ignoring generated files labels Jul 21, 2026
@lllamnyp
Timofei Larkin (lllamnyp) marked this pull request as ready for review July 21, 2026 11:54
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a regression where the migration to Gateway API caused browser traffic to silently downgrade to HTTP/1.1 due to the lack of ALPN advertisement. By enabling ALPN by default, the platform restores native HTTP/2 support for the client-to-gateway hop, improving performance by eliminating head-of-line blocking. The change is designed to be safe for non-HTTP/2 clients and provides clear opt-out mechanisms for operators.

Highlights

  • HTTP/2 Support via ALPN: Enabled ALPN advertisement on Gateway API listeners by default, allowing browsers to negotiate HTTP/2 (h2) instead of falling back to HTTP/1.1.
  • Platform Configuration: Added a new platform-wide setting gateway.http2 (defaulting to true) to control ALPN, with the ability to opt-out at the platform or individual tenant cluster level.
  • Testing and Verification: Introduced new helm-unittest suites to ensure correct propagation of the ALPN configuration across different bundles and override scenarios.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request enables HTTP/2 (ALPN "h2") by default on TLS-terminating Gateway API listeners served by the Cilium dataplane, preventing silent downgrades to HTTP/1.1. The changes span the platform, kubernetes, and cilium packages, along with corresponding unit tests. Feedback suggests using the Helm "dig" helper when referencing ".Values.gateway.http2" to prevent potential nil pointer evaluation errors if the gateway configuration is omitted. Additionally, the PR scope should be updated to a valid scope (such as "platform" or "cilium") to comply with the repository's Conventional Commits style guide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 22 to +26
{{- $ciliumValues := dict "cilium" (dict
"k8sServiceHost" "localhost"
"k8sServicePort" "7445"
"cgroup" (dict "autoMount" (dict "enabled" false))) -}}
"cgroup" (dict "autoMount" (dict "enabled" false))
"gatewayAPI" (dict "enableAlpn" .Values.gateway.http2)) -}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To prevent potential nil pointer evaluation errors if .Values.gateway is completely omitted or set to null in custom values, we should use the dig helper to safely retrieve http2 with a default value of true.

{{- $ciliumValues := dict "cilium" (dict\n  "k8sServiceHost" "localhost"\n  "k8sServicePort" "7445"\n  "cgroup" (dict "autoMount" (dict "enabled" false))\n  "gatewayAPI" (dict "enableAlpn" (dig "gateway" "http2" true .Values))) -}}

Comment on lines 90 to +94
{{- $ciliumValues := dict "cilium" (dict
"k8sServiceHost" $apiHost
"k8sServicePort" $apiPort
"cgroup" (dict "autoMount" (dict "enabled" true))) -}}
"cgroup" (dict "autoMount" (dict "enabled" true))
"gatewayAPI" (dict "enableAlpn" .Values.gateway.http2)) -}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Similarly, use the dig helper here to safely retrieve gateway.http2 with a default of true to avoid nil pointer dereferences if gateway is not defined in custom values.

{{- $ciliumValues := dict "cilium" (dict\n  "k8sServiceHost" $apiHost\n  "k8sServicePort" $apiPort\n  "cgroup" (dict "autoMount" (dict "enabled" true))\n  "gatewayAPI" (dict "enableAlpn" (dig "gateway" "http2" true .Values))) -}}

# isp-hosted the hosting-side CNI owns this setting. Flipping it re-rolls
# the cilium DaemonSet on the next platform upgrade (rollOutCiliumPods),
# the same disruption profile as any cilium config change.
http2: true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

low

The PR title and commit message use the scope gateway (e.g., feat(gateway): ...). However, according to the repository style guide, gateway is not listed as a valid System, Apps, or Meta scope, nor does it match a package directory under packages/ (the package is named gateway-application). Since the changes in this PR span platform, cilium, and kubernetes, please consider using one of the valid scopes such as platform or cilium to adhere to the Conventional Commits requirements.

References
  1. Each commit must follow Conventional Commits format with valid scopes (System, Apps, Meta, or Package-specific matching a directory under packages/). (link)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — restores the browser HTTP/2 support that was lost when the platform moved from ingress-nginx to Gateway API, fixed at the correct layer (Cilium agent config) with a working opt-out at every management layer and full contract test coverage.

Business context: Cilium ships enable-gateway-api-alpn=false, so every TLS-terminating Gateway listener advertised no ALPN and browsers silently downgraded to HTTP/1.1; this turns ALPN (h2, then http/1.1) on by default and adds a gateway.http2 platform knob plus a per-tenant-cluster opt-out.

I verified the load-bearing claims against the vendored chart, the merge semantics, and the actual rendered output.

Consumption is real end-to-end: cilium.gatewayAPI.enableAlpn maps to enable-gateway-api-alpn in cilium-configmap.yaml (and flips enable-gateway-api-app-protocol on via or enableAppProtocol enableAlpn); rendering packages/system/cilium produces "true" by default and "false" when the flag is threaded false. The shipped Cilium is 1.19.5, which honors this agent flag, so the option is not a dead feature.

The opt-out works on both paths. On the platform path gateway.http2 is threaded as inline Package component values in both true and false states, so an operator's http2: false overrides the chart default. On the tenant path deepCopy .Values.addons.cilium.valuesOverride | mergeOverwrite (fromYaml defaults) resolves (by pipe order) to mergeOverwrite(defaults, valuesOverride), so the operator override wins. Both are confirmed by passing tests that go red if the override were shadowed.

No regression from turning ALPN on: it advertises h2 then http/1.1, so HTTP/1.1-only clients keep negotiating unchanged. The one real side effect — enabling enable-gateway-api-app-protocol — is inert here: no cozystack-shipped Gateway/HTTPRoute backend Service declares a protocol-changing appProtocol (h2c/grpc), and the only http/https values in-tree are on an ingress-nginx Ingress backend that Cilium's Gateway path never touches. isp-hosted uses noop networking and is correctly left untouched.

Tests define the full contract and pass: tenant suite covers addon-off, default-on, and explicit opt-out; platform suite covers default-on and http2: false for both isp-full and isp-full-generic plus a guard that the Talos endpoint values survive alongside the new flag. Full suites are green (platform 93/93, kubernetes 184/184), no existing test regressed.

Non-blocking follow-ups

  1. The user-facing gateway.http2 knob should get a row in the website's platform values table; the PR already acknowledges this and leaves it for a docs sweep. Non-blocking.
  2. CI shows E2E red, but the failure is flux-plunger failing its own liveness/readiness probes (connection refused on its health port) during install — an application-startup issue independent of this change: every other release installed, and a fresh install does not re-roll Cilium. Recommend re-running E2E for a green signal before merge. Non-blocking.
  3. Optional: the system/cilium/values.yaml default (enableAlpn: true) is currently asserted only indirectly through the platform/tenant suites; a one-line assertion in the cilium chart's own tests would pin it directly. Non-blocking.

Timofei Larkin (lllamnyp) added a commit to cozystack/website that referenced this pull request Jul 21, 2026
Adds a `gateway.http2` row to the `next/` platform-package values table,
between `gateway.enabled` and `gateway.attachedNamespaces`, matching the
value's position in the platform chart's `values.yaml`.

The value ships with cozystack/cozystack#3398, which enables HTTP/2 via
TLS ALPN (`h2`, then `http/1.1`) by default on Gateway API listeners
served by the bundled Cilium dataplane. The row documents the default
(`true`), the frontend-only scope (backends stay HTTP/1.1 unless a
Service declares `appProtocol: kubernetes.io/h2c` per GEP-1911), the
cluster-wide granularity, the bundles it applies to (`isp-full`,
`isp-full-generic`), and the cilium DaemonSet re-roll on flipping it.

`next/` only: the feature is unreleased, so no released version
directory is touched.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Documented the new `gateway.http2` setting for controlling HTTP/2
advertisement on Gateway API listeners.
* Clarified backend-to-Gateway protocol behavior, rollout
considerations, and supported Cilium bundle limitations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
xrmtech-isk pushed a commit to katamarina-ru/cozystack-website that referenced this pull request Jul 22, 2026
Add the gateway.http2 row to the next/ platform-package values table.
The value ships with cozystack/cozystack#3398, which turns on HTTP/2
via TLS ALPN (h2, then http/1.1) for Gateway API listeners served by
the bundled Cilium dataplane, restoring the browser HTTP/2 support the
ingress-nginx path had before the Gateway API migration.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
@lllamnyp
Timofei Larkin (lllamnyp) merged commit 2823b76 into main Jul 23, 2026
51 of 55 checks passed
@lllamnyp
Timofei Larkin (lllamnyp) deleted the feat/gateway-http2-alpn branch July 23, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/networking Issues or PRs related to networking (ingress, gateway, vpn, metallb, cilium, kube-ovn) kind/feature Categorizes issue or PR as related to a new feature size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants