워크호스에 대한 웹소켓 채널 지원

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

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

워크호스는 웹소켓 업그레이드 및 웹소켓 연결에 대한 장기 연결을 관리하여 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 문자열이어야 합니다.

브라우저에서 워크호스까지

터미널을 예로 들어 보겠습니다:

  1. GitLab은 브라우저에 https://gitlab.com/group/project/-/environments/1/terminal과 같은 URL로 JavaScript 터미널 에뮬레이터를 제공합니다.

  2. 이 URL은 wss://gitlab.com/group/project/-/environments/1/terminal.ws에 대한 웹소켓 연결을 엽니다. 이 엔드포인트는 워크호스에만 존재하며, GitLab에는 존재하지 않습니다.

  3. 연결을 수신할 때, 워크호스는 먼저 클라이언트가 요청된 터미널에 접근할 수 있도록 권한이 있는지 확인하기 위해 GitLab에 preauthentication 요청을 수행합니다:
    • 클라이언트가 적절한 권한을 가지고 있고 터미널이 존재하는 경우, GitLab은 클라이언트가 연결해야 할 터미널의 세부정보를 포함한 성공적인 응답을 반환합니다.
    • 그렇지 않으면, 워크호스는 적절한 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 to 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와 같은.
  • 지원할 WebSocket 서브 프로토콜: ["channel.k8s.io"]와 같은.
  • 전송할 헤더: Authorization: Token xxyyz와 같은.
  • 선택 사항. wss 연결을 검증할 인증 기관.

Workhorse는 주기적으로 이 엔드포인트를 재확인합니다. 만약 오류 응답을 받거나

터미널의 세부 사항이 변경되면 websocket 세션을 종료합니다.

Workhorse to the WebSocket server

GitLab에서는 환경 또는 CI 작업이

KubernetesService와 같은 배포 서비스와 연결될 수 있습니다.

이 서비스는 터미널이나 환경을 위한 서비스를 찾는 방법을 알고 있으며

GitLab은 이러한 세부 정보를 Workhorse에 반환합니다.

이 URL들은 또한 WebSocket URL입니다.

GitLab은 Workhorse에 연결을 위한 서브 프로토콜을 알려주며

원격 쪽에서 필요한 인증 세부 정보를 전달합니다.

브라우저의 연결을 websocket으로 업그레이드하기 전에

Workhorse는:

  1. Workhorse에 의해 제공된 세부 사항에 따라 HTTP 클라이언트 연결을 엽니다.
  2. 그 연결을 websocket으로 업그레이드하려고 시도합니다.
    • 실패할 경우, 오류 응답을 브라우저에 전송합니다.
    • 성공할 경우, 브라우저도 업그레이드됩니다.

Workhorse는 이제 서로 다른 서브 프로토콜을 가진 두 개의 websocket 연결을 보유하게 되며, 이어서:

  • 브라우저에서 들어오는 프레임을 디코드하고, 이를 채널의 서브 프로토콜로 재인코딩하여

채널에 전송합니다. - 채널에서 들어오는 프레임을 디코드하고, 이를 브라우저의 서브 프로토콜로 재인코딩하여

브라우저에 전송합니다.

어느 연결이든 종료되거나 오류 상태에 들어가면

Workhorse는 오류를 감지하고 다른 연결을 닫아 채널 세션을 종료합니다.

브라우저가 연결이 끊어진 경우, Workhorse는

채널에 ANSI Transmission 종료 제어 코드(0x04 바이트)를 적절한 서브 프로토콜에 따라 인코딩하여 보냅니다.

연결이 끊어지지 않도록 하기 위해

Workhorse는 채널에서 전송된 모든 websocket ping 프레임에 응답합니다.

Workhorse는 다음의 서브 프로토콜만 지원합니다:

새로운 배포 서비스를 지원하려면 새로운 서브 프로토콜이 필요합니다.

channel.k8s.io

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

제어 프레임은 일반적인 의미를 가집니다. TextMessage 프레임은 유효하지 않습니다. BinaryMessage 프레임은 특정 파일 설명자에 대한 I/O를 나타냅니다.

BinaryMessage 프레임의 첫 번째 바이트는 uint8 형식의 파일 설명자(fd) 번호를 나타냅니다. 예를 들어:

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

나머지 바이트는 임의의 데이터를 나타냅니다. 서버에서 수신한 프레임의 경우, 이는 해당 fd에서 수신된 바이트입니다. 서버로 전송된 프레임의 경우, 이는 해당 fd에 기록해야 하는 바이트입니다.

base64.channel.k8s.io

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

  • TextMessage 프레임은 유효하며, BinaryMessage 프레임은 유효하지 않습니다.
  • TextMessage 프레임의 첫 번째 바이트는 숫자 UTF-8 문자로서 파일 설명자를 나타내므로, 문자 U+0030, 즉 “0”은 fd 0, STDIN입니다.
  • 나머지 바이트는 base64로 인코딩된 임의의 데이터를 나타냅니다.