Система управления Ansible

от автора

Представьте себе, что вам нужно управлять парком серверов, расположенных к тому же в разных географических точках. Каждый из этих серверов требует настройки, регулярного обновления и мониторинга. Конечно, для решения этих задач можно воспользоваться самым простым способом: подключиться к каждому серверу по 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).

Установка

Требования для установки 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 может получать двумя основными способами:

Файл 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 (если, конечно, этому пользователю это разрешено использовать sudo):

--- - 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 — пример сценария с подробными комментариями, правда для старой версии.

www.ansibleworks.com/docs/contrib.html — ещё больше ссылок на примеры использования, включая в том числе и очень сложные конфигурации.

Для тех кто не может комментировать посты на Хабре, приглашаем к нам в блог.

ссылка на оригинал статьи http://habrahabr.ru/company/selectel/blog/196620/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *