Fabric과 함께 하는 배포 자동화
아래 포스트는 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,
))