Skip to content

Instantly share code, notes, and snippets.

@dahlia
Created March 21, 2011 05:23
Show Gist options
  • Save dahlia/879076 to your computer and use it in GitHub Desktop.
Save dahlia/879076 to your computer and use it in GitHub Desktop.
LEP 11 — 서비스 애플리케이션 배포 표준

LEP: 11
LEP-Scope: lunant
Title: 서비스 애플리케이션 배포 표준
Author: Hong MinHee dahlia@lunant.net
Status: Active
Content-Type: text/x-markdown; charset=utf-8
Content-Language: ko-kr
Created: 04-Aug-2010

서비스 애플리케이션 배포 표준

기존의 Lunant에서 만든 서비스들은 하나의 서버 안에서 여러 서비스들이 동시에 운영되고 있는 경우가 많고, 또 이것 각각들이 의존하는 프로그램이나 라이브러리들이 한 서버에 전역적으로 설치되고 있다. 의존성을 해결하기 위해 설치하는 절차는 정해져있지 않고 임의적이며, 서버의 공동 관리자들이 그것에 대해 제대로 파악하지 못하고 있는 실정이다.

가끔은 같은 라이브러이에 대한 의존성이 있는 둘 이상의 서비스 애플리케이션이 한 서버에서 돌아가는 상황도 있으며, 이때 각각이 의존하는 라이브러리의 버전이 달라지는 경우 어느 한쪽은 깨지게(broken) 된다.

이러한 문제들을 해결하고 모든 서비스 애플리케이션을 통합적으로 관리하여, 관리 비용을 줄이고, 추후 인수인계할 상황에서도 최대한 쉽게 인계가능하도록 정비하여 표준화하는 것이 이 제안의 취지다.

표준 플랫폼

이 제안서는 Lunant의 표준 플랫폼이 Python이라고 가정한다. 이하의 모든 예시는 Python을 중심으로 설명되어 있으며, 다른 플랫폼의 경우 적절히 같은 문제를 해결하는 동등한 솔루션을 사용하면 된다. 예를 들어, Python의 virtualenv는 Ruby에서는 Bundler로 대체 가능하고, Python의 WSGI는 Ruby에서는 Rack으로 대체 가능하다.

또한 운영체제는 Apt 패키지 관리 시스템을 사용하는 Debian 계열의 Linux를 가정한다.

애플리케이션 식별자

모든 서비스 애플리케이션들은 각각의 식별자를 갖는다. 식별자는 모두 로마자 소문자와 숫자로 표현한다. 예를 들면 vlaah, antsnest 등이다. 이 식별자는 하이픈(-)이나 언더바(_)를 포함할 수 있지만 최대한 지양한다. 네임스페이스 시스템에 따라 둘 중 하나를 허용하지 않는 경우가 있기 때문이다. 예를 들면 Python의 모듈 이름은 하이픈을 허용하지 않는다. 마찬가지 이유에서 식별자는 숫자로 시작하지 않는 것이 좋다. 대부분의 프로그래밍 언어가 식별자의 첫 글자로 숫자를 허용하지 않기 때문이다.

다음은 좋은 식별자의 예이다.

  • vlaah
  • antsnest
  • webthumb

다음은 좋지 않은 식별자들의 예이다.

  • bestidol-kr
  • fontface.kr

애플리케이션 식별자를 한번 정해두면 저장소 이름부터 배포할 시스템의 계정 이름까지 폭넓게 통일해서 적용할 수 있다.

시스템 계정

모든 서비스 애플리케이션은 특정인의 계정 안쪽에서 운영되면 안되며, 그 서비스 애플리케이션을 위한 별도 계정을 생성해서 관리해야 한다. 앞서 정한 식별자를 시스템 계정의 이름으로 사용한다.

계정은 일반 사용자로 생성하며, 비밀번호를 설정하지 않는다. 이렇게 해두면 SSH로 직접 원격 접속할 수 없다. 대신 su 명령어를 이용해 로그인한다. 편의를 위해 작업자의 공개키를 ~/.ssh/authorized_keys에 추가해서 사용하는 것은 허용한다. 자동화 등을 위해 패스프레이즈(passphrase)가 없는 키를 사용할 경우에는 해당 키의 비밀키 보안에 각별히 유의하여 사본이 존재하지 않도록 관리한다.

쉘은 가급적 bash로 설정한다.

위와 같은 규칙에 따라 useradd 명령어를 사용한다면 다음과 같은 형식이 된다. 아래 예제는 서비스 애플리케이션 식별자가 appid라고 가정한다.

$ useradd -G users -m -d /home/appid -s `which bash` appid

개인용 키 생성

Windows를 제외한 대부분의 주요 운영체제에서는 ssh-keygen 명령어가 있으므로 이것을 사용해서 생성이 가능하다. 키 생성은 본인의 로컬 컴퓨터에서 하고, 사본 관리를 철저히하는 것이 보안상 좋다. 패스프레이즈(passphrase)는 꼭 정하도록 한다.

Windows의 경우에는 PuTTY에서 제공하는 puttygen.exe 유틸리티로 키 생성을 할 수 있는데, 해당 유틸리티는 ssh-keygen 명령어로 생성한 키를 PuTTY 형식으로 변환할 수도 있으므로 후자를 추천한다.

자동화된 프로그램에서 사용할 키 생성

여러 서버간에 SSH를 통해 자동화된 절차를 프로그램으로 만드려면 보안상 비밀번호를 프로그램 소스에 노출하는 것은 좋지 않고, 또 앞서 규칙에서 말했듯 계정에 비밀번호를 부여하지 않게 되어 있으므로 패스프레이즈(passphrase)가 공백인 키를 생성해서 사용한다. 키 생성은 역시 ssh-keygen 명령어로 할 수 있지만 패스프레이즈를 입력하지 않아도 된다. 이렇게 하면 비밀번호를 묻지 않고 키를 사용하므로 자동화할 때 유용하다.

서비스 애플리케이션 각각의 의존성을 독립적으로 관리하기 위해 virtualenv를 설정하여 사용한다. 편의를 위해 홈디렉토리 자체를 환경 디렉토리로 만들며 source bin/activate라고 입력하는 것을 잊고 실수하지 않도록 아예 .bashrc 파일이나 .profile 파일 등에 source bin/activate 명령을 추가해둔다.

의존성 관리는 setuptools 대신 distribute를 사용한다. virtualenv 명령어에 --distribute 옵션을 붙이면 된다. (예전 버전의 virtualenv 명령어는 해당 옵션이 없을 수 있다. 이와 같은 경우 옵션을 빼고 환경을 구성한 다음, 환경 안쪽에서 easy_install distribute 명령으로 distribute를 설치하면 된다.)

위와 같은 규칙을 종합하면 아래와 같은 절차를 따르게 된다. (서비스 계정에 로그인된 상태라고 가정.)

$ cd ~
$ virtualenv --distribute .
$ echo source bin/activate >> .bashrc

예전 버전의 virtualenv--distribute 옵션이 없으니 아래와 같이 하면 된다.

$ cd ~
$ virtualenv .
$ bin/easy_install distribute
$ echo source bin/activate >> .bashrc

설정을 모두 마친 뒤 로그아웃했다가 다시 로그인하면 자동으로 구성한 환경 안쪽으로 진입한다.

서비스 데몬 관리

서비스의 종류에 따라 백그라운드에서 동작하는 데몬도 함께 필요한 경우가 있다.

데몬이 스스로 데몬화되어 있고 시작 및 정지에 대한 커맨드라인 인터페이스를 제공한다면 별도의 작업을 할 필요는 없다. 다만 프로젝트의 문서에 해당 부분에 대해 설명이 있어야 한다.

그렇지 않은 경우라도 screen 명령어로 데몬을 관리하지 않도록 한다. 대신 daemonize 명령어를 사용하여 데몬을 관리해야 한다. screen을 사용하지 않아야 하는 이유는 다음과 같다.

  • 데몬의 시작과 정지를 명령어 하나로 처리하지 못한다. 추후 자동화된 시스템을 만들 때 문제가 된다.
  • 서비스 계정에 직접 로그인하지 못하기 때문에 screen이 정상적으로 작동하지 않는다.

프로세스 ID(pid) 보존

스스로 데몬화되어있지 않은 프로그램을 daemonize 등으로 데몬화했다면 프로세스 ID(pid)를 잘 관리해야 한다. 홈디렉토리 안쪽에 programname.pid 등으로 저장하면 된다.

데몬 관리 스크립트

스스로 데몬화된 프로그램이 아니면 데몬 관리 스크립트를 별도로 작성해야 한다. 해당 스크립트는 홈 디렉토리의 ~/bin/ 안쪽에 적당한 이름(예를 들어 start.shstop.sh 따위)으로 만들면 된다. 해당 스크립트는 보존된 프로세스 ID(pid)를 보고 프로세스를 안전하게 종료하고 종료 뒤에는 해당 프로세스 ID(pid)가 저장된 파일을 제거하는 등의 일을 한다. 물론 프로세스가 제대로 종료됨을 보장해야 한다.

해당 스크립트에 대한 간단한 설명을 프로젝트 문서에 포함시키도록 한다.

cron

cron을 사용할 때는 해당 서비스 계정으로 로그인한 상태에서 crontab -e 명령을 통해 작업을 등록한다. 절대 개인 사용자의 계정이나 시스템 관리자 계정에서 작업을 등록하지 않도록 한다.

WSGI 애플리케이션 디플로이

WSGI 애플리케이션을 디플로이하는 것은 각 서버의 정책을 최대한 따르도록 한다. WSGI가 무엇인지 알지 못하는 경우 PEP 333 문서를 참고한다.

WSGI 컨테이너는 여러 종류가 있으며 WSGI 서버 목록을 참고한다.

Apache + mod_wsgi

mod_wsgi를 통해 디플로이하는 경우 /etc/apache2/sites-available/ 디렉토리에 사이트의 도메인 이름으로 설정 파일을 만들어서 다음과 같은 패턴으로 설정한다.

<VirtualHost *:80>
    ServerName appid.com
    ServerAlias www.appid.com
    DocumentRoot /home/appid/appid/templates/

    WSGIScriptAlias / /home/appid/wsgi.py
    WSGIScriptReloading Off  # 개발할 때는 On으로 켜는 것이 편하나
                             # production에서는 효율을 위해 Off로 둔다.
    WSGIReloadMechanism Process

    ErrorLog /var/log/apache2/appid.com/error.log
    LogLevel error
</VirtualHost>

DocumenRoot는 크게 의미가 없으나 해당 웹 애플리케이션에서 HTML 템플릿 파일들이 위치한 경로를 써주는 것이 좋다.

예제와 같이 에러 로그 경로(ErrorLog)는 /var/log/apache2/ 디렉토리 안쪽에 서비스 도메인 이름(즉 앞서 만든 설정 파일 이름과 동일한 이름)으로 디렉토리를 생성하여 그 안쪽에 error.log라는 파일명으로 저장되도록 설정한다. 접근 로그는 access.log라는 파일 이름을 사용한다.

WSGIScriptAplias 설정이 WSGI 애플리케이션을 실행시키는 스크립트인데, 서비스 애플리케이션 계정의 홈 디렉토리에 wsgi.py라는 파일명으로 다음과 같은 스크립트를 작성한다. (서비스 애플리케이션 식별자가 appid고 WSGI 애플리케이션 객체가 appid.web 모듈에 app이라는 이름으로 존재한다고 가정한다.)

import sys
service_id = "appid"
sys.path.append("/home/{0}/forward/".format(service_id))
import site
# virtualenv에서 설치한 site-packages를 이용한다.
site.addsitedir("/home/{0}/lib/python2.6/site-packages".format(service_id))
from appid.web import app as application

mod_wsgi는 애플리케이션 이름을 항상 application이라고 가정하므로 위와 같이 application이라는 이름으로 노출해야 한다.

독립 WSGI 서버

Green Unicorn이나 Julep 같은 독립적인(standalone) WSGI 서버를 사용하는 경우 보통은 Apache 등 해당 서버에서 80포트와 바인드되어 있는 다른 웹 서버에서 프록시를 설정해서 사용한다.

독립 WSGI 서버들은 대부분 스스로 데몬화하는 옵션을 제공하며, 그런 경우 해당 옵션을 활용하여 실행한다. 서비스 애플리케이션 계정의 홈 디렉토리 안쪽에 프로세스 ID(pid)를 저장하는 파일을 만들고, 그것을 이용해서 시작 및 정지를 제어하는 스크립트를 ~/bin/ 디렉토리 안쪽에 작성한다.

WSGI 서버가 바인드할 포트 번호는 다른 서비스 애플리케이션이 쓰는 것과 겹치지 않게 조심하여 10000 이상의 번호를 사용하고, 해당 서버의 공동 관리자들과 협의하여 결정한다. 로드를 고려하여 포트를 둘 이상 할당해도 된다. 또한 바인드할 IP 주소는 127.0.0.1로 한다. (보안 문제가 있으므로 0.0.0.0으로 설정하여 외부에서 직접 접근하도록 하면 안된다.)

이후 Nginx나 Apache 등의 서버에서 프록시를 설정한다. Apache의 경우 먼저 a2enmod 명령어로 mod_proxy 등의 모듈을 켠 뒤에 다음과 같이 설정한다. (서비스 애플리케이션 식별자가 appid고 포트 번호를 11111과 11112로 설정했다고 가정한다.)

<Proxy balancer://appid_wsgi>
    BalancerMember http://127.0.0.1:11111
    BalancerMember http://127.0.0.1:11112
    Allow from all
</Proxy>

<VirtualHost *:80>
    ServerName appid.com
    ServerAlias www.appid.com
    ProxyPass / balancer://appid_wsgi/
    ProxyPassReverse / balancer://app_id_wsgi/
</VirtualHost>

적용

본 제안서의 적용은 가결 시점 이후에 디플로이되는 서비스 애플리케이션들부터 적용하며, 기존의 서비스 애플리케이션들은 당장 이러한 형식으로 이전하는 것을 의무화하지는 않고 다만 권고 사항으로 두고 천천히 적용해 나가도록 한다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment