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 Found
や500 Internal Server Error
のような標準的な HTTP レスポンスを送信できます。
サーバーがアップグレードを許可すると決定した場合、HTTP101 Switching Protocols
。この時点から、接続はもはや HTTP ではありません。この時点から、接続は HTTP ではなくなり、WebSocket になり、HTTP リクエストではなくフレームが流れます。接続は、クライアントまたはサーバーが接続を閉じるまで持続します。
サブプロトコルに加えて、個々の WebSocket フレームはメッセージタイプを指定することができます:
BinaryMessage
TextMessage
Ping
Pong
Close
バイナリフレームだけが任意のデータを含むことができます。フレームはサブプロトコルの期待値に加えて、有効な UTF-8 文字列であることが期待されます。
ブラウザから Workhorse へ
ターミナルを例に
- GitLabはJavaScriptのターミナルエミュレータを
https://gitlab.com/group/project/-/environments/1/terminal
のようなURLでブラウザに提供します。 - このURLは
wss://gitlab.com/group/project/-/environments/1/terminal.ws
へのウェブソケット接続を開きます。このエンドポイントはWorkhorseにのみ存在し、GitLabには存在しません。 - 接続を受け取ると、WorkhorseはまずGitLabに
preauthentication
リクエストを行い、クライアントがリクエストされた端末にアクセスする権限があることを確認します:- クライアントが適切な権限を持ち、端末が存在する場合、GitLabはクライアントが接続すべき端末の詳細を含む成功レスポンスを返します。
- そうでない場合は、Workhorse は適切な HTTP エラーレスポンスを返します。
- GitLabが有効な端末の詳細をWorkhorseに返した場合:
- 指定されたターミナルに接続します。
- ブラウザをWebSocketにアップグレードします。
- ブラウザの認証情報が有効である限り、2つの接続間のプロキシを行います。
- ブラウザが存在する間、介在するプロキシが接続を終了するのを防ぐために、通常の
PingMessage
制御フレームをブラウザに送信します。
ブラウザは特定のサブプロトコルでアップグレードを要求しなければなりません:
terminal.gitlab.com
このサブプロトコルはTextMessage
のフレームを無効とみなします。PingMessage
やCloseMessage
のような制御フレームは通常の意味を持ちます。
-
BinaryMessage
ブラウザからサーバに送られるフレームは任意のテキスト入力です。 -
BinaryMessage
サーバからブラウザに送られるフレームは、任意のテキスト出力です。
これらのフレームは ANSI テキスト制御コードを含むことが期待されており、どのようなエンコーディングであってもかまいません。
base64.terminal.gitlab.com
このサブプロトコルではBinaryMessage
のフレームは無効と見なします。PingMessage
やCloseMessage
のような制御フレームは、通常の意味を持ちます。
-
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:
- Workhorseによって与えられた詳細に従って、HTTPクライアント接続を開きます。
- その接続をウェブソケットにアップグレードしようとします。
- 失敗した場合は、エラー・レスポンスがブラウザに送信されます。
- 成功した場合は、ブラウザもアップグレードされます。
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
. fd
NET Framework から受信したバイトです。サーバfd
に送信されたフレームについては、その .NET Framework に書き込まれるべきバイト fd
です。
base64.channel.k8s.io
Kubernetesでも使用されるこのサブプロトコルは、channel.k8s.io
と同様の多重化チャネルを定義します。主な違いは以下のとおりです:
-
TextMessage
フレームが有効であり、BinaryMessage
フレームではありません。 - 各
TextMessage
フレームの最初のバイトは、ファイル記述子を数値の UTF-8 文字で表しますので、文字U+0030
、つまり “0” は、fd 0
、STDIN
。 - 残りのバイトは、base64 エンコードされた任意のデータを表します。