스프링노트 2008년 첫번째 마일스톤에 맞춰서 스프링노트 배포 환경도 살짝 변화를 줬다. 혹시 유용할지 모르니, 그 내용과 이유를 간단하게 설명한다.
일년 전(처음 스프링노트 배포 스크립트를 작성하던 시점)에는 Capistrano의 제약이 심했다. 그중 가장 큰 제약으로 느껴졌던 부분이 배포 전략(Deployment Strategy)이 소스 저장소(SCM)에서 최신 버전을 가져오는(checkout) 것으로 유일하다는 사실이었다. 하지만 우리는 스테이징 서버가 따로 있고, 실전 서버에서는 SCM에 접근이 안되는 보안 모델을 가지고 있었다. 그래서 Capistrano를 사용하지 않고 직접 몇가지 스크립트(코드를 SCM에서 가져와 스테이징에 배포하고, 테스트하고 그 코드를 실전 애플리케이션 서버에 rsync로 배포하고, 몽그렐을 재시작하는 일련의 과정)을 직접 작성해서 사용했다.
이런 접근의 아쉬운 점은 역시 뭔가 필요할 때마다 직접 만들어야한다는 점이다. 그리고 웹서버 각각을 직접 돌면서 해줘야하는 일이 점점 늘어났다(뭐, 아직 몇대 되지는 않지만...). 무엇보다 배포 스크립트를 다시 만들어야겠다는 생각이 들었던 두가지는,
1년만에 capistrano gem을 업데이트하고 천천히 소스를 읽어보았다. 먼저 1.x에서 2.x로 바뀐 버전이 눈에 띈다. (Upgrading from 1.x to 2.x?)
가장 눈의 띄는 변화는 역시 배포 전략이 다양해졌다는 점이다. lib/recipes/deploy/strategy 디렉터리에 보면 추가된 전략들을 볼 수 있다.
물론, 직접 추가할 수도 있다. 우리팀에 필요한 것은 rsync를 이용하는 전략이다. 2.x에서 제공하는 RemoteCache를 기반으로 rsync를 도입한 플러그인(Capistrano rsync_with_remote_cache)을 어렵지 않게 찾을 수 있었다.
그 외에도 강화된 이름 공간, 도움말, 이벤트 프레임워크 등을 보니 점점 체계를 갖춰가는 모습을 알 수 있었다.
실제 capfile을 작성하는 과정은 여기서 설명할 필요도 없을 정도로 간단했다. 몇가지 변수만 설정하면 되는 수준이니 말이다. 몽그렐을 재시작하는 deploy:restart 명령만 재정의했는데, 이 부분은 뒤에서 설명하도록 하자.
capistrano를 통한 배포로 바꾸고 나서 자연스럽게 위에서 언급한 두가지 단점이 해결되었다. 그리고 배포에 걸리는 시간도 단축되었다(우리는 rsync를 하고 그 디렉터리를 복사해서 작업 디렉터리를 만들었는데 위 플러그인은 rsync 결과를 작업 디렉터리에 또 rsync하는 방법으로 시간을 단축했다). 무엇보다 capistrano가 제공하는 shell이 정말 편리하다. 진작 capistrano를 도입해서 써볼걸 그랬다. 다행이다. 이런 툴을 사용할 수 있어서.
monit을 이용한 몽그렐 프로세스 모니터링에 대해서는 여러차례 이야기한 바 있다. 급변하는 웹 애플리케이션이기에 monit 같은 보험은 필수다. 아주 잘 사용하고 있기는 하지만, 이런 monit에도 단점이 있다. 그 중 가장 눈에 띄는 것이 좀 복잡하고 중복이 많을 수 밖에 없는 설정 파일이다.
그래서 나온 것이 god다. god는 monit과 같은 목적을 위해 태어난 툴이지만, 루비 코드로 설정 파일을 만들 수 있는 장점이 있다. 그래서 좀 더 많은 로직을 담을 수 있고, 더 많은 기능(예를 들면 상태 변이, 이벤트 기반 구조, 나만의 통지 시스템을 구현할 수 있는 점, 데몬이 아닌 프로세스도 모니터링)을 지원한다.
호기심에 한번 monit으로 하던 일을 god로 옮겨보았다. 결론은? monit을 잘 사용하고 있다면, monit으로 충분하고, 혹시 모니터링 프로그램을 처음 도입한다면 god 쪽이 낫다. 하지만, god가 제아무리 루비라지만, 기본 컨셉을 이해하는데 다소 시간이 필요했다. 그리고 설정 파일도 생각보다 장황했다. 차라리 capistrano 처럼 기본값들을 충분히 주고, 이를 재정의할 수 있는 방법이 낫지 않았을까 생각해본다(너무 많은 선택을 해야해서 스크립트를 완성하는데 시간이 필요했다). 그리고 monit 파일의 반복이 불만이라면 이 파일을 만들어주는 루비 스크립트를 두면 된다(실제 우리팀에서는 이렇게 사용한다).
몽그렐 클러스터가 필요한 이유에서 클러스터링에 대해 설명했다. 클러스터의 맨 앞에는 요청을 클러스터에 어떤 규칙에 따라 분배해주는 밸런서가 자리잡고 있게 마련이다. 이 밸런서가 똑똑하지 못하면, 사용자의 응답성은 말 그래도 '운'에 맡겨지게 된다. 군대에서는 줄을 잘 서야한다는 말 처럼 HTTP 요청도 줄을 잘 서야 빨리 용무를 해결할 수 있는 것이다.
다행히 nginx에는 010 nginx를 위한 똑똑한 로드 밸런서가 있다. 그렇다면 아파치는? ProxyBalancer의 밸런싱 알고리즘은 우리가 생각하는 것 만큼 정교하지 못하다. 게다가 투명하지도 않다. BalancerManager가 상태를 보여주고는 있지만 그리 유용하지 못하다. 그래서 HAProxy를 고려하게 되었다. 다행히 스프링노트에서 파일 업로드/다운로드를 위해 사용하는 아파치 모듈을 만들어주는 팀에서 HAProxy 쪽의 성능이 더 낫다는 데이터를 줘서 믿고 쓸 수 있었다. 나도 기회가 되면 벤치마크 결과를 글로 남길 생각이다.
HAProxy가 더 좋다고 생각되는 점은,
HAProxy의 대안으로는 Swiftiply Proxy도 있으니, 참고하기 바란다.
레일스 애플리케이션을 배포하면서 하게되는 마지막 일은 레일스 애플리케이션 서버 즉, 몽그렐 프로세스를 재시작하는 것이다. 하지만, 이게 단순히 몽그렐이 말을 듣지 않으면 Kill -9로 끝나는 일이 아니다. 생각보다 복잡하다. 특히나 pid 파일과 실제 프로세스가 어긋나는 경우도 생기는데, 이 경우는 해당 프로세스가 먹통이 되어버릴 수도 있다. 그래서 다른 프로그램을 살펴보니,
그런데 mongrel_rails의 경우는 단순히 pid_file의 프로세스를 찾아서 KILL이나 TERM 시그널을 보낼뿐이다. 이 경우 혹시 뭔가 문제가 생겨서 pid 파일이 어긋나 있거나하면 문제가 생긴다. 특히나 monit 등을 이용해 수시로(?) 프로세스가 재시작되는 환경이라면 간혹 문제 상황이 일어날 수 있다.
그래서 나는 mongrel_cluster의 --clean 코드를 가져와서 mongrel 프로세스를 하나씩 stop/start 할 때도 사용할 수 있도록 만들었다. MongrelManager.new.stop(4000)이라고 하면 어떤 상황에서도 4000번 포트에 바인딩된 몽그렐을 KILL하고 pidfile을 지운다. 반대로 MongrelManager.new.start(4000)이라고 하면 몽그렐을 띄우고 그 pid가 pidfile에 잘 들어있음을 보장한다.
전체 소스 코드는 여기에서 볼 수 있다.
5회 루비 세미나 후기 - 즐거운 사람들의 크리스마스에서 다음 캘린더팀에서는 배포시에 Seesaw - High-Availability Mongrel Packs를 적용했다는 발표를 들었다. 나도 몽그렐을 우아하게 재시작하는 법에서 비슷한 고민을 한 적이 있었다. Seasaw는 재시작하는 동안 몽그렐 클러스터를 조작해서 재시작중인 몽그렐로 요청이 가지않도록 하는 트릭이다. 한글 문서를 반가운 곳(?)에서 발견했으니, 링크를 남긴다.
아파치 + ProxyBalancer 또는 nginx를 사용하고 있다면, seesaw 젬을 설치해서 사용하면 된다. 하지만 앞에서 설명한 이유로 우리팀은 HAProxy로 바꿨다. 그렇다면 어떻게 할까? seesaw의 아이디어를 가져와 구현하면 된다. 그리 어려운 작업이 아니다. HAProxy도 우아하게 재시작하는 방법이 있으니, 이를 이용하면 된다. 먼저 cluster_all.conf, cluster_1.conf, cluster_2.conf 등 설정 3개를 만들어둔다. 그리고 이렇게 한다.
1번 클러스터를 사용하도록 해두고, 2번 클러스터를 재시작, 이제 재시작된 2번 클러스터를 사용하도록 하고 1번 클러스터를 재시작, 그리고 이를 마치면 모든 클러스를 사용할 수 있는 상태가 된다. 혹시 재시작 중 monit이 끼어들까 싶어 모니터링을 켜고 끄는 코드가 들어있다.
HAProxy를 우아하게 재시작하기 위해서는 이렇게 한다.
한가지 주의할 점은 1번 클러스터가 정확하게 재시작되어 해당 클러스터의 포트를 실제 사용할 수 있을 때까지 기다렸다가 proxy를 바꿔야 한다. 그래서 우리 팀에서는 이런 메서드를 사용했다.
클러스터를 재시작한 이후에 해당 클러스터의 마지막 포트를 사용할 수 있을 때까지 기다리는 코드를 볼 수 있다.
레일스에도 배포와 관련된 노하우가 많이 쌓인 것 같다. rsync 커맨드 하나로 끝이라는 PHP 진영보다는 복잡하지만, 그리도 이만하면 간단하다고 할 만하다. 말은 많이 듣고 있었지만, 실제로 Capistrano를 프로젝트에 도입해보니 이거 정말 괜찮다. 다음에는 경쟁 프로젝트인 Vlad the Deployer를 한번 살펴보면 좋겠다.
한가지 아쉬운 점은 처음 접하는 사람들을 대상으로하는 튜토리얼이 다소 부족하다는 점이다. 내가 이렇게 적고 있는 글들(Rails Deployment)은 이런 분들에게는 그리 큰 도움이 되지는 않을 것 같다. 그래도 레일스 배포 구조와 방법, [5회동영상2-1] 루비와 레일스를 이용한 진짜 개발 - 김정현 같은 자료도 만들어지고 있으니, 점차 좋아지리라 생각한다.
- 2008/02/03 22:25:20