아래 포스트는 Ubuntu 15.10 환경에서 작성되었습니다.

Fabric이란?

fabric은 SSH를 통한 application 배포나 system 관리자 일을 도와주는 python library와 command-line tool이다. 쉽게 말해 SSH로 할 수 있는 다양한 기능들을 script 작성을 통해 쉽고 간편하게 할 수 있도록 도와주는 툴이다.

기본적으로 몇몇가지 Linux 명령어에 대한 지식은 있어야 더 편하게 사용할 수 있을 것 같다.

설치

아래 툴이 있다는 가정 하에 다른 툴들이 그렇듯이 간단히 설치할 수 있다.

  • python2.x
  • pip2
$ sudo pip2 install fabric

fabric이 설치되었다.

python 3 버전은 무슨 일인지 동작하지 않는 것으로 보인다.

공식 사이트에 따르면 package manager로도 설치가 가능하다고 한다.(해보지는 않았다.)

$ sudo apt-get install fabric

시작하기

국민 시작 코드인 hello world를 작성해보도록 하자. fabfile.py 파일을 만들고 아래 코드를 작성한다.

def helloworld():
    print('helloworld!')

fabfile.py가 있는 디렉토리에서 아래 명령어를 입력한다.

$ fab helloworld
helloworld!

Done.

현재 directory에서 fabfile.py 을 찾아 helloworld method를 실행하는 것을 볼 수 있다.

또한 아래와 같이 인자를 전달 할 수도 있다.

def helloworld(name='fabric'):
    print('hello %s!' % (name))
$ fab helloworld
hello fabric!

Done.
$ fab helloworld:name=python
hello python!

Done.

같은 directory 내의 fabfile.py이 아닌 다른 파일을 사용하고자 할 경우에는 -f PATH--fabfile=PATH 옵션을 주어 변경할 수 있다.

이제 시작이다 SSH를 사용하는 몇몇 기능을 살펴보도록 하자.

SSH 기능들

fabric의 기능들을 사용하기 위해서는 SSH 접속이 가능한 서버가 있어야 한다. 해당 포스트에서는 amazon aws를 사용하고 있으며 서버 주소는 앞으로 ${REMOTE_ADDRESS}로 작성하도록 하겠다. 편의를 위해 서버 주소와 유저 이름을 변수로 지정해 놓는 것을 권장한다.

$ export REMOTE_USER=YOUR_USER_NAME
$ export REMOTE_ADDRESS=xxxx.amazonaws.com

만약 별도의 서버가 없을 경우에는 localhost에 계정을 하나 만들어서 해도 동작 확인은 가능하다.(있을 경우 아래 절차는 생략해도 좋다.)

$ sudo useradd -m test
$ sudo passwd test
$ export REMOTE_USER=test
$ export REMOTE_ADDRESS=locahost

fabric은 크게 아래 3개 기능을 제공한다.

  • local 명령어 실행(fab 명령어를 치는 환경에서 실행)
  • 원격 환경에서 쉘 명령 실행
  • local <-> remote 간의 파일 전송

local 명령어와 원격 명령 실행은 각각 local(), run() 함수를 통해 이루어지며 아래 코드를 작성해보자

from fabric.api import local, run 

def helloworld(name='fabric'):
     print('hello %s!' % (name))

def whoareyou():
    local('uname -a')
    local('id -gn')
    run('uname -a')
    run('id -gn')

uname -a은 ubuntu pc 정보를, id -gn 리눅스 사용자를 확인하는 명령어이다

$ fab whoareyou:host=${AMAZON}
[ec2-54-186-212-174.us-west-2.compute.amazonaws.com] Executing task 'whoareyou'
[localhost] local: uname -a
Linux soyeon 4.2.0-35-generic #40-Ubuntu SMP Tue Mar 15 22:15:45 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
[localhost] local: id -gn
amazingguni
[ec2-54-186-212-174.us-west-2.compute.amazonaws.com] run: uname -a
[ec2-54-186-212-174.us-west-2.compute.amazonaws.com] out: Linux ip-172-31-45-160 3.13.0-74-generic #118-Ubuntu SMP Thu Dec 17 22:52:10 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
[ec2-54-186-212-174.us-west-2.compute.amazonaws.com] out: 

[ec2-54-186-212-174.us-west-2.compute.amazonaws.com] run: id -gn
[ec2-54-186-212-174.us-west-2.compute.amazonaws.com] out: amazingguni
[ec2-54-186-212-174.us-west-2.compute.amazonaws.com] out: 


Done.
Disconnecting from ec2-54-186-212-174.us-west-2.compute.amazonaws.com... done.

위와 같이 localhost와 remote 서버의 정보가 출력되는 것을 알 수 있다.

참고로 django 배포 스크립트를 아래에..

from fabric.contrib.files import append, exists, sed
from fabric.api import env, local, run
import random

REPO_URL = 'https://github.com/amazingguni/pomodoro-web.git'

def deploy():
    site_folder = '/home/%s/sites/%s' % (env.user, env.host)
    source_folder = site_folder + '/source'
    _create_directory_structure_if_necessary(site_folder)
    _get_latest_source(source_folder)
    _update_settings(source_folder, env.host)
    _update_virtualenv(source_folder)
    _update_static_files(source_folder)
    _update_database(source_folder)

def _create_directory_structure_if_necessary(site_folder):
    for subfolder in ('database', 'static', 'virtualenv', 'source'):
        run('mkdir -p %s/%s' % (site_folder, subfolder))

def _get_latest_source(source_folder):
    if exists(source_folder + '/.git'):
        run('cd %s && git fetch' % (source_folder,))
    else:
        run('git clone %s %s' % (REPO_URL, source_folder))
    current_commit = local("git log -n 1 --format=%H", capture=True)
    run('cd %s && git reset --hard %s' % (source_folder, current_commit))

def _update_settings(source_folder, site_name):
    settings_path = source_folder + '/pomodoro_web/settings.py'
    sed(settings_path, "DEBUG = True", "DEBUG = False")
    sed(settings_path,
        'ALLOWED_HOSTS = .+$',
        'ALLOWED_HOSTS = ["%s"]' % (site_name,)
    )
    secret_key_file = source_folder + '/pomodoro_web/secret_key.py'
    if not exists(secret_key_file):
        chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
        key = ''.join(random.SystemRandom().choice(chars) for _ in range(50))
        append(secret_key_file, "SECRET_KEY = '%s'" % (key,))
    append(settings_path, '\nfrom .secret_key import SECRET_KEY')
    
def _update_virtualenv(source_folder):
    virtualenv_folder = source_folder + '/../virtualenv'
    if not exists(virtualenv_folder + '/bin/pip'):
        run('virtualenv --python=python3 %s' % (virtualenv_folder,))
    run('%s/bin/pip install -r %s/requirements.txt' % (
        virtualenv_folder, source_folder
    ))

def _update_static_files(source_folder):
    run('cd %s && ../virtualenv/bin/python3 manage.py collectstatic --noinput' % (
        source_folder,
    ))

def _update_database(source_folder):
    run('cd %s && ../virtualenv/bin/python3 manage.py migrate --noinput' % (
        source_folder,
    ))

참고

Welcome to Fabric! — Fabric documentation