Калина Алексей блог программиста

Ansible + Alt Linux

Сегодня мы обсудим проблему конфигурирования серверов. Когда ваша система размещается на одном физическом сервере, такой проблемы не возникает. Вы постепенно настраиваете операционную систему, устанавливаете необходимое программное обеспечение, накатываете изменения, когда это необходимо. Если же вы решите горизонтально масштабировать систему, появится ряд вопросов: Как автоматически собрать кластер из нескольких серверов с голой ОС? Как одновременно обновить программное обеспечение на разных машинах? Причем такие вопросы будут актуальны для нескольких групп серверов, различных по задачам: это могут быть сервера приложений или баз данных, кластеры полнотекстового поиска или кэширования.

Необходим инструмент, который позволит гибко управлять состоянием множества машин, на котором живет ваша система. Такие инструменты называются системами управления конфигурациями. Ansible, Chef, Puppet – лидеры на этом рынке, и далее мы рассмотрим, в чем их основные отличия. Как вы поняли из названия статьи, мой выбор пал на Ansible. Мы попробуем использовать этот продукт на примере развертывания кластера Elasticsearch.

В последние несколько лет в законодательстве России началось активное движение в сторону отечественного программного обеспечения в государственных структурах. Это движение коснулось и нашей компании, так как многие заказчики являются различными министерствами. Скоро в гос. структурах будет установлен российский дистрибутив ОС ALT Linux (Альт Линукс). В связи с этим в задаче конфигурирования серверов с помощью Ansible возникает новый вопрос, с которым мы будем разбираться: насколько поддерживает Ansible российскую ОС?

Системы управления конфигурациями

Рассмотрим три главные системы управления конфигурациями на сегодняшний день:

Все эти системы управления конфигурациями – хорошо проработанные крупные продукты. Тем не менее, мой выбор пал на Ansible. Несмотря на мощь конкурентов, Ansible берет своей простой: отсутствием необходимости установки агентов и изучения не самого популярного языка Ruby, а также своей легкой начальной настройкой.

Первоначальная настройка

Для демонстрации возможностей Ansible я буду использовать несколько виртуальных машин с установленным ALT Linux Server. Скачать дистрибутив можно отсюда. Одна из виртуалок будет использоваться как Ansible-хост, остальные будем пробовать конфигурировать.

В первую очередь нужно установить Ansible на один из серверов:

apt-get install ansible

Далее проведем начальную настройку машин, которые будем конфигурировать. Основное отличие использования Ansible с дистрибутивами ALT Linux от других дистрибутивов заключается именно в этом шаге. Непосредственное же использование системы на разных ОС семейства Unix отличается незначительно (например, вызываются разные менеджеры пакетов).

Необходимо предоставить пользователю, под которым мы будем заходить на сервер, права sudo. Пользователь, который создается при установке ОС, автоматически добавляется в группу wheel. Поэтому достаточно раскомментировать строчку WHEEL_USERS ALL = (ALL) ALL в файле /etc/sudoers.

Далее добавим возможность подключаться к удаленному серверу под созданным пользователем без ввода пароля. Для этого на Ansible-сервере выполним команды ssh-keygen и ssh-copy-id. Первая создает ключ SSH, а вторая устанавливает его на удаленный сервер.

И последний шаг перед использованием Ansible – это установка Python-пакета simplejson. Все ответы Ansible отправляет в формате JSON, а дистрибутив ALT Linux не содержит этот пакет. Решается это двумя командами:

apt-get install python-module-pip
pip install simplejson

Группы серверов

Чтобы управлять состоянием машин, не перечисляя каждый раз список айпишников, в Ansible есть файл Inventory. В нем можно определить группы серверов и указать имена, по которым к ним обращаться. Файлом Inventory на самом деле называется файл hosts, который находится в директории /etc/ansible:

[servers]
alt.linux1 ansible_ssh_host=192.168.33.10 ansible_ssh_user=kalina
alt.linux2 ansible_ssh_host=192.168.33.11 ansible_ssh_user=kalina
alt.linux3 ansible_ssh_host=192.168.33.12 ansible_ssh_user=kalina

Для каждого хоста можно указать название и пользователя, под которым мы будем на него заходить. В Linux системах не рекомендуется использовать для подключения по SSH root-пользователя. Поэтому используем обычного пользователя, которого добавили в группу wheel на этапе настройки Ansible.

Модули

Модули – это готовые библиотеки, которые позволяют взаимодействовать с системными ресурсами (например: сервисами, пакетами, файлами) и обрабатывать системные команды. Модули можно использовать в командной строке или в плейбуках (об этом в следующем разделе). Для вызова из командной строки нужно выполнить команду ansible “host” -m “module”. Рассмотрим простейший модуль ping, который подключается к серверу, проверяет, что все необходимые python модули установлены и отвечает pong.

ansible servers -m ping

Ответ:

alt.linux1 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
alt.linux3 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
alt.linux2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Большинство модулей требует передачи аргументов. В командной строке для этого служит ключ -a, после которого следует строка из имен и значений аргументов в формате “name1=value1 name2=value2”.

В Ansible есть модули для самых разных задач, поэтому прежде чем описывать сложную последовательность действий для решения вашей проблемы, проверьте нет ли в Ansible соответствующего модуля. Полный список представлен в документации. Рассмотрим несколько простых модулей, которые наверняка вам пригодятся:

Важная идея модулей в их идемпотентности. То есть, состояние машины после повторного выполнения модуля останется тем же, что и при первом. Этой же идее следуют и плейбуки.

Плейбуки

Использование готовых модулей для целых групп серверов добавит автоматизации в наш процесс настройки хостов, но этого еще явно недостаточно. В реальных задачах мало вызова одного модуля, в большинстве случаев необходима определенная последовательность действий на сервере. Для этого можно написать скрипты с набором нужных команд, но в Ansible для этого есть более удобное решение.

Плейбуки (playbooks) – основной инструмент для конфигурирования серверов в Ansible. На первый взгляд это и есть та самая последовательность команд, только представленная не bash-скриптом, а YAML файлом. Тем не менее, плейбуки обладают возможностями, которые простыми скриптами реализовать значительно сложнее.

Рассмотрим структуру плейбуков:

Elasticsearch

Давайте используем Ansible для решения реальной задачи. Напишем плейбук, который разворачивает кластер из поисковых движков Elasticsearch на нескольких хостах. Для начала добавим в файл Inventory новую группу хостов, которую назовем elastic. Пусть в ней будет пара серверов из тех, что мы уже определили.

[elastic]
alt.linux1 ansible_ssh_host=192.168.33.10 ansible_ssh_user=kalina
alt.linux2 ansible_ssh_host=192.168.33.11 ansible_ssh_user=kalina

Пропишем в поле hosts плейбука только что созданную группу. Первое задание будет устанавливать Java. Для этого воспользуемся модулем apt_rpm, который устанавливает указанный пакет. Это действие нужно выполнять с sudo-привелегиями, поэтому используем параметр become в начале плейбука.

Теперь воспользуемся модулем unarchive. Он распаковывает архив с Ansible-хоста на удаленный сервер. Интересно, что в первоначальной версии плейбука я использовал три другие модуля для этого действия: copy для копирования архива, command с выполнением tar для распаковки и command с выполнением chown для присвоения нужных прав. Однако, в результате выполнения плейбука, Ansible вывел подсказку об упрощении этих команд.

Далее создадим конфиг Elasticsearch, который будем копировать на сервера. Пока укажем в нем только параметр network.host со значением 0.0.0.0. По умолчанию Elasticsearch использует значение 127.0.0.1, но для создания кластера нам необходимо, чтобы движок был доступен в публичной сети. Добавим задание copy, которое копирует созданный конфиг на удаленные хосты.

Осталось только запустить полнотекстовый движок. Для этого используем модуль command. Это задание будем выполнять не под sudo, поэтому укажем become: false. Также мы не будем дожидаться окончания выполнения этого задания, для этого пропишем async: 10 (асинхронно в течение 10 секунд) и poll: 0 (не проверять состояние). Так выглядит наша первая версия плейбука:

hosts: elastic
become: yes
tasks:

  name: install java
  apt_rpm: pkg=java-1.8.0-openjdk
  
  name: unpuck elastic
  unarchive:
    src: ../files/elasticsearch-6.2.3.tar.gz
    dest: /home/kalina
    owner: kalina
    mode: 0700
  
  name: copy config 
  copy: 
    src: ../configs/elasticsearch.yml 
    dest: /home/kalina/elasticsearch-6.2.3/config/elasticsearch.yml 
    owner: kalina 
    mode: 0700

  name: start elastic
  become: false
  command: /home/kalina/elasticsearch-6.2.3/bin/elasticsearch -d -p pid
  async: 10
  poll: 0

Выполнение плейбука осуществляется командой ansible-playbook “имя плейбука”.

Обработка событий

Преобразуем задание по запуску Elasticsearch в обработчик события. Событие же будем генерировать при изменении конфига движка. Я вижу два преимущества событий перед обычными заданиями:

Так изменился плейбук:

tasks:
  #…
  name: copy config 
  copy: 
    src: ../configs/elasticsearch.yml 
    dest: /home/kalina/elasticsearch-6.2.3/config/elasticsearch.yml 
    owner: kalina 
    mode: 0700
    notify:
      start elastic     

handlers:
  name: start elastic
  become: false
  command: /home/kalina/elasticsearch-6.2.3/bin/elasticsearch -d -p pid
  async: 10
  poll: 0

Переменные

На текущий момент в нашем конфиге Elasticsearch, есть информация только о том, что мы используем публичный ip. Эта информация представляется одинаковым образом для всех хостов, поэтому проблемы с реализацией этого в Ansible не возникло. Но если нам потребуется использовать специфичную для серверов информацию, то появятся вопросы: где ее указать и как ее оттуда извлечь? (Переменные и шаблоны)

В Ansible есть несколько мест, где можно определять переменные, и с двумя из них мы уже столкнулись.

Создадим файлы переменных для группы elastic и каждого из хостов этой группы. Определим в них имя кластера и имена нод (каждого сервера).

Файл host_vars/alt.linux1 (для alt.linux2 аналогично):

node_name: test-node 1

Файл group_vars/elastic:

cluster_name: test-cluster

Шаблоны

Теперь, когда переменные определены, нужно научиться их использовать в нашем конфиге. Для этого в Ansible существует модуль template. Он заполняет указанный шаблон данными и переменными, доступными при выполнении плейбука, и копирует его на удаленный сервер. В Ansible используется Python-шаблонизатор Jinja2. Создадим шаблон конфига Elasticsearch, куда добавим информацию о имени кластера и именах нод.

network.host: 0.0.0.0
node.name: {{ node_name }}
cluster.name: {{ cluster_name }}

Шаблонизатор Jinja2 поддерживает и более сложные конструкции, чем подстановка значений переменных. Например, в шаблонах можно описывать условия и циклы. Кроме того, в шаблонах можно использовать не только переменные, которые мы определили самостоятельно. В них доступно все, что было собрано первым модулем любого плейбука (кроме тех, где было указано обратное) - setup. Воспользуемся этими двумя идеями для того, чтобы добавить настройку обнаружения узлов кластера в конфиг. В поле discovery.zen.ping.unicast.hosts укажем список IP серверов группы elastic.

discovery.zen.ping.unicast.hosts: [
  {% set comma = joiner(", ") %} 
  {% for host in groups['elastic'] -%} {{ comma() }}
    {{ hostvars[host]['ansible_enp0s3']['ipv4']['address'] }} 
  {%- endfor -%}
]

Осталось изменить задание с копированием конфига, указав модуль template и путь к шаблону.

name: copy config 
template:
  src: ../templates/elasticsearch.j2
  dest: /home/kalina/elasticsearch-6.2.3/config/elasticsearch.yml 
  owner: kalina 
  mode: 0700 
  notify:
    start elastic

Выполнение плейбука разворачивает кластер Elasticsearch на конфигурируемых серверах.

Роли

Напоследок преобразуем наш плейбук в роль. Это нужно, чтобы держать все необходимые артифакты в единообразной структуре и получать от этого разные плюшки. Роль реализуется засчет особой структуры директорий:

roles/
  some_role/
    files/
    templates/
    tasks/
    handlers/
    vars/
    meta/

Один из плюсов ролей состоит в том, что это стандартная структура, и вы можете использовать готовые роли из публичных репозиториев. Помимо этого, Ansible предлагает некоторые дополнительные возможности при использовании ролей. Например, зависимости ролей друг от друга – роль будет автоматически выполнена после той, от которой зависит.

Итак, создадим роль elastic_role. Для этого перенесем архив с Elasticsearch в папку files, а шаблон конфига в папку templates. Список заданий вынесем в файл main.yml (дефолтное имя) директории tasks, а обработчик события в main.yml директории handlers. Директория vars используется для переменных, специфичных для роли (нам такие не нужны), а meta включает описание зависимостей. Осталось описать новый плейбук, который будет запускать нашу роль:

hosts: elastic
become: yes
roles:
  { role: elastic_role }

Заключение

Мы рассмотрели только некоторые основы Ansible. После внедрения этого решения в производственную среду будет больше реальных примеров и описания подводных камней. Первый же обзор показал, что с Ansible можно без труда решать задачи конфигурирования серверов, причем с полноценной поддержкой ALT Linux.