Skip to content
@GoormBread

GoormBread

프로젝트 소개

고전 게임을 다시 플레이하려면 게임을 실행하는 에뮬레이터와 게임을 찾아야 하고 설치를 진행해야 한다. 이는 불편할 뿐만 아니라 바이러스 감염의 위험도 있는 과정이다 따라서 본 프로젝트에서는 사용자가 웹사이트에 접속해서 게임룸을 생성하고 웹브라우저를 통해 바로 게임을 플레이할 수 있는 클라우드 기반 고전 게임 서비스 를 제작하였다.

시연 영상

git.mov

프로젝트 기술 스택 및 아키텍처

쿠버네티스 환경, 헬름, 게임 이미지 까지는 어떻게는 넣음. 내부 구조를 표현하면 되기 때문. 그러나 CI/CD의 경우 작업 흐름이기 때문에 넣기가 애매함. 변경할 필요가 있을듯? 추가로 ArgoCD가 어떻게 배치되었는지 표기해야하는데 이는 추가 작업 해야할듯.

게임 파드

게임 에뮬레이터(원격 키조작 (Go), 펄스오디오 스트림 생생)

  • 원격 키 조작

GamePlay페이지에서 원격으로 에뮬레이터를 조작하기 위해 웹소켓 통신을 사용하였다. 게임 에뮬레이터 코드 내부에 웹소켓 서버를 추가하였고 1p와 2p를 구분하기 위해서 서로 다른 2개의 포트로부터 웹소켓 메시지를 받을 수 있도록 했다. 웹소켓 메시지는 JSON 형식으로 전달받으며 8개의 키와, 각 키의 상태 정보를 담고 있다. 웹소켓 메시지는 키의 상태가 변할 때마다 GamePlay페이지에서 에뮬레이터로 전송되고 버튼이 눌린 상태인지 아닌지를 true, false로 구분하여 입력 상태가 처리된다. 이때 1p와 2p의 입력은 병렬적으로 처리된다.

  • PulseAudio 스트림 생성

기존의 에뮬레이터에서는 portaudio로 오디오 스트림을 생성하였다. portaudio로 생성된 오디오 스트림을 받기 위해서는 ALSA 오디오 시스템이 필요했지만 컨테이너 환경에서는 사운드 카드가 존재하지 않아 ALSA 오디오 시스템을 사용하기엔 어려움이 있었다. 그리하여 기존의 portaudio를 사용하는 부분을 pulseaudio를 사용하게 변경하였다.

가상 장치 (펄스 오디오, 가상 화면)

  • 가상 화면

컨테이너 환경은 기본적으로 GUI를 지원하지 않는다. 따라서 그래픽 자원을 필요로 하는 프로그램을 컨테이너에서 실행하고자 한다면 가상 디스플레이 장치를 설치해야 한다. Xvfb는 X11 디스플레이 서버 프로토콜을 구현하는 디스플레이 서버이다. 이를 컨테이너 내부에 설치함으로써 실제로 보여지는 화면 없이도 모든 그래픽 처리를 가능하게 할 수 있다.

  • 가상 오디오

컨테이너 환경에서는 오디오 시스템이 존재하지 않아 따로 오디오 시스템을 설치해주어야 할 필요성이 있다. 그리하여 pulseaudio를 설치하였다.

컨테이너 환경에서는 사운드 카드나 사운드 장치가 존재하지 않기 때문에 오디오 스트림을 받고 처리해 줄 오디오 장치가 필요하였다. 오디오 장치는 pulseaudio의 module-null-sink 모듈을 사용하여 가상 오디오 장치를 만들어서 처리하였다.

PULSE_LATENCY_MSEC 환경변수를 통해 레이턴시를 강제로 설정해주어 딜레이를 줄였다.

영상 스트리밍 (FFMPEG)

게임 화면을 GamePlay페이지에서 스트리밍하기 위해 Xvfb로 생성한 가상 디스플레이 화면과 PulseAudio로 생성한 가상 오디오 장치를 FFmpeg로 캡처하고 외부로 송출하는 방식을 사용하였다. 스트리밍되는 영상은 60fps에 768x768 크기를 가진다. 캡처한 화면 및 오디오를 RTSP 스트림으로 송출하고 MediaMTX 미디어 서버에 해당 스트림을 Publish 하여 GamePlay페이지의 JSmpeg Player를 통해 화면 스트리밍이 가능하도록 하였다.

FFmpeg로 영상을 인코딩할 때 기존에는 CPU를 사용해서 해당 과정을 수행하였다. 하지만 CPU 연산만으로는 정상적으로 스트리밍이 이루어지지 않았다. 이 문제를 해결하기 위해서 도커 이미지 내부에 NVIDIA 드라이버를 설치하고 FFmpeg의 -hwaccel cuda 옵션 사용과 h264_nvenc라는 전용 코덱을 사용으로 컨테이너에 할당된 GPU 자원을 사용할 수 있도록 하였다. 또한 FFmpeg의 -preset p2, -tune ll 옵션을 사용해서 딜레이를 최소화 하였고, -af aresample=async 옵션을 통해 영상과 오디오의 싱크가 틀어지지 않도록 하였다.

이를 통해서 스트리밍 품질과 성능 향상이 있었지만, 오디오 인코딩에 대해서는 여전히 CPU 자원만을 사용해야 한다는 한계가 있었다. 이 방식으로 스트리밍을 진행했을 때, 초당 2~3mbps의 대역폭 사용량이 측정되었다.

Web (+ 게임 배치 부분 포함)

프론트 & 백 (React & NestJS)

고전 클라우드 게임 플랫폼을 운영하기 위한 프론트와 백엔드로 React와 NestJS를 선택하였다.

  • 대다수 팀원들의 분야가 웹 개발이 아니었기 떄문에 빠른 개발을 위해서 자바스크립트를 중심으로 한 개발을 구성하였다.
  • 자바스크립트에서 React와 스프링과 유사한 MVC 패턴을 가지고 있는 Nest.JS 프레임워크를 활용하여 프론트엔드와 백엔드를 통합으로 구축할 수 있었다.

클라우드 게임을 운영하기 위해 다음과 같은 기술 스택을 도입하였다.

게임 배치 (Ingress에 웹소켓 허용)

게임을 사용자 요청에 따라 배치하기 위해 헬름을 제작하였다. 헬름에는 게임 파드, 서비스, 인그레스가 포함되어 있다. 인그레스의 경우 총 4가지가 존재하며 이 중 3 개는 웹 소켓을 연결하기 위한 용도로 사용한다.

헬름에는 게임 이름 변수, 난수 경로 변수가 존재한다. 게임 이름은 배치할 게임의 종류를 나타내며 난수 경로는 사용자에게 전달해줄 고유 링크를 설정하도록 한다.

파드에서 헬름을 배치하기 위해 적절한 권한을 가진 서비스 계정을 포함 시켰다. 이를 이용하여 웹 서버는 사용자 요청에 따라 게임을 배치 시키고, 사용자가 해당 경로로 접속하도록 도울 수 있다.

클러스터 매니페스트

모니터링

게임 파드를 운영하는 것은 상당한 리소스를 필요로 한다. 따라서 현재 클라우드 환경을 모니터링 하여 서비스 운영 시 노드를 확장할 여부를 검토할 필요가 있다. 이를 확인하기 위해 클러스터의 상태를 모니터링하는 기능을 추가하였다.

cAdvisor와 Kube-System-Metics를 이용하여 노드와 클러스터의 상태를 수집하고, 프로메테우스를 통해 이 정보를 가져온다. 그 후 그라파나를 이용해 프로메테우스의 정보를 대시 보드 형태로 볼 수 있도록 하였다.

로그

쿠버네티스를 운영하며 발생하는 로그를 중앙 집권적으로 관리할 필요가 있다. 이에 따라 플루언트 비트, 엘라스틱 서치, 키바나를 이용하여 로그 수집 및 검색 기능을 구현하였다.

플루언트 비트는 각 파드에서 발생한 로그에 타임 스탬프를 포함하여 엘라스틱 서치에 전달한다. 엘라스틱 서치를 이용하여 로그들을 검색할 수 있게 도와준다. 마지막으로 키바나를 이용하여 이를 좀 더 쉬운 UI로 볼 수 있게 하였다.

Nvidia 리소스 설정

게임 파드에서는 게임을 구동하고, 화면을 녹화하는 등 많은 그래픽 작업이 필요하다. 만약 이를 CPU를 사용하기만 하여 구동할 경우 적은 수의 게임으로도 금방 과부화가 되는 문제가 있었다. 이에 따라 가장 무거운 그래픽 작업인 화면 녹화 작업을 그래픽 카드를 이용하여 구동할 수 있도록 하였다.

Nvidia 드라이버를 설치한 뒤, Nvidia device plugin파드를 배치하여 그래픽 카드 리소스를 사용할 수 있도록 하였다. CPU와 달리 GPU의 경우 기본적으로 정수 단위로 파드에 리소스를 할당할 수 있다. 이에 따라 GPU를 Timeslice로 분할하도록 ConfigMap을 주입하였다. 이를 통해 여러 게임 파드에서 GPU를 이용하여 화면 녹화를 진행할 수 있게 되었다.

운영 환경

CI/CD

프로젝트를 개발하면서 기존 개발 환경은 다음과 같은 방식으로 테스트가 이루어졌다.

  1. 로컬에서 프로젝트 개발
  2. 로컬에서 도커 컴포즈를 통한 테스트
  3. 테스트가 성공적으로 완료되었을 경우, 깃허브에 푸시
  4. 깃허브에서 변경된 내용을 호스트 머신에서 pull 하고 새로운 도커 이미지를 생성.
  5. 호스트 머신에서의 도커 컴포즈 환경에서의 테스트
  6. 테스트가 성공적으로 완료되었을 경우, 쿠버네티스 환경으로 이식 시작.

이러한 개발 환경을 유지한 채로 진행했을 경우 다음과 같은 문제가 발생하였다.

  1. 팀원들이 개발 환경을 구성하기 위해 알아야 할 사전지식의 증가
  2. 팀원들의 오류 상황이 로컬 환경에 의해서 변칙적으로 발생하기 때문에, 정규화된 매뉴얼 및 대응 수칙을 지키기 어려움.
  3. 전반적으로 개발을 완료하고 테스트를 로컬에서 진행해야 하므로 각 팀원들에게 테스트에 대한 부담이 증가.

그래서 Github Action과 Argo CD를 활용한 쿠버네티스 CI / CD 파이프라인을 구축함으로써 이러한 불편을 해소하려고 하였다. cd

전반적인 CI / CD 파이프라인은 다음과 같은 진행을 따른다.

  1. git flow 방식을 채택한 깃허브 브랜치에서 팀원이 develop 브랜치에 개발이 완료된 feat 브랜치를 merge 시키기 위하여 Pull Request를 날린다.
  2. 한 헬름 차트에서 관리하고 있는 여러 레포 중 현재 다른 레포에서 Github Action이 실행되고 있는지 확인하고 실행되지 않고 있다면 Github Action을 실행한다.(동시에 Helm Chart를 커밋하는 경우를 막기 위해서(동시성 문제))
  3. 팀원들의 동의 하에 Pull Request가 허용되면 develop 브랜치로 feat 브랜치에 있던 기능이 merge 됨과 동시에 Github Action이 실행된다.(CI 실행)
  4. Github Action에서 도커 이미지 빌드 및 도커 허브에 도커 이미지를 업데이트 한다.
  5. 도커 이미지가 성공적으로 업데이트가 완료되었다면, 업데이트 되어야 하는 Helm Chart의 values.yaml에서 Helm Chart가 사용할 이미지 버전을 작성해 놓은 부분에 방금 만들어서 도커 허브에 업데이트 한 버전으로 업데이트 한다. ⇒ 이때 Github에 있는 Helm Chart 레포지토리를 가져와서 업데이트하고 푸시한다.
  6. 성공적으로 변경이 되었다면, Github Action이 종료된다.
  7. Argo CD에서는 해당 헬름 차트가 업데이트 되었다는 것을 인지하고 Sync를 맞춘다.
  8. 이렇게 Sync가 맞춰지면 팀원이 추가한 기능이 현재 운영되고 있는 쿠버네티스 클러스터에 자동으로 반영된다.

Pinned Loading

  1. front front Public

    Front for GoormBread project

    CSS

  2. back back Public

    Back for GoormBread project

    TypeScript

  3. infra infra Public

    Infra for GoormBread project

  4. web-helm web-helm Public

    Web helm chart for GoormBread project

  5. game-helm game-helm Public

    Game helm chart for GoormBread project

  6. manifests-helm manifests-helm Public

    Manifests helm chart for GoormBread project

Repositories

Showing 10 of 10 repositories

People

This organization has no public members. You must be a member to see who’s a part of this organization.

Top languages

Loading…

Most used topics

Loading…