Ansible — сравнительно молодая система управления конфигурацией, его история насчитывает чуть более трех лет. Но, несмотря на это, он стремительно и быстро ворвался в мир систем управления конфигурацией, потеснив Chef, Puppet и SaltStack.
Давайте посмотрим на него внимательно, чтобы понять, почему он так любим технарями.
Итак, чем же хорош ansbile:
- низкий порог входа;
- декларативный язык описания конфигурации;
- на управляемые узлы не нужно устанавливать никакого дополнительного ПО;
- просто написать дополнительный модуль.
Низкий порог входа
Начать пользоваться ansible можно за пару минут. Допустим, вы используете OSX.
$ brew install ansible
$ ansible --version
ansible 1.8.4
configured module search path = None
Теперь создадим файл hosts
:
[test]
localhost ansible_connection=local
Поехали:
$ ansible -i hosts -m ping all
localhost | success >> {
"changed": false,
"ping": "pong"
}
Что мы сделали? Для всех хостов (параметр all
) из файла hosts выполнить модуль ping. Давайте посмотрим еще что-нибудь.
$ ansible -i hosts -a "ls -lah" all
localhost | success | rc=0 >>
total 12K
drwxr-xr-x 5 brun staff 170 Apr 1 11:50 .
drwxr-xr-x 91 brun staff 3.1K Apr 1 11:37 ..
-rw-r--r-- 1 brun staff 230 Apr 1 12:07 export.sh
-rw-r--r-- 1 brun staff 42 Apr 3 14:48 hosts
-rw-r--r-- 1 brun staff 376 Apr 1 12:49 playbook.yml
Если модуль (ключ -m
) не задан, то используется модуль command. Фактически, ansible можно использовать не только как систему управления конфигурацией, но и как фреймворк для распределенного выполнения команд.
Декларативный язык описания конфигурации
Помимо утилиты ansible, есть еще утилита ansible-playbook, которой вы будете пользоваться наиболее часто.
Для дальнейших примеров я завел машинку t2.micro на aws с Ubuntu 14.04 и прописал ее в hosts.
# hosts
[web]
111.111.111.111
Также, чтобы не вводить каждый раз в командную строку параметры, я в директории проекта создал файл ansible.cfg
.
# ansible.cfg
[defaults]
hostfile = hosts
После этого создадим наш первый сценарий (playbook в терминологии ansible) web.yml
.
# web.yml
---
- hosts: all
user: ubuntu
tasks:
- name: Update apt cache
apt: update_cache=yes
sudo: yes
- name: Install required packages
apt: name={{ item }}
sudo: yes
with_items:
- nginx
- postgresql
Запускаем наш первый сценарий, который обновляет кэш apt, а потом ставит два пакета: nginx и postgresql.
$ ansible-playbook web.yml
PLAY [all] ********************************************************************
GATHERING FACTS ***************************************************************
ok: [111.111.111.111]
TASK: [Update apt cache] ******************************************************
ok: [111.111.111.111]
TASK: [Install required packages] *********************************************
changed: [111.111.111.111] => (item=nginx,postgresql)
PLAY RECAP ********************************************************************
111.111.111.111 : ok=3 changed=1 unreachable=0 failed=0
Фактически, мы два раза вызвали модуль apt, только с разными параметрами. Сам же файл сценарии представляет себя файл на языке Yaml с вкраплениями шаблонизатора Jinja2.
На управляемые узлы не нужно устанавливать никакого дополнительного ПО
Действительно, для того, чтобы управлять машиной, на ней должен быть установлен Python (а он стоит по-умолчанию на всех современных linux системах) и должен быть доступ по ssh. Сравните это с остальными системами, где необходимо поставить клиента, которому нужны определенные версии различных языков и библиотек. Именно этот факт, кстати, делает старт с ansible гораздо более простым, чем для остальных систем.
Просто написать дополнительный модуль
Модуль можно написать на любом языке, он должен уметь принимать параметры на вход и выдавать json в ответ. Но зачем писать новый модуль, если уже есть 242 модуля на все случаи жизни (в версии 1.8.4). На случай, если вам действительно чего-то не хватает, есть хорошее описание того, как написать свой модуль.
Что-нибудь серьезное
Делать огромную простыню сценария не хотелось бы, поэтому давайте разобьем сценарий на части, используя механизм ролей.
Для тех, кому лень что-то писать самому, уже есть тысячи готовых ролей на сайте ansible galaxy, и они вполне достойного качества, чтобы пользоваться ими в бою.
Мы же создадим чистую роль.
$ ansible-galaxy init nginx -p roles
- nginx was created successfully
$ tree
├── roles
│ └── nginx
│ ├── README.md
│ ├── defaults
│ │ └── main.yml
│ ├── files
│ ├── handlers
│ │ └── main.yml
│ ├── meta
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ ├── templates
│ └── vars
│ └── main.yml
Доработаем файл web.yml
.
---
- hosts: all
user: ubuntu
sudo: yes
roles:
- nginx
Обратите внимание на ключ sudo: yes
. Он позволяет не писать в каждом задании эту строку. Большинство административных задач все равно должны выполняться с правами root
, поэтому имеет смысл оставить его здесь.
А в файле roles/nginx/tasks/main.yml
напишем следующее:
---
- name: Update apt cache
apt: update_cache=yes
- name: Install required packages
apt: name=nginx
Используя роли, можно повторно использовать код (хотя является ли Yaml кодом — философский вопрос). Конечно, пример намеренно упрощен, но из него становится ясно, как организовывать на ansible проекты с большим количеством компонент.
Факты
В ansible есть встроенный модуль setup
, который выполняется первым для всех управляемых нод. Давайте посмотрим, что он делает.
$ ansible -m setup all -u ubuntu
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"172.31.7.80"
],
"ansible_architecture": "x86_64",
"ansible_bios_date": "12/03/2014",
"ansible_bios_version": "4.2.amazon",
"ansible_cmdline": {
"BOOT_IMAGE": "/boot/vmlinuz-3.13.0-44-generic",
"console": "ttyS0",
"ro": true,
"root": "UUID=fd803688-5c41-4188-8a06-382a65a520bf"
},
"ansible_default_ipv4": {
"address": "172.31.7.80",
"alias": "eth0",
"gateway": "172.31.0.1",
"interface": "eth0",
"macaddress": "06:a8:07:41:47:a5",
"mtu": 9001,
"netmask": "255.255.240.0",
"network": "172.31.0.0",
"type": "ether"
}
... и так далее
Модуль setup собирает различные данные для ноды, это аналог ohai в chef (кстати, ansible тоже может использовать ohai для сбора информации). Все эти данные потом можно использовать в сценариях и шаблонах. Например, ip адрес по-умолчанию можно получить, обратившись к переменной ansible_default_ipv4
.
# web.yml
tasks:
- debug: msg={{ansible_default_ipv4}}
$ ansible-playbook web.yml
PLAY [all] ********************************************************************
GATHERING FACTS ***************************************************************
ok: [111.111.111.111]
TASK: [debug msg="{{ansible_default_ipv4}}"] **********************************
ok: [111.111.111.111] => {
"msg": "{u'macaddress': u'06:a8:07:41:47:a5', u'network': u'172.31.0.0', u'mtu': 9001, u'alias': u'eth0', u'netmask': u'255.255.240.0', u'address': u'172.31.7.80', u'interface': u'eth0', u'type': u'ether', u'gateway': u'172.31.0.1'}"
}
Шаблоны
В ansible есть шаблоны, которые используют шаблонизатор Jinja2. Давайте сделаем 2 шаблона.
# roles/nginx/templates/ansible.conf.j2
server {
listen 80 default_server;
root /usr/share/nginx/html;
index index.html index.htm;
}
# roles/nginx/templates/index.html.j2
<html>
<body>
<pre>
{{ ansible_default_ipv4 }}
{{ ansible_env }}
</pre>
</body>
</html>
Обратите внимание на переменные в двойных фигурных скобках (например, {{ ansible_env }}
) — это переменные ansible, которые мы собрали с помощью уже известного нам модуля setup
.
Хендлеры
Для того, чтобы отрабатывать какие-то действия асинхронно, в ansible есть хендлеры (обработчики), которые можно вызвать из задач. Создадим свой хендлер, который перезагружает nginx.
# roles/nginx/handlers/main.yml
---
# handlers file for nginx
- name: reload nginx
service: name=nginx state=reloaded
Доработаем теперь файл задач роли nginx.
# roles/nginx/tasks/main.yml
---
- name: Update apt cache
apt: update_cache=yes
- name: Install required packages
apt: name=nginx
- name: Start nginx service
service: name=nginx state=started
- name: Delete default nginx site
file: path=/etc/nginx/sites-enabled/default state=absent
notify: reload nginx
- name: Create default nginx site
template: src=ansible.conf.j2 dest=/etc/nginx/sites-enabled/ansible owner=www-data group=www-data
notify: reload nginx
- name: Create index.html file
template: src=index.html.j2 dest=/usr/share/nginx/html/index.html owner=www-data group=www-data
Как понятно из описания, данная задача ставит nginx, запускает его, удаляет файл конфигурации nginx по-умолчанию, создает файл конфигурации из шаблона ansible.conf.j2
, который мы написали выше, и генерирует файл index.html
из шаблона, который мы описали выше. Обратите внимание, что некоторые задачи посылают нотификацию notify: reload nginx
. Причем, в данном случае должно быть послано 2 нотификации на перезагрузку nginx, но, по факту, они объединятся в одну, как будет видно ниже. Нотификации необходимы, чтобы перезагружать nginx (или любой другой сервис) только в случае, если шаблон (или что-то другое) изменился, чтобы не делать это каждый раз при запуске ansible.
Итак, запустим получившийся сценарий.
$ ansible-playbook web.yml
PLAY [all] ********************************************************************
GATHERING FACTS ***************************************************************
ok: [111.111.111.111]
TASK: [nginx | Update apt cache] **********************************************
ok: [111.111.111.111]
TASK: [nginx | Install required packages] *************************************
ok: [111.111.111.111]
TASK: [nginx | Start nginx service] *******************************************
ok: [111.111.111.111]
TASK: [nginx | Delete default nginx site] *************************************
changed: [111.111.111.111]
TASK: [nginx | Create default nginx site] *************************************
changed: [111.111.111.111]
TASK: [nginx | Create index.html file] ****************************************
changed: [111.111.111.111]
TASK: [debug msg="{{ansible_default_ipv4}}"] **********************************
ok: [111.111.111.111] => {
"msg": "{u'macaddress': u'06:a8:07:41:47:a5', u'network': u'172.31.0.0', u'mtu': 9001, u'alias': u'eth0', u'netmask': u'255.255.240.0', u'address': u'172.31.7.80', u'interface': u'eth0', u'type': u'ether', u'gateway': u'172.31.0.1'}"
}
NOTIFIED: [nginx | reload nginx] **********************************************
changed: [111.111.111.111]
PLAY RECAP ********************************************************************
111.111.111.111 : ok=9 changed=4 unreachable=0 failed=0
Как видите, в конце nginx перезагрузил конфигурацию. Заглянем в браузер.
Вуаля! Мы установили и запустили nginx с нужным нам файлом конфигурации, сгенерировали для него страницу с данными из ansible, и перезагрузили nginx.
Недостатки
И чтобы вы не подумали, что ansible идеальный продукт, которому некуда развиваться, я расскажу о его недостатках.
- Отсутствие менеджера зависимостей. Сейчас все роли можно хранить в репозитарии, но как работать с зависимостями и обновлять роли — внятного ответа нет. Все пользуются этим подходом (так же было с Chef, например, 3 года назад), но при разрастании проекта очевидны недостатки такого подхода. UPDATE Менеджер зависимостей в каком-то виде есть, непонятно только почему об этом написано «снизу мелким шрифтом».
- Неполная документация. Ответы на некоторые очевидные вопросы приходится гуглить, и зачастую они находятся в частных блогах и тикетах на github.
- Режим ansible-pull для больших инсталляций потребует серьезной «доработки напильником».
- Неудобный дебаг. Даже при вызове с ключом
-vvvv
не всегда понятно, какая же именно команда выполнялась на сервере и что помешало ей выполниться. - Быстрая разработка. Конечно, у этого недостатка есть обратная сторона — ansible действительно быстро развивается. Но я наткнулся на баг, когда версия 1.8.1 работала, а 1.8.4 — сломалась.
Автор: evtuhovich