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
$ 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
# 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
$ 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
$ ansible-galaxy init nginx -p roles
- nginx was created successfully

$ tree
+-- roles
¦   L-- nginx
¦       +-- README.md
¦       +-- defaults
¦       ¦   L-- main.yml
¦       +-- files
¦       +-- handlers
¦       ¦   L-- main.yml
¦       +-- meta
¦       ¦   L-- main.yml
¦       +-- tasks
¦       ¦   L-- main.yml
¦       +-- templates
¦       L-- vars
¦           L-- main.yml



Доработаем файл web.yml.
web.yml
---
- hosts: all
  user: ubuntu
  sudo: yes

  roles:
    - nginx


Обратите внимание на ключ sudo: yes. Он позволяет не писать в каждом задании эту строку. Большинство административных задач все равно должны выполняться с правами root, поэтому имеет смысл оставить его здесь.

А в файле roles/nginx/tasks/main.yml напишем следующее:

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 -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
$ 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
# 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
$ 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 — сломалась.

Комментарии (22)


  1. grossws
    06.04.2015 13:18
    +2

    Стоит отметить, что пространство имён обработчиков (handler'ов) общее и не поддаётся шаблонизации. Возможны конфликты имён.

    Т. е., если у вас две роли используют notify: restart nginx и имеют - name: restart nginx в своих handlers/*, то выполнятся только действия из одной роли. Порядок, конечно, детерминирован, но от этого не легче.

    Об этой «особенности» забывают почти во всех вводных статьях. И в документации, до кучи. Хотя теперь есть упоминание про flush_handlers, который может использоваться, как workaround для обхода вышеописанной проблемы.


    1. evtuhovich Автор
      06.04.2015 15:56

      Спасибо, я сам на это не натыкался, поэтому и не знал.


  1. inkvizitor68sl
    06.04.2015 17:06
    +2

    > а он стоит по-умолчанию на всех современных linux системах
    Чушь не несите. То, что python положили в убунту — не значит, что он стоит по-умолчанию «на всех современных linux-системах».

    Debian в минимальной установке, arch, шлакварь — они прекрасно работают без питона.


    1. grossws
      06.04.2015 19:10

      Стоит сказать, что разработчики ansible предполагали, что python2 может не присутствовать или называться иначе, поэтому есть модуль raw (который выполняет просто команду по ssh) и параметры ansible_python_interpreter.


      1. inkvizitor68sl
        06.04.2015 19:13

        > и параметры ansible_python_interpreter.
        Не поможет, если python-а нет вообще )

        > модуль raw
        Если его в плейбук вписывать — то будет на каждый запуск говорить «changed» на равку.
        Проще, в общем, пойти руками и поставить python, если его там нет.

        А вообще я больше про то, что автор не прав насчет того, что пайтон везде есть.


        1. grossws
          06.04.2015 19:22
          +1

          Не поможет, если python-а нет вообще )
          А вообще я больше про то, что автор не прав насчет того, что пайтон везде есть.
          А с этим я и не спорю. Ибо полностью согласен.

          Если его в плейбук вписывать — то будет на каждый запуск говорить «changed» на равку.
          Проще, в общем, пойти руками и поставить python, если его там нет.
          Он скорее не для playbook'а, а для ansible some-group -m raw -a 'apt-get install -y python'. Хотя можно и при начальном provisioning принести python, если планируется использовать ansible.


        1. Honeyman
          07.04.2015 00:57

          > Если его в плейбук вписывать — то будет на каждый запуск говорить «changed» на равку.
          Сделать экшн с командой raw, проверяющий/печатающий наличие Python на удалённой стороне. У экшна будут changed_when: False и register: test_python_presence_result.
          Сделать второй экшн с командой raw, устанавливающий Python в случае, если test_python_presence_result показывает отсутствие Python.


          1. inkvizitor68sl
            07.04.2015 00:59
            +1

            Так ansible будет ругаться на отсутствие пайтона ещё на этапе GATHERING FACTS, емнип?


            1. clickfreak
              10.04.2015 17:06
              +1

              В случае, если на этапе бутстрапа вы заранее знаете что питон может отсутствовать, то можно добавить «gather_facts: no» в описание плея

              P.S. с помощью raw можно даже сетевыми железками подруливать.


    1. evtuhovich Автор
      06.04.2015 19:17

      Спасибо, поставил вам плюсик. Формулировка, действительно, спорная.


  1. ReanGD
    06.04.2015 17:19

    А Ansible имеет какую-то обертку, которая позволяет пратформонезависимо ставить пакеты, делать настройки и т.п. или для каждой системы нужно писать свой сценарий?


    1. grossws
      06.04.2015 19:13

      Для каждой свой. Стоит понимать, что на разных дистрибутивах названия пакетов могут запросто отличаться и SCM это за вас не решит, всё равно будет вариативность.


    1. mynameisdaniil
      06.04.2015 22:52

      Как уже сказали — не имеет. Но зато, умеет запускать или не запускать какие-то задачи в зависимости от условий в т.ч. в зависимости от платформы и архитектуры. Так что, если нужно поддерживать зоопарк — вы можете это сделать, не придется заводить разные роли для каждого случая.


      1. grossws
        06.04.2015 23:47
        +1

        Можно и в рамках одной роли. Просто использовать что-то типа when: ansible_os_family == "RedHat" and ansible_distribution_major_version == '6'. Тогда несоответствующие будут пропускаться, а подходящие обрабатываться.


  1. amarao
    08.04.2015 01:44

    Я сейчас столкнулся с странной задачей, которую не могу решить малыми силами.

    Дано: 10 объектов (допустим, имён каталогов). В инвентори сколько-то серверов.

    Надо раскидать round-robin'ом имена по разным серверам (то есть примерно по 2 шт на сервер).

    Все роли выполняются в контексте сервера и сделать что-то вида:

    file: path={{item}} mode=directory state=exist
    with_next_unused_element: dir_list


    1. mynameisdaniil
      09.04.2015 12:50
      +1

      как показывает практика, Ansible лучше всего использовать с внешними скриптами. Вот и в данном случае, я бы запускал плейбук не напрямую, а через баш-скрипт, который бы вызывал %YOU_NAME_IT% скрипт перед запуском ансибла, который бы формировал файл внешних переменных. Затем, в роли/таске/где-то-еще я бы подключал этот файл (нужно, чтобы он всегда был — пусть и пустой по дефолту) и делал бы примерно так:

      file: path={{file_distribution[inventory_hostname]}} mode=directory state=exist
      when: inventory_hostname in file_distribution
      


      1. amarao
        09.04.2015 14:38

        Спасибо, интересная мысль, попробую.


      1. clickfreak
        10.04.2015 17:01
        +2

        Внешние скрипты можно оформить в vars_plugins, примеры:
        github.com/ansible/ansible/tree/devel/lib/ansible/inventory/vars_plugins
        github.com/ginsys/ansible-plugins/blob/devel/vars_plugins/group_vars_dirs.py (под старую версию ansible, но совместимо)

        Только нужно быть внимательным к порядку раскрытия переменных:
        1. Сначала запускаются vars_plugins
        2. На переменные из 1 накладываются переменные из inventory_dir/{group,host}_vars/
        3. Сверху прилетают переменные из playbook_dir/{group,hosts}_vars

        Поведение update или merge настраивается в ansible.cfg

        Мы используем vars_plugins для разделения переменных пулов следующим образом:
        — общие переменные для всех пулов лежат в /common_group_vars/ и читаются плагином
        — переменные пулов лежат в /pools/<имя пула>/{group,host}_vars/ (инвенторий, соответственно в /pools/<имя пула>/hosts)
        — глобальнные переменные, ссылающиеся на пулоспецифичные можно положить в /group_vars/

        P.S. нужно быть осторожным с версией 1.9.x, оно пытается «раскрыть» при использовании lookup-плагина переменные, которые не используются не то что в текущем таске, но и вообще в плейбуке, поэтому если в переменных есть что-то что ссылается на ещё не созданное или просто ненужное именно сейчас, то можно получить остановку выполнения плейбука на ровном месте.


        1. mynameisdaniil
          10.04.2015 17:41

          В общем то, да. Но мне комфортнее когда я контролирую то, что происходит. В том плане, что когда скрипт заранее генерирует файлик я всегда могу пойти и посмотреть что он там нагенерировал, могу перезапустить все с теми же параметрами, добавить в гит и т.д.


  1. bushart
    08.04.2015 14:19

    Я правильно понял, что у них нету бесплатной и не ограниченной по времени лицензии для частного использования?


    1. evtuhovich Автор
      08.04.2015 15:57

      Сам клиент полностью бесплатен и Open-Source.

      docs.ansible.com/intro_installation.html

      Но на сайте они продают Ansible Tower, а про сам ansible не так много говорят.


    1. amarao
      09.04.2015 14:39

      Для большинства примерений просто ansible (вместе с -play и -galaxy) за глаза и за уши. А он opensource'нутый и во всех дистрибутивах.

      Хотя я бы из вредности настаивал на перетаскивании его из main в contrib, ибо предлагают (хотя бы для чего-то) юзать проприетарщину.