NestJS 서버를 두 개의 프로세스에서 돌리는 과정에서 겪은 Windows 환경 문제를 기록한다.
(프로세스를 분리하게 된 구체적인 상황은 Ask-It 프로젝트 Wiki에서 확인할 수 있다.)
우리 서버의 HTTP, WebSocket 서버 분리 로직은 다음과 같다. 한 대의 물리 서버에서 HTTP 프로세스와 WebSocket 프로세스가 각각 실행된다.
const args = process.argv;
if (args.includes('http')) {
bootstrap(Number(process.env.API_SERVER_PORT ?? '3000'));
} else if (args.includes('ws')) {
bootstrap(Number(process.env.SOCKET_SERVER_PORT ?? '4000'));
}
"scripts": {
"start:prod:http": "node dist/main http",
"start:prod:ws": "node dist/main ws",
},
module.exports = {
apps: [
{
name: 'http-server', // PM2 프로세스의 이름
script: 'pnpm', // 실행할 실제 파일 또는 명령어
args: 'run start:prod:http',// script에 전달할 인자
interpreter: 'none', // 스크립트 실행을 위한 해석기 (none은 직접 실행)
cwd: './', // 실행될 작업 디렉토리
env: { PORT: 3000, },
},
{
name: 'ws-server',
script: 'pnpm',
args: 'run start:prod:ws',
interpreter: 'none',
cwd: './',
env: { PORT: 4000, },
},
],
};
이렇게 하면,
pnpm run start:prod:http
→ node dist/main http
pnpm run start:prod:ws
→ node dist/main ws
형태로 PM2가 두 개의 프로세스를 관리하게 된다.
팀원들의 맥북과 서버 운영환경인 Ubuntu에서는 pm2 start ecosystem.config.js
로 잘 구동되었다. 같은 설정을 GitHub Actions로 배포 자동화까지 했음에도 문제 없이 동작했다.
그러나 ! 윈도우에서는 다음과 같은 문제가 발생한다. (젠장 또 윈도우야)
PS C:\Users\Desktop\Ask-It-Refactor\apps\server> pm2 start ecosystem.config.js [PM2][WARN] Applications http-server, ws-server not running, starting...
[PM2][ERROR] Process failed to launch spawn EINVAL
[PM2][ERROR] Process failed to launch spawn EINVAL
여기서 spawn EINVAL
에러는 "Invalid Argument Error"의 줄임말로, PM2가 프로세스를 생성(spawn)하려고 할 때 유효하지 않은 실행 파일이나 인자가 전달되었다는 의미였다.
실제 문제의 원인은 운영체제별로 pnpm
실행 파일이 다르게 설치된다는 점이었다.
Unix/Linux에서는 pnpm
이라는 이름으로 설치되는 반면, Windows에서는 pnpm.cmd
라는 이름으로 설치된다. 그래서 PM2 설정의 script: 'pnpm'
에서 Windows가 실행 파일을 찾지 못했던 것이다.
문제를 파악하고 나니 몇 가지 선택지가 있었다:
pnpm.cmd
로 설정 파일 수정하기처음에는 1번 방법을 고려했지만, 개발 환경에 맞춰 코드를 변경하는 건 바람직하지 않아 보였다.
2번 방법은 다음처럼 하는 것이다:
module.exports = {
apps: [
{
name: 'http-server',
script: './dist/main.js', // 빌드된 파일 직접 실행
args: 'http', // 'http' 인자 전달
cwd: './',
env: {
PORT: 3000,
},
},
{
name: 'ws-server',
script: './dist/main.js', // 빌드된 파일 직접 실행
args: 'ws', // 'ws' 인자 전달
cwd: './',
env: {
PORT: 4000,
},
},
],
};
이렇게 하면 OS 상관없이 동작한다.
하지만 이는 패키지 매니저를 거치지 않는 방식이라 다른 부분과의 일관성이 떨어질 수 있다는 단점이 있었다.
이로 인해 3번과 4번 중 고민을 했다. 이미 WSL2에 Ubuntu 환경이 구성되어 있었으므로, 내 상황에선 이 편이 빠르겠다는 판단이 들어 WSL2 Ubuntu를 채택했다.
이번 이슈를 겪으면서 개발/운영 환경 일치화의 중요성을 다시 한번 느꼈다. 다음 프로젝트에서는 Docker 도입을 진지하게 고려해볼 생각이다.
초기에는 번거롭더라도 이런 환경 설정에 시간을 더 투자하는 게 나중에 발생할 수 있는 예기치 못한 문제들을 줄일 수 있는 방법이라는 걸 배웠다.