CODEOK.NET

Page history of 서버 프로세스를 관리하는 올바른 방법



Title: 서버 프로세스 띄우기 | edited by Youngrok Pak at 9 years, 3 months ago.

웹사이트 개발을 완료하고 서비스할 프로덕션(production) 서버에 배치(deploy)할 때 고민하게 되는 문제 중 하나가 서버에서 띄워야 하는 여러 가지 프로세스들을 어떻게 띄울 것인가 하는 문제다. 프로세스가 죽으면 다시 띄우는 것도 물론 필요하다. monit이나 supervisord, god 등은 이런 목적으로 나온 것이다(몇 가지 부가적인 목적이 더 있긴 하다). 하지만 이건 OS에 대한 이해 부족에서 나온 것이다. 프로세스 관리는 OS의 핵심적인 기반이 되는 기능이다. 본 연구에서는 왜 monit 등이 잘못된 선택인지, 올바른 방법은 무엇인지에 대해 다룰 것이다.

프로세스 No. 1 Init

올바른 방법은 OS의 init 시스템, 혹은 그와 유사한 대안 시스템을 사용하는 것이다. init은 유닉스에서 부팅될 때 첫번째로 만들어지는 프로세스다. 그래서 프로세스 번호가 1이고, 이후의 모든 프로세스는 init 프로세스의 자손이 되고, 시스템이 부팅될 때 뜨는 모든 프로세스는 init이 띄우게 된다. respawn이 설정된 프로세스는 죽었을 때 다시 띄우는 역할도 한다. 그러니 서버에 프로세스를 띄워야 한다면 init에 맡기는 것이 자연스럽지 않겠는가.

물론 init 시스템은 낡고 문제점이 있고, 그건 부팅 과정의 문제점이 크고, 프로세스를 관리하는 데는 별 문제가 없다. 다만, 조금 번거롭긴 하다. 사용법은 Managing Linux daemons with init scripts를 참조하라. 보면 알겠지만 좀 귀찮다.

하지만, 이제 이 init 시스템은 역사 속으로 사라질 예정이다. 아니, 많은 리눅스 배포판에서 이미 사라졌다. 우분투는 이미 수년 전 init을 upstart로 대체했고, 다른 배포판들은 systemd로 방향을 잡았으며 최근에 우분투도 systemd에 합류하기로 했다. 그러니까, 미래는 systemd에 있고, 서버에서 프로세스를 띄우고 관리해야 한다면 systemd를 쓰는 게 정답이다.

monit에서 시작된 흑역사

그런데 왜 init이 있는데 monit 같은 툴들이 등장했을까? 여기에는 약간의 역사가 있다. 수년 전, Ruby on Rails 개발자들 사이에서 monit이 유행을 타기 시작했다. Rails가 뜨기 전까지 웹 개발의 주류는 PHP와 자바였는데, PHP는 아파치에 모듈로 붙으니 따로 프로세스 관리를 할 필요가 없고, 자바 WAS들은 자체적인 프로세스 관리를 탑재했었다. 그러다보니 아마도 웹 개발자가 서버의 프로세스를 어떻게 띄우고 내리고 리스타트시키는지에 대해 고민할 기회가 별로 없었을 것이다. 당시 웹 개발의 시대를 이끈 것은 그 이전에 유닉스/리눅스에 빠져 있던 해커들이 아니었다.

이런 상황에서 Rails가 탄생했는데 Rails를 돌릴 마땅한 애플리케이션 서버가 없어서 mongrel 같은 서버를 루비 개발자들이 직접 만들었다. 그런데 자바 WAS들처럼 프로세스 관리 기능을 통째로 개발해서 탑재하기는 쉽지 않았고(물론 좋은 방법도 아니고) 아직 Rails가 충분히 안정화되지 않은 시기다보니 mongrel 프로세스가 죽는 일이 잦았다. 그러니 프로세스가 죽는지 모니터링하다가 죽으면 살려주자는 발상을 하게 되었고, 그 결과가 monit이다. monit이 Rails + mongrel 조합의 불안정성을 개선해주면서 인기를 끌게 된 것이다.

문제는 앞서서 본 것처럼 이런 목적에는 이미 init 시스템이 있었기 때문에 monit이 필요 없었다는 것이다. 명백한 ReinventTheWheel이다. 물론 바퀴를 더 좋게 개선하면 재발명할 수도 있다. 하지만 monit은 init보다 더 느리고, 더 리소스도 많이 먹었고, 더 불안정했다. running-processes에서는 다음과 같이 표현하고 있다.

“Wow. You reinvented init and cron, but managed to make them both less reliable and consume more CPU than I could’ve imagined.”

monit을 위한 변명

하지만 monit도 할 말이 있었을 것이다. 당시 upstart는 초기였고, systemd는 없었으며, init의 문제점은 오래 전부터 지적되어 왔었다(그게 프로세스 관리와 상관이 있든 없든). 게다가 init의 설정법은 루비 개발자들의 취향과 거리가 멀다. Managing Linux daemons with init scripts에서 보듯 /etc/init.d의 스크립트들은 별로 아름답지 않다. 이런 점들이 ReinventTheWheel을 좋아하는 루비 개발자들의 성향과 맞물렸을 것이다.

그리고 monit에는 init에서 소화하지 않는 기능이 하나 있었다. 프로세스가 죽었을 때 메일 등으로 알림을 보내주는 것이다(물론 이것도 불가능한 것은 아니었지만, 적어도 init의 기능은 아니었다). 이 두 가지가 그나마 monit을 위한 변명이 될 것이다.

유닉스의 철학

하지만, 이 변명으로는 충분하지 않다. 유닉스의 소프트웨어는 일반적인 사용자가 사용하는 소프트웨어와 다른 철학이 반영되어 있다. 여러 개의 목적을 수행하는 사용자 애플리케이션과 달리 유닉스의 프로그램은 여러 목적을 수행하지 않고 하나의 일만 담당하되, 그 일을 가장 잘하도록 설계한다. 그리고 파이프, 리다이렉션, 시그널 등의 기본적인 수단을 통해 이런 프로그램들을 조합해서 복잡한 일을 해낸다. 그런데 monit은 프로세스가 죽으면 다시 살려주는 일과 그것을 개발자에게 알려주는 일을 합쳐놓았다. 당시에 이미 유닉스에 더 좋은 모니터링 도구가 많았기 때문에 monit의 모니터링 기능이 그리 좋은 편은 아니었다. 그러니까 두 일을 묶어놓았는데, 두 일 다 원래 있던 다른 도구들보다 못하는 것이다. 그래서 두 가지 목적을 대충 때우고 싶은 상황에서는 약간의 이점이 있지만, 그에 대한 대가로 시스템 자원도 많이 쓰고, 더 안 좋은 모니터링 서비스를 받게 된 것이다.

그렇다고 monit이 유닉스의 철학에 집중해서 프로세스를 관리하는 일에만 초점을 맞췄다면, 이젠 앞서 이야기한 것처럼 완전한 ReinventTheWheel이 되어 쓸데 없는 짓이 된다. supervisord가 바로 그렇다. supervisord는 init이 하는 일의 부분집합인데 성능이 더 떨어지니 그야말로 티없이 맑고 순수한 잉여인 것이다.

god는 monit보다 훨씬 더 나아갔다. 여러 가지 막강한 기능을 갖추고 있어서 사실 monit이나 supervisord와 같이 묶기 애매할 정도. 그래서 프로세스 관리보다 다른 목적들에 초점을 맞춘다면 그럭저럭 쓸만할 수도 있지만, 여전히 그 분야에 더 좋은 도구가 많이 있기 때문에 의문이 생기는 건 마찬가지다.

현 시점의 정답

Upstart

우분투 사용자 중 많은 사람들이 아직 14.04 LTS 버전, 혹은 그 이전 버전을 쓰고 있을 것이다. 이 경우도 systemd를 쓸 수 있긴 하나, upstart가 기본이므로 upstart를 그냥 계속 쓰다가 systemd가 기본으로 바뀐 배포판을 적용하면서 systemd를 적용해도 된다. 그래서, upstart의 사용법을 간략하게 알아보겠다.

upstart는 /etc/init에 스크립트를 넣으면 실행할 수 있다. 태스크 큐로 널리 쓰이는 celery의 예를 보자. celery 서버는 보통 다음과 같은 명령으로 띄운다.

celery -A myapp.tasks worker -l INFO -E

이러면 작업을 처리하는 worker 프로세스를 띄우게 된다. 이걸 upstart로 관리를 하고 싶으면 /etc/init/celery.conf 파일을 다음과 같이 만든다.

# Celery

description "Celery Worker"
start on runlevel [2345]
stop on runlevel [06]
respawn limit 10 5
script chdir /home/ubuntu/myproject exec sudo -uwebuser celery -A myapp.tasks worker -l INFO -E > /home/ubuntu/logs/celery.log 2>&1 end script

init과는 달리 실행 권한을 줄 필요는 없다. 그러면 서버 띄우기는 다음과 같이 한다.

sudo start celery

종료, 재시작은 다음과 같다. 우분투의 경우 tab을 누르면 어떤 서비스를 띄울 수 있는지 자동 완성이 되므로 편리하다.

sudo stop celery
sudo resart celery

위의 설정 파일에서 respawn 부분은 죽었을 때 다시 실행시키는 설정이다. 죽었을 때 10번까지 5초 간격으로 시작 재시도를 한다. script 부분에 실제로 실행할 코드를 적는다.

한 가지 주의해야 할 점은 cron과 비슷하게 환경 변수가 로드되지 않는다는 것이다. 로케일 등을 환경변수에 의존하고 있다면 LC_ALL 같은 환경 변수를 지정해두어야 한다.

systemd

upstart가 현재라면 systemd는 미래다. 앞으로 대부분의 리눅스 배포판이 systemd로 통합될 것이므로 systemd를 미리 배워두면 좋을 것이다. 물론 지금도 최신 배포판들은 systemd가 기본은 아닐지언정 설치는 대부분 되어 있을 것이고, 설치가 안되어 있더라도 패키지로 설치할 수 있을 것이다. sshd의 예를 보면 이해하기 쉬울 것이다. 우분투 배포판에는  /etc/systemd/system/sshd.service 파일이 다음과 같이 작성되어 있다.

[Unit]
Description=OpenBSD Secure Shell server
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure

[Install]
WantedBy=multi-user.target
Alias=sshd.service

시작, 재시작 등은 다음과 같이 한다.

systemctl start sshd.service
systemctl stop sshd.service
systemctl restart sshd.service

 

 

Wiki at WikiNamu