markdown # Workhorse를 위한 웹소켓 채널 지원

일부 경우에 GitLab은 웹소켓을 통해 다음을 제공할 수 있습니다:

  • 브라우저에서 환경에 대한 터미널 액세스: 실행 중인 서버 또는 컨테이너로, 프로젝트가 배포된 위치에.
  • CI에서 실행 중인 서비스에 액세스.

Workhorse는 웹소켓 업그레이드와 웹소켓의 장기 연결을 관리하여 GitLab이 다른 요청을 처리할 수 있게 해줍니다. 이 문서에서는 이러한 연결의 아키텍처를 개요합니다.

웹소켓 소개

웹소켓은 “업그레이드된” HTTP/1.1 요청입니다. 이것은 클라이언트와 서버 간에 양방향 통신을 허용합니다. 웹소켓은 HTTP가 아닙니다. 클라이언트는 언제든지 서버로 메시지(프레임이라고 함)를 보낼 수 있으며, 그 반대도 마찬가지입니다. 클라이언트 메시지는 반드시 요청이 아니며, 서버 메시지도 반드시 응답이 아닙니다. 웹소켓 URL은 ws:// (암호화되지 않음) 또는 wss:// (TLS-보안)와 같은 체계를 가집니다.

웹소켓 업그레이드를 요청할 때, 브라우저는 다음과 같은 HTTP/1.1 요청을 보냅니다:

GET /path.ws HTTP/1.1
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Protocol: terminal.gitlab.com
# 보안 조치를 포함한 더 많은 헤더

이 시점에서 연결은 아직 HTTP이므로 이것은 요청입니다. 서버는 404 Not Found 또는 500 Internal Server Error와 같은 표준 HTTP 응답을 보낼 수 있습니다.

서버가 업그레이드를 허용하기로 결정하면, HTTP 101 Switching Protocols 응답을 보냅니다. 이 시점부터 연결은 더 이상 HTTP가 아닙니다. 이제 웹소켓이며, HTTP 요청이 아닌 프레임이 흐릅니다. 연결은 클라이언트 또는 서버가 연결을 닫을 때까지 지속됩니다.

서브 프로토콜 외에도 개별 웹소켓 프레임은 다음과 같은 메시지 유형을 지정할 수도 있습니다:

  • BinaryMessage
  • TextMessage
  • Ping
  • Pong
  • Close

이진 프레임만이 임의의 데이터를 포함할 수 있습니다. 프레임은 임의의 UTF-8 문자열을 포함해야 하며, 서브 프로토콜 요구 사항도 충족해야 합니다.

브라우저에서 Workhorse로

터미널을 예로 들어 설명하면 다음과 같습니다:

  1. GitLab은 브라우저에 https://gitlab.com/group/project/-/environments/1/terminal와 같은 URL에서 JavaScript 터미널 에뮬레이터를 제공합니다.
  2. 해당 URL은 wss://gitlab.com/group/project/-/environments/1/terminal.ws로의 웹소켓 연결을 엽니다. 이 엔드포인트는 Workhorse에만 존재하며, GitLab에는 존재하지 않습니다.
  3. 연결을 받으면, Workhorse는 먼저 클라이언트가 요청된 터미널에 액세스할 권한이 있는지 확인하기 위해 GitLab에 preauthentication 요청을 수행합니다:
    • 클라이언트가 적절한 권한을 갖고 있고 터미널이 있는 경우, GitLab은 클라이언트가 연결할 터미널의 세부 정보를 포함하는 성공적인 응답을 반환합니다.
    • 그렇지 않으면, Workhorse는 적절한 HTTP 오류 응답을 반환합니다.
  4. GitLab이 유효한 터미널 세부 정보를 반환하면:
    1. 지정된 터미널에 연결합니다.
    2. 브라우저를 웹소켓으로 업그레이드합니다.
    3. 브라우저의 자격 증명이 유효한 동안 두 연결 사이를 프록시합니다.
    4. 브라우저가 있을 때 중간 프록시가 연결을 종료하지 않도록, 정기적인 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 터미널 제어 코드를 포함하고 어떤 인코딩이든 상관없을 것으로 예상됩니다.

Workhorse에서 GitLab으로

