열여덟번째 날: Rex/Boxes로 개발 환경을 관리해보자

저자

@corund - 요즘 체력이 부족함을 느끼고 있는 Computer Programmer

시작하며

가상화를 이용하여 개발 환경을 실제 운영될 서버 환경과 비슷하게 관리하는 일이 많아졌습니다. 개발자들의 데스크탑이 서로 다른 환경이더라도 가상화를 이용하면 개발 환경을 쉽게 통일할 수 있습니다. Rex/Boxes를 이용해 이런 가상 환경을 쉽게 설정하고 공유할 수 있습니다. 같은 작업을 하는 도구로는 Vagrant가 유명합니다.

준비물

Rex/Boxes(R)?ex라는 원격 관리 도구(Deployment and Configuration Management)의 일부분입니다. Chef와 같은 일을 하는 도구인데 펄로 만들어져 있으며 서버 측에는 오직 SSH 서버만 설치되어 있으면 동작합니다. RexXML::Simple 모듈Net::SSH2 모듈에 의존성이 있으므로 expatssh2 라이브러리가 설치되어 있어야 정상적인 설치가 가능합니다. 데비안 계열의 리눅스를 사용하고 있다면 다음 명령을 이용해서 개발 의존 패키지를 설치합니다.

$ sudo apt-get install libexpat1-dev libssh2-1-dev

필요한 모듈은 다음과 같습니다.

직접 CPAN을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.

$ sudo cpan Rex

사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나 perlbrew를 이용해서 자신만의 Perl을 사용하고 있다면 다음 명령을 이용해서 모듈을 설치합니다.

$ cpan Rex

윈도우라면 Strawberry Perl을 설치한 다음 perl 명령창에서 다음 명령을 실행합니다.

$ cpan Rex

윈도우에서는 터미널 관련하여 설치 중 테스트 에러가 나는 경우가 있습니다. 테스트에서 에러일 뿐 동작 자체에 문제는 없으므로 다음과 같이 테스트를 무시하고 설치합니다.

$ cpan -T Rex

Rex/BoxesVirtualBox를 이용해서 가상 머신들을 관리합니다. 따라서 VirtualBox를 설치합니다. 더불어 VirtualBox Extension Pack도 같이 설치합니다. 또한 VirtualBoxVBoxManage.exe(리눅스의 경우 VBoxManage)가 반드시 PATH에 있어야 하므로 PATH도 적절하게 설정합니다.

처음 실행해보기

다음 명령을 통해 Rex/Boxes에서 제공하는 우분투 가상 머신을 등록하고 실행해 볼 수 있습니다.

$ rexify <project_name> --template=box
$ cd <project_name>
$ rex init --name=<vmname> --url=http://box.rexify.org/box/ubuntu-server-12.10-amd64.ova

이 경우 Basic Ubuntu Server 12.10 파일을 다운로드한 후 <vmname>이라는 이름으로 Virtualbox에 등록해 가상 환경을 구성합니다. 가상 환경의 부팅이 완료된 후 id=root, password=box로 로그인할 수 있습니다. 경우에 따라서 오류가 발생할 수 있는데 이것은 아직 설정 등이 정확하지 않기 때문입니다. 관련해서는 더 자세히 알아보죠.

Rexfile

RexRexfile 파일을 이용해 작업을 설정합니다. 이 파일은 일반 펄 구문이며 rex 패키지가 제공하는 서브루틴을 명령어처럼 사용할 수 있게 되어 있습니다. 위의 예제에서 실행한 rexify ... 명령은 box 작업을 하는 Rexfile 뼈대를 생성하는 작업입니다. 이 파일을 적절히 수정하여 자신이 원하는 작업을 합니다.

Rexfile에서 작업의 단위는 태스크(task)입니다. 위의 예제에서 rex initinit 태스크를 실행한 것입니다. 이 태스크는 task 명령어로 정의할 수 있습니다. task 명령어는 다음과 같은 형식입니다.

task '<task_name>' => sub {
    <...>
};

이제 기본 생성된 파일을 수정하여 원하는 작업을 수행해보겠습니다. 먼저 맨 아랫줄에 require <project_name>; 줄을 삭제합니다. 그리고 현재 디렉터리 밑에 lib 디렉터리도 삭제합니다. Rexfile이 커질 경우 lib 디렉터리 밑에 따로 태스크를 정의해서 Rexfile을 좀더 구조화시킬 때 이용하는 것이기 때문이 지금은 필요 없습니다. 최종적으로 만들 Rexfile의 첫 부분과 init 태스크는 아래와 같습니다.

use strict;
use warnings;

use Rex -feature => 0.40;
use Rex::Commands::Box;
use Rex::Commands::Fs;
use Rex::Box::VBox;

use Cwd qw/abs_path/;

set user     => 'root';
set password => 'box';
set -passauth;

use Cwd qw( abs_path );

my $my_vm_name = 'my_vm_name_wanted';

task 'init' => sub {
    my $param = shift;

    box {
        my ($box) = @_;

        # vm 이름 지정
        $box->name($my_vm_name);

        # vm 이미지를 다운로드할 주소를 지정
        # 명령행으로 지정할 경우 그 값을 사용하며 없으면 미리 지정한 값을 사용
        my $url = $param->{url} || 'http://<myserver>/<my_vm_image>.ova';
        $box->url($url);

        # 사용할 포트를 지정
        $box->forward_port(
            ssh  => [2222 => 22],
            http => [8080 => 80],
        );

        # 공유 폴더 지정
        # 현재 디렉터리를 share 라는 이름으로 공유
        $box->share_folder( share => abs_path() );

        # vm 접속 정보
        # 설정한 값으로 변경
        $box->auth(
            user     => "root",
            password => "box",
        );

        # on_init 태스크를 호출해 프로비전 작업을 수행
        $box->setup(qw/ on_init /);
    };
};

기본 생성한 Rexfile에서는 VM 이름을 명령행으로 지정하지만 여기에서는 따로 내부에서 정의해줍니다. 많은 경우 프로젝트 별로 이름을 지정하면 되기 때문에 이렇게 지정하는 것이 더 좋습니다.

VirtualBox 이미지 만들기

Rex/Boxes에서 제공하는 기본 이미지로 가상 머신을 만들고 프로비전(provision) 작업으로 원하는 설정 작업을 하여 환경을 구성할 수 있습니다. 하지만 기본 이미지로 생성한 가상 머신을 원하는 환경으로 만들기 위해서 작업할 것이 꽤 많습니다. 특히 기본 이미지가 영어권에서 작성된 것이므로 한국 환경에 맞추는 작업(로케일, 시간대 설정, 저장소 설정 등)이 추가로 필요합니다. 더구나 설치하는 패키지가 많다면(특히 Catalyst처럼 대량의 cpan 모듈을 설치하는 경우) 프로비전 작업이 오래 걸립니다. rex init를 수행할 때마다 모든 인원의 데스크탑에서 이런 작업을 반복하는 것은 비효율적입니다. 이런 류의 작업은 미리 환경을 구성한 이미지를 공유하는 것이 훨씬 효율적입니다. Rex/Boxes를 사용하면 이런 작업을 매우 쉽게 처리할 수 있습니다.

잠깐 곁가지로 Vagrant는 이런 면에서 조금 불편합니다. 고유의 포맷을 사용하는데다 몇 가지 기능들을 편하게 작동시키기 위해 특별히 설정해야 할 것들이 있습니다. box를 만드는 일이 보통의 유저에게는 거의 필요없는 일이라고 말하고 있지만 한국 유저에게는 해당되지 않는 말입니다.

Rex/Boxes에서는 VirtualBox의 기본 이미지 파일(OVA)을 이미지로 사용합니다. 따라서 VirtualBox에서 가상 머신을 설치하고 필요한 설정을 마친 후 파일 > 가상 시스템 내보내기(export)로 OVA 이미지를 만들면 간단히 box를 만들 수 있습니다. 가상 머신에 설치할 운영체제는 Rex가 관리할 수 있는 운영체제이기만 하면 되며 SSH 서버만 실행되면 됩니다. 추가로 호스트 머신과 가상 머신 사이에 폴더를 공유하려면 Virtualbox Guest Addition을 설치해 두어야 합니다.

OVA 이미지가 준비되면 이 이미지를 HTTP나 FTP로 다운로드할 수 있도록 서버에 업로드합니다. 이 이미지의 주소를 사용해 가상 머신을 생성합니다.

$box->url('http://<my_server>/<my_vm_image_path>.ova');

앞의 예제에서는 명령행 인자로 URL을 지정하면 그곳에서 다운로드하고 따로 특별히 지정하지 않으면 미리 지정된 위치의 파일을 사용하도록 설정했습니다. 명령행 인자를 사용하는 예를 보이기 위해 추가한 것이므로 실제 사용할 때는 직접 지정하는 방식으로만 사용하는 경우가 많을 것입니다.

가상 머신 설정

보통 많이 사용하는 NAT 방식으로 네트워킹을 사용할 때에는 포트 포워딩 설정만 해주면 됩니다. 포트 포워딩 설정에서 SSH 포트 설정이 반드시 필요합니다. Rex는 SSH로 접속해 프로비전 작업을 수행하기 때문입니다. 이 때 포트 이름으로 ssh라는 이름을 써야만 제대로 작동합니다.

$box->share_folder로 호스트 머신과 가상 머신 사이에 폴더 공유를 지정할 수 있습니다. $box->share_folder( '<sharename>' => '<호스트 디렉터리 path>' ); 형식으로 지정합니다. 앞의 예제에서는 Cwd 모듈abs_path() 함수를 이용해 현재 디렉터리를 공유하도록 설정했습니다. <sharename>은 가상 머신에서 마운트할 때 사용합니다.

마지막 설정으로 $box->setup 명령으로 가상 머신이 최초로 기동된 후 Rex로 처리할 프로비전 작업을 설정합니다.

가상 머신을 시작/중지하는 태스크 추가

가상 머신을 시작/중지하는 태스크는 다음 코드를 이용해 추가할 수 있습니다.

task 'up' => sub {
    run qq{VBoxManage startvm "$my_vm_name" --type=headless};
};

task 'down' => sub {
    my $box = Rex::Commands::Box->new( name => $my_vm_name );
    $box->stop;
}

가상 머신을 시동하는 태스크는 VBoxManage 명령을 직접 사용하였습니다. 현재 Rex/Boxes 구현에서는 $box->start로 가상 머신을 시동할 경우 윈도우즈의 경우 headless 모드로 동작하지 않고 가상 머신 창이 뜨게 됩니다. 그래서 직접 VBoxManage 명령을 사용해 headless 모드로 시동하게 하였습니다.

이제 다음과 같이 가상 머신을 시작/중지할 수 있습니다.

#
# 가상 머신 시작
#
$ rex up

#
# 가상 머신 중지
#
$ rex down

가상 머신을 실행한 후 접속은 포트 포워딩으로 설정한 SSH로 접속하면 됩니다.

공유 폴더 마운트

$box->share_folder로 지정한 공유 폴더를 실제 가상 머신 안에서 마운트하려면 추가적인 작업이 필요합니다. 이 작업은 on_init 태스크에서 지정하고 $box->setup을 통해 실행되도록 하였습니다.

task 'on_init' => sub {
    if (! is_dir('/srv/share')) {
        mkdir '/srv/share';
        chown '<normal_user>',  '/srv/share';
        chgrp '<normal_group>', '/srv/share';
    }
    if ( !is_readable("/srv/share/Rexfile") ) {
        mount('share', '/srv/share',
            fs         => 'vboxsf',
            options    => [qw/ defaults uid=1000 gid=1000 /],
            persistent => TRUE,
        );
    }
    my $content = cat("/etc/rc.local");
    if ( $content !~ /^mount share$/m ) {
        run qq{echo "\\\$i\nmount share\n\n.\nw\nq" | ed /etc/rc.local};
    }
};

여기서 <normal_user>, <normal_group>은 가상 머신에 접속할 때 사용할 일반 유저 아이디와 그룹입니다. 공유 폴더 share/srv/share 디렉터리에 이 일반 유저가 내용을 수정할 수 있도록 마운트합니다.

여기서 사용한 명령어들은 모두 rex 명령어들입니다. 자세한 사용법은 rex 매뉴얼을 참조하면 됩니다. 다만 mount 명령어로 마운트하고 이를 /etc/fstab에 등록해도 가상 머신이 부팅시에 자동으로 이 공유 폴더를 마운트하지 못합니다. 이는 Virtualbox Guest Addition 커널 모듈이 로드되는 시점이 마운트하는 시점보다 늦기 때문입니다. 따라서 부팅 스크립트 중 맨 마지막에 실행되는 /etc/rc.local에서 직접 마운트를 해야 합니다. rc.local의 맨 마지막 명령어는 exit 0이어야 하므로 간단히 마운트 명령어를 추가할 수가 없어서 ed를 이용하여 조금 복잡하게 작업을 했습니다.

이어 프로비전할 내용을 on_init 태스크에 추가해도 되고 아니면 따로 태스크를 정의하고 $box->setup에서 순서대로 호출해도 됩니다. 이를테면 apt-get update를 하고 upgrade한다면 다음처럼 추가할 수 있습니다.

task 'init' => sub {
    ...
    $box->setup(qw/on_init apt_upgrade/);
};

...

task 'apt_upgrade' => sub {
    update_package_db;
    update_system;
};

git으로 관리하기

Rexfile은 일반 텍스트 파일이므로 쉽게 git으로 관리할 수 있습니다. 가상 머신을 생성할 때 다운로드받은 이미지나 가상 머신 디스크 파일들은 tmp 디렉터리 밑에 저장됩니다. 따라서 이 디렉터리를 .gitignore에 추가해서 버전 관리되지 않도록 할 필요가 있습니다.

정리하며

가상화를 이용하여 개발 환경을 구성하는 것에는 잇점이 많습니다. 최종 전개 환경에 맞추어 작업을 할 수 있기 때문에 프로젝트 막바지의 혼란을 줄일 수 있습니다. 또한 서버 사이트 개발에 익숙하지 않은 프론트엔드 개발자와 협업하기에도 좋습니다. 개발이 완료된 후 다른 개발자에게 유지 보수가 이관될 때에도 개발 환경을 쉽게 갖추게 할 수 있습니다. 하지만 일일이 가상 환경을 세팅하는 것도 번거로운 일입니다. 이런 번거로운 작업을 RexRex/Boxes를 이용해 쉽게 자동화할 수 있습니다. ;-)

blog comments powered by Disqus