ansible

от автора

Ansible — yet another система управления конфигурациями. Отличительная особенность — простота, при большой гибкости. И это не просто слова — дальше я покажу на примерах несколько простейших операций и познакомлю вас с некоторыми “бест практис”.

Итак, у нас есть группы хостов:

WebServersG1 webserver1-g1, webserver2-g1
WebServersG2 webserver1-g2, webserver2-g2
WebServersProxy webserver-proxy1, webserver-proxy2
DataBase db1, db2
DataBaseSlave dbs1, dbs2
SomeServers someserver1, someserver2

Мы хотим подготовить все хосты к адекватной работе — установить необходимый набор софта (htop, zsh, vim, iftop, sudo, mc, tmux, wget), скопировать свои ключи и конфиги и поставить и сконфигурировать софт специфичный для этого сервера.
Ansible подразумевает минимум два файла для начала работы — инвентарный файл, в который мы пишем список хостов и делим их по группам — inventory и файл задачplaybook.
Они нужны для того, чтобы когда мы все сделаем запустить все красиво:

ansible-playbook -i инвентарный_файл playbook.yml 

Давайте создадим инвентарный файл по имени “infrastructure” на основе наших хостов:

[WebServersG1] webserver1-g1 webserver2-g1 [WebServersG2] webserver1-g2 webserver2-g2 [WebServersProxy] webserver-proxy1 webserver-proxy2 [DataBase] db1 db2 [DataBaseSlave] dbs1 dbs2 [SomeServers] someserver1 someserver2 

Собственно все не плохо, но наши хосты из групп WebServersG1 и WebServersG2 отличаются только структурой каталогов, количеством подключений и репозиторием. А WebServersProxy отличается от них только конфигом nginx и отсутствием какого-то софта. К тому же может понадобиться сделать какую-то задачу сразу на всех трех группах. Так что давайте сродним эти три группы и дадим им родителя:

[WEB:children] WebServersG1 WebServersG2 WebServersProxy 

По структуре инвентарного файла: ansible считает группой все, чья строка в описании начинается с ‘[‘ и заканчивается ‘]’. Все что под этой строчкой и до начала следующей группы — хосты. У группы могут быть дети — другие группы, которые перечисляются после [название группы:children] и существуют.
Еще я коротко коснусь переменных, которые тоже можно описывать сразу в этом списке.

webserver1-g1 ansible_ssh_port=5555 ansible_ssh_host=192.168.1.50 

Тут мы назначили ssh порт и ssh хост. По практике скажу что другие переменные в inventory файле его захламляют и делают нечитаемым. Да и нестандартные порты лучше перечислять в таком виде:

webserver1-g1:5555 

И раз уж зашла речь о портах — порт по умолчанию для всех хостов (как и многое другое) можно назначить и в ansible.cfg, но не суть.
Итак, мы только что создали список хостов. Давайте теперь создадим список задач, а я окунусь в воспоминания.
В далекие времена (примерно май-июль этого года), до выхода версии 1.2 ролей не было в принципе, мы довольствовались обычными тасками и плейбук выглядел как елка пестревшая инклудами. Потом появились роли, а совсем недавно, неделю-две назад, в версии 1.3 — наследование ролей. И мы, как истинные джедаи, будем пользоваться тем что имеем сейчас. И давайте разберемся, наконец, что такое эти плейбуки и роли, а то непонятно.

Плейбуки — исполняемый набор чего-угодно. Они, в отличии от chef, запускаются один раз и только по вашей команде. Хотя нет ничего, что бы помешило поставить задачу в крон. Раньше под плейбуком подразумевался основной список задач, но сейчас они превратились в набор указателей на нужные нам роли.
Роли, в свою очередь, это набор задач, шаблонов, тригеров-обработчиков, переменных, файлов и ссылок на другие роли распиханых по каталогам в стандартной для ansible структуре. Роли лучше всего группировать по логическим группам. Я, например, в рамках поставленной выше задачи, выделю такие роли:
1) преподготовка — установка разных админских софтов, создание пользователей с ключами, генерация локалей, копирование конфигов и т.п.
2) установка софта для нужного в итоге сервиса и копирование его конфигов
3) создание необходимой структуры директорий, копирование гита и т.п.

Итак, начнем создание плейбука main.yml c пока единственной ролью preconf

- hosts: all   roles:      - preconf   tags: preconf 

Тут мы для всех хостов ‘all’ назначили роль preconf и добавили тег preconf. Теги нужны для того, чтобы потом иметь возможность исполнить только какую-то часть плейбука.
Дальше, когда ansible видит назначение роли он начинает судорожно искать одноименный каталог в ./roles. В нашем случае это будет ./roles/preconf
Там уже должна быть готова структура роли, а именно: tasks/main.yml
Это основной файл, в котором перечисляются задачи.
Также там может существовать каталог ‘templates’, в который мы складываем шаблоны конфиг-файлов, ‘files’, в котором будут лежать разные нужные нам файлы, ‘handlers’ — те самые преславутые тригеры-обработчики, а так же meta/main.yml в котором мы описываем ссылки на роль и variables — переменные роли.
Я предлагаю разбираться по-порядку, но если хотите — можете читать текст серху вниз резко дергая головой справа-налево.
Итак, задачи.
Для нашей роли preconf создадим файл ./roles/preconf/tasks/main.yml
Обычно задачи выглядят так:

- name: Имя задачи   модуль: параметры модуля. 

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

- name: installing zsh   apt: pkg=zsh 

В данном случае мы использовали модуль apt для установки на наши Debian сервера программы zsh.
Конечно, можно заспамить таск-лист отдельной задачей под установку каждой программы, но такие файлы очень плохо читаются. Поэтому мы воспользуемся очередью, котораю можно вызвать через ‘with_items

- name: installing zsh   apt: pkg=$item   with_items:     - zsh     - htop     - sudo     - iftop     - tcpdump     - mc     - wget     - vim     - tmux     - facter 

В синтаксисе YAML, если я ничего не путаю, подобная запись обозначает массив. И элементы этого масива, словно бы пройдя через xargs по очереди присваиваются переменной item, которую мы вызвали выше и уже с новым значением выполняются в задаче. Раз за разом, пока не закончится список.
Теперь мы создадим всех пользователей отдела сопровождения и к случаю воспользуемся переменными.
Добавление пользователей выглядит как-то так:

- name: Add User Pupkin   user: name=’pupkin’ 

Так наш пользователь добавится на ура. Но мы же хотим чтобы пользователь пупкин пользовался zsh и являлся членом группы sudo? А давайте так и сделаем, ведь модуль ‘user’ поддерживает кучу всего.

- name: Add User Pupkin   user: name=’pupkin’ shell=’/bin/zsh’ groups=’sudo’ 

Мы уже выучили with_items, поэтому мы им воспользуемся для добавения нескольких пользователей… Но… это ведь надо две переменных передавать в потоке…
Ничего сложного. Ansible поддерживает хеши — массивы в виде ‘ключ: значение’. Удобнее всего записывать хеши в jinja2 формате.

- name: Add BestAdminsTeam   user: name={{ item.user }} shell={{ item.shell }} groups=’sudo’   with_items:     - { user: ‘pupkin’, shell: ‘/bin/zsh’ }     - { user: ‘oldfag’, shell: ‘/bin/sh’ } 

Собственно что мы только что сделали? Мы создали массив такого вида:
{ {user: ‘pupkin’, shell: ‘/bin/zsh’ }, { user: ‘oldfag’, shell: ‘/bin/sh’ } } из которого будем брать элементы по-одному и присваивать их переменной $item ( или, выражаясь jinja2 языком — {{ item }} ). После этого мы будем открывать элементы хеша — {{ item.user }} и {{ item.shell }} соответственно. То есть мы получим консистентный список переменных для каждого пользователя.
Теперь давайте добавим ключи нашим пользователям. Для этого существует отличный модуль ‘authorized_key

- name: Add BestAdminsKey   authorized_key: user={{ item.user }} key="{{item.key}}"   with_items:     - { user: ‘pupkin’, key: ‘ssh-rsa pupkin_pub_key’ }     - { user: ‘oldfag’, key: ‘ssh-rsa oldfag_pub_key’ } 

В принципе оно работать будет и так, но немного неудобно каждый раз перечислять пользователей: очень легко забыть куда-то добавить нового члена команды и мне надо как-то рассказать про переменные, а тут такой случай 🙂
Давайте заведем переменную, в которую складем всех наших пользователей с их ключами, шелами, возможно, путями к конфигам и всем остальным. Скласть юзеров в переменную легко, но куда скласть саму переменную?
Для этого у ansible существует несколько вариантов решения.
Можно указывать переменные конкретно для каждого хоста в папке ./host_vars/имя_хоста — это не пододит для нашей цели
Можно делать переменные для группы хостов (группы, напомню, мы делали в файле инвентаризации) и ложить их в ./group_vars/имя_группы — можно, конечно, создать переменную тут, для группы all. Но это не “чистая работа”.
Можно присваивать переменные прямо в плейбуках. Но так смотриться неопрятно.
Еще у каждой роли есть “значения по умолчанию” в каталоге defaults, но это не совсем то, что нам надо
Ну и наконец переменные роли — похоже это самое то.

Создадим файлик ./roles/main/variables/main.yml в который запишем:

ssh_super_team:     - { user: ‘pupkin’, key: ‘ssh-rsa pupkin_pub_key’, shell: ‘/bin/zsh’}     - { user: ‘oldfag’, key: ‘ssh-rsa oldfag_pub_key’, shell: ‘/bin/sh’ } 

Теперь в задаче можем использовать переменную $ssh_super_team вот так:

- name: Add BestAdminsTeam   user: name={{ item.user }} shell={{ item.shell }} groups=’sudo’   with_items:     - $ssh_super_team  - name: Add BestAdminsKey   authorized_key: user={{ item.user }} key="{{item.key}}"   with_items:     - $ssh_super_team 

Ну и чтобы сделать всем удобно — скопируем нашим пользователям файл .vimrc — одинаковый для обоих пользователей. В каталог ./roles/main/files/ сунем файл по имени ‘vimrc’ и сделаем такой таск:

- name: copy vimrc file   copy: src=”vimrc” dest=”/home/{{item.user}}/.vimrc”   with_items:     - $ssh_super_team 

Вуаля.

Кстати, я иногда перечитываю то, что пишу — получилось обьемно. Пора закруглять все что еще не круглое, поэтому я сразу перескочу на конфигурацию nginx:
По кальке создадим роль nginx и в файл ./roles/nginx/tasks/main.yml запишем:

- name: install nginx    apt: pkg=’nginx’ 

Хм, но ведь не может быть на всех серверах nginx с одинаковым конфигом? Может, конечно, но это неудобно. Давайте придумаем шаблон — в ansible для этого надо использовать знакомый многим jinja2 синтаксис.
Создаем файл в ./roles/nginx/templates/ и назовем его nginx_site_conf.j2
А содержимое сделаем таким:

server {   listen       80;   server_name  {{ item.sitename }};   root “/var/www” } 

Дальше нам надо разложить уникальные для каждого хоста перменные: накидаем файлов в ./host_vars/имя_хоста

nginx:   - { sitename: “www.example.com” } 

И определим переменную по-умолчанию для этой роли: в файле ./roles/nginx/defaults/main.yml

nginx:   - { sitename: “default” } 

Теперь создадим задачу:

- name: nginx config for sites   template: src=”nginx_site_conf.j2” dest=”/etc/nginx/sites-enabled/{{ item.sitename }}   with_items:     - $nginx   notify:     - restart nxinx 

И ansible, при проходе этого таска попробует скопировать шаблон ”nginx_site_conf.j2” с переменной $nginx, которая будет прочитана либо из переменных хоста, либо из дефолтных переменных роли. И если копируемый шаблон будет отличаться от конфига на машине — выполнится handler, который выглядит так: ./roles/nginx/handlers/main.yml

- name: restart nginx   action: service name=nginx state=reloaded 

Теперь соберем из этих двух намеренно упрощенных ролей одну единственную для группы хостов WebServersG1. Сейчас мы не будем ничего усложнять, а просто сделаем в ней ссылку на две уже готовые роли. Запишем в файл roles/myapp/meta/main.yml такие строчки:

--- dependencies:   - { role: preconf }   - { role: nginx } 

И, наконец, плейбук playbook.yml:

- hosts: WebServersG1   roles:     - WebServersG1 

Кстати, во время построения зависимостей тоже можно использовать переменные для уточнения какой-то информации конкретно для этой роли. Но об этом, как и о зависимостях, сложных шаблонах, проверки выполнения задачи, распаралеливание выполнения и многом-многом другом мы поговорим уже в следующий раз.
А пока можно почитать отличную, не то что у других, документацию: www.ansibleworks.com/docs/

P.S. Мой внутренний редактор ушел в отпуск. Прошу простить за ошибки и неровный почерк. Я всегда читаю ЛС и всегда говорю «спасибо» за исправления 🙂

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