작은 프로젝트라도 로컬-운영 경계 정도는 필요하다(feat. ngrok)
돌다리도 두들기고 건너듯, 테스트도 두들기고 건너자
들어가는 글
운영이 되고 있던 디스코드 봇 하나의 개발을 물려받은 적이 있다. 넘겨 받고 나서 전임자에게 이것 저것 질문해보며 가장 궁금했던 건 ‘그전까지 테스트를 어떻게 해왔냐’는 거였다. 하지만 로컬 환경을 따로 두지 않고, 운영 중인 봇에 그대로 코드를 배포해가며 테스트하고 있었다. 즉 커뮤니티 멤버들이 실시간으로 쓰고 있는 봇으로 테스트를 해왔다는 것이었다.
하지만 필자는 운영 중인 봇과 별개로 로컬에서의 테스트가 필수라고 생각했기에 디스코드 봇으로 로컬 테스트를 하는 방법을 찾아볼 필요가 있었다. 따라서 이 글은 디스코드 봇을 개발하는 과정에서 로컬 개발 환경에서 테스트하여 안전하게 개발하는 방법, 그리고 그 과정에서 ngrok을 쓰게 된 이유와 방법을 정리한 글이다.
다음의 독자들에게 특히 도움이 되길 바란다.
- 사이드 프로젝트 / 커뮤니티 도구를 홀로 or 소수로 운영하는 개발자
- 로컬-운영기에 대한 개념은 알고 있으나 서비스 운영 경험이 적은 개발자
왜 로컬에서 먼저 검증해야 하는가?
운영 중인 시스템에 코드를 바로 반영하면, 문제가 생겼을 때 그 영향을 고스란히 받는 건 지금 그 시스템을 쓰고 있는 실제 사용자들이다. 그래서 코드를 운영 환경에 반영하기 전에, 로컬(== 본인이 개발에 이용하고 있는 컴퓨터 자체)에서 먼저 그 코드가 의도대로 동작하는지 확인하는 과정이 필요하다.
대규모 서비스의 경우에는 이를 로컬 서버 검증 이후로도 개발기 서버 → 스테이징 서버 → 운영기 서버 순으로 배포와 검증을 반복하기도 한다. 그만큼 운영기에 들어가는 코드는 안정성을 극대화 시켜야 한다는 점을 꼭 인지해야 한다. (해당 글에서는 로컬에서의 테스트와 운영기에 코드 반영만 한 사례를 소개하므로 개발기와 스테이징에 대한 설명은 따로 하지 않는다. 스테이징 환경 구축 관련 글은 별도 글 에서 다뤘다.)
이 원칙을 지키는 기준은 조직의 규모나 형태가 아니라, 지금 그 시스템을 실제로 쓰고 있는 사람이 있는지다. 커뮤니티 봇이든 사이드 프로젝트든, 사용자가 있다면 동일하게 적용된다. 그래서 필자는 이 봇을 넘겨받은 뒤부터 로컬에서 먼저 검증하고, 문제가 없는 걸 확인한 뒤에 운영 환경에 반영하는 흐름으로 작업하려고 했다.
로컬에서 개발한 봇 이벤트 서버 테스트 하기
디스코드 봇 서버를 테스트하려면 운영기와 동일한, 한 벌의 테스트 인프라들이 필요하다. 이번 프로젝트에서는 다음의 인프라가 필요했다.
-
디스코드 서버(여기서의 ‘서버’는 워크스페이스, 커뮤니티 단위를 뜻함. 이는 운영 서버로 대체 가능)
-
디스코드 서버 내 테스트 채널(운영 중인 채널과 별개)
-
디스코드 봇 어플리케이션(운영 중인 어플리케이션과 별개)
-
실행이 되고 있는 로컬 서버(현재의 글에서는 supabase 서버. fastapi 서버나 node 서버, spring 서버가 될 수도 있다.)
필자의 귀여운(?) 테스트봇. 급하게 만들어서 카페에서 찍은 느좋 사진이 앱 아이콘이다. -
테스트용 DB(운영기에서 사용하는 DB와 동일한 스펙으로 맞춘다. PostgreSQL, MySQL 등. 가끔은 spreadsheet가 될 수도 있음)
디스코드 봇을 만드는 법
디스코드 봇을 만드는 방식은 크게 두 가지로 나뉜다.
- Gateway(WebSocket) 방식은 봇이 디스코드 서버에 WebSocket으로 상시 연결을 맺고, 이벤트가 생길 때마다 그 연결을 통해 데이터를 받는 방식이다.
- HTTP Interactions 방식은 반대다. 디스코드가 슬래시 커맨드 등의 이벤트를 서버로 직접 HTTP POST 요청을 보내는 구조다. 이 경우엔 디스코드가 요청을 보낼 수 있는 퍼블릭 URL이 반드시 있어야 한다. 그래서 Interactions Endpoint URL 설정이 필수가 된다.
필자가 물려받았던 디스코드 봇이 동작하는 서비스는 Supabase 기반이었다. Supabase는 Edge Function으로 백엔드를 구성하는데, 이는 서버리스 구조이며 "요청이 올 때만 실행되는 HTTP 함수"에 가깝다.
따라서 상시 연결을 유지하는 Gateway 방식(웹소켓 서버)을 사용할 수 있는 상황이 아니었기 때문에 HTTP Interactions 방식이 이 아키텍처에 더 자연스럽게 맞아 떨어진다.
근데.. 디스코드 봇이 로컬 서버를 어떻게 바라보게 하지?
그렇다면 내 컴퓨터에서 개발해서 실행하고 있는 봇 이벤트 서버가 있다고 하자. 디스코드 봇 입장에서는 이 서버를 어떻게 바라보게 만들 수 있을까?
Edge Function은 아직 배포되지 않았으니 퍼블릭 URL이 존재하지 않으며 디스코드 봇 입장에서는 내가 만든 코드에 접근할 수 있는 방법이 없다. 하지만 지금 당장 유효한 Interactions Endpoint URL을 요구한다. 이미 운영에서 쓰고 있는 봇과 별개로 나만의 테스트용 봇도 만들었지만 여기에서 막혀버렸다.
방법은 간단하다. 이 때는 터널링 도구를 이용하여 로컬 서버를 퍼블릭하게 접근할 수 있도록 한다. 필자는 터널링 도구로 ngrok이란 서비스를 사용했다.
ngrok을 이용하여 내 서버까지 들어오는 “터널”을 뚫어주다
ngrok은 로컬에서 실행 중인 서버의 포트를, 임시로 발급되는 퍼블릭 URL에 매핑해주는 도구다. 설치하는 방법은 매우 간단하며, 자세한 내용은 ngrok 다운로드 페이지 참고.
로컬에서 supabase 서버를 8000번 포트로 띄운 후, ngrok http 8000 로 cli를 실행하여 포트 바인딩, https://xxxx.ngrok.io 같은 임시 퍼블릭 URL을 발급받을 수 있다. 다음의 예시 사진에서는 https://9ba3-116-34-236-78.ngrok-free.app 이란 퍼블릭 URL을 받은 것이고 이는 현재 내 로컬의 8000번 포트로 터널링 해주고 있다는 의미다.
이런 식으로 실행하면 이 URL로 들어오는 요청은 ngrok 서버를 거쳐 내 로컬 8000번 포트로 그대로 전달된다. 즉 내 컴퓨터 앞에 "터널"을 하나 뚫어서, 외부 요청이 그 터널을 타고 로컬까지 들어올 수 있게 해주는 방식이다.
렌더링 중...
이 발급된 URL을 그대로 디스코드 개발자 사이트의 인터랙션 엔드포인트 URL에 등록하면, 로컬에서 실행 중인 봇 코드를 디스코드가 실시간으로 호출할 수 있게 된다. 운영 중인 봇을 건드리지 않고도, 로컬에서 실제 디스코드 요청을 받아가며 검증할 수 있게 된 것이다.
이후 아주 마음껏 로컬 서버 테스트를 해보았으며, 충분한 테스트를 한 이후 운영기에 코드를 반영하였다.
이번에 쓴 건 ngrok이었지만, 이 역할을 하는 도구가 ngrok뿐인 건 아니다. cloudflared(Cloudflare Tunnel), localtunnel 같은 대체 도구들도 있다고 알려져 있다. 이번 글에서는 직접 써본 ngrok을 기준으로 정리했다.
마치며
이 글에서 가장 하고 싶었던 말은 이렇다. — 지금 이 코드를 건드리면 어디서 누가 영향을 받는가를 먼저 따져야 한다는 것. 다만 이 프로젝트는 규모가 크지 않았기 때문에 (개발기 및 스테이징 없이) 로컬-운영 2단계로 충분했다. 그럼에도 그 규모와 무관하게, 위의 핵심 질문을 먼저 따지는 습관 자체는 간과할 수 없었다. 그래서 더더욱 테스트 서버를 따로 두어야 하고, 이는 운영기와 최대한 비슷한 환경으로 만들어 줘야 한다는 점을 다시금 새겼다. (서버의 안정성이 가장 중요하지만, 필자가 마음껏 테스트를 하기 편한 것도 사실이었음!)
다음엔 더 큰 규모의 프로젝트를 맡게 된다면, 이번처럼 로컬-운영 2단계로 끝내지는 않을 것 같다. 로컬 → 개발기 → 스테이징 → 운영기 서버 구성을 기준으로 하여, 개발기와 스테이징을 각각 왜 두는지 목적을 먼저 분명히 하고, 그에 맞게 단계를 늘려갈 것이다.
디스코드 봇을 개발할 수 있는 기회가 있어서 ngrok 등의 툴을 다시 써보는 계기도 되었다. 사실 실무할 때는 써볼 일이 거의 없었기에 잊고 있었던 툴도 오래된 서랍 속에서 꺼내듯 사용해볼 수 있는 찬스였던 경험이었다.