14 августа 2012 г. Deploy Django Fabric Python

Python. Автоматизируем деплой и рутину с Fabric

Fabric (далее просто "фабрика") - это швейцарский нож в мире развертывания приложений на Python, она содержит огромное количество инструментов для этого. Вам более не придется подключаться по SSH с целью обновить проект, загрузить веб-сервер, запустить "compilemessages" или "collectstatic", теперь всю эту рутину можно описать в вашем файле команд фабрики.

Также в фабрике есть понятие ролей (по сути это группы серверов), например, вы можете создать роли "production" или "stage", далее при запуске fab указать эти роли или непосредственно в файле при помощи декоратора "@roles" и ваши команды будут применяться к этим группам серверов непосредственно. Либо вы можете указывать конретные хосты вместо группы. Вообщем об этом чуть ниже...

В статье будет рассматриваться Fabric версии 1.4.3

Установка

Для установки достаточно установить одноименный пакет Fabric:

pip install Fabric

Настройка fabfile

Для создания своих команд по развертыванию проекта и вспомогательных функций (создание бекапов, работа с системой контроля версий, запуск любых команд как удаленно так и локально, и т.п.) необходимо создать в корне Django-проекта файл fabfile.py, для примера, со следующим содержимым:

# coding: utf-8
import os
from fabric.api import run, env, cd, roles

# Списком можно перечислить несколько серверов, которые у вас считаются "продакшеном"
env.roledefs['production'] = ['git@example.org:2244']

def production_env():
    """Окружение для продакшена"""
    env.key_filename = [os.path.join(os.environ['HOME'], '.ssh', 'git_example_org')]  # Локальный путь до файла с ключами
    env.user = 'git'  # На сервере будем работать из под пользователя "git"
    env.project_root = '/home/username/work/project'  # Путь до каталога проекта (на сервере)
    env.shell = '/usr/local/bin/bash -c'  # Используем шелл отличный от умолчательного (на сервере)
    env.python = '/home/username/work/project/venv/bin/python'  # Путь до python (на сервере)


@roles('production')
def deploy():
    production_env()  # Инициализация окружения
    with cd(env.project_root):  # Заходим в директорию с проектом на сервере
        run('git pull origin master')  # Пуляемся из репозитория
        run('find . -name "*.mo" -print -delete')  # Чистим старые скомпиленные файлы gettext'а
        run('{} manage.py compilemessages'.format(env.python))  # Собираем новые файлы gettext'а
        run('{} manage.py collectstatic --noinput'.format(env.python))  # Собираем статику

@roles('production')
def pip_install():
    production_env()
    run('{pip} install --upgrade -r {filepath}'.format(pip=env.pip,
        filepath=os.path.join(env.project_root, 'requirements.txt')))

Теперь создадим приватный и публичный ключ "git_example_org" для подключения к серверу:

$ ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/home/username/.ssh/id_rsa): /home/username/.ssh/git_example_org
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/username/.ssh/git_example_org.
Your public key has been saved in /home/username/.ssh/git_example_org.pub.
...

Вы можете вместо ключей передавать пароль, либо после подключения к серверу указывать его каждый раз, либо непосредственно указать его в файле fabfile.py:

env.password = "some_password"

Использование

После того как мы создали fabfile.py и настроили подключение по ssh, мы теперь можем перейти в каталог проекта и запустить fab deploy (или любую другую определенную вами команду):

$ cd ~/work/project && source venv/bin/activate
$ fab deploy another_command

Также вы можете указывать определенные роли или хосты, для которых выполнится какая-либо команда, например "deploy":

$ fab -R production,stage deploy
$ fab -H master.example.org,slave.example.org deploy

# А вот так можно исключить хост из группы во время запуска:
$ fab -R stage -x git@stage4.example.net,git@stage5.example.net deploy

Можете запускать произвольные команды (например "uname -a"):

$ fab -R production -- uname -a
$ fab -H git@example.org:2244 -- uname -a

# Можно передать ssh-ключ:
$ fab -H git@example.org:2244 -i ~/.ssh/git_example_org -- uname -a

# Или пароль:
$ fab -H git@example.org:2244 -p PASSWORD -- uname -a

Для отображения всех (определённых вами) команд в fabfile.py необходимо выполнить:

$ fab --list

В принципе, все ключи описаны в документации.

Передача аргументов

Всё очень просто, вы определяете args/kwargs у себя в команде:

def deploy(arg1, arg2, kwarg1='something', kwarg2=True):
    pass

После чего, в консоли эти аргументы можно будет передать так:

fab deploy:'arg1',42,kwarg1=sometext,kwarg2='text with spaces'

Подробнее о базовой функциональности фабрики

Я буду рассматривать только функции которыми я пользуюсь:

cd
Используется для указания каталога в который необходимо перейти, будет вызван "/bin/cd" перед каждым запуском команды в контексте. Пример:
def something():
    with cd('/var/www'):
        run('ls') # cd /var/www && ls
    with cd('website1'):
        run('ls') # cd /var/www/website1 && ls
lcd
Аналогично "cd", но только локально. Пример:
def something():
    with lcd('/tmp'):
        local('ls -la')
run
run(command, shell=True, pty=True, combine_stderr=True)
Запуск любой, доступной пользователю (через которого выполняется подключение к удаленной машине), команды на сервере. Пример:
def something():
    run('cat /etc/issue')
$ fab something
>>> [git@example.org:2244] run: cat /etc/issue
>>> [git@example.org:2244] out: Debian GNU/Linux 6.0 \n \l
local
local(command, capture=False)
Запуск любой команды на локальной машине. Пример:
def something():
    local('cat /etc/issue')
$ fab something
>>> [localhost] local: cat ssue
>>> Ubuntu 12.04 LTS \n \l
sudo
sudo(command, shell=True, pty=True, combine_stderr=True, user=None)
Запуск команды из под sudo. Пример:
def something():
    sudo('service mysql restart', user="mysql")
open_shell
Получение shell'a с удаленного хоста. Пример:
def something():
    # Что-то делаем...
    open_shell()  # Запускаем shell, а выходим по "exit" или "Ctrl+D"
    # После выхода из шелла продолжаем!
abort
Прерывает выполнение команды. Пример:
from fabric.api import abort
from fabric.contrib.files import exists

def something():
    if not exists('/tmp/super.sock'):
        abort('Alarm!')
warn
Выводит warning-сообщение, но не прерывает выполнение команды. Пример:
def something():
    warn("Something wrong!")
puts
Выводит сообщение, аналогично print(), но работает через output фабрики. Пример:
def something():
    puts("Don't worry, be happy!")
env
Переменная окружения, в которую можно добавить необходимые атрибуты и использовать их при необходимости, пример использования был уже выше:
...
def production_env():
    """Окружение для продакшена"""
    env.key_filename = [os.path.join(os.environ['HOME'], '.ssh', 'git_example_org')]  # Локальный путь до файла с ключами
    env.user = 'git'  # Будем работать из под пользователя "git"
    env.project_root = '/home/username/work/project'  # Путь до каталога проекта
    env.shell = '/usr/local/bin/bash -c'  # Используем шелл отличный от умолчательного
    env.python = '/home/username/work/project/venv/bin/python'  # Путь до python

@roles('production')
def something():
    production_env()  # Инициализация окружения
    # Используем проинициализированный "env"
    run('{python} -V'.format(python=env.python))
$ fab something
>>> [git@example.org:2244] run: /home/username/work/project/venv/bin/python -V
>>> [git@example.org:2244] out: Python 2.8
settings
Вы можете использовать settings для выполнения функций с указанными настройками, то есть можно было бы определить через env: "env.warn_only=True", но тогда бы действовало глобально, а так получается только в контексте "with". Пример:
def something():
    with settings(warn_only=True):
        run('test -e {}'.format(path))  # Даже если произойдет ошибка, warn_only не дас прерваться фабрике

@with_settings
Декоратор, который работает по аналогии с "settings", но влияет на всю команду. Пример:
@with_settings(warn_only=True)
def something():
    run('test -e {}'.format(path))
@roles
Декоратор позволяющий указать для какой группы хостов будет выполнена ваша команда. Пример:
from fabric.api import env, roles, run

env.roledefs['production'] = ['git@example.org:2244']
env.roledefs['stage'] = ['git@198.51.100.42', 'git@example.net', 'git@stage4.example.net', 'git@stage5.example.net']

@roles('stage')
def something():
    run('ls /var/log')
@hosts
Декоратор позволяющий указать список хостов для которых будет выполнена ваша команда. Пример:
from fabric.api import hosts, run

@hosts('git@example.org:2244', 'git@example.com')
def something():
    run('ls /var/log')
@task
Декоратор указывающий что функция является командой. Если вы не используете этот декоратор вообще в fabfile.py, то все функции будут командами, если хоть для одной функции указать этот декоратор, то придется указывать его для всех команд.

Принимает ряд параметров, например "alias" для того чтобы переименовывать команды на более короткие:
from fabric.api import task

@task(alias='boom')
def deploy_with_migrations():
    pass
get
get(filepath)
Получение (скачивание) файла с удаленного хоста. Пример
def something():
    get('/path/to/somefile.jpg')
$ fab something
>>> [git@example.org:2244] download: /home/username/git@example.org-2244/somefile.jpg <- /path/to/somefile.jpg
put
put(filepath)
Заливка (upload) на удаленный хост файла. Пример:
def something():
    put('/path/to/somefile.txt', '/tmp/file.txt')
    # или так
    with cd('/tmp'):
        put('/path/to/somefile.txt', 'file.txt')
@parallel
Декоратор для распараллеливания запуска комманды. Пример:
@parallel(pool_size=10)
def something():
    pass
@serial
Декоратор, который делает обратное. Пример:
@serial
def something():
    pass
prefix
prefix(command)
Для всех run/sudo будет выполнятся команда переданная в качестве "command".
Например для virtualenv:
def something():
    with prefix("source /path/to/venv/bin/activate"):
        run('./manage.py syncdb')
Получится в итоге:
$ source /path/to/venv/bin/activate && ./manage.py syncdb
prompt
prompt(sometext)
Аналогичен питоновскому raw_input(), запросит у пользователя данные, после ввода можно докрутить логики на основе ответа от пользователя. Пример:
def something():
    response = prompt('You are sure?')
    if response == "Yes":
        run('rm -rf /')
reboot
Перезагрузка машины для указанных хостов. Пример:
def something():
    reboot(wait=120)

В комплекте с фабрикой идут батарейки:

  • fabric.contrib.console
  • fabric.contrib.django
  • fabric.contrib.files
  • fabric.contrib.project

Например в последнем есть "rsync_project", которым можно пользоваться например так:

from fabric.contrib.project import rsync_project

env.roledefs['production'] = ['git@example.org:2244']

def production_env():
    env.directory = '/home/username/work/project/'
    env.rsync_excludes = ['*.pyc' , '*.db', '*~', ...]

@roles('production')
def rsync():
    production_env()
    rsync_project(env.directory, 'files', exclude=env.rsync_excludes)

Рекомендую

Для расширения стандартного API рекомендую посмотреть проект https://github.com/ronnix/fabtools, там вы найдете функциональность для работы с Supervisor, PostgreSQL, MySQL, Deb, Crontab, VirtualEnv, Nginx, Redis, Postfix и т.д.

Также рекомендую обратить внимание на проекты Chef, Puppet и Buildout.

Комментарии

Для Джанги стоит обратить внимание на https://bitbucket.org/kmike/django-fab-deploy/overview

dZ, спасибо, я знал только про https://github.com/ronnix/fabtools

Святослав 15 августа 2012 г. 6:29

Да, прикольная штука - давно юзаем)

Для деплоя пхп приложений разве не подходит ?
В badoo используют вроде

Да вполне можно, почему и нет :-) Как и все остальные инструменты такого рода.
Однако чаще используют в python проектах, видимо из-за того что надо знать язык чтобы писать команды для Fabric

Хорошая статья, скрол только на вашем сайте бесит.

@deface, о каком скроле речь?

Обычный скрол страницы, не могу понять толи скорость скрола изменена, толи что...очень не удобно пользоваться. браузер firefox linux

Оставьте свой комментарий

Markdown