Jieun 7 분 분량 수정
Systems

16GB 맥북 4대의 개발 작업을 96GB 서버 한 대로 옮긴 이유

16GB 맥북 네 대의 개발 작업을 96GB VPS로 옮겼습니다. 계정·홈·포트와 Git worktree로 작업을 분리하고 운영 API 터널을 전용 서비스로 옮긴 과정과 남은 제약을 정리했습니다.

맥북 16GB에서 SSH로 개발 VPS 96GB에 접속하고 공용 터널로 운영 API·DB에 연결하는 남색 도식
화면은 맥북에서 보고, 코드·빌드·에이전트는 VPS에서 실행합니다.
목차

들어가며

개발자 두 명, 디자이너 한 명, 마케터 한 명이 하나의 서버를 함께 사용하도록 개발 환경을 바꿨습니다. 맥북의 메모리 부족을 해결하려던 선택이었지만, 막상 네 명이 같은 서버를 사용하게 되자 다른 문제도 생겼습니다.

각자 실행한 개발 서버가 충돌하지 않아야 했고, 다른 사람의 파일이나 운영 서버 자격증명에 접근하지 못하도록 해야 했습니다. 운영 API에 연결하는 SSH 터널도 특정 팀원의 노트북에 의존하고 있었습니다.

서버를 옮긴 이유부터 네 명이 함께 사용할 수 있도록 작업 공간과 권한을 분리한 과정까지 정리했습니다.

1. 맥북 대신 개발 서버를 사용하기로 했습니다

처음에는 각자 16GB RAM 맥북에서 개발했습니다. 코드 편집과 빌드, Next.js 개발 서버, 코딩 에이전트를 동시에 실행하다 보니 메모리가 부족해졌습니다. 빌드가 무거워질수록 팬이 돌고 다른 작업도 느려졌습니다.

개발 서버 하나가 실제로 얼마나 메모리를 사용하는지 확인해 봤습니다.

실행 시점메모리 사용량
05:281,259MB
08시경2,015MB
09:232,741MB
10:003,759MB

약 4시간 동안 메모리 사용량이 1.2GB에서 3.7GB까지 늘었습니다. 이것만으로 메모리 누수라고 단정할 수는 없지만, 개발 서버 하나가 장시간 상당한 메모리를 차지한다는 사실은 확인할 수 있었습니다.

맥북을 각각 업그레이드하는 대신, 당시 더 낮은 비용으로 사용할 수 있었던 Contabo VPS를 선택했습니다.

  • CPU 18코어, RAM 96GB
  • 리전: 도쿄
  • 코드 편집과 빌드, 개발 서버, 코딩 에이전트를 VPS에서 실행
  • 맥북은 SSH 접속과 브라우저 화면 확인에 사용

한국에서 접속하므로 서버 위치도 중요했습니다. 코드를 변경할 때마다 브라우저와 개발 서버 사이에 통신이 발생하기 때문에, 지연을 줄이기 위해 도쿄 리전을 선택했습니다.

개발 프로세스를 nohup으로 백그라운드에서 실행해 맥북의 SSH 연결이 끊겨도 서버에서 실행 중인 빌드나 개발 프로세스는 계속 유지할 수 있습니다. 코딩 에이전트가 수정한 파일도 같은 서버의 개발 프로세스가 바로 읽으므로 별도의 파일 동기화가 필요하지 않았습니다.

대신 네 명이 한 서버를 공유하면서 각자의 작업을 구분해야 하는 문제가 생겼습니다.

2. 같은 서버를 쓰더라도 작업 공간은 분리했습니다

공용 서버를 사용한다고 해서 모든 사람이 같은 계정과 권한을 사용할 필요는 없었습니다.

개발자는 SSH 셸과 코딩 에이전트가 필요했지만, 디자이너와 마케터에게는 각자의 업무에 필요한 화면과 작업 환경만 제공하면 됐습니다.

그래서 사람마다 Linux 계정과 홈 디렉터리를 분리했습니다. 프로젝트 파일도 각자의 홈 아래에 두고, 다른 사람의 작업 공간과 운영 서버 자격증명에는 접근하지 못하도록 했습니다.

그런데 계정을 분리하는 것만으로는 충분하지 않았습니다.

같은 포트를 사용하면 다른 사람의 화면을 볼 수 있었습니다

Next.js의 next dev는 기본 포트가 이미 사용 중이면 다음 포트로 이동할 수 있습니다. 다만 -p로 포트를 명시한 경우에는 충돌 시 실행이 실패할 수 있습니다.

