Kubernetes CRIにおけるexec、attach、ポートフォワーディングの処理方法
2024年の詳細な解説記事では、Kubernetesコンテナランタイムインターフェース(CRI)コマンドを支える独自のURLベースのストリーミングアーキテクチャについて説明しています。
英語の原文から自動翻訳されました。
2024年5月のKubernetes Blogへの投稿で、Sascha Grunert氏は3つの特定のContainer Runtime Interface (CRI) リモートプロシージャコール(RPC)の内部メカニズムを詳述しました。この記事では、Exec、Attach、PortForwardが標準的なgRPCインタラクションと異なり、2016年の設計以来一貫して維持されている独自のURLベースのストリーミングモデルを使用している仕組みを解説しています。
何が起きたか
Kubernetes CRIはkubeletとコンテナランタイム間の主要なブリッジとして機能し、ランタイムには定義されたProtocol Bufferインターフェースに準拠したgRPCサーバーの公開が求められます。多くのCRI操作は単純なユニークールやサーバー側ストリーミングに依存していますが、Grunert氏はExec、Attach、PortForwardが異なる動作をする点を強調しました。これらの3つの機能は、コンテナ内でコマンドを実行したり、ライブ出力を表示したり、デバッグ用にネットワークポートを転送したりする開発者にとって不可欠です。
Grunert氏はこれらの機能の歴史を、現代のKubernetes Enhancement Proposalsより前の2016年の設計ドキュメントまで遡って追跡しました。CRIイニシアチブ以前は、これらの機能はDockerやrktなどの特定ランタイムに強く結合されていました。コミュニティはネイティブRPCストリーミングの実装を検討しましたが、kubeletでのネットワークボトルネックの発生やランタイムの柔軟性の制限につながるため却下されました。代わりに、ランタイムがストリーミングサーバーを提供し、各実装が接続を独立して管理できるモデルが採用されました。
このアーキテクチャ上の決定により、初期リクエストは標準的なgRPCインターフェースを経由しますが、実際のデータ転送は別のHTTP接続で行われます。この分離により、コアCRI定義を変更することなく、ランタイムはストリーミング実装を進化させることができます。過去数年間でいくつかのマイナーな強化がマージされましたが、URLをリクエストしてから直接接続するという基本的なパターンは変更されていません。
仕組み
プロセスは、kubectlやcrictlなどのクライアントがExec、Attach、またはPortForwardセッションのためにランタイムへgRPCリクエストを送信することで始まります。データを直接返す典型的なAPI呼び出しとは異なり、ランタイムはリクエストを検証し、接続トラッキングキャッシュに保存します。その後、完全修飾URLのみを含むレスポンスを返します。クライアントはこのURLに接続する必要があり、データのストリーミングを開始するために、SPDYプロトコルまたは近年増加しているWebSocketsを使用して接続をアップグレードします。
ExecとAttachの場合、Kubernetesは現在v5.channel.k8s.ioまでの5バージョンを持つ特定のプロトコルを定義しています。このプロトコルは、すべてのパケットの最初のバイトを使用して、標準入力、標準出力、標準エラー、ターミナルのリサイズやクローズなどの制御信号といったストリームタイプを識別します。kubeletのソースコードには、このプロトコルの解釈を処理する再利用可能なライブラリが提供されており、ランタイムはコマンドの実行やプロセスへのアタッチに関するロジックのみを実装すれば済みます。PortForwardは厳格なプロトコル定義がないため、異なる動作をします。代わりに、ランタイムはコンテナのネットワーク名前空間に入り、生SPDYフレームをストリーミングし、データフローの管理にはmoby/spdystreamなどのライブラリに依存します。
重要な詳細
- CRIの
Exec、Attach、PortForwardRPCは、レスポンスで実際のデータストリームではなく、URL文字列のみを返します。 - クライアントはURLを受信した後、ストリーミングセッションを確立するためにHTTP接続をSPDYまたはWebSocketsにアップグレードする必要があります。
ExecおよびAttachプロトコルは、stdin、stdout、stderr、エラー、リサイズイベント、クローズ信号を区別するために、各パケットの最初のバイトを使用します。- リモートコマンドプロトコルには5つのバージョンが存在し、v5ではWebSockets用のCLOSE信号サポートが追加されています。
- kubeletは、基盤となるコマンド実行やネットワーク名前空間へのエントリーを処理するためにランタイムが実装すべき
Runtimeインターフェースを持つ再利用可能なライブラリを提供します。 - 今後の取り組みはSPDYからWebSocketsへの置き換えに焦点を当てており、
crictlv1.30などのツールはすでに両者の選択を行うための--transportフラグをサポートしています。
なぜ重要なのか
コンテナランタイムの構築や保守を行うソフトウェアエンジニアにとって、このアーキテクチャを理解することは正しい実装のために不可欠です。コントロールプレーン(gRPC)とデータプレーン(HTTPストリーミング)のデカップリングにより、ランタイム開発者はこれらの呼び出しを単なる標準的なAPIリクエストとして扱うことはできません。並行ストリーミングセッションの管理、プロトコルアップグレードの処理、複数のプロトコルバージョンとの互換性の確保が必要です。データパケットの最初のバイトを誤解釈したり、ターミナルのリサイズイベントをサポートしなかったりすると、一般的な開発ワークフローでのユーザー体験が損なわれる可能性があります。
この設計は、デバッグツールがクラスターとどのようにやり取りするかにも影響します。初期URL取得後のデータはkubeletをバイパスするため、ネットワークポリシーやプロキシは、クライアントとコンテナをホストするノード間の直接通信を許可する必要があります。エコシステムがWebSocketsへ移行するにつれ、エンジニアはツールが新しいトランスポート層をサポートしていることを確認しなければなりません。CRI-Oなどのプロジェクトでの継続的な作業(ストリーミングロジックをconmon-rsへ移動)は、メインランタイムプロセスが再起動してもセッションを維持できるような柔軟性を示しており、長時間のデバッグセッションの信頼性を向上させます。
実施できること
- コンテナランタイムが最新の
v5.channel.k8s.ioプロトコルをサポートしていることを確認し、ストリームクローズ信号の適切な処理を保証してください。 - クライアントツール(
crictlなど)をバージョン1.30以降に更新し、--transportフラグを使用してWebSocketsトランスポートサポートをテストしてください。 - クライアントがAPIサーバーだけでなく、ストリーミング接続で使用されるノードIPとポートに到達できることを確認するために、ネットワークポリシーを見直してください。
- カスタムランタイムを開発している場合は、プロトコル解析を一から実装するのではなく、kubeletの再利用可能なストリーミングライブラリを使用してください。
- SPDYはより現代的なWeb標準へと段階的に廃止されているため、環境内でのWebSocketsの採用状況を監視してください。
- ランタイムの更新時の耐障害性を高めるために、ランタイムが
conmon-rsなどの外部モニターへストリーミングセッションをオフロードできるかどうかを確認してください。


