Итак, у нас есть группы хостов:
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. Мой внутренний редактор ушел в отпуск. Прошу простить за ошибки и неровный почерк. Я всегда читаю ЛС и всегда говорю «спасибо» за исправления :)
This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:
- Massacres That Matter - Part 1 - 'Responsibility To Protect' In Egypt, Libya And Syria
- Massacres That Matter - Part 2 - The Media Response On Egypt, Libya And Syria
- National demonstration: No attack on Syria - Saturday 31 August, 12 noon, Temple Place, London, UK
Комментариев нет:
Отправить комментарий