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. Пример:sudo(command, shell=True, pty=True, combine_stderr=True, user=None)
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
-
Заливка (upload) на удаленный хост файла. Пример:put(filepath)
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
-
Для всех run/sudo будет выполнятся команда переданная в качестве "command".prefix(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
-
Аналогичен питоновскому raw_input(), запросит у пользователя данные, после ввода можно докрутить логики на основе ответа от пользователя. Пример:prompt(sometext)
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 рекомендую посмотреть проект
Также рекомендую обратить внимание на проекты Chef, Puppet и Buildout.
Комментарии
Для Джанги стоит обратить внимание на https://bitbucket.org/kmike/django-fab-deploy/overview
dZ, спасибо, я знал только про https://github.com/ronnix/fabtools
Да, прикольная штука - давно юзаем)
Writing a simple deploy sсript with Fabric and @roles.io blog
Installation Automation: Fabric Basics
Me running fab deploy
Для деплоя пхп приложений разве не подходит ?
В badoo используют вроде
Да вполне можно, почему и нет :-) Как и все остальные инструменты такого рода.
Однако чаще используют в python проектах, видимо из-за того что надо знать язык чтобы писать команды для Fabric
Хорошая статья, скрол только на вашем сайте бесит.
@deface, о каком скроле речь?
Обычный скрол страницы, не могу понять толи скорость скрола изменена, толи что...очень не удобно пользоваться. браузер firefox linux
Тоже скролл раздражает медленный, как будто в два раза шаг скролинга порезан.
@Keln какой браузер, какая ОС? Я проверил в Mac OS под Chrome и Firefox, всё впорядке, скролл как везде, пролистывается плавно
@adw0rd
Manjaro и Antergos последние, браузер Firefox 63.0.3
Оно пролистывается то плавно, но вот именно если сравнить пролистывание на других сайтах, то почти в два раза меньше информации на экране перемещается, я хз как правильно сказать, просто в хромиуме я за одно прокручивание колесиком могу 4-5 комментариев верхних листнуть, а в лисе максимум 2 получалось, и то если пол пальца залезет на него
В хромиуме кстати норм, но фишка в том что в firefox на обоих ПК нет ничего что могло бы давать такой эффект, они без каких либо расширений
@Keln, спасибо за развёрнутый ответ! Давно думал сменить оформление сайта, видимо пора переделать фронт :-)
Всегда пожалуйста=) Удачи с версткой=) Никогда ее не любил, лучше бэк пилить
Оставьте свой комментарий