Система управления конфигурацией Ansible
Представьте себе, что вам нужно управлять парком серверов, расположенных к тому же в разных географических точках. Каждый из этих серверов требует настройки, регулярного обновления и мониторинга. Конечно, для решения этих задач можно воспользоваться самым простым способом: подключиться к каждому серверу по ssh и внести необходимые изменения. При всей своей простоте этот способ сопряжен с некоторыми трудностями: он чрезвычайно трудоемок, а на выполнение однообразных операций уходит очень много времени. Чтобы упростить процессы настройки и конфигурирования серверов, можно также писать shell-скрипты. Но и этот способ вряд ли можно назвать совершенным. Скрипты нужно постоянно изменять, подстраивая их под каждую новую задачу. При их написании необходимо учитывать различие операционных систем и версий. Не будем забывать и о том, что отладка скриптов отнимает много усилий и забирает немало времени.
Представьте себе, что вам нужно управлять парком серверов, расположенных к тому же в разных географических точках. Каждый из этих серверов требует настройки, регулярного обновления и мониторинга. Конечно, для решения этих задач можно воспользоваться самым простым способом: подключиться к каждому серверу по ssh и внести необходимые изменения. При всей своей простоте этот способ сопряжен с некоторыми трудностями: он чрезвычайно трудоемок, а на выполнение однообразных операций уходит очень много времени.
Чтобы упростить процессы настройки и конфигурирования серверов, можно также писать shell-скрипты. Но и этот способ вряд ли можно назвать совершенным. Скрипты нужно постоянно изменять, подстраивая их под каждую новую задачу. При их написании необходимо учитывать различие операционных систем и версий. Не будем забывать и о том, что отладка скриптов отнимает много усилий и забирает немало времени.
Оптимальным вариантом решения описанных проблем является внедрение системы удаленного управления конфигурацией. В таких системах достаточно лишь описать нужное состояние управляемого узла. Система должна сама определить, что нужно сделать для достижения этого состояния, и осуществит все необходимые действия.
Со всеми сложностями, о которых идет речь выше, мы хорошо знакомы на собственном опыте: у нас имеется 10 точек присутствия с NS-серверами, расположенные в разных точках планеты. На них необходимо регулярно вносить различные изменения: обновлять операционную систему, устанавливать и обновлять различное ПО, изменять конфигурцию и т.п. Мы решили все эти операции автоматизировать и внедрить систему удаленного управления конфигурациями. Изучив имеющиеся решения, мы остановили свой выбор на Ansible.
В этой статье мы бы хотели подробно рассказать о его возможностях этого инструмента управления конфигурациями и поделиться собственным опытом его использования.
Что такое Ansible?
Ansible — опенсорсное программное решение для удаленного управления конфигурациями, разработанное Майклом Де Хаанном в 2012 году. Название продукта взято из научно-фантастической литературы: в романах американской писательницы Урсулы Ле Гуин ансиблом называется устройство для оперативной космической связи.
Ansible берет на себя всю работу по приведению удаленных серверов в необходимое состояние. Администратору необходимо лишь описать, как достичь этого состояния с помощью так называемых сценариев (playbooks; это аналог рецептов в Chef). Такая технология позволяет очень быстро осуществлять переконфигурирование системы: достаточно всего лишь добавить несколько новых строк в сценарий.
Почему Ansible?
Преимущества Ansible по сравнению с другими аналогичными решениями (здесь в первую очередь следует назвать такие продукты, как Puppet, Chef и Salt) заключаются в следующем:
- на управляемые узлы не нужно устанавливать никакого дополнительного ПО, всё работает через SSH (в случае необходимости дополнительные модули можно взять из официального репозитория);
- код программы, написанный на Python, очень прост; при необходимости написание дополнительных модулей не составляет особого труда;
- язык, на котором пишутся сценарии, также предельно прост;
- низкий порог вхождения: обучиться работе с Ansible можно за очень короткое время;
- документация к продукту написана очень подробно и вместе с тем — просто и понятно; она регулярно обновляется;
- Ansible работает не только в режиме push, но и pull, как это делают большинство систем управления (Puppet, Chef);
- имеется возможность последовательного обновления состояния узлов (rolling update).
Push или pull?
“Из коробки” все сценарии и команды выполняются методом push: когда возникает необходимость, мы запускаем сценарий, и он последовательно выполняется на удалённых серверах. Однако разработчики также предусмотрели метод pull и даже написали специальное приложение для установки необходимой для этого части ansible на удалённые хосты.
Установка
Требования для установки Ansible минимальны. На машине с которой производится управление должен быть установлен Python 2.6 или выше. На управляемых узлах должен быть установлен только Python версии не ниже 2.4, но он, как правило, по умолчанию включен в состав большинства дистрибутивов linux-систем. MS Windows не поддерживается.
Вам также могут потребоваться следующие модули Python, устанавливаемые через pip или пакетный менеджер вашей операционной системы:
- paramiko;
- PyYAML;
- jinja2.
В Ubuntu установка самого Ansible и зависимостей осуществляется добавлением репозитория и установкой пакета:
sudo add-apt-repository -y ppa:rquillo/ansible sudo apt-get update sudo apt-get install ansible -y
О процедуре установки в других ОС можно прочитать в официальной документации.
Группы серверов
Список групп серверов, которыми нужно управлять, Ansible может получать двумя основными способами:
- из специального текстового файла (далее этот вариант будет рассмотрен более подробно);
- с помощью внешнего скрипта, возвращающего нужный нам список серверов, например из MongoDB. В официальном github-репозитории есть готовые скрипты для получения списка из Cobbler, Digital Ocean, EC2, Linode, OpenStack Nova, Openshift, Spacewalk, Vagrant, Zabbix.
Файл hosts
Дефолтное расположение файла — /etc/ansible/hosts, но оно может также быть задано параметром окружения $ANSIBLE_HOSTS или параметром -i при запуске ansible и ansible-playbook. Содержимое этого файла может выглядеть, например, так (в квадратных скобках указаны имена групп управляемых узлов, ниже перечисляются входящие в эти группы серверы):
[dbservers] one.example.com two.example.com three.example.com [dnsservers] rs1.example.com ansible_ssh_port=5555 ansible_ssh_host=192.168.1.50 rs2.example.com ns[01:50].example.com
Помимо списка управляемых узлов, в файле hosts могут быть указаны и другие сведения, необходимые для работы: номера портов для подключения по SSH, способ подключения, пароль для подключения по SSH, имя пользователя, объединения групп и т.п. В некоторых случаях — в частности, при работе с большими и сложными конфигурациями, — различные параметры можно выносить в отдельные файлы и каталоги (о структуре каталогов см. ниже).
Более подробно о файле hosts и правилах его написания можно почитать в официальной документации.
Информация об узлах (Facts)
Перед внесением изменений Ansible подключается к управляемым узлам и собирает информацию о них: о сетевых интерфейсах и их состоянии, об установленной операционной системе и т.п. Он может делать это как с помощью собственного модуля, так и с помощью инструментов ohai и facter, если они установлены (такая возможность специально предусмотрена для пользователей, уже имеющих опыт работы с системами удаленного управления конфигурациями: ohai и facter являются библиотеками фактов для Chef и Puppet).
Переменные
Во время деплоя, как правило, требуется не только установить какое-либо приложение, но и настроить его в соответствии с определенными параметрами на основании принадлежности к группе серверов или индивидуально (например, ip-адрес BGP-соседа и номер его AS или параметры для базы данных). Как уже было сказано, загромождать файл hosts будет не очень красиво, поэтому разработчики Ansible пошли следующим путём:
- файлы с переменными групп хранятся в директории “group_vars/имя_группы”;
- файлы с переменными хостов в директории “hosts_vars/имя_хоста”;
- файлы с переменными роли (о них речь пойдет ниже) в директории “имя_роли/vars/имя_задачи.yml”;
Помимо пользовательских переменных можно (и даже нужно) использовать факты, собранные ansible перед выполнением сценариев и отдельных задач.
Модули Ansible
В состав Ansible входит огромное количество модулей для развёртывания, контроля и управления различными компонентами, которые можно условно разделить на следующие группы (в скобках приведены названия некоторых продуктов и сервисов):
- облачные ресурсы и виртуализация (Openstack, libvirt);
- базы данных (MySQL, Postgresql, Redis, Riak);
- файлы (шаблонизация, регулярные выражения, права доступа);
- мониторинг (Nagios, monit);
- оповещения о ходе выполнения сценария (Jabber, Irc, почта, MQTT, Hipchat);
- сеть и сетевая инфраструктура (Openstack, Arista);
- управление пакетами (apt, yum, rhn-channel, npm, pacman, pip, gem);
- система (LVM, Selinux, ZFS, cron, файловые системы, сервисы, модули ядра);
- работа с различными утилитами (git, hg).
О том, с чем умеет работать Ansible “из коробки”, можно прочитать в официальной документации. Список действительно впечатляет.
Примеры простых задач
С помощью Ansible можно одновременно выполнить одну задачу на целой группе серверов. Попробуем, например, отправить запрос ping на серверы выбранной группы:
$ ansible dnsservers -m ping dns1.example.com | success >> { "changed": false, "ping": "pong" } dns2.example.com | success >> { "changed": false, "ping": "pong" }
Следующий пример соберёт информацию о хостах и выведёт её на консоль в формате JSON:
$ ansible dnsservers -m setup
dns1.example.com | success >> { "ansible_facts": { "ansible_all_ipv4_addresses": [ "192.168.1.35" ], "ansible_all_ipv6_addresses": [ "fe80::ac2a:eaff:fe96:ea53" ], "ansible_architecture": "x86_64", "ansible_bios_date": "", "ansible_bios_version": "", "ansible_cmdline": { "barrier": "off", "console": "ttyS0", "panic": "15", "ro": true, "root": "UUID=c5412437-f80e-4db4-81bc-75f751a60792", "xencons": "ttyS" }, "ansible_date_time": { "date": "2013-10-04", "day": "04", "epoch": "1380891466", "hour": "16", "iso8601": "2013-10-04T12:57:46Z", "iso8601_micro": "2013-10-04T12:57:46.130144Z", "minute": "57", "month": "10", "second": "46", "time": "16:57:46", "tz": "MSK", "year": "2013" }, "ansible_default_ipv4": { "address": "192.168.1.35", "alias": "eth0", "gateway": "192.168.1.1", "interface": "eth0", "macaddress": "ae:aa:ea:96:ea:53", "mtu": 1500, "netmask": "255.255.255.0", "network": "192.168.1.0", "type": "ether" }, "ansible_default_ipv6": {}, "ansible_devices": { "xvda": { "holders": [], "host": "", "model": null, "partitions": { "xvda1": { "sectors": "290816", "sectorsize": 512, "size": "142.00 MB", "start": "2048" }, "xvda2": { "sectors": "16482304", "sectorsize": 512, "size": "7.86 GB", "start": "292864" } }, "removable": "0", "rotational": "0", "scheduler_mode": "cfq", "sectors": "16777216", "sectorsize": "512", "size": "8.00 GB", "support_discard": "0", "vendor": null } }, "ansible_distribution": "Ubuntu", "ansible_distribution_release": "precise", "ansible_distribution_version": "12.04", "ansible_domain": "", "ansible_eth0": { "active": true, "device": "eth0", "ipv4": { "address": "192.168.1.35", "netmask": "255.255.255.0", "network": "192.168.1.0" }, "ipv6": [ { "address": "fe80::ac2a:eaff:fe96:ea53", "prefix": "64", "scope": "link" } ], "macaddress": "ae:aa:ea:96:ea:53", "module": "xennet", "mtu": 1500, "type": "ether" }, "ansible_form_factor": "", "ansible_fqdn": "dns1.example.com", "ansible_hostname": "dns1", "ansible_interfaces": [ "lo", "eth0" ], "ansible_kernel": "3.1.0-1.2-xen", "ansible_lo": { "active": true, "device": "lo", "ipv4": { "address": "127.0.0.1", "netmask": "255.0.0.0", "network": "127.0.0.0" }, "ipv6": [ { "address": "::1", "prefix": "128", "scope": "host" } ], "mtu": 16436, "type": "loopback" }, "ansible_lsb": { "codename": "precise", "description": "Ubuntu 12.04.3 LTS", "id": "Ubuntu", "major_release": "12", "release": "12.04" }, "ansible_machine": "x86_64", "ansible_memfree_mb": 181, "ansible_memtotal_mb": 1061, "ansible_mounts": [ { "device": "/dev/mapper/system-root", "fstype": "ext4", "mount": "/", "options": "rw,errors=panic,barrier=0", "size_available": 6332063744, "size_total": 7798611968 }, { "device": "/dev/xvda1", "fstype": "ext2", "mount": "/boot", "options": "rw", "size_available": 110679040, "size_total": 139539456 } ], "ansible_os_family": "Debian", "ansible_pkg_mgr": "apt", "ansible_processor": [ "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz" ], "ansible_processor_cores": 1, "ansible_processor_count": 8, "ansible_processor_threads_per_core": 1, "ansible_processor_vcpus": 8, "ansible_product_name": "", "ansible_product_serial": "", "ansible_product_uuid": "", "ansible_product_version": "", "ansible_python_version": "2.7.3", "ansible_selinux": false, "ansible_ssh_host_key_dsa_public": "AAAAB3NzdC1kc3MAAACBAI09PTx0Jv2dAhmwGoPV45G6ZEiZ84TwjVm6HYbGOHUZe+CKnYwWThD8ZqXYzRyvVxCcVefiS6m0PKY6a5id2GySyQlTM952bDaifd09ot9pCWjwNp5q4/EQdIG3R9Kt96DfsraVrvmJWG1qQMaUlnsiZzxHWv4Fn+7BvP0Kn6AtAAAAFQDIeO7uTIVR/kzNTV9xHN/uW6KJ8wAAAIALATT5RMZUQhtwz42ek8254hrlEqSyMnWyq+vCDOp+2rE/dIkcBcd+xnfV2lTkeizAMTzYETOE8IES4rXWKFf2AlBTk9IQDnZI0ABlpUmXQVZvHxl8pKwLwzRPA7XeW4f4bXQXimUPHzCdnrwxLj7Qht4JaspL2znMCKOtpwWBrAAAAIB45bgP1JIlVpWaj1FJ/NKhDDv5D9yM7GXaljsUXL1T7KGtZ9yMA+sJa7Sw/HF88ag/gjxe6kUwmkrsvtrsza3WpfaMYupKFZtJwmQabxYPM1QWAtVONxeSo30IimFLQuaj6tgzfD1faJVyDdFydWNDUfZ3cn5iNsCz6khsc241zQ==", "ansible_ssh_host_key_ecdsa_public": "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHA43NTYAAABBBH3b5e6ZUbR+gMLMiOwcQzwuEPE+KIXHmzywNcOIltWY4ZiGRXlQZMyEFMENiOSivFHByMBV0wJj8VMxJocHd7s=", "ansible_ssh_host_key_rsa_public": "AAAAB3NzaC1yc2EAAAADAQABAAABAQDH5WKsJ0UJ8LDQMDBCcbkbdDVXcG2lhdBOmxCVm128ztp3PJrHQoNwy1njit/Sty34HYvwjVXvuaT8ksCSAGhi8VPvRo+oqGaSdt3T39Ew5DsKeJTOZDqL1Vz1jNbPvvVjsdB7v34zTgEdnjuTzlwPvtNtXyTJonXC0KDlLl5WAiYSb9XpLB0rjjKAGNautp0Mgx6olWadpMT/NWT0Ub5yHBJCWK+mYAwq0M2tK+QSrsukmG93flGLboVlWTfMIM+UUR2MH3OxI7ew6Oc5P2ligH3rcHhcAWwXLIAsMJ5vcmH0+pEvTGr9ucNMbXoZzAhX3hPN+KG8hbZ+AX3z0TXn", "ansible_swapfree_mb": 482, "ansible_swaptotal_mb": 487, "ansible_system": "Linux", "ansible_system_vendor": "", "ansible_user_id": "root", "ansible_userspace_architecture": "x86_64", "ansible_userspace_bits": "64", "ansible_virtualization_role": "guest", "ansible_virtualization_type": "xen" }, "changed": false } dns1.example.com | success >> { "ansible_facts": { "ansible_all_ipv4_addresses": [ "192.168.1.43" ], "ansible_all_ipv6_addresses": [ "fe80::cc2b:97ff:fe7b:d221" ], "ansible_architecture": "x86_64", "ansible_bios_date": "", "ansible_bios_version": "", "ansible_cmdline": { "autorun": "fsck", "barrier": "off", "console": "xvc0", "ro": true, "root": "/dev/mapper/system-root" }, "ansible_date_time": { "date": "2013-10-04", "day": "04", "epoch": "1380891479", "hour": "16", "iso8601": "2013-10-04T12:57:59Z", "iso8601_micro": "2013-10-04T12:57:59.276859Z", "minute": "57", "month": "10", "second": "59", "time": "16:57:59", "tz": "MSK", "year": "2013" }, "ansible_default_ipv4": { "address": "192.168.1.43", "alias": "eth0", "gateway": "192.168.1.1", "interface": "eth0", "macaddress": "ce:cb:97:7b:d2:21", "mtu": 1500, "netmask": "255.255.255.0", "network": "192.168.1.0", "type": "ether" }, "ansible_default_ipv6": {}, "ansible_devices": { "xvda": { "holders": [], "host": "", "model": null, "partitions": { "xvda1": { "sectors": "290816", "sectorsize": 512, "size": "142.00 MB", "start": "2048" }, "xvda2": { "sectors": "12288000", "sectorsize": 512, "size": "5.86 GB", "start": "292864" } }, "removable": "0", "rotational": "0", "scheduler_mode": "cfq", "sectors": "12582912", "sectorsize": "512", "size": "6.00 GB", "support_discard": "0", "vendor": null } }, "ansible_distribution": "Debian", "ansible_distribution_release": "NA", "ansible_distribution_version": "7.0", "ansible_domain": "", "ansible_eth0": { "active": true, "device": "eth0", "ipv4": { "address": "192.168.1.43", "netmask": "255.255.255.0", "network": "192.168.1.0" }, "ipv6": [ { "address": "fe80::cc2b:97ff:fe7b:d221", "prefix": "64", "scope": "link" } ], "macaddress": "ce:cb:97:7b:d2:21", "module": "xennet", "mtu": 1500, "type": "ether" }, "ansible_form_factor": "", "ansible_fqdn": "dns2.example.com", "ansible_hostname": "dns2", "ansible_interfaces": [ "lo", "eth0" ], "ansible_kernel": "3.1.0-1.2-xen", "ansible_lo": { "active": true, "device": "lo", "ipv4": { "address": "127.0.0.1", "netmask": "255.0.0.0", "network": "127.0.0.0" }, "ipv6": [ { "address": "::1", "prefix": "128", "scope": "host" } ], "mtu": 16436, "type": "loopback" }, "ansible_lsb": { "codename": "wheezy", "description": "Debian GNU/Linux 7.0 (wheezy)", "id": "Debian", "major_release": "7", "release": "7.0" }, "ansible_machine": "x86_64", "ansible_memfree_mb": 9, "ansible_memtotal_mb": 547, "ansible_mounts": [ { "device": "/dev/mapper/system-root", "fstype": "ext3", "mount": "/", "options": "rw,relatime,errors=panic,barrier=0,data=ordered", "size_available": 3733434368, "size_total": 5684838400 }, { "device": "/dev/xvda1", "fstype": "ext2", "mount": "/boot", "options": "rw,relatime,user_xattr,acl,barrier=1", "size_available": 112991232, "size_total": 139539456 } ], "ansible_os_family": "Debian", "ansible_pkg_mgr": "apt", "ansible_processor": [ "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz", "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz" ], "ansible_processor_cores": 1, "ansible_processor_count": 8, "ansible_processor_threads_per_core": 1, "ansible_processor_vcpus": 8, "ansible_product_name": "", "ansible_product_serial": "", "ansible_product_uuid": "", "ansible_product_version": "", "ansible_python_version": "2.7.3", "ansible_selinux": false, "ansible_ssh_host_key_dsa_public": "AAAAB3szaC1kc3MAAACBAJFX2aR1G5QM57/3vLSlLmPR46nXNPAx0jtf6fPWkit/64W5FFBH7BW9YtPHGrucAagz1drKd9SiE+U5GlVqg/4xXOLMHmWUHitivVV9obtkyF2BM/+1OKTwxGIBP6Vu3YP/Wbpbv5TDCxjClWpZs3kCWrqRsScTdZTkk66YDTmbAAAAFQCEEjs6jtnyfF45scSgIxy60we9bQAAAIAzlb3pno+ljpE7yEjh6oBvl1RgUeYzwJZxHkBRMfOt30DyaCuXhNVhykhGYFqybv66BSu3C2br+Zk3peQRf6rie7QWV/lAXyDfInbGxgklFX6yAcd+JYj4u2vJ9j2k3GinnN9TLL3kafn0oqduy8sujozTCFZcG7dJx+4NZY29ZgAAAIBB94cFFAxC56HApvuRAcU/Wr+YeyKtJ3IHDz0hLRO+ziyuMgr2ajG80LNBGzG3rV2AEXSlH6egXaLfzcn9iPlB7VFpB/Fg/GZGOSpIUCFSSpEke6AoO8Z19Y5uR2EfcegyHhWVXGkIsaIon5KnH1bC//XAn9ir7AmANUCeXSz1Fg==", "ansible_ssh_host_key_ecdsa_public": "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBM70PfnLbbXU+cJ27tWcKoom+P+TC08EncjB71bF4zp7Kw46YrWVjtPoFqAy3b1E2KkzUNcSrbJyEoCIgfzCC3Y=", "ansible_ssh_host_key_rsa_public": "AAAAB3NzaC1yc2EAAAADAQABAAABAQCqltJwL3ThbfWbwBSuaZ2zZNRtcrjld0Z/ulAM6sygWTjHIeIuxT1lbJJFfKZneyo29nPho1q/HAlYGDRdcDZhKufNqDN/c9iFDbjnuPvCetUxxf+t9jKnUHnqDpO+fLYbosIEio9cmS/pOEwAU4+VBB8mdNAj9fjqrE08xcdEgt8QnAjIRlKDCdtTuYbisyt96GR10RrLPkr0epqGmHE6vzC1PyidqmQkGuCrcJHPJiS40J7S8QVP11TRS4Un+V6B7fTcfoZcPDrMsPj/NpOVh3egCJGg5VRJ2D3Fmoapg/R3ZPrMD/AW+PNQLa+1GSIVTc3cNu4ctXgnwQJwSjWL", "ansible_swapfree_mb": 412, "ansible_swaptotal_mb": 487, "ansible_system": "Linux", "ansible_system_vendor": "", "ansible_user_id": "root", "ansible_userspace_architecture": "x86_64", "ansible_userspace_bits": "64", "ansible_virtualization_role": "guest", "ansible_virtualization_type": "xen" }, "changed": false }
А вот так можно создать логический том (или, в зависимости от текущего состояния, изменить его размер) с именем examplevolume в группе examplegroup:
$ ansible dnsservers -m lvol -a "vg=examplegroup lv=examplevolume size=1024 state=present" dns1.example.com | success >> { "changed": true, "msg": "" } dns2.example.com | success >> { "changed": false, "msg": "" }
Ansible позволяет не только выполнять единичные задачи, но и писать сценарии, которые необходимо выполнить на управляемых узлах. Рассмотрим структуру и правила написания таких сценариев более подробно.
Cценарии (playbooks)
Все сценарии в Ansible пишутся на YAML. Это — человекочитаемый формат сериализованных данных, гораздо более простой, чем XML или JSON.
Чтобы выполнить сценарий используется команда ansible-playbook со следующим сиснтаксисом:
ansible-playbook <имя_файла_сценария.yml> ... [другие параметры]
В начале сценария обязательно должна присутствовать последовательность символов «–––» (так в YAML обозначается начало документа). Перед каждым новым разделом списка ставится дефис ( – ):
--- - hosts: webservers
Основными параметрами/группами простого сценария являются:
- hosts — в нем указываются управляемые узлы или группы узлов, к которым нужно применить изменения;
- tasks — здесь описывается состояние, в которое необходимо привести управляемый узел, альтернативой этому могут служить роли;
Также в сценарии перед непосредственным описанием задач могут быть указаны следующие параметры или группы параметров:
- gather_facts — собирать или нет информацию о хостах перед выполнением задач, по умолчанию — да;
- vars — в нем указываются различные переменные, которые будут использованы при выполнении сценария;
- connection — можно указать метод соединения с хостами: pure ssh, paramiko, fireball, chroot, jail, local, accelerate (применимо также для выполнения отдельного модуля);
- sudo — после установления соединения выполнять задачу с привилегиями другого пользователя, по умолчанию другой пользователь — root;
- sudo_user — в сочетании с предыдущим параметром можно указать с привилегиями какого именно пользователя будет выполнена задача;
- vars_prompt — перед выполением плэйбука Ansible в интерактивном режиме может уточнить указанные в этом разделе параметры;
- remote_user (в предыдущих версиях — просто user) — имя пользователя для авторизации на удалённом хосте.
Рассмотрим все эти разделы более подробно.
В разделе hosts указывается группа управляемых узлов, к которой будут применены описываемые в сценарии изменения.
Так, строка формата:
hosts: webservers
означает, что изменения будут применены к узлам из группы webservers.
Сценарии могут выполняться не только от имени пользователя, под именем которого установлено соедиение, но и любого другого. В следующем примере авторизация на хосте будет произведена с именем yourname, но задачи будут выполняться от имени пользователя root (если, конечно, этому пользователю это разрешено):
--- - hosts: webservers user: yourname sudo: yes
Если добавить параметр “user: postgres”, то все действия будут выполняться с привилегиями пользователя postgres.
В разделе vars указываются переменные, которые будут использованы в сценарии, и их значения:
- hosts: webservers vars: http_port: 80 max_clients: 200
Список изменений, которые необходимо произвести на управляемом узле, приводится в разделе tasks. Каждой задаче (task) присваивается имя (name). Далее указываются модули Ansible, которые будут задействованы при ее выполнении:
- hosts: webservers user: yourname tasks: - service: name=nginx state=started
Для каждой задачи можно указывать пользователя, от имени которого она будет выполнена:
--- - hosts: webservers user: yourname tasks: - service: name=nginx state=started sudo: yes
Шаблонизация
В Ansbile используется шаблонизатор Jinja2. Приведём пример шаблона (часть конфига powerdns):
# пароль для подключения к базе данных gpgsql-password={{ lookup('password', 'credentials/' + inventory_hostname + '/postgresql/powerdns', length=15) }} # IPv4-адрес, который будет “слушать” powerdns local-address={{ ansible_default_ipv4.address }} # IPv6-адрес, который будет “слушать” powerdns local-ipv6={{ ansible_default_ipv6.address }} # nsid dns-сервера (EDNS option 3, rfc5001) server-id={{ ansible_hostname }}
В приведённом примере мы подставляем в шаблон следующие значения:
- из заранее собранных фактов о хосте:
- ansible_default_ipv4.address — основной IPv4-адрес хоста;
- ansible_default_ipv6.address — основной IPv6-адрес хоста;
- ansible_hostname — имя хоста (результат выполнения команды hostname).
- inventory_hostname — имя хоста в инвентарном файле;
- пароль пользователя powerdns из внешнего источника данных (в данном случае файл) для подключения к базе Postgresql , полученный с помощью lookup-плагина password из стандартной поставке. Особенность некоторых lookup-плагинов — если данных нет, то они могут их сгенерировать и сохранить для последующего использования.
Обработку шаблонов и, в данном случае, генерацию конфигурационного файла выполняет модуль template; он же может задать необходимые права доступа и изменить владельца/группу:
- name: generate powerdns config template: src=pdns.conf.j2 dest=/etc/powerdns/pdns.conf owner=powerdns group=powerdns mode=600
Обратим внимание на то, что файл шаблона и файл с паролем пользователя базы данных находятся на машине управления, а результатом будет файл на удалённом узле.
Обработчики событий (Handlers)
Ansible не просто выполняет задачи в указанном порядке, но и проверяет их состояние на наличие изменений. Если при выполнении сценария требовалось, например, добавить строку в конфигурационный файл, и в результате выполнения он изменился (необходимой строки действительно не было), то Ansible может выполнить специальную задачу, описанную как обработчик события (handler). Если при выполнении строка уже была в конфигурационном файле, то обработчик выполнен не будет. Обработчики событий описываются в конце сценария; в описании задачи они указываются через параметр notify. Приведём пример:
--- - hosts: webservers vars: max_clients: 200 tasks: # сгенерируем файл конфигурации на основе шаблона # и укажем, что требуется выполнить задачу “restart apache” # если файл изменился - name: write the apache config file template: src=/srv/httpd.j2 dest=/etc/httpd.conf notify: - restart apache - name: ensure apache is running service: name=httpd state=started # раздел описания обработчиков handlers: - name: restart apache # используем модуль service для перезапуска веб-сервера service: name=httpd state=restarted
Контроль выполнения
Допустим, что при выполнении сценария нам нужно проверять определённые переменные или состояния и, в зависимости от них, выполнять или не выполнять какие-либо задачи. Для этого можно использовать оператор “when”:
tasks: # сохраняем файл шаблона и сохраняем результат задачи # в переменную last_result - template: src=/templates/foo.j2 dest=/etc/foo.conf register: last_result # проверяем переменную last_result.changed и если она имеет # значение true - задача будет выполнена, иначе - будет пропущена - command: echo 'the file has changed' when: last_result.changed
Делегирование задачи другому хосту
Иногда требуется выполнить задачу на определённом узле, но в контексте другого узла. Например, во время обновления узла может возникнуть необходимость отключить для него мониторинг, находящийся на отдельном сервере. Для этого используется управляющая директива delegate_to. Приведём пример:
- name: disable nagios alerts for this host webserver service nagios: action=disable_alerts host={{inventory_hostname}} services=dnsserver delegate_to: mon_host.example.com
Результатом выполнения этой задачи будет отключение сообщений для сервиса dnsserver в Nagios.
Роли
Ролью называется типовой набор переменных и задач, назначаемых для одного или нескольких серверов. Если вам нужно применить к серверу или группе серверов типовой набор операций, вам достаточно просто назначить ему роль. Предварительно в проекте каталоге проекта должна быть создана соответствующая структура. В сценариях роли назначаются следующим образом:
--- - name: check and apply basic configuration to all hosts hosts: all roles: - common - name: check and apply configuration to group1 hosts: group1 roles: - pgsql - name: check and apply configuration to group2 hosts: group2 roles: - fooapp
Структура проекта
├── production # инвентарный файл для продакшн-серверов ├── stage # инвентарный файл для stage-окружения │ ├── group_vars/ │ ├── group1 # здесь назначаются переменные для │ └── group2 # конкретных групп ├── host_vars/ │ ├── hostname1 # специфические переменные для хостов в │ └── hostname2 # случае необходимости прописываются здесь │ ├── site.yml # основной сценарий ├── webservers.yml # сценарий для веб-сервера ├── dbservers.yml # сценарий для сервера базы данных │ └── roles/ ├── common/ # здесь описываются роли │ ├── tasks/ # │ │ └── main.yml # - файл задач роли, может включать файлы │ │ # меньшего размера │ ├── handlers/ # │ │ └── main.yml # - файл с обработчиками (handlers) │ ├── templates/ # - директория для шаблонов, в данном │ │ └── ntp.conf.j2 # случае - для конфига ntp │ ├── files/ # │ │ ├── bar.txt # - файл-ресурс для копирования на хост │ │ └── foo.sh # - скрипт для выполнения на удалённом хосте │ └── vars/ # │ └── main.yml # - ассоциированные с ролью переменные │ ├── pgsql/ # такая же структура, как выше, для роли pgsql └── fooapp/ # такая же структура, как выше, для роли fooapp
Пример сценария
Чтобы понять, как это все работает, рассмотрим практический пример: простой сценарий развёртывания новой версии PostgreSQL 9.3 на debian-based ОС. Роли в этом примере не используются.
--- - name: install postgresql 9.3 # имя playbook'a # секция, описывающая параметры, которые нужно уточнить у пользователя в начале запуска vars_prompt: hosts: "Please enter hosts group name" # спрашиваем имя группы серверов в инвентаре (в нашем случае файл $ANSIBLE_HOSTS) username: "Please enter username for auth" # спрашиваем имя пользователя для подключения к серверам hosts: $hosts # user: $username sudo: True accelerate: true vars: app_username: 'app_user' # имя пользователя мифического приложения, которое работать с базой данных app_host_ip: '192.168.0.100' # ip-адрес хоста с запущенным приложением, с него будут поступать запросы в базу данных app_database_name: 'appdb' # имя базы данных приложения tasks: # Проверяем установлен ли и устанавливаем пакет python-software-properties # для обеспечения работы модуля apt. Параметры модуля: # pkg - имя пакета для установки # state - устанавливаем последнюю версию пакета, # update_cache - обновляем список доступных пакетов перед установкой - name: check add-apt-repository apt: pkg=python-software-properties state=latest update_cache=yes # добавляем ключ официального apt-репозитория проекта postgresql # Параметры модуля: # url - URL до файла с ключём # state - добавить ключ - name: add apt key apt_key: url=http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc state=present # добавляем репозиторий, адрес формируется на основе имени релиза установленной ОС - name: add apt repo apt_repository: repo='deb http://apt.postgresql.org/pub/repos/apt/ ${ansible_lsb.codename}-pgdg main' # устанавливаем пакет с ключём для последующего возможного автоматического обновления - name: install pgdg-key apt: pkg=pgdg-keyring state=latest update_cache=yes # устанавливаем пакеты postgresql-9.3 (непосредственно сам сервер баз данных) # и python-psycopg2 - для работы модулей postgresql_user, postgresql_db, postgresql_privs - name: install packages apt: pkg=$item state=latest with_items: - postgresql-9.3 - python-psycopg2 - python-keyczar # создаём пользователя для работы нашего мифического приложения c атрибутом LOGIN # сгенерированный пароль будет сохранён в credentials/имя_хоста/postgres/имя_пользователя - name: create postresql user for some app # выполнение задачи будем производить с правами пользователя postgres (создаётся при установке postgresql) sudo: yes sudo_user: postgres postgresql_user: user=${app_username} password="{{ lookup('password','example/credentials/' + inventory_hostname + '/postgres/' + app_username, length=15) }}" role_attr_flags=LOGIN # создаём базу данных для мифического приложения с говорящими за себя параметрами - name: create db for our app sudo: yes sudo_user: postgres action: postgresql_db name=${app_database_name} owner=${app_username} encoding='UTF8' lc_collate='en_US.UTF-8' lc_ctype='en_US.UTF-8' template='template0' state=present # Следующая задача будет выполнена хосте приложения, а не на текущем настраиваемом хосте - name: add app_user password to .pg_pass file on server with our app sudo: yes sudo_user: ${app_username} delegate_to: ${app_host_ip} lineinfile: dest=/home/${app_username}/.pgpass regexp='^{{ inventory_hostname }}\:\*\:${app_database_name}\:${app_username}' line='{{ inventory_hostname }}:*:${app_database_name}:${app_username}:{{ lookup('password','example/credentials/' + inventory_hostname + '/postgres/' + app_username, length=15) }}' create=yes state=present backup=yes # добавляем в pg_hba.conf строчку, описываюшую разрешение подключение с ip-адреса приложения для ранее созданной базы и пользователя - name: add entry to pg_hba.conf lineinfile: dest=/etc/postgresql/9.3/main/pg_hba.conf regexp='host ${app_database_name} ${app_username} ${app_host_ip}/32 md5' line='host ${app_database_name} ${app_username} ${app_host_ip}/32 md5' state=present # если файл изменился, то вызовем задачу по перечитыванию конфига postgresql # напоминаем что модули ansible возвращают состояние "изменилось/не изменилось" после выполнения, # хэндлеры описываются либо в конце playbook'a или в отдельном файле notify: - reload postgres # по умолчанию postgresql слушает только localhost # изменияем соответствующий параметр в postgresql.conf на ip-адрес сервера - name: add entry to postgresql lineinfile: dest=/etc/postgresql/9.3/main/postgresql.conf regexp='^listen_addresses' line="listen_addresses = '${ansible_default_ipv4.address}'" state=present # если файл изменился, то вызовем задачу по перезапуску postgresql, т.к. # параметр listen_addresses можно изменить только перезагрузкой сервера postgresql notify: - restart postgres # описание хэндлеров handlers: # перечитываем конфигурацию postgresql - name: reload postgres sudo: yes action: service name=postgresql state=reloaded # перезагружаем postgresql - name: restart postgres sudo: yes action: service name=postgresql state=restarted
Ansible AWX
Во всех приведенных выше примерах управление Ansible осуществляется с помощью интерфейса командной строки. Но с официального сайта можно загрузить графическую панель управления Ansibleworks AWX, очень симпатичную внешне и удобную в использовании. Собственно, за счет ее распространения и осуществляется монетизация продукта: управление небольшим (до 10) количеством серверов обходится бесплатно, если же серверов больше — нужно приобретать лицензию. Похожие варианты монетизации используются и разработчиками конкурирующих решений — Puppet и Chef.
Заключение
Ansible — достаточно простой, но при этом эффективный инструмент для удаленного управления конфигурациями. В рамках этой статьи мы сделали лишь беглый обзор его возможностей и рассказали, как пишутся сценарии для решения простых задач. Все возможности и варианты использования Ansible в рамках одной статьи охватить невозможно. О применении этого инструмента для решения более специфических задач мы расскажем в последующих публикациях.
Для желающих узнать больше — несколько ссылок:
https://github.com/ansible/ — официальный аккаунт на github c исходным кодом проекта и хорошим набором примеров проектов
http://www.ansibleworks.com/docs/ — официальная документация;
http://jpmens.net/2012/06/06/configuration-management-with-ansible/ — статья Яна Пита Менса об управлении конфигурациями с помощью Ansible (в его блоге есть и много других материалов по теме).
https://gist.github.com/marktheunissen/2979474 — пример сценария с подробными комментариями, правда для старой версии.
http://www.ansibleworks.com/docs/contrib.html — ещё больше ссылок на примеры использования, включая в том числе и очень сложные конфигурации.
ansible-playbook <filename.yml> … [options]