예를 들어 한 사람이 3000번 포트에서 개발 서버를 실행하고 있는데 다른 사람이 같은 포트로 실행하면, 두 번째 서버는 3001번에서 시작될 수 있습니다.

이때 두 번째 사람이 localhost:3000을 열면 자신이 수정한 코드가 아니라 첫 번째 사람의 화면을 보게 됩니다.

이런 상황을 막기 위해 계정별로 사용할 포트를 정했습니다.

계정Admin 개발 서버
A3000
B3100
C3200
미배정 계정의 기본값3900

원본에 기록된 고정 슬롯은 A·B·C 세 개입니다. 네 명이 사용했다는 사실과 별개로 네 번째 사용자의 개별 포트 배정은 이 기록만으로 확인할 수 없습니다.

포트 번호는 공통 실행 스크립트에서 관리했습니다.

개발 서버를 실행하기 전에 해당 포트를 누가 사용하고 있는지 확인하고, 다른 사람의 프로세스가 사용 중이라면 실행을 중단하도록 했습니다.

기존처럼 Next.js가 빈 포트를 찾아 실행되도록 두는 대신, 포트 충돌을 개발 서버 실행 전에 확인하도록 바꾼 것입니다.

또한 개발 서버 포트는 외부에 직접 공개하지 않고 127.0.0.1에 바인딩했습니다. 각자 SSH 포트 포워딩을 통해 필요한 화면에 접근하도록 했습니다.

구글 OAuth 콜백 주소가 특정 포트로 등록돼 있어 처음에는 계정별 로그인이 제한되는 문제도 있었습니다. 이후 필요한 포트를 추가로 등록해 해결했습니다.

3. 운영 API 연결은 개인 노트북에서 분리했습니다

개발 서버는 사람마다 분리했지만, 운영 API로 연결하는 SSH 터널은 반대로 하나만 있으면 됐습니다.

당시 관리자 화면은 Next.js 기반의 BFF(Backend For Frontend)였고, 화면을 렌더링할 때 운영 API의 데이터가 필요했습니다. 별도의 개발용 API와 DB를 준비하지 않은 상태였으므로 개발 서버에서 운영 API를 호출하는 구조를 사용하고 있었습니다.

처음에는 한 팀원의 맥북에서 역방향 SSH 터널을 열어 공유 서버가 운영 API에 접근하도록 했습니다.

이 구조에는 문제가 있었습니다.

그 팀원의 노트북이 꺼지거나 연결이 끊기면 다른 팀원들도 운영 API에 접근할 수 없었습니다. 터널이 끊어졌을 때 다시 연결하는 작업 역시 그 사람에게 의존했습니다.

공유 개발 서버의 핵심 연결이 개인 노트북에 매달려 있었던 것입니다.

SSH 터널을 전용 서비스로 옮겼습니다

해결 방법은 터널을 공유 VPS에서 직접 관리하는 것이었습니다.

wdot-tunnel이라는 전용 Linux 계정을 만들고, systemd 서비스가 운영 API 연결을 담당하도록 변경했습니다.

변경 전에는 다음과 같은 구조였습니다.

맥북 → 공유 VPS → 팀원의 맥북 SSH 터널 → 운영 API

변경 후에는 중간의 개인 노트북이 사라졌습니다.

맥북 → 공유 VPS의 SSH 터널 서비스 → 운영 API

전용 계정에는 일반적인 로그인 셸이나 sudo 권한을 주지 않았습니다.

운영 서버의 SSH 키도 필요한 목적지로만 포트 포워딩할 수 있도록 제한했습니다. authorized_keys의 restrict, port-forwarding, permitopen으로 로컬 포워딩 목적지를 제한하고, command="/bin/false"로 셸·명령 실행을 막았습니다. permitopen은 -L의 목적지 제한이며, 모든 역방향 포워딩을 제한하는 설정은 아닙니다.

공유 VPS의 127.0.0.1:8787로 들어온 요청은 SSH 터널을 통해 운영 서버의 127.0.0.1:8987로 전달됩니다. 운영 서버에서도 해당 포트를 외부 네트워크가 아닌 루프백에만 열었습니다.

systemd에는 재시작 설정을 추가했습니다. 서비스가 종료되면 다시 실행하고, 서버가 재부팅돼도 서비스를 시작하도록 구성했습니다.