터미널을 예로 들어, 브라우저를 업그레이드하기 전에 Workhorse는 https://gitlab.com/group/project/environments/1/terminal.ws/authorize와 같은 URL에 대해 GitLab에 표준 HTTP 요청을 보냅니다. 이 요청은 터미널이 어디에 있는지 및 어떻게 연결할지에 대한 세부 정보가 담긴 JSON 응답을 반환합니다. 특히, 성공한 경우 다음 세부 정보가 반환됩니다:

  • 연결할 WebSocket URL, 예: wss://example.com/terminals/1.ws?tty-1.
  • 지원할 웹소켓 서브 프로토콜, 예: ["channel.k8s.io"].
  • 보내야 할 헤더, 예: Authorization: Token xxyyz.
  • 선택 사항. wss 연결을 확인하기 위한 인증서 기관.

Workhorse는 이 엔드포인트를 주기적으로 다시 확인합니다. 오류 응답을 받거나 터미널의 세부 정보가 변경되면 웹소켓 세션을 종료합니다.

Workhorse에서 웹소켓 서버로

GitLab의 환경이나 CI 작업에는 이를 위한 배포 서비스(예: KubernetesService)가 있을 수 있습니다. 이 서비스는 터미널이나 환경을 위한 서비스가 어디에 있는지 알고 있으며, GitLab은 이러한 세부 정보를 Workhorse에게 반환합니다.

이러한 URL 또한 웹소켓 URL입니다. GitLab은 Workhorse에 대화를 위해 사용할 서브 프로토콜과 원격 단말로부터 요구되는 인증 세부 정보를 알려줍니다.

브라우저의 연결을 웹소켓으로 업그레이드하기 전에, Workhorse는 다음을 수행합니다:

  1. Workhorse에게 주어진 세부 정보에 따라 HTTP 클라이언트 연결을 엽니다.
  2. 해당 연결을 웹소켓으로 업그레이드를 시도합니다.
    • 실패할 경우, 브라우저로 오류 응답이 전송됩니다.
    • 성공할 경우, 브라우저도 업그레이드됩니다.

Workhorse는 현재 서브 프로토콜이 다른 두 개의 웹소켓 연결을 가지고 있으며, 그 후:

  • 브라우저에서 오는 프레임을 디코딩하고, 해당 채널의 서브 프로토콜로 다시 인코딩하여 채널로 보냅니다.
  • 채널에서 오는 프레임을 디코딩하고, 브라우저의 서브 프로토콜로 다시 인코딩하여 브라우저로 보냅니다.

두 연결 중 하나가 종료되거나 오류 상태로 들어가면, Workhorse는 오류를 감지하고 다른 연결을 닫아 채널 세션을 종료합니다. 브라우저가 연결을 해제한 경우, Workhorse는 해당 프로토콜에 따라 인코딩된 ANSI End of Transmission 제어 코드(0x04 바이트)를 채널로 보냅니다. 연결이 끊기지 않도록하기 위해, Workhorse는 채널에서 보낸 웹소켓 ping 프레임에 응답합니다.

Workhorse는 다음과 같은 서브 프로토콜만을 지원합니다:

새로운 배포 서비스를 지원하려면 새로운 서브 프로토콜을 지원해야 합니다.

channel.k8s.io

Kubernetes에서 사용되며, 이 서브 프로토콜은 간단한 다중화된 채널을 정의합니다.

컨트롤 프레임은 평상시와 같은 의미를 가집니다. TextMessage 프레임은 유효하지 않습니다. BinaryMessage 프레임은 특정 파일 디스크립터에 대한 I/O를 나타냅니다.

BinaryMessage 프레임의 첫 번째 바이트는 uint8fd 번호를 나타냅니다. 예를 들면:

  • 0x00fd 0, STDIN에 해당합니다.
  • 0x01fd 1, STDOUT에 해당합니다.

나머지 바이트는 해당 fd에서 받은 바이트입니다. 서버로부터 받은 프레임의 경우, 해당 fd에서 받은 바이트입니다. 서버로 보낸 프레임의 경우, 해당 fd에 쓰여야 하는 바이트입니다.

```markdown

base64.channel.k8s.io

Kubernetes에서도 사용되는 이 하위 프로토콜은 channel.k8s.io와 유사한 다중화된 채널을 정의합니다. 주요 차이점은 다음과 같습니다:

  • TextMessage 프레임이 유효하며, BinaryMessage 프레임은 아닙니다.
  • TextMessage 프레임의 첫 번째 바이트는 파일 디스크립터를 숫자로 표현한 UTF-8 문자이며, 문자 U+0030 또는 “0”은 fd 0, STDIN을 나타냅니다.
  • 나머지 바이트는 base64로 인코딩된 임의의 데이터를 나타냅니다.