WorkhorseのWebsocketチャネルのサポート

場合によっては、GitLabはWebSocketを通して以下を提供することができます:

  • 環境(プロジェクトがデプロイされた稼働中のサーバーやコンテナ)へのブラウザ端末からのアクセス。
  • CIで実行されているサービスへのアクセス。

WorkhorseはWebSocket接続のアップグレードと長期間の接続を管理し、GitLabを他のリクエストの処理に解放します。このドキュメントでは、これらの接続のアーキテクチャの概要を説明します。

ウェブソケット入門

ウェブソケットは、HTTP/1.1 リクエストを「アップグレード」したものです。クライアントとサーバー間の双方向通信を可能にします。ウェブソケットは HTTPではありません。クライアントはいつでもサーバにメッセージ (フレームとして知られています) を送ることができます。クライアントのメッセージは必ずしもリクエストではなく、サーバーのメッセージは必ずしもレスポンスではありません。ウェブソケットのURLには、ws:// (暗号化されていない)またはwss:// (TLSで保護されている)のようなスキームがあります。

WebSocket へのアップグレードを要求する場合、ブラウザは次のようなHTTP/1.1 リクエストを送信します:

GET /path.ws HTTP/1.1
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Protocol: terminal.gitlab.com
# More headers, including security measures

この時点では、接続は HTTP のままなので、これはリクエストです。サーバーは404 Not Found500 Internal Server Error のような標準的な HTTP レスポンスを送信できます。

サーバーがアップグレードを許可すると決定した場合、HTTP101 Switching Protocols 。この時点から、接続はもはや HTTP ではありません。この時点から、接続は HTTP ではなくなり、WebSocket になり、HTTP リクエストではなくフレームが流れます。接続は、クライアントまたはサーバーが接続を閉じるまで持続します。

サブプロトコルに加えて、個々の WebSocket フレームはメッセージタイプを指定することができます:

  • BinaryMessage
  • TextMessage
  • Ping
  • Pong
  • Close

バイナリフレームだけが任意のデータを含むことができます。フレームはサブプロトコルの期待値に加えて、有効な UTF-8 文字列であることが期待されます。

ブラウザから Workhorse へ

ターミナルを例に

  1. GitLabはJavaScriptのターミナルエミュレータをhttps://gitlab.com/group/project/-/environments/1/terminal のようなURLでブラウザに提供します。
  2. このURLはwss://gitlab.com/group/project/-/environments/1/terminal.ws へのウェブソケット接続を開きます。このエンドポイントはWorkhorseにのみ存在し、GitLabには存在しません。
  3. 接続を受け取ると、WorkhorseはまずGitLabにpreauthentication リクエストを行い、クライアントがリクエストされた端末にアクセスする権限があることを確認します:
    • クライアントが適切な権限を持ち、端末が存在する場合、GitLabはクライアントが接続すべき端末の詳細を含む成功レスポンスを返します。
    • そうでない場合は、Workhorse は適切な HTTP エラーレスポンスを返します。
  4. GitLabが有効な端末の詳細をWorkhorseに返した場合:
    1. 指定されたターミナルに接続します。
    2. ブラウザをWebSocketにアップグレードします。
    3. ブラウザの認証情報が有効である限り、2つの接続間のプロキシを行います。
    4. ブラウザが存在する間、介在するプロキシが接続を終了するのを防ぐために、通常のPingMessage 制御フレームをブラウザに送信します。

ブラウザは特定のサブプロトコルでアップグレードを要求しなければなりません:

terminal.gitlab.com

このサブプロトコルはTextMessage のフレームを無効とみなします。PingMessageCloseMessage のような制御フレームは通常の意味を持ちます。

  • BinaryMessage ブラウザからサーバに送られるフレームは任意のテキスト入力です。
  • BinaryMessage サーバからブラウザに送られるフレームは、任意のテキスト出力です。

これらのフレームは ANSI テキスト制御コードを含むことが期待されており、どのようなエンコーディングであってもかまいません。

base64.terminal.gitlab.com

このサブプロトコルではBinaryMessage のフレームは無効と見なします。PingMessageCloseMessage のような制御フレームは、通常の意味を持ちます。

  • TextMessage ブラウザからサーバに送られるフレームは base64 エンコードされた任意のテキスト入力です。サーバはそれらを入力する前に base64 デコードしなければなりません。
  • TextMessage サーバからブラウザに送られるフレームは、base64エンコードされた任意のテキスト出力です。ブラウザはそれらを出力する前にbase64デコードしなければなりません。

base64 エンコードされた形式では、これらのフレームは ANSI 端末制御コードを含むことが期待されており、どのようなエンコーディングであってもかまいません。

GitLabへのWorkhorse

端末を例にすると、ブラウザをアップグレードする前に、Workhorse はhttps://gitlab.com/group/project/environments/1/terminal.ws/authorize のような URL で GitLab に標準的な HTTP リクエストを送信します。このリクエストは、ターミナルの場所と接続方法の詳細を含むJSONレスポンスを返します。特に、成功した場合は以下の詳細が返されます:

  • wss://example.com/terminals/1.ws?tty=1 のような、** 接続先の WebSocket URL。
  • サポートするWebSocketサブプロトコル、例えば["channel.k8s.io"]
  • Authorization: Token xxyyz のように、送信するヘッダー。
  • オプション。wss 接続を検証する作成者。

Workhorse はこのエンドポイントを定期的に再チェックします。エラー応答を受け取るか、端末の詳細が変更されると、ウェブソケットセッションを終了します。

WorkhorseからWebSocketサーバーへ

GitLabでは、環境やCIジョブは(KubernetesService のような)デプロイサービスに関連付けられていることがあります。このサービスは環境の端末やサービスがどこにあるかを知っており、GitLabはこれらの詳細をWorkhorseに返します。

これらのURLはWebSocket URLでもあります。GitLabはWorkhorseに、リモート側が必要とする認証の詳細とともに、接続上でどのサブプロトコルを話すかを伝えます。

ブラウザの接続をウェブソケットにアップグレードする前に、Workhorse:

  1. Workhorseによって与えられた詳細に従って、HTTPクライアント接続を開きます。
  2. その接続をウェブソケットにアップグレードしようとします。
    • 失敗した場合は、エラー・レスポンスがブラウザに送信されます。
    • 成功した場合は、ブラウザもアップグレードされます。

Workhorseは、サブプロトコルが異なるとはいえ、2つのウェブソケット接続を持つようになりました:

  • ブラウザからの受信フレームをデコードし、チャネルのサブプロトコルに再エンコードしてチャネルに送信します。
  • チャネルからの受信フレームをデコードし、ブラウザのサブプロトコルに再エンコードし、ブラウザに送信します。

どちらかの接続が閉じるかエラー状態になると、Workhorse はエラーを検出し、もう一方の接続を閉じ、チャネルセッションを終了します。ブラウザが接続を切断した場合、Workhorse は適切なサブプロトコルに従ってエンコードされた ANSIEnd of Transmission 制御コード (0x04 バイト) をチャネルに送信します。切断されないように、Workhorse はチャネルから送信されたウェブソケット ping フレームに返信します。

Workhorse は以下のサブプロトコルのみをサポートしています:

新しいデプロイサービスをサポートするには、新しいサブプロトコルをサポートする必要があります。

channel.k8s.io

Kubernetesで使用されるこのサブプロトコルは、単純な多重化チャネルを定義します。

制御フレームは通常の意味を持ちます。TextMessage フレームは無効です。BinaryMessage フレームは特定のファイル記述子への I/O を表します。

BinaryMessage フレームの最初のバイトは、ファイル記述子 (fd) 番号をuint8 として表します:

  • 0x00 fd 0 ,STDINに対応。
  • 0x01 fd 1 ,STDOUTに対応。

残りのバイトは任意のデータを表します。サーバから受信したフレームについては、そのfd. fdNET Framework から受信したバイトです。サーバfdに送信されたフレームについては、その .NET Framework に書き込まれるべきバイト fdです。

base64.channel.k8s.io

Kubernetesでも使用されるこのサブプロトコルは、channel.k8s.io と同様の多重化チャネルを定義します。主な違いは以下のとおりです:

  • TextMessage フレームが有効であり、BinaryMessage フレームではありません。
  • TextMessage フレームの最初のバイトは、ファイル記述子を数値の UTF-8 文字で表しますので、文字U+0030 、つまり “0” は、fd 0STDIN
  • 残りのバイトは、base64 エンコードされた任意のデータを表します。