이제 운영 연결을 유지하기 위해 특정 팀원이 노트북을 켜두거나 수동으로 SSH 명령을 실행할 필요가 없도록 구조를 바꿨습니다.

다만 이것이 모든 장애 상황에서 자동 복구를 검증했다는 뜻은 아닙니다. 당시 기록으로 확인할 수 있는 것은 제한된 SSH 키의 권한과 서비스의 실행·재시작 설정까지입니다.

4. 여러 작업이 섞이지 않도록 Git worktree를 사용했습니다

한 서버에서 여러 코딩 에이전트와 개발 작업을 동시에 실행하면 파일 충돌도 관리해야 합니다.

같은 작업 폴더에서 브랜치를 계속 바꾸면 한 작업의 파일 상태가 다른 작업에 영향을 줄 수 있습니다.

이를 막기 위해 Git worktree를 사용했습니다.

작업 하나에 브랜치 하나와 폴더 하나를 대응시키고, 각 계정의 홈 디렉터리 안에서 관리했습니다.

작업이 끝난 폴더는 자동으로 정리하되, 단순히 오래됐다는 이유로 삭제하지 않도록 했습니다.

삭제하기 전에 다음 상태를 확인했습니다.

  • 저장하지 않은 변경이나 push하지 않은 커밋이 있는지
  • 아직 열려 있는 PR이 있는지
  • PR이 실제로 머지됐는지

Squash merge를 사용하면 일반적인 Git 브랜치 비교만으로 머지 여부를 정확하게 판단하기 어려울 수 있습니다. 그래서 GitHub의 PR 상태를 기준으로 정리하도록 했습니다.

작업 폴더가 사라졌는데 개발 서버 프로세스만 남아 있는 경우에는 해당 프로세스도 함께 정리했습니다.

반면 폴더가 살아 있고 단지 오랫동안 사용하지 않은 것으로 보이는 서버는 자동 종료하지 않았습니다. 사용하지 않는다는 추정만으로 다른 사람의 작업을 중단하고 싶지는 않았기 때문입니다.

5. 여전히 남아 있는 제약도 있습니다

공유 VPS로 옮기면서 개발 환경의 메모리 문제와 작업 공간 충돌을 줄일 수 있었지만, 모든 문제가 해결된 것은 아닙니다.

가장 큰 제약은 개발 서버가 운영 API에 연결돼 있다는 점입니다.

관리자 화면을 확인하기에는 편리합니다. 별도의 개발용 데이터를 준비하지 않아도 실제 데이터를 보면서 작업할 수 있기 때문입니다.

하지만 저장·발행·삭제 기능을 실행하면 실제 운영 데이터가 변경될 수 있습니다.

운영 DB 자격증명을 직접 제공하지 않는 것만으로 이 위험이 사라지지는 않습니다. API 자체에 쓰기 기능이 있다면 해당 API를 호출하는 사용자도 운영 데이터에 영향을 줄 수 있습니다.

특히 자동화된 E2E 테스트는 이 환경에서 실행하지 않기로 했습니다.

E2E 테스트는 같은 작업을 반복해서 실행하고 데이터를 생성하거나 삭제할 수 있습니다. 운영 API에 연결된 개발 환경에서 이런 테스트를 실행하는 것은 위험합니다.

따라서 향후 E2E 테스트를 도입할 때는 공유 VPS 안에 별도의 API·DB·Redis를 구성하고, 테스트가 운영 서버에 접근하지 않도록 분리할 계획입니다.

이 부분은 아직 구현한 결과가 아니라 앞으로 해결해야 할 작업입니다.

마무리하며

처음에는 맥북에서 실행하던 개발 도구를 메모리가 넉넉한 서버로 옮기는 작업이었습니다.

그런데 네 명이 함께 사용하려니 단순히 서버 사양이 좋은 것만으로는 부족했습니다.

계정과 작업 폴더를 나누고, 서로의 개발 서버를 혼동하지 않도록 포트를 분리했습니다. 개인 노트북에 의존하던 운영 API 연결은 전용 서비스가 담당하도록 바꿨습니다.

지금의 구조는 화면 개발과 협업에는 적합하지만 운영 API에 의존한다는 한계가 있습니다. 그래서 운영 데이터를 변경할 수 있는 자동화 테스트는 별도 환경으로 분리하려고 합니다.

서버 한 대를 함께 사용하는 것보다 중요했던 것은 공유해야 하는 자원과 분리해야 하는 자원을 구분하는 일이었습니다.

댓글