며칠 전, LXC 기반의 운영체제 수준 가상화 기술인 Docker에 대한 길고 상세한 가이드를 발행했습니다. Docker는 컨테이너화된 애플리케이션을 빠르고 가볍고 안전하게 프로비저닝할 수 있는 훌륭한 방법을 제공합니다.
하지만 첫 번째 서비스인 SSH와 Apache를 테스트하면서 마주한 문제 중 하나는 바로 이러한 서비스들의 제어였습니다. 컨테이너 내부에는 init 스크립트나 systemd가 없었고, 솔직히 말해 굳이 그것들을 넣고 싶지도 않았습니다. 그럼에도 우리는 서비스를 시작하고 중지하는 등의 작업을 수행할 일종의 메커니즘이 필요했습니다. 이때 등장하는 것이 바로 supervisord이며, 이것이 이번 튜토리얼의 주제입니다. 함께 따라와 주세요.
Supervisord란 무엇인가?
Supervisord는 프로세스를 모니터링하고 제어하도록 설계된 프로세스 제어 시스템입니다. init을 대체하는 것이 목적이 아니라, 프로세스를 자체 프레임워크 안에 캡슐화하여 부팅 시점에 자동으로 시작할 수 있게 해줍니다. 바로 우리가 원하는 기능이죠. 이 소프트웨어에 대해 더 깊이 들어갈 필요는 지금 당장 없습니다.
Supervisord 설치
기본적으로 supervisord는 파이썬 모듈입니다. setuptools의 일부인 easy_install을 사용해 설치할 수 있으며, setuptools 자체는 Python distutils 패키지의 확장입니다. 네, 이 시점부터 다소 복잡해지기 시작합니다. 다행히 대부분의 리눅스 배포판은 easy_install을 기본 포함하고 있으며, 오늘의 테스트 플랫폼인 CentOS도 예외는 아닙니다.
시작하려면 먼저 컨테이너 안에 supervisord를 설치해야 합니다. 그런 다음 이미지를 커밋하고, 앞서 언급한 SSH와 Apache 같은 서비스를 포함하는 빌드의 기준(base image)으로 활용합니다. 호스트에 supervisord를 설치하면 아무 문제 없이 성공합니다:
easy_install supervisor
Searching for supervisor
Reading https://pypi.python.org/simple/supervisor/
Best match: supervisor 3.1.3
Downloading https://pypi.python.org/packages/source/s/supervisor/
supervisor-3.1.3.tar.gz#md5=aad263c4fbc070de63dd354864d5e552
Processing supervisor-3.1.3.tar.gz
Writing /tmp/easy_install-vbOcMG/supervisor-3.1.3/setup.cfg
Running supervisor-3.1.3/setup.py -q bdist_egg --dist-dir /tmp/easy_install-vbOcMG/supervisor-3.1.3/egg-dist-tmp-i96mIs
warning: no previously-included files matching '*' found under directory 'docs/.build'
Adding supervisor 3.1.3 to easy-install.pth file
Installing echo_supervisord_conf script to /usr/bin
Installing pidproxy script to /usr/bin
Installing supervisorctl script to /usr/bin
Installing supervisord script to /usr/bin
Installed /usr/lib/python2.7/site-packages/supervisor-3.1.3-py2.7.egg
Finished processing dependencies for supervisor
반면 컨테이너 내부에서는 오류가 발생합니다. 컨테이너에 포함된 Python 프레임워크가 불완전하여 일부 모듈이 누락되어 있기 때문입니다.
# easy_install supervisor
Traceback (most recent call last):
File "/usr/bin/easy_install", line 5, in <module>
from pkg_resources import load_entry_point
ImportError: No module named pkg_resources
이 경우 easy_install을 수동으로 설정해 주어야 합니다:
wget https://bitbucket.org/pypa/setuptools/raw/ ->
-> bootstrap/ez_setup.py -O - | python
Supervisord 구성
다음 단계는 컨테이너를 위한 구성 파일을 만드는 것입니다. 호스트에서 파일을 생성한 뒤, Dockerfile의 COPY 명령을 사용해 빌드 과정 중 이미지로 복사합니다.
COPY ./supervisord.conf /etc/supervisord.conf
구성 파일의 내용은 대략 다음과 같습니다:
[supervisord]
nodaemon=true
[program:sshd]
command=/usr/sbin/sshd -D
[program:httpd]
command=/bin/bash -c "exec /usr/sbin/httpd -DFOREGROUND"
여기서 각각 무엇을 의미할까요? 대괄호 쌍 하나하나가 섹션을 정의합니다. supervisord 자체 섹션에서는 데몬으로 백그라운드에 뜨는 대신 포어그라운드(foreground)에서 시작하도록 지정합니다. Docker 컨테이너는 PID 1 프로세스가 종료되면 함께 종료되기 때문에 이 설정이 중요합니다.
sshd라는 이름의 프로그램에는 해당 명령을 실행해 사실상 SSHD를 백그라운드에서 구동합니다. httpd라는 이름의 프로그램에는 별도의 셸에서 서버를 포어그라운드로 시작합니다. 사용 가능한 옵션은 이 외에도 많지만, 현재로서는 supervisord를 시작하기 위한 최소한의 설정입니다.
컨테이너 실행 및 문제 해결
이미지를 빌드하고 실행하면 supervisord가 개입해 프로세스들을 시작할 것입니다. 하지만 그 전에 흔히 마주하는 오류 몇 가지를 먼저 살펴보겠습니다. 다음과 같은 오류를 볼 수 있습니다:
docker run -ti -p 22 -p 80 image-3:latest
/usr/lib/python2.7/site-packages/supervisor-3.1.3-py2.7.egg/
supervisor/options.py:296: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a "-c" argument specifying an absolute path to a configuration file for improved security.
Error: No config file found at default paths (/usr/etc/supervisord.conf, /usr/supervisord.conf, supervisord.conf, etc/supervisord.conf, /etc/supervisord.conf); use the -c option to specify a config file at a different path
For help, use /usr/bin/supervisord -h
supervisord.conf 파일의 경로를 잘못 지정했거나 파일 이름이 틀렸다면 서비스가 실행되지 않습니다. 이 경우 COPY 명령을 수정하고 이미지를 다시 빌드한 후 처음부터 다시 진행해야 합니다. 그다음으로 마주할 수 있는 오류는 다음과 같습니다:
# docker run -ti -p 22 -p 80 image-3:latest
372 CRIT Supervisor running as root (no user in config file)
375 INFO supervisord started with pid 1
378 INFO spawned: 'httpd' with pid 9
381 INFO spawned: 'sshd' with pid 10
602 INFO exited: httpd (exit status 1; not expected)
606 INFO success: sshd entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
671 INFO exited: httpd (exit status 1; not expected)
826 INFO gave up: httpd entered FATAL state, too many start retries too quickly
여기서는 Apache 스레드가 계속 종료되면서 supervisord가 재시작을 반복하다가 결국 포기하고 치명적인(FATAL) 상태에 빠집니다. 이 시점의 컨테이너는 사실상 쓸모가 없습니다.
해결 방법은 supervisord.conf 파일을 편집해 httpd 프로그램 섹션에 추가 지시문을 넣는 것입니다. 이렇게 하면 프로세스 생성을 좀 더 안정적으로 처리할 수 있습니다.
[program:httpd]
startsecs = 0
autorestart = false
command=/bin/bash -c "exec /usr/sbin/httpd -DFOREGROUND"
startsecs = 0과 autorestart = false 두 가지를 추가했습니다. 첫 번째 지시문의 의미는 다음과 같습니다:
프로그램이 시작된 후 성공적인 시작으로 간주되기 위해 유지되어야 하는 총 시간(초)입니다. 프로그램이 시작된 후 이 시간만큼 유지되지 않으면, 예상된 종료 코드로 종료되더라도(exitcodes 참조) 시작은 실패로 간주됩니다.
두 번째 지시문은 supervisord가 Apache 스레드의 재시작을 관리하지 않고, 웹 서버 자체가 들어오는 HTTP 요청을 처리하듯이 이를 직접 관리하도록 한다는 의미입니다.
false, unexpected 또는 true 중 하나의 값을 가질 수 있습니다. false인 경우 프로세스는 절대 자동 재시작되지 않습니다. unexpected인 경우 프로세스가 이 프로세스 구성과 연관된 종료 코드가 아닌 코드로 종료될 때 재시작됩니다(exitcodes 참조). true인 경우 종료 코드와 관계없이 프로세스가 종료될 때 무조건 재시작됩니다.
이제 컨테이너를 다시 실행해 볼 수 있습니다. 원래 가이드에서 마주했던 /run/httpd 문제를 기억하시나요? /run 디렉터리가 준비되어 있지 않으면 다음과 같은 메시지가 출력됩니다:
docker run -ti -p 22 -p 80 image-3:latest
913 CRIT Supervisor running as root (no user in config file)
916 INFO supervisord started with pid 1
919 INFO spawned: 'httpd' with pid 8
921 INFO spawned: 'sshd' with pid 9
079 INFO success: httpd entered RUNNING state, process has stayed up for > than 0 seconds (startsecs)
105 INFO exited: httpd (exit status 0; expected)
122 INFO success: sshd entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
이 시점에는 컨테이너에 연결(attach)하거나 exec 명령으로 BASH 셸을 실행해 로그를 확인하고, 서비스가 예상대로 동작하지 않는 원인을 파악해야 합니다.
성공적인 테스트
그리고 마침내 모든 문제를 해결하고 나면, supervisord가 sshd와 httpd 프로세스를 안정적으로 관리하면서 컨테이너가 의도한 대로 동작하는 것을 확인할 수 있습니다.
결론
다소 괴짜스러운 튜토리얼이긴 하지만, 이를 통해 우리의 도구 상자에 또 하나의 유용한 도구가 추가되었습니다. 이제 systemd처럼 복잡하고 무거운 프레임워크를 설치하지 않고도 컨테이너 내부의 서비스를 견고하고 우아한 방식으로 제어할 수 있습니다. 아울러 Docker를 다루는 방법과 문제 해결 노하우에 대해서도 더 많이 배울 수 있습니다.
Supervisord는 엄밀히 말해 필수는 아닙니다. 실제로 sshd와 httpd 프로세스를 이것 없이도 실행할 수 있었으니까요. 하지만 서비스를 자주 시작하고 재시작해야 하는 환경이라면 이 방법을 선호하는 분들이 많습니다. 어느 쪽이든, 이 유틸리티에 익숙해지는 것은 Docker 메커니즘에 대한 이해와 자신감을 높이는 데 큰 도움이 됩니다. 이 가이드가 유용하기를 바라며, 다음에 어떤 주제를 다뤄볼지 여러분의 요청을 기다립니다.
즐겁게 코딩하세요!