Добро пожаловать в Fabric!
Этот документ - быстрый тур по возможностям Fabric и короткое руководство по его использованию. Дополнительная документация может быть найдена тут.
Что такое Fabric?
Как написано в README:
Fabric - это библиотека для Python (2.5 или выше) и инструмент командной строки для использования SSH при развёртывании приложений или выполнении административных задач.
Более конкретно:
- Инструмент, который позволяет Вам выполнить любую функцию Python при помощи командной строки
- Библиотека подпрограмм (построенная на более низкоуровневой библиотеке), переназначенная для выполнения команд оболочки через SSH легко и по-питонски.
Обычно, большинство пользователей используют обе эти возможности, применяя Fabric для записи и выполнения функций Python или заданий, чтобы автоматизировать работу с удалённым сервером. Давайте на это посмотрим.
Hello, fab
Это не было бы хорошим руководством без стандартного примера:
def hello():
print("Hello world!")
Если разместить эту функцию в модуле с именем fabfile.py в вашей рабочей директории, то эту функцию hello можно выполнить при помощи инструмента fab (устанавливаемого как часть Fabric) и будет делать именно то, что Вы ожидаете:
$ fab hello
Hello world!
Done.
Вот и всё. Таким образом Вы можете использовать Fabric как (очень) простой способ сборки даже без импорта какого либо из его API.
Примечание: Инструмент fab просто просто импортирует ваш fabfile и выполняет функцию или функции, которые Вы в нём определили. Тут нет никакой магии - всё, что Вы можете сделать в обычном скрипте Python, можно сделать и в fabfile.
Аргументы задачи
Очень часто полезно передать параметры в процессе выполнения в вашу задачу, как Вы это можете сделать и в обычном скрипте Python. Fabric поддерживает эту возможность при помощи такой нотации: <task name>:<arg>,<kwarg>=<value>,.... Давайте расширим наш первый пример:
def hello(name="world"):
print("Hello %s!" % name)
По умолчанию, если Вы используете команду fab hello - Вы получите тот же результат, что и в раньше; но теперь Вы можете передать и имя, кого приветствовать:
$ fab hello:name=Jeff
Hello Jeff!
Done.
Те, кто уже программируют на Python, могут предположить, что то же самое можно сделать и немного по другому:
$ fab hello:Jeff
Hello Jeff!
Done.
На данный момент, аргумент, который Вы передаёте, в любом случае будет передан как строка, так что Вам могут потребоваться дополнительные манипуляции с полученным значением. В более поздних версиях может быть добавлена система типов, чтобы облегчить этот процесс.
См также: аргументы для задачи
Локальные команды
В примере выше fab всего лишь экономит Вам несколько строк, начинающихся после `if __name__=="__main__"`. Обычно же используется API Fabric, который содержит функции (или операции) для выполнения команд оболочки, передачи файлов и т.д.
Давайте построим гипотетическое Web приложение fabfile. Этот сценарий делает следующее: Web приложение управляется через Git на удалённом хосте vcshost. На localhost у нас есть локальный клон web-приложения. Когда мы размещаем обновления на vcshost, мы хотим иметь возможность немедленно установить эти обновления на удалённых хост my_server в автоматическом режиме. Мы будем делать это, автоматизируя локальные и удалённые команды Git.
Fabfile обычно лучше всего работают из корня проекта:
. |-- __init__.py |-- app.wsgi |-- fabfile.py <-- our fabfile! |-- manage.py `-- my_app |-- __init__.py |-- models.py |-- templates | `-- index.html |-- tests.py |-- urls.py `-- views.py
Примечание: мы будем использовать приложение Django, но лишь в качестве примера - Fabric не привязан к какому-то внешнему коду, используя лишь SSH библиотеку.
Для начала, возможно, мы захотим запустить наши тесты и отправить обновления на наш VCS, так что мы будем готовы к развёртыванию:
from fabric.api import local def prepare_deploy(): local("./manage.py test my_app") local("git add -p && git commit") local("git push")
Результат этого может быть таким:
$ fab prepare_deploy [localhost] run: ./manage.py test my_app Creating test database... Creating tables Creating indexes .......................................... ---------------------------------------------------------------------- Ran 42 tests in 9.138s OK Destroying test database... [localhost] run: git add -p && git commit <interactive Git add / git commit edit message session> [localhost] run: git push <git push session, possibly merging conflicts interactively> Done.
Сам код вполне понятен: мы импортируем функцию API Fabric, local, и используем её для запуска и взаимодействия с локальной оболочкой. Все остальные API Fabric аналогичны - это лишь Python.
См также: Операции, исследование fabfile
Сделайте это по своему
Так как Fabric - "всего лишь Python", Вы можете организовать свой fabfile так, как Вам это удобно. Например, часто полезно выделить некоторые вещи в подзадачи:
from fabric.api import local def test(): local("./manage.py test my_app") def commit(): local("git add -p && git commit") def push(): local("git push") def prepare_deploy(): test() commit() push()
Задача prepare_deploy может быть вызвана как в примерах выше, но теперь Вы можете использовать и более детализированные подзадачи, если захотите.
Ошибки
Всё может работать хорошо, но что будет, если наш тест завершится с ошибкой? Скорее всего, мы захотим остановить наш процесс и сперва исправить ошибку.
Fabric проверяет возвращаемое значение программы, которую он вызывает, и прерывает своё выполнение если вызванная программа завершилась как-то не так. Давайте посмотрим, что будет, если один из наших тестов завершится с ошибкой:
$ fab prepare_deploy [localhost] run: ./manage.py test my_app Creating test database... Creating tables Creating indexes .............E............................ ====================================================================== ERROR: testSomething (my_project.my_app.tests.MainTests) ---------------------------------------------------------------------- Traceback (most recent call last): [...] ---------------------------------------------------------------------- Ran 42 tests in 9.138s FAILED (errors=1) Destroying test database... Fatal error: local() encountered an error (return code 2) while executing './manage.py test my_app' Aborting.
Замечательно! Нам даже ничего не нужно делать: Fabric обнаружил ошибку и зам завершил свою работу, не дойдя до ``commit``.
См также: Обработка ошибок (документация)
Обработка ошибок
Но что если мы хотим быть немного более гибкими и дать пользователю выбор? Настройка (или переменная окружения, обычно сокращается до env var) под названием warn_only позволяет не прерывать выполнение, а лишь вывести предупреждение, что даёт возможность организовать гибкую обработку ошибок.
Давайте воспользуемся этой настройкой для нашей функции test и затем посмотрим на результат вызова local:
Кроме того, о чём мы уже сказали, мы воспользовались тут ещё несколькими новыми вещами:
from __future__ import with_statement from fabric.api import local, settings, abort from fabric.contrib.console import confirm def test(): with settings(warn_only=True): result = local('./manage.py test my_app', capture=True) if result.failed and not confirm("Tests failed. Continue anyway?"): abort("Aborting at user request.") [...]
Кроме того, о чём мы уже сказали, мы воспользовались тут ещё несколькими новыми вещами:
- Импорт __future__ необходим для использования with в Python 2.5
- Подмодуль contrib.console содержит функцию confirm, которая используется для вывода в консоль вопроса да /нет
- Менеджер контекста settings используется для применения конкретных настроек к отдельному блоку кода
- Операция запуска команд наподобие local может вернуть объекты, содержащие информацию о результате выполнения запущенной команды (например .failed или .return_code)
- Функция abort используется для ручного прерывания программы
И, несмотря на некоторое усложнение, код всё ещё прост и достаточно гибок.
См также: Менеджеры контекста, Полный список env vars
Создаём подключения
Давайте улучшим наш fabfile, добавив туда задачу deploy, которая будет запускаться на удалённом сервере и проверять, что находящийся на нём код актуален:
def deploy(): code_dir = '/srv/django/myproject' with cd(code_dir): run("git pull") run("touch app.wsgi")
Опять же, тут мы используем некоторые новые возможности:
- Fabric это лишь Python, так что мы можем свободно использовать обычный код Python, переменные и строки
- cd - простой способ предварить команду вызовом cd /в/какую-либо/директорию (то есть изменить каталог перед вызовом другой команды). Это аналогично команде lcd, которая делает то же самое на локальной машине
- run аналогичен local, но запускает команду удалённо, а не локально
Кроме того, мы должны быть уверены, что мы импортировали новые функции в начале файла:
from __future__ import with_statement from fabric.api import local, settings, abort, run, cd from fabric.contrib.console import confirm
Теперь со всеми этими изменениями пора приниматься за новый сервер:
$ fab deploy No hosts found. Please specify (single) host string for connection: my_server [my_server] run: git pull [my_server] out: Already up-to-date. [my_server] out: [my_server] run: touch app.wsgi Done.
Но мы ещё не создали ни одного подключения, так что Fabric не знает, на каком удалённом хосте должны выполняться эти команды. В таком случае Fabric спросит это у нас в процессе выполнения задачи. Определения подключения задаётся в форме SSH подобной строки подключения, т.е. user@host:port и по умолчанию будет использовать локальное имя пользователя, так что в нашем примере достаточно указать только имя сервера - my_server.
Взаимодействие с удалённым сервером
git pull работает хорошо если Вы уже получили исходники, но что если это первое получение кода? Неплохо было бы обработать и эту ситуацию и в таком случае запустить сперва git clone:
def deploy(): code_dir = '/srv/django/myproject' with settings(warn_only=True): if run("test -d %s" % code_dir).failed: run("git clone user@vcshost:/path/to/repo/.git %s" % code_dir) with cd(code_dir): run("git pull") run("touch app.wsgi")
Как и в случае с local выше, run так же позволяет нам строить логические выражения, основанные на выполнении команд оболочки. Самая интересная часть тут - вызов git clone. Так как мы используем SSH метод Git'a для доступа к репозиторию на нашем сервере Git, это означает, что вызов run должен аутентифицировать себя.
Старые версии Fabric (и схожие высокоуровневые библиотеки SSH) запускали удалённые программы в limbo и не могли подключиться к ней с локальной машины. Это проблематично, особенно когда Вам нужно ввести пароль или как-то ещё взаимодействовать с удалённо запущенной программой.
Fabric 1.0 и более поздние версии решают эти проблемы и позволяют Вам связаться с программой на удалённой машине. Давайте посмотрим, что происходит, когда мы запускаем нашу задачу deploy на новом сервере:
$ fab deploy No hosts found. Please specify (single) host string for connection: my_server [my_server] run: test -d /srv/django/myproject Warning: run() encountered an error (return code 1) while executing 'test -d /srv/django/myproject' [my_server] run: git clone user@vcshost:/path/to/repo/.git /srv/django/myproject [my_server] out: Cloning into /srv/django/myproject... [my_server] out: Password: <enter password> [my_server] out: remote: Counting objects: 6698, done. [my_server] out: remote: Compressing objects: 100% (2237/2237), done. [my_server] out: remote: Total 6698 (delta 4633), reused 6414 (delta 4412) [my_server] out: Receiving objects: 100% (6698/6698), 1.28 MiB, done. [my_server] out: Resolving deltas: 100% (4633/4633), done. [my_server] out: [my_server] run: git pull [my_server] out: Already up-to-date. [my_server] out: [my_server] run: touch app.wsgi Done.
Обратите внимание на запрос пароля (Password) - git, запущенный на нашем сервере спрашивает у нас пароль к серверу Git. И мы можем вручную ввести его, после чего клонирование пройдёт нормально.
См также: Взаимодействие с удалёнными программами
Как объявить подключения заранее
Указание адреса хоста для подключения во время выполнения не самое удобное решение, так что Fabric предоставляет несколько способов сделать это либо в fabfile, либо в командной строке. Мы не будем говорить обо всех этих способах здесь, но мы покажем наиболее популярный из них - установку глобального списка хостов, env.hosts.
env - глобальный объект, похожий на словарь, определяющий многие из настроек Fabric, и в него можно заносить свои значения (на самом деле, settings, о котором мы говорили выше, всего лишь обёртка над этим объектом). Таким образом, мы можем изменить его на уровне модуля вверху fabfile:
from __future__ import with_statement from fabric.api import * from fabric.contrib.console import confirm env.hosts = ['my_server'] def test(): do_test_stuff()
Когда fab загружает наш fabfile, исполняется наша модификация env и она сохраняет наше изменение настройки. Конечный результат будет аналогичным - наша задача deploy будет запущена на сервере my_server.
Таким образом, мы можем легко сказать Fabric выполнить задачу сразу на нескольких серверах: так как env.hosts - это список, fab проходит по нему, вызывая задачу для каждого узла.
См также: Словарь окружения env, Как устроен список хостов
Заключение
Итак, наш fabfile всё ещё не большой, как и должно быть. Вот его содержание:
from __future__ import with_statement from fabric.api import * from fabric.contrib.console import confirm env.hosts = ['my_server'] def test(): with settings(warn_only=True): result = local('./manage.py test my_app', capture=True) if result.failed and not confirm("Tests failed. Continue anyway?"): abort("Aborting at user request.") def commit(): local("git add -p && git commit") def push(): local("git push") def prepare_deploy(): test() commit() push() def deploy(): code_dir = '/srv/django/myproject' with settings(warn_only=True): if run("test -d %s" % code_dir).failed: run("git clone user@vcshost:/path/to/repo/.git %s" % code_dir) with cd(code_dir): run("git pull") run("touch app.wsgi")
Зато он использует большую часть возможностей Fabric:
- определяет задачи в fabfile и выполняет их при помощи fab
- вызывает локальные команды оболочки при помощи local
- модифицирует переменную env при помощи settings
- обрабатывает ошибки выполнения команд, запрашивает ввод от пользователя и может вручную прервать выполнение
- определяет список хостов и запускает удалённые команды на них при помощи run
Однако, есть ещё много тем, которые мы ещё не покрыли. Обратитесь к ссылкам в разделах "см также" и к содержанию документации.
Спасибо за внимание!
Комментариев нет:
Отправить комментарий