데몬 프로세스에 대한 이해

웹 서버를 배포할 때 데몬 프로세스의 개념이 등장합니다. 물론 패키지를 이용하면 데몬 프로세스에 대한 이해 없이 손쉽게 배포할 수 있지만 원리를 알고 있는 것도 중요하다고 생각합니다. 데몬 프로세스에 대해 작성하면서 이와 관련된 포그라운드 프로세스와 백그라운드 프로세스까지 함께 정리해보려고 합니다.

Node.js로 웹 서버를 배포하는 시나리오를 가정했습니다.

포그라운드 프로세스

Node.js로 프로젝트를 마친 뒤 서비스로 운영하기 위해서 클라우드 서버에 올렸다고 해보겠습니다. 서버 프로세스를 실행하기 위해서 node app.js 명령어를 입력하면 마무리될 것 같지만 여러 가지 문제가 발생합니다.

node app.js 명령어는 프로세스를 포그라운드(Foreground Process)로 실행하기 때문에 프로세스가 실행 중인 터미널에서 다른 명령어를 이용할 수 없습니다. 포그라운드 프로세스는 터미널과 키보드를 통해 사용자와 대화하는데 입력한 명령이 실행되고 결과가 출력될 때까지 기다리기 때문입니다.

node app.js
> server is running...

프로세스가 끝나지 않고 동작 중이기 때문에 다른 명령어를 입력할 수 없습니다. 뿐만 아니라 터미널이 종료되거나 접속 세션이 끊기면 프로세스가 종료됩니다.

백그라운드 프로세스

그렇다면 프로세스를 포그라운드와 반대로 백그라운드(Background Process)로 실행한면 어떨까요? 백그라운드 프로세스는 터미널과 관계를 끊고 독립적으로 동작하기 때문에 프로세스가 실행 중이어도 다른 명령어를 입력할 수 있습니다.

앞서 실행한 명령어 뒤에 &(ampersand)를 붙여주면 백그라운드로 실행할 수 있습니다. 이제 node app.js & 명령어를 통해서 프로세스를 실행해보겠습니다.

node app.js &
> [1] 40313

이어서 프롬프트가 나오는 것을 볼 수 있는데 이어서 다른 명령어를 입력할 수 있습니다. 이제 서버 프로세스를 백그라운드로 실행해놓고 끝내고 싶지만 다른 문제가 발생합니다. 백그라운드 프로세스도 마찬가지로 터미널이 종료되거나, 접속 세션이 끊기면 프로세스도 종료됩니다.

프로세스가 종료되는 이유

터미널이 종료되거나 접속 세션이 끊기면 프로세스가 종료되는 이유가 무엇일까요? 터미널을 종료하거나 접속 세션이 끊어지면 Bash에게 SIGHUP 시그널이 전달되고, Bash는 관리 중인 모든 프로세스 그룹에 SIGHUP 시그널을 전송한 뒤 종료됩니다. 시그널을 받은 프로세스 그룹은 고아 프로세스 그룹이 되고, 이 그룹에 속한 프로세스는 마찬가지로 SIGHUP 시그널을 받고 종료됩니다.

SIGNUP(HUP)은 전화선이 끊어졌을 때 발생하는 시그널인데 기본 동작(default action)이 종료이다. 이 때문에 별다른 설정을 하지 않고, 이 시그널을 받는 프로세스는 종료된다.

정리하자면 터미널이 종료되거나 접속 세션이 끊긴 경우 프로세스가 종료 시그널을 받게 되므로 기본 동작에 따라 프로세스가 종료됩니다.

데몬 프로세스

데몬 프로세스(Daemon Process)는 백그라운드 프로세스 중 하나입니다. 정확히는 PPID가 1이거나 부모 프로세스가 데몬 프로세스인 프로세스를 말합니다. 그렇다면 데몬 프로세스와 백그라운드 프로세스의 차이는 무엇일까요? 결정적으로 터미널이 종료되었을 때 터미널에서 실행한 프로세스가 함께 종료되는가입니다.

PPID가 1이라는 의미는 무엇일까요? PID가 1인 프로세스는 시스템 부팅 과정 중 생성되는 최초의 프로세스(Init Process)로 시스템이 종료될 때까지 유지되는 프로세스입니다. 부모 프로세스가 자식 프로세스를 만든 후에 죽게 되면 자식 프로세스는 고아 프로세스(Orphan Process)가 되는데 이때 부모 프로세스를 Init Process로 지정하면 시스템이 종료될 때까지 유지되는 데몬 프로세스가 됩니다.

PID와 PPID

PID: PID(Process Identifier, 프로세스 식별자)는 운영 체제에서 프로세스 각각을 구분하기 위해 부여하는 값이다.

PPID: PPID(Parent Process Identifier, 부모 프로세스 식별자)는 프로세스를 생성한 부모 프로세스의 PID를 나타낸다.

쉘 프롬프트에서 프로세스를 생성한 경우 쉘이 부모 프로세스가 되고, 생성된 프로세스는 자식 프로세스가 되어서 쉘의 PID를 PPID로 갖는다.

이 부분을 가시적으로 확인하기 위해서 터미널에서 ps 명령어를 이용하면 됩니다. Process Status를 확인할 수 있는 명령어로 확인에 필요한 일부 옵션만 사용하려고 합니다.

ps --help all
> Usage:
> ps [options]
> Basic options:
> -A, -e all processes
> Output formats:
> -f full-format, including command lines

백그라운드 프로세스를 실행 한 뒤 ps -ef 명령어를 통해서 PID와 PPID를 확인해보자 결과가 여러 개 나오기 때문에 grep 명령어를 함께 이용했습니다.

node app.js &
> [1] 40313
ps -ef | grep 30473
> UID PID PPID C STIME TTY TIME CMD
> 501 30473 30472 0 4:36AM ttys001 0:03.87 -zsh
> 501 40313 30473 0 4:40AM ttys002 0:01.26 node app.js

결과를 확인해보면 node app.js PPID가 이 프로세스를 실행한 -zsh 쉘의 PID와 동일한 것을 볼 수 있습니다.

nohup과 PM2

nohup은 "no hang up" 이라는 의미로 앞서 설명한 HUP을 무시하도록 해주는 명령어입니다. 엄밀하게 말하면 데몬 프로세스를 만들어주는 것은 아니고 데몬 프로세스처럼 동작하게 해줍니다. 터미널이 종료될 때 보내는 SIGHUP 신호를 거부합니다.

nohup node app.js &
> [1] 43330
ps -ef | grep 40967
> UID PID PPID C STIME TTY TIME CMD
> 501 42967 42966 0 6:18PM ttys001 0:00.44 -zsh
> 501 43330 42967 0 6:18PM ttys001 0:01.00 node app.js &

반면에 PM2는 데몬 프로세스로 만들어줍니다.

pm2 start app.js
ps -ef | grep 873
> UID PID PPID C STIME TTY TIME CMD
> ubuntu 873 1 0 2021 ? 00:46:23 PM2 v5.1.0: God Daemon (/home/ubuntu/.pm2)
> ubuntu 196447 873 0 2021 ? 00:54:10 node app.js

부모 프로세스가 PPID 1을 가지는 데몬 프로세스이므로 자식도 데몬 프로세스입니다.