Работа с Ansible — задачи с несколькими неизвестными

от автора

Гуглил информацию по Ansible, наткнулся на статью на Хабре. Прочитал и сильно удивился: ведь можно сделать красивее! Если вы заинтересованы — добро пожаловать под кат!

Сразу замечу — это всё работает на реальном проекте (количество узлов — больше 80, но меньше 100).

Вот хитрые задачи с неизвестными, с которыми сталкивается как минимум каждый третий, а может, и второй DevOps.

  1. В качестве параметра конфигурационного файла использовать заранее неизвестные адреса узлов, причём эти узлы относятся к другой роли (решение)
  2. Сформировать inventory из неизвестных адресов узлов динамически — через обращение к службам AWS (решение)
  3. Cформировать конфигурационный файл Nginx, в котором должны быть прописаны заранее неизвестные адреса backend-узлов (решение)

Итак, сначала — самое простое:

Подстановка адресов узлов

Возьмём пример: у нас есть группа узлов «app_nodes», на которые ставится некое приложение с помощью файла заданий вот такого вида:

application.yml

 --- - hosts: app_nodes   gather_facts: yes   roles:     - role: common     - role: application 

Представьте, что у нас есть сервис ZooKeeper, а в конфигурационный файл приложения необходимо прописать настройку для работы с этим сервисом:
zk.connect=127.0.0.1:2181,127.0.0.2:2181,127.0.0.3:2181,127.0.0.4:2181, 127.0.0.5:2181
Понятно, что ручками прописывать адреса всех, к примеру, пяти узлов, на каждый из которых установлен ZooKeeper, на узлы с приложением — никакого удовольствия. Что же сделать, чтобы Ansible вставлял в шаблон конфигурационного файла все адреса этих узлов?
Да ничего особенного. Ansible использует шаблонизатор Jinja2 поверх YAML, поэтому используем цикл шаблонизатора:
zk.connect={% for host in groups['zk_nodes'] %}{{ hostvars[host]['ansible_eth0']['ipv4']['address'] }}:{{ zk_port }}, {% endfor %}
В результате после работы шаблонизатора должна получиться искомая строчка, но вот незадача: мы работаем с узлами app_nodes, а используем в этом шаблоне сведения («факты») об узлах zk_nodes. Как получить адреса узлов zk_nodes, если в данном файле заданий мы вообще не работаем с этими узлами? Назначим этой конкретной группе узлов (zk_nodes) пустой список заданий:

application.yml

 --- - hosts: zk_nodes   gather_facts: yes   tasks: [] - hosts: app_nodes   gather_facts: yes   roles:     - role: common     - role: application 

Для работы этого файла заданий необходимые группы узлов должны быть заданы в inventory-файле:

environments/test/inventory

[zk_nodes]
127.0.0.1
127.0.0.2
127.0.0.3
127.0.0.4
127.0.0.5
[app_nodes]
127.0.0.10
127.0.0.11
127.0.0.12

А что же делать, если адреса хостов заранее неизвестны? К примеру — используются виртуальные машины в EC2? Здесь мы плавно переходим к ответу на второй вопрос.

Работа с динамическим inventory

Информации по этой теме в Интернете не слишком много — насколько я понял, ввиду существования Ansible Tower.

Итак, у вас есть некоторое количество узлов в EC2, и вы хотите централизованно и легко управлять ими. Одно из возможных решений — использовать Ansible + скрипт динамического inventory.

Вот как выглядит структура соответствующего inventory-каталога:

tree ./environments/dynamic

 . ├── ec2.ini ├── ec2.py ├── groups └── group_vars     ├── all     ├── zk_nodes     ├── proxies     └── app_nodes 

Здесь: ec2.py — сам скрипт, его можно взять здесь, ссылка прямая; ec2.ini — файл с настройками скрипта, его можно взять здесь, ссылка прямая; groups — файл, описывающий группы узлов, с которыми вы собираетесь работать в этом inventory, group_vars — каталог, содержащий значения переменных как для каждой конкретной группы, так и общие для всех.

