ansible

в 5:29, , рубрики: Ansible, chef, puppet, системное администрирование, метки: , ,

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. Мой внутренний редактор ушел в отпуск. Прошу простить за ошибки и неровный почерк. Я всегда читаю ЛС и всегда говорю «спасибо» за исправления :)

Автор: ctrlok

Источник

Поделиться

* - обязательные к заполнению поля