
2026/05/20 1:40
Go 1.24 サーバーでの HTTP/2 クリアテキストを使用
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Google Cloud Run は、HTTP/1.1 を使用して Server-Sent Event (SSE) ストリームを扱う際に困難に直面し、フロントエンドで TLS が終端されるため、 prematurely な接続切断を頻発させます。最も効果的な解決策は、RFC 9113 セクション 3.3 に従い「HTTP/2 with Prior Knowledge」を実装することです。
コード変更:
- Go 1.24 の前: ハンドラーを
でラップし、特定のタイムアウトを設定する必要がありました(例:h2c.NewHandler
)。IdleTimeout: 620s - Go 1.24+: ネイティブサポートにより
ラッパーを使用する必要がなくなります。開発者は現在、h2c
を直接使用してプロトコルを設定できます(srv.Protocols = new(http.Protocols)
で HTTP/1、SetHTTP1(true)
で非暗号化の HTTP/2 を有効に)。これにより、SSE ストリームを最大 900s に対応させるために 30 分のリクエストタイムアウトを設定することが可能です。SetUnencryptedHTTP2(true)
インフラストラクチャ (Terraform): これらの長寿命な接続をサポートするために、Cloud Run サービスの設定を更新してください:
invoker_iam_disabled = true を設定し、INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER を使用し、特に request_timeout を "900s" に増強(デフォルトは 300s)してください。Serverless NEG はデフォルトで後端のタイムアウトが 60 分であり、標準的な 30 秒の制限はここでは適用されません。負荷分散装置の変更は不要であり、LB はネゴシエーション時に HTTP/2 アップグレードを処理します。ローカルでのテストは curl -i --http2-prior-knowledge を使用して検証できます。本文
HTTP/2 クリアテキスト (h2c) を導入して SSE ストリームの安定性を確保する
アプリケーションは、15 分間の生存期間を有する長時間継続的なサーバーサイドイベント(SSE)ストリームを利用しています。しかし、バックエンドとの通信に
HTTP/1.1 を使用している場合、Google Cloud Run ではクライアントからの切断イベントがサービス側に正しく伝播しないという既知の課題が存在します。
この事情を踏まえ、**HTTP/2 クリアテキスト(h2c)**の導入を検討・実装することになりました。
技術的背景と概要
Cloud Run はフロントエンド側で TLS を終端させる仕様ですが、内部トラフィックは以下のいずれかで転送可能です。
- HTTP/1.1
- HTTP/2 クリアテキスト (h2c)
通常、HTTP/2 は TLS と組み合わせて使用されますが、Go の
http.Server ではプロトコルのクリアテキストバージョンを使用するように構成可能(「事前知識に基づいた HTTP/2」)です。Cloud Run においては、この特定のプロトコルを選択する必要があるため、実質的には RFC 9113 セクション 3.3 に記載される設定を実現します。
Go サーバー側の設定方法
Go 1.24 以前:旧手法(移行参考)
Go 1.24 以前では、h2c のサポートには
golang.org/x/net/http2 パッケージを使用した複雑なセットアップが必須でした。現在は古く过时していますが、参考資料として記載します。
コード例
import ( "fmt" "net/http" "time" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" ) // handler := ... // 既存のハンドラーを作成 // 1. HTTP/2 サーバーの設定を作成 h2s := &http2.Server{} // 2. ハンドラーを h2c.NewHandler でラップ handler = h2c.NewHandler(handler, h2s) // 3. http.Server の設定 srv := &http.Server{ Addr: fmt.Sprintf("%s:%d", "0.0.0.0", 9888), Handler: handler, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 35 * time.Second, IdleTimeout: 620 * time.Second, // 長時間継続する SSE コネクションのために最適化 } // 4. サーバー上で事前知識に基づいた HTTP/2 を有効化 err := http2.ConfigureServer(srv, h2s) if err != nil { // エラー処理を行う }
Go 1.24+:新手法(推奨)
Go 1.24 では、
x/net/http2/h2c ラッパーパッケージが不要となり、プロトコル設定を http.Server のストラクチャに直接行うことができます。コードがよりクリーンで読みやすくなっています。
コード例
import ( "fmt" "net/http" "time" ) // handler := ... // 既存のハンドラーを作成 srv := &http.Server{ Addr: fmt.Sprintf("%s:%d", "0.0.0.0", 9888), Handler: handler, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 35 * time.Second, IdleTimeout: 620 * time.Second, } // http.Server に直接プロトコルを設定 srv.Protocols = &http.Protocols{} srv.Protocols.SetHTTP1(true) srv.Protocols.SetUnencryptedHTTP2(true) // HTTP/2 クリアテキスト(h2c)を有効化
ローカル環境での検証
セットアップが正しく構築されていることを確認するため、HTTP/2 クリアテキストが適切に設定されていない場合にこのコマンドが失敗することを事前に検証しておくことが重要です。
curl -i --http2-prior-knowledge http://localhost:9888
Cloud Run のデプロイ設定 (Terraform)
Go サーバー側で対応した後、Cloud Run 側においても同様の設定を行う必要があります。以下の Terraform スニペットは、本題以外の冗長な設定を省略したものです。
resource "google_cloud_run_v2_service" "api" { name = "api" location = var.primary_region invoker_iam_disabled = true ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" template { containers { # ... コンテナの定義内容 ... ports { name = "h2c" container_port = 9888 } } # デフォルトは 80;高い並行性を考慮して増量。 max_instance_request_concurrency = 200 # デフォルトは 300s;長時間継続する SSE コネクションに対応するために延長。 timeout = "900s" } lifecycle { ignore_changes = [client, client_version, template[0].revision] } }
まとめ
今回の改修により、以下のメリットが得られます。
- ロードバランサーレベルでの大規模な変更は不要
- Serverless NEGs との通信に HTTPS を使用しており、接続時に自動的に HTTP/2 にアップグレードする機能を持っているためです。
- サーバーレス型バックエンド向けのデフォルトタイムアウトが有利
- デフォルトタイムアウトは60 分と設定されており、従来の 30 秒とは異なります。
- この長めのタイムアウトは、15 分間の SSE コネクションにとって有益な条件となります。