Далее под спойлером — те настройки, которые у меня отличаются от ini-файла по ссылке:

Изменённые параметры ec2.ini

#регионы, где находятся наши узлы
regions=us-east-5, us-west-2
#работаем только с «живыми» (running) узлами
instance_states=running
#все параметры group_by_…. закомментированы, кроме одного:
group_by_tag_keys=true
#Отбираем, какие именно узлы войдут в наш inventory. В данном случае — те, у которых прописан тег «Name» с указанными значениями.
instance_filters = tag:Name=zk_node, tag:Name=app_node, tag:Name=proxy

Для того, чтобы Ansible корректно распознавал эти группы и узлы, пишем файл groups:

файл groups

 [all:children] zk_nodes proxies app_nodes  [zk_nodes:children] tag_Name_zk_node  [proxies:children] tag_Name_proxy  [app_nodes:children] tag_Name_app_node  [tag_Name_zk_node] [tag_Name_proxy] [tag_Name_app_node] 

Тут мы сообщаем Ansible, что группа узлов all, с которой он работает по умолчанию, содержит вложенные группы zk_nodes, proxies, app_nodes. Далее сообщаем, что эти группы также имеют вложенные группы, которые формируются динамически, а поэтому в их описаниях узлы вообще не указываются. Такая вот чёрная магия — во время своей работы скрипт динамического inventory создаст группы вида tag_<Имя тега>_<Значение тега> и наполнит эти группы узлами, а дальше можно работать с этими группами обычными средствами Ansible.

Да, сразу о каталоге group_vars. Он автомагически читается Ansible при загрузке inventory, и каждая группа в этом inventory получает переменные из файла group_vars/имя_группы. Один из примеров использования — отдельный ключ для конкретной группы узлов:

group_vars/zk_nodes

ansible_ssh_private_key_file: "/tmp/key.pem"

Рассмотрев динамический inventory, мы можем изящно решить третью задачу:

Формирование конфигурационного файла Nginx

Понятно, что шаблоном конфигурации Nginx никого на Хабре не удивить, поэтому ограничусь только блоком upstream с пояснениями.

nginx.conf upstream

upstream app_nodes {
{% for item in groups[‘app_nodes’] %} server {{ hostvars[item][‘ec2_private_ip_address’] }}:{{ app_port}};
{% endfor %}
keepalive 600;
}

Этот блок конфигурации определяет группу upstream-серверов с разными адресам и общим для всех номером порта.
Шаблонизатор пробежится по всем узлам группы app_nodes, сгенерировав по строчке для каждого узла. Получится вот такой

Пример результата

 upstream app_nodes { 127.0.0.1:3000; 127.0.0.2:3000; 127.0.0.3:3000; 127.0.0.4:3000; keepalive 600; } 

Здесь отличие от ситуации с первым решением в отсутствии необходимости дополнительно обращаться с пустым списком заданий к группе узлов «app_nodes» — эта группа автоматически создаётся в числе прочих согласно файлу groups, приведённому выше, благодаря скрипту динамического inventory. Ну и, конечно, используется обращение по внутренним адресам VPC.


Послесловие

Названия окружений, задач, inventory, узлов, IP-адреса заменены на вымышленные. Любые совпадения являются случайными. Там, где имена файлов или каталогов важны для функционала, приведены объяснения, почему они именно так называются.

Помните, вы и только вы сами несёте ответственность за состояние ваших проектов перед менеджером, заказчиком и собственной совестью. Я не претендую на полноту изложения. Надеюсь, эта статья сэкономит кому-то время, а кого-то подтолкнёт в верном направлении. В меру возможности и своего понимания отвечу на вопросы в комментариях.

ссылка на оригинал статьи http://habrahabr.ru/post/266481/