부트캠프에서 학생들을 상대하다 보니 백엔드에서 서버 애플리케이션을 띄우는 방법에 대해 혼동이 많아 여기에 정리할 겸, nginx Unit을 다시 들여다 보면서 내가 잘못 생각했던 것도 있고 이것의 진가를 잘 몰라봤다는 생각이 들어 다시 여기에 정리를 같이 한다.

과거 서버 프로세스를 띄우는 법

서버 프로세스를 띄우는 법의 기본은 ‘백그라운드’ 로 실행시키는 것이다. 예를 들어 스프링부트의 배포 jar파일을 띄운다면

$ java -jar build/libs/application.jar &

와 같이 뒤에 ‘&’을 붙여 백그라운드로 띄우는 것이다.

이와 같이 띄우면 어떤 문제가 있을 까?

  1. 프로세스의 관리가 어렵다.
    • 별도의 관리도구가 없다면 프로세스 관리의 기본은 로그를 보는 것인데 로그를 별도로 애플리케이션에서 남기도록 해야 한다. 장인정신으로 로그 생성 및 저장을 구현한다.
  2. 오토 스케일링
    • 만일 부하가 발생하면 프로세스의 수를 늘리던지, 부하가 줄어들면 프로세스 수를 줄이던지 해야 한다. 이것도 관리 도구가 없다면 힘든 부분이다.

⚠️ 예전에 부캠 교육생들들은 ‘&’ 를 붙이는 것도 몰라서 그냥 서버를 띄웠던 경우가 많았다. 물론 이렇게 띄우다가 터미널을 종료하면 프로세스도 같이 종료된다.

관리도구 : nginx unit

nginx제작팀에서 이제 백엔드 프로세스까지 관리를 하고 싶었나 보다. java, python, node 등의 프로세스를 직접 관리하도록 하는 솔루션인 nginx unit을 만들었다.

nginx unit의 장단점은 다음과 같다.

장점:

  • 다양한 언어 지원: Python, PHP, Perl, Ruby, Go, Java 등 여러 프로그래밍 언어를 네이티브로 지원한다
  • 동적 구성: REST API를 통해 구성 변경이 가능하여, 서비스 중단 없이 실시간으로 애플리케이션을 업데이트할 수 있다
  • 이식성: 다양한 운영 체제에서 실행될 수 있으며, 개발과 배포 환경 간의 차이를 최소화한다
  • 내장된 프로세스 관리: 애플리케이션의 프로세스를 자동으로 관리하며, 부하에 따라 자동으로 스케일링할 수 있다

단점:

  • nginx보다는 낮은 사용사례: 국내에서는 이를 사용하는 사례가 잘 보이지 않는다. 커뮤니티도 nginx에 비해 약하다.
  • 리버스 프록시 및 로드 밸런싱 기능: Nginx에 비해 리버스 프록시와 로드 밸런싱 기능이 제한적일 수 있으며, Nginx와 함께 사용하여 이러한 기능을 확장하기도 한다.
  • 성능: Nginx에 비해 동적 콘텐츠 처리 성능이 떨어질 수 있으나, 실제 성능은 사용 사례에 따라 다를 수 있다.

눈에 띄는 것은 동적 구성과 내장된 프로세스 관리 기능이다. 부하가 늘어나면 자동으로 스케일링을 해 주며 이것도 옵션으로 조정 가능하다.

⚠️ nginx사에서 nginx로 프록시및 로드밸런싱을 커버하고 각 단위 서버로 nginx unit 를 사용하도록 기획을 한 것 같다.

nginx unit의 설치

nginx unit의 설치는 macOS 및 대부분의 Linux 를 지원한다.

설치의 세부 사항은 공식 홈페이지를 참조하면 되지만, 주목할 사항은 unit 메인 설치 및 패키지를 설치해야 한다는 점이다. 예를 들어 ubuntu 20.04(필자가 쓰는 버전)의 경우 설치 명령어는 다음과 같다.

sudo apt update
sudo apt install unit
sudo apt install unit-dev unit-jsc11 unit-perl  \
      unit-php unit-python3.8 unit-ruby unit-wasm
sudo systemctl restart unit

여기서 unit-dev, unit-jsc11, unit-python3.8… 등은 각각 범용, java, perl, python3.8의 프로세스를 unit에서 관리하겠다는 의미이다.

nest.js를 unit으로 실행시키기

많이 쓰이는 프레임워크(SpringBoot, flask 등등)및 언어들은 이미 공식 홈페이지에 설정 방법이 나와있다. 여기에서는 공식 홈페이지에는 나와있지 않는 nest.js를 unit로 실행시키도록 하겠다.

nest.js 테스트 프로젝트의 폴더 이다.

~/nestjs/tddtest$ ll

total 396
drwxr-xr-x   7 ubuntu ubuntu   4096 Mar  5 06:03 ./
drwxrwxr-x   3 ubuntu ubuntu   4096 Nov  2 05:16 ../
-rwxr-xr-x   1 ubuntu ubuntu    663 Nov  2 05:16 .eslintrc.js*
drwxr-xr-x   7 ubuntu ubuntu   4096 Nov  2 05:16 .git/
-rwxr-xr-x   1 ubuntu ubuntu    391 Nov  2 05:16 .gitignore*
-rwxr-xr-x   1 ubuntu ubuntu     51 Nov  2 05:16 .prettierrc*
-rwxr-xr-x   1 ubuntu ubuntu   3340 Nov  2 05:16 README.md*
drwxrwxr-x   2 ubuntu ubuntu   4096 Mar  5 05:29 dist/
-rwxr-xr-x   1 ubuntu ubuntu    171 Nov  2 05:16 nest-cli.json*
drwxr-xr-x 485 ubuntu ubuntu  20480 Mar  5 04:39 node_modules/
-rwxr-xr-x   1 ubuntu ubuntu 325381 Nov  2 05:16 package-lock.json*
-rwxr-xr-x   1 ubuntu ubuntu   1948 Nov  2 05:16 package.json*
drwxr-xr-x   2 ubuntu ubuntu   4096 Mar  5 05:25 src/
drwxr-xr-x   2 ubuntu ubuntu   4096 Nov  2 05:16 test/
-rwxr-xr-x   1 ubuntu ubuntu     97 Nov  2 05:16 tsconfig.build.json*
-rwxr-xr-x   1 ubuntu ubuntu    638 Nov  3 04:11 tsconfig.json*

우리가 관심있는 것은 npm run build의 결과물인 /dist폴더이다.

~/nestjs/tddtest/dist$ ll
-rw-rw-r-- 1 ubuntu ubuntu    181 Mar  5 05:25 app.controller.d.ts
-rw-rw-r-- 1 ubuntu ubuntu   1595 Mar  5 05:25 app.controller.js
-rw-rw-r-- 1 ubuntu ubuntu    428 Mar  5 05:25 app.controller.js.map
-rw-rw-r-- 1 ubuntu ubuntu     35 Mar  5 05:25 app.module.d.ts
-rw-rw-r-- 1 ubuntu ubuntu   1146 Mar  5 05:25 app.module.js
-rw-rw-r-- 1 ubuntu ubuntu    351 Mar  5 05:25 app.module.js.map
-rw-rw-r-- 1 ubuntu ubuntu    104 Mar  5 05:25 app.service.d.ts
-rw-rw-r-- 1 ubuntu ubuntu   2855 Mar  5 05:25 app.service.js
-rw-rw-r-- 1 ubuntu ubuntu    863 Mar  5 05:25 app.service.js.map
-rw-rw-r-- 1 ubuntu ubuntu    193 Mar  5 05:25 constants.d.ts
-rw-rw-r-- 1 ubuntu ubuntu    363 Mar  5 05:25 constants.js
-rw-rw-r-- 1 ubuntu ubuntu    265 Mar  5 05:25 constants.js.map
-rw-rw-r-- 1 ubuntu ubuntu     11 Mar  5 05:25 main.d.ts
-rw-rw-r-- 1 ubuntu ubuntu    340 Mar  5 05:25 main.js
-rw-rw-r-- 1 ubuntu ubuntu    287 Mar  5 05:25 main.js.map
-rw-rw-r-- 1 ubuntu ubuntu 108543 Mar  5 05:25 tsconfig.build.tsbuildinfo
-rw-rw-r-- 1 ubuntu ubuntu    143 Mar  5 05:25 user.json

nest.js 에서 메인 엔트리는 main.js파일이며 배포 폴더를 실행하려면 node main.js 명령으로 프로세스를 띄우지만, 이를 unit으로 실행해 보도록 하겠다.

먼저 config.json(파일 명은 다른것도 무방하다)을 다음과 같이 작성한다.

{
    "listeners": {
        "*:4000": {
            "pass": "applications/nestjs_app"
        }
    },

    "applications": {
        "nestjs_app": {
            "type": "external",
            "working_directory": "/home/ubuntu/nestjs/tddtest/dist",
            "executable": "/usr/bin/env",
            "arguments": [
                    "node",
                    "--loader",
                    "unit-http/loader.mjs",
                    "--require",
                    "unit-http/loader",
                    "main.js"
            ]

        }
    }
}

이 파일을 간단히 설명하면 4000번 포트를 사용하며, type은 “external”로, working_directory는 dist폴더로 설정해 준다. executable 는 env(/usr/bin/env)로 하고 arguement를 통해 node 명령 및 main.js파일 구동을 셋팅한다.

이는 다음 명령을 통해 반영한다(ubuntu Linux기준. 다른 OS의 경우는 공식 홈페이지 참조).

$ sudo curl -X PUT --data-binary @config.json --unix-socket\
/var/run/control.unit.sock http://localhost/config

성공하면 다음과 같이 출력된다.

{
        "success": "Reconfiguration done."
}

프로세스가 떴는지를 확인해 보자.

~$ ps -ef | grep unit
root      196630       1  0 05:17 ?        00:00:00 unit: main v1.32.0 [unitd]
unit      196632  196630  0 05:17 ?        00:00:00 unit: controller
unit      196633  196630  0 05:17 ?        00:00:00 unit: router
unit      196799  196630  0 05:31 ?        00:00:00 unit: "nestjs_app" prototype
unit      196800  196799  0 05:31 ?        00:00:00 node --loader unit-http/loader.mjs --require unit-http/loader main.js
  1. unit: main [unitd]
    • 이는 Nginx Unit의 메인 프로세스로, 전체 시스템의 관리와 조정을 담당한다. 시작 시에 초기화를 수행하고, 이후에는 다른 프로세스들의 실행을 관리한다.
  2. unit: controller
    • 컨트롤러 프로세스는 Nginx Unit의 구성 및 관리 API 요청을 처리한다. 이 프로세스는 새로운 구성 변경 사항을 받아서, 해당 변경 사항을 시스템에 적용하는 역할을 한다.
  3. unit: router
    • 라우터 프로세스는 들어오는 요청을 적절한 애플리케이션 프로세스로 전달하는 역할을 한다. 요청의 URL, 헤더 등을 분석하여 설정된 규칙에 따라 어떤 애플리케이션에 요청을 전달할지 결정한다.
  4. unit: “nestjs_app” prototype
    • 프로토타입 프로세스는 특정 애플리케이션(이 경우 “nestjs_app”)의 초기 인스턴스를 생성한다. 이 프로토타입은 실제 요청 처리를 위한 애플리케이션 프로세스를 생성하는 데 사용되는 템플릿 역할을 한다. 새로운 요청이 도착하고 추가 프로세스가 필요한 경우, 이 프로토타입을 기반으로 애플리케이션 프로세스가 생성된다.
  5. unit: “nestjs_app” application
    • 애플리케이션 프로세스는 실제로 들어오는 웹 요청을 처리한다. “nestjs_app” 프로세스는 node 애플리케이션의 코드를 실행하여 요청에 응답한다. 이 프로세스는 필요에 따라 동적으로 생성 및 종료될 수 있으며, 각 인스턴스는 별도의 요청을 독립적으로 처리할 수 있다.

이렇게 되면 unitd프로세스가 전체적으로 nestjs_app을 관리하면서 스케일링 등을 조정할 수가 있다. 나중에 스케일링 테스트 부분은 다시 다뤄보도록 하겠다.

4000 번 포트로 수행한 결과는 아래와 같다.