Выполнение повседневных операций на большом количестве *nix-систем становится все более актуальной задачей. Количество серверов растет. Если вручную отдавать одну и ту же команду нескольким системам и отслеживать результат выполнения, то можно потратить уйму времени. Сегодня рассмотрим возможности Fabric, позволяющего выполнить задачу одновременно на большом количестве систем.
Начнем с основ
За последние несколько лет для *nix разработано, наверное, десяток различных систем управления конфигурациями (Chef, Puppet, CFEngine...), разных по назначению и сложности. Но объединяет их одно — они не всегда подходят для одноразовых операций на уже работающих серверах или для несложных заданий. Согласитесь, чтобы выполнить определенную проверку, занимающую одну-две команды, придется писать манифест или сookbooks. Это долго и неудобно. Иногда быстрее полученную информацию проанализировать визуально, чем поручить разбор машине. Вот как раз здесь стоит присмотреться к Fabric, позволяющему выполнить любые задачи администрирования, запуская команды параллельно на любом количестве систем.
Для работы Fabric не требуется какого-либо дополнительного ПО. На управляемых системах необходим лишь работающий SSH-сервер. Имея навыки программирования на Python, можно создавать достаточно сложные примеры, обрабатывать вывод и ошибки. Здесь администратор практически ничем не ограничен. Поэтому вполне можно использовать Fabric вместо системы управления конфигурациями. Хотя, признаюсь, это не всегда удобно, тут придется найти баланс. Все-таки Python — это более общий язык, и, усложняя задачу, можем просто увязнуть в программировании.
Проект не предоставляет никаких заготовок наподобие Chef Cookbooks, потому что задачи здесь проще. В этом нет смысла. Хотя пользователи сами выкладывают в интернет готовые скрипты и расширения (их очень много на github.com), которые можно использовать в качестве базы для своих проектов. Например,проект Fabtools расширяет стандартный API Fabric функциональностью для работы с PostgreSQL, MySQL, Crontab, Node.js, nginx, OpenVZ, Redis, Postfix, deb/rpm-пакетами и другим. Проще показать все на примерах.
Знакомимся
Установка Fabric проблем не вызывает, тем более нужный пакет уже имеется в репозитории большинства дистрибутивов Linux. В Ubuntu/Debian и производных команда проста:
$ sudo apt-get install fabric
Вместе с Fabric будет установлен модуль paramiko, используемый в Python для работы с SSH. Для установки последней версии ставим через PIP:
$ sudo apt-get install python-pip
$ sudo pip install fabric
После установки можно сразу давать команды удаленным системам, дополнительные настройки не требуются. По умолчанию подключение производится с логином текущей учетной записи, в процессе будет запрошен пароль. Узлы указываются при помощи параметра -H/--host, можно использовать IP-адрес или имя. Если логин или порт отличается, они задаются так, как это принято в SSH, или при помощи ключа -u/--user:
$ fab -H server1,admin@example.org:2222 -- hostname
Результат, собранный со всех систем, будет выводиться сразу в консоли. Подключение по умолчанию производится последовательно. Это немного тормозит процесс при работе с большим количеством систем. Но процесс можно и ускорить. Пароль можно сразу задать при помощи параметра -p/--initial-password-prompt, но нужно помнить, что он останется в истории и может быть узнан посторонним.
Изначально используется последовательная модель, когда fab разбирает все задачи и узлы, формирует команды, которые выполняются одна за другой. Предположим, нам нужно перед выполнением определенных действий обновить систему, при таком подходе это займет много времени. При помощи ключа -P/--parallel запускается параллельное выполнение всего задания, есть возможность распараллелить только определенные, но об этом дальше.
По умолчанию производится только одна попытка подключения к удаленной системе; чтобы изменить поведение, следует установить значение connection_attempts. Все доступные параметры командной строки легко узнать, использовав ключ -h.
В принципе, минимум автоматизации уже есть, вместо нескольких консолей мы можем выполнять все действия в одной, отдавая команды сразу нескольким системам. Но это не все. Большинство операций администрирования редко выполняются одной командой, поэтому админы и придумали скрипты, в которых прописано все что нужно. Fabric позволяет использовать специальный файл, выполняющий все нужные действия.
Командный файл Fabric
Просмотрев вывод справки, мы увидим, что в вызове fab при помощи параметра -f можно указать имя командного файла, например «-f update.py». Это стандартно и сюрпризов не приносит. Но опять же — удобно, если проект простой. В более сложных ситуациях проекты разрастаются до нескольких файлов и найти правильный сложнее, поэтому предложен вариант проще. По умолчанию Fabric в текущем каталоге или уровнем выше ищет файл fabfile.py, именно это имя лучше использовать для проекта. В этом случае достаточно перейти в каталог и просто дать команду «fab» с указанием выполняемой задачи. Задача — это, по сути, функция Python, которую необходимо вызвать по имени. Задача является базовой единицей, с которой работает Fabric. Файл может описывать одну или более задач, одни задачи можно вызвать из других. Таким образом, при помощи Fabric можно создавать и поддерживать проекты. Вызов при наличии дефолтного файла происходит примерно так. Предположим, у нас в файле прописаны две функции hostupdate и hostupgrade:
$ fab host_update host_upgrade
Здесь кроется главное отличие от скриптов: нельзя просто вызвать все задачи, прописанные в файле, указав что-то вроде «fab *». Имя задачи указывается обязательно. Так администратор подстраховывается от возможных ошибок. Хотя никто не мешает создать задачу, которая будет запускать все остальные задачи, и затем в строке запуска указать только ее. Вся информация из fabfile, в том числе пути, просто импортируются на время исполнения, по окончании все данные очищаются. Те, кто уже программировал на Python, найдут при создании и использовании fabfile много знакомого.
Естественно, список всех задач, которые содержит файл, в голове удержать будет проблематично, но в этом и нет необходимости. Команда fab содержит специальный параметр --list, выдающий нужный список.
$ fab --list
В результате получим все задачи, описанные в файле fabfile.py, находящемся в текущем каталоге.
Поэтому при работе с Fabric удобнее разделять скрипты не по файлам, а по каталогам, давая им понятное название. Внутри файл fabfile.py. Затем просто переходим в нужный каталог и даем команду для запуска. Для определенных задач в fabfile.py потребуются специфические функции, описанные в fabric.api. Их немного, импорт стандартен для Python.
from fabric.api import settings, run, env
Полный список доступен в документации или в файле /usr/share/pyshared/fabric/api.py. Чтобы их не запоминать, чаще всего импортируют сразу все.
from fabric.api import *
Каких-либо проблем такой подход не вызывает.
Встроенные переменные Fabric
В Fabric предусмотрен ряд договоренностей, которые призваны упростить работу. Среди них более 50 встроенных переменных, полный список которых доступен в документации. Некоторые параметры уже заданы, значения других по умолчанию пусты, их при необходимости указывает сам пользователь. Установить или переопределить переменные можно непосредственно в командной строке, в fabfile или в специальном пользовательском файле ~/.fabricrc.
Приоритет при совпадении названия переменной может быть разный и нередко зависит от самой переменной. Например, пароль, заданный в командной строке, переопределяет то, что записано в fabricrc (он считывается первым) и в fabfile (перекрывает fabricrc). Такое поведение обычно для большинства переменных. А вот список узлов определяется с точностью наоборот (подробнее рассмотрим далее). В самом файле переменные могут быть видимыми глобально или привязаны к определенному контексту/блоку.
Например, переменные user и password позволяют задать учетную запись для подключения к узлу:
$ nano ~/.fabricrc
user = 'admin'
password = 'P@ssw0rd'
В fabfile для установки встроенных переменных применяются два метода. Если нужно задать параметр глобально, то используется переменная env.[параметр]. Например, пользователь, пароль для подключения, разрешим параллельное выполнение заданий, установим рабочий каталог и уровень вывода ошибок:
$ nano fabfile.py
env.user = 'user'
env.password = 'P@ssw0rd'
env.parallel = 'True'
env.cwd='/var/www'
env.warn_only='True'
В том случае, когда переменная переопределяется временно, на период текущей задачи используется инструкция settings:
def task(...): with settings(warn_only=True): какой то код
Или так:
@with_settings(warn_only=True)
С изучения встроенных переменных и нужно начать знакомство с Fabric, их много, а с каждой новой версией добавляются новые.
Пользовательские переменные Fabric
Конечно, сам пользователь может создавать свои переменные, указывая их в файле или в командной строке. Классический пример: создадим функцию, которая выводит «Hello world».
def hello(name="world"): print("Hello %s!" % name)
Имя функции hello и есть задача Fabric. Запустим с указанием задачи:
$ fab hello
Hello world!
Но можно и переопределить переменную, указав свое значение:
$ fab hello:name=][akep
Hello ][akep!
В Fabric передаваемые аргументы доступны всем задачам (per-task arguments). Если аргументов несколько, их разделяют запятыми. В этом случае используется позиционирование, то есть они распределяются по параметрам по порядку. Хотя лучше сразу их именовать в строке вызова (name=][akep), для привязки аргумента конкретной задаче используется двоеточие (hello:name=][akep). Причем есть особенность — задачу можно вызвать несколько раз. То есть не нужно ее запускать несколько раз с разными аргументами, можно сразу указать их.
$ fab hello hello:][akep
Hello world!
Hello ][akep!
Все эти особенности следует знать и учитывать при составлении и использовании fabfile.
Хосты, хосты
Задачи, для которых не указаны узлы, всегда выполняются на локальной системе и запускаются один раз. Так как Fabric применяется для параллельного выполнения задач на нескольких удаленных системах, реализовано несколько способов их указать при запуске скрипта. Список узлов задается как глобально (то есть для всех выполняемых задач), так и для конкретной задачи. Один из вариантов глобальной установки мы уже рассматривали в примерах выше, это аргумент «-H host» команды fab.
Список узлов для всех задач в fabfile традиционно задается при помощи глобальной переменной env.hosts:
$ nano fabfile.py
env.hosts = ['host1', 'host2', '192.168.10.10']
def task_hostname():
run('hostname')
def status():
run('uptime')
run('cat /proc/loadavg')
run('free')
run('df -h')
Теперь, если запустить «fab task_hostname status», то команды, прописанные в одноименных задачах, будут выполнены на двух узлах. Вместо имени можно указать и IP-адрес. Как видим, системные команды запускаются при помощи директивы run (об этом далее).
В случае хостов параметры командной строки считываются перед fabfile, а поэтому глобальные параметры, прописанные в файле, будут их переопределять. Чтобы работали оба варианта, вместо env.hosts следует использовать env.hosts.extend, тогда задачи будут выполнены на узлах, указанных и в командной строке, и в файле. Но, предположим, нас на момент запуска не интересует узел host1, тогда проще его исключить при помощи параметра --exclude-hosts/-x.
$ fab task_uptime status -x host1
Задачи будут запущены только на оставшихся двух узлах. Чтобы указать список узлов, видимых в пределах определенной задачи, используется питоновский декоратор (decorator) @hosts:
@hosts('host1','user2@host2')
Или так:
test_hosts = ('host1', 'host2')
@hosts(test_hosts)
Запись аналогична и такому вызову:
def test(): env.hosts = ['host1', 'host2']
В этом случае узлы, указанные в env.hosts, будут видны только в пределах задачи test. Такой подход применяется, если системы имеют разное назначение или ОС. А значит, и задачи или отдельные команды должны различаться. Но есть более удобный и наглядный вариант — роли, устанавливаемые при помощи env.roledefs и позволяющие сгруппировать системы.
env.roledefs = {
'web': ['www.site1.com', 'www.site2.com'],
'mail': ['smtp.site.com', 'pop3.site.com']
}
Когда роли определены, обратиться к ним можно, используя питоновские декораторы.
@roles('web')
def task1():
...
@roles('mail')
def task2():
…
Специальный декоратор @parallel позволяет запустить определенное задание одновременно на нескольких системах.
env.hosts = ['host1', 'host2']
@parallel
def apache_reload():
sudo('service apache2 reload)
Теперь команда
$ fab apache_reload
позволит быстро перезапустить веб-сервер сразу на двух узлах, не дожидаясь, пока задание выполнится на первом, а затем на втором. Вывод информации при этом с побайтового (оптимального при интерактивном взаимодействии) автоматически переключится в построчный, но обычно никаких проблем это не вызывает. При большом количестве систем, вероятно, также стоит ограничить количество потоков, иначе перегрузки не избежать. Для этого используется ключ командной строки -z или параметр pool_size:
@parallel(pool_size=5)
Декоратор @serial, наоборот, указывает всегда выполнять блок последовательно, игнорируя параметры командной строки и значение env.parallel.
Операции, доступные в Fabric
Теперь самое время разобрать операции (fabric.operations), которые Fabric позволяет выполнить. Полный список приведен в документации API проекта. Остановлюсь лишь на наиболее часто используемых:
- get — получение файла с удаленной системы;
- put — загрузка файла на удаленный узел;
- run — команда, которую следует выполнить на удаленной системе с правами текущей учетной записи;
- sudo — команда, которую нужно выполнить на удаленной системе с правами root;
- local — выполнение команды на локальной системе;
- open_shell — вызов командного интерпретатора на удаленной системе для интерактивного ввода команд, выход по Exit или <Ctrl + D>;
- warn и puts — вывод сообщений;
- prompt — запрос данных от пользователя, полученное значение может быть присвоено переменной локальной или глобальной (env.). Параметр default устанавливает значение по умолчанию, а validate позволяет проверить введенное значение при помощи регулярных выражений.def all_del(): response = prompt('You are sure?') if response == "Yes": run('rm -rf /')
Остальные также проще объяснить на примере. Нам нужно развернуть зеркало Apache2 на двух узлах, скопировав на него файлы с текущего. Для установки веб-сервера используем пакетный менеджер и перенесем все файлы из локального каталога /var/www на удаленные системы.
$ nano fabfile.py
from fabric.api import *
env.hosts = ['host1', 'host2']
# Обновляем систему, ставим Apache и инструменты для работы с Git
def install_package():
@parallel
sudo('apt-get update && apt-get -y dist-upgrade')
sudo('apt-get -y install apache2 git-core')
# Создаем каталоги, устанавливаем владельца и копируем файлы с Git
def deploy_site():
run("cd /var/www/; git clone http://example.org/~user/repository/project.git")
sudo("chown -R www-data:www-data /var/www/*")
Теперь запускаем:
$ fab install_package deploy_site
Заключение
Как видим, Fabric — это одновременно простой и функциональный способ автоматизации, позволяющий выполнить любые задачи на нескольких системах, прилагая минимум усилий. В условиях постоянного роста количества подчиненных серверов этот инструмент является оптимальным для одиночных команд или несложных операций. А имея навыки программирования на Python, можно создавать более сложные скрипты на любой случай.