Ansible с чего начать

ansible-300x300.pngВ последние пару лет я все чаще использую Ansible для решения практически любых задач связанных с автоматизацией, будь то конфигурирование, резервное копирование или деплой проектов. Не смотря на то, что система очень хорошо документирована, я думаю смогу добавить немного полезной информации для тех кто еще только начинает пользоваться Ansible. Для начала я хотел бы рассказать об основных вещах, таких как структура проекта в котором будут содержаться плейбуки, роли, переменные, шаблоны и файлы необходимые для автоматизации развертывания серверов, кода и всего другого, что можно сделать с помощью Ansible.

Итак, Ansible это очень гибкий и легкий инструмент для написания сценариев автоматизации любой сложности. Вы можете описать в нем как простое окружение разработчика так сложную структуру крупного проекта с несколькими окружениями (dev/stage/prod).Как мне видится с Ansible можно решать следующие задачи:
  • Установка/удаление ПО;
  • Конфигурирование ПО;
  • Создание/удаление пользователей;
  • Контроль пользовательских паролей/ключей;
  • Создание/удаление контейнеров/виртуальных машин;
  • Деплой кода вашего ПО;
  • Запуск различных скриптов/тестов.

Пара замечаний для тех кто еще не знаком с Ansible:
 — Прежде чем приступить к написанию плейбуков/ролей вам нужно почитать документацию Ansible-playbook, Ansible-role и научиться понимать YAML-syntax (это очень просто);
 — Также стоит сразу сказать, что лучше вести разработку в git-репозитории, поэтому не поленитесь обзавестись аккаунтом на github или заведите локальный git-repo (mercurial, svn, etc). Если вы еще не научились пользоваться git, то сейчас самое время.

На мой взгляд, Ansible гораздо проще и легче для «усвоения» чем puppet и chef (мне доводилось использовать и то и другое) хоть это и немного разные продукты. После того как вы ознакомились с введением и заглянули в Module Index, вы уже можете начинать писать плейбуки которые заметно облегчат вам жизнь.

Пример плейбука для распространения вашего ssh pub-key на серверы:

# file: keys.yml
---
- hosts: app-servers
  tasks:
  - name: Set up authorized_keys for the backup user # Всегда указывайте подробные имена для тасков, так будет вам проще читать вывод и понятнее для коллег что вы хотите здесь сделать.
    authorized_key: user=backup key="{{ item }}"
    with_file:
    - keys/backup.pub
...

ansible-playbook playbooks/keys.yml

Это все понятно, но что дальше?

Расширенная структура проекта

Если хотите описывать структуру более или менее сложного проекта с n-tier архитектурой, стоит сразу определиться со списком групп хостов по ролям и на основании этого прикинуть какие задачи должны быть общими для всех хостов (например подключение репозиториев, создание пользователей и т.д.), а какие частные (конфигурирование nginx, mysql, создние python venv и т.д.) и исходя из этого начинать писать роли начиная с базовой общей роли постепенно переходя к частным.

Расширенная структура проекта может включать в себя:

  • Переменные (общие и частные);
  • Плейбуки (сценарии);
  • Роли (структурированные плейбуки);
  • Списки групп хостов (inventory).

Список групп хостов (inventory)

Если вы хотите иметь несколько окружений то придется завести несколько файлов inventory, по умолчанию (в deb-пакете) в конфиге ansible.cfg используется файл hosts (/etc/ansible/hosts). Путь к inventory не обязательно прописывать в конфиге можно задавать разные файлы с ключом -i.

Чтобы логика описанная в ваших ролях или плейбуках работала одинаково на разных окружениях заводите группы с одинаковыми именами для всех окружений, например:

Production inventory

# file: production

[app-servers:children]
app-php-servers
app-python-servers

[app-php-servers]
appserv-01.example.com
appserv-03.example.com

[app-python-servers]
appserv-02.example.com
appserv-04.example.com

[db-servers]
dbserv-[01:02].example.com

[cdn]
cdn-[01:03].example.com

[app-servers:vars]
env=PROD
ansible_ssh_user=admin

Develop inventory
# file: develop

[app-servers:children]
app-php-servers
app-python-servers

[app-php-servers]
appserv-01.dev.example.com
appserv-03.dev.example.com

[app-python-servers]
appserv-02.dev.example.com
appserv-04.dev.example.com

[db-servers]
dbserv-[01:02].dev.example.com

[cdn]
cdn-[01:02].dev.example.com

[app-servers:vars]
env=DEV
ansible_ssh_user=admin

Как видно из примера, группы могут включать в себя другие группы, а также можно объявлять общие переменные прямо в inventory.

Переменные

Структура директорий может выглядеть следующим образом:

ansible/
# Групповые (общие) переменные
 - group_vars/
          - all/
            - common
            - secret  
          - dev/
            - common
            - secret    
          - stage/
            - common
            - secret
          - prod/
            - common
            - secret
# Переменные отдельных хостов (частные) 
 - host_vars/
         - appserv-01.example.com/
            - common
            - secret
         - dbserv-01.example.com/
            - common
            - secret
         - cdn-01.example.com/
            - common
            - secret

Каждая группа и каждый хост могут иметь набор переменных в разных файлах (например common, secret).Названия файлов в директориях здесь не приципиальны, главное чтобы имя директории совпадало с именами (группы или хоста) в соответствующем inventory. Заводить директорию для группы или единичного хоста не обязательно, можно просто создать файл и записать в нем все необходимые переменные. Но если вы хотите хранить пароли в шифрованном виде (не хеши, а именно пароли), отдельно от общих переменных, то стоит завести описанную выше структуру, об этом я расскажу ниже. 

Шифрование переменных с Ansible-vault
Ansible-vault утилита для шифрования (default AES256) файлов групповых или хостовых переменных и в принципе любых файлов в которых вы хотите хранить секретные переменные (пароли, ключи и т.д.). Таким образом, вы сможете хранить в репозитории (даже в публичном, хотя это всё же не желательно) любые данные и не переживать за их безопасность.

Пользовательские пароли имеет смысл хранить в групповых файлах (group_vars) по имени группы или как all если у вас на всех окружениях одни и те же юзеры.

В описанной выше структуре я указывал файлы secret в которых и предполагается хранить шифрованные переменные.

Зашифровать файл:

ansible-vault encrypt host_vars/cdn-01.example.com/secret

Отредактировать зашифрованный файл:
ansible-vault edit host_vars/cdn-01.example.com/secret

Показать зашифрованный файл:
ansible-vault encrypt host_vars/cdn-01.example.com/secret

Конечно, чтобы шифровать и расшифровывать файлы нужен будет достаточно длинный ключ и хранить его нужно в надежном месте (например KeePass). Чтобы автоматически расшифровывать файлы во время запуска (runtime) плейбуков нужно будет указывать ключ --vault-password-file либо задавать путь к файлу через конфиг ansible.cfg, в этом случае также нужно будет позаботиться о сохранности ключа и выставить ему нужные права (0400). Ну и конечно не стоит его хранить в репозитории вместе с зашифрованными файлами.

Плейбуки

Все действия (tasks) которые вы хотите выполнить можно записывать в плейбуки (если их скажем не больше десятка и вы не используете файлы и шаблоны) в остальных случаях стоит использовать роли.

В плейбуке как минимум должны быть указаны:
 — Целевая группа хостов (hosts) здесь можно задавать исключения по маске ( — hosts: app-servers:! app-04.*), либо несколько групп через двоеточие (-hosts: db-servers: cdn);
 — Действия (tasks) или роли (roles).
Дополнительно можно указать:
 — Пользователя для ssh-connect (remote_user);
 — Пользователя sudo (become_user);
 — Использование повышения привилегий (become: True/False);
 — Переменные (vars)
 — Файлы переменных (vars_files)
 —  Количество одновременных коннектов (serial), если выставить 1 — то будет происходить последовательное выполнение плейбука по одному хосту за раз.

Роли

Роль представляет собой структурированный плейбук содержащий набор (как и минимум) тасков (task), и дополнительно — обрабработчиков событий (handler), переменных (defaults), файлов (files), шаблонов (templates), , а также описание и зависимости (meta).

Структура роли Deploy

- deploy/
   - defaults/
      - main.yml
   - files/
      - maintenance.html
   - handlers/
      - main.yml
   - meta/
      - main.yml
   - tasks/
      - deploy.yml
      - main.yml
      - migrations.yml
      - tests.yml
   - templates/
      - app_config.j2

Файл с переменными по умолчанию:
# file: deploy/defaults/main.yml
---
branch_name: master
mysql_host: localhost
...

Здесь очень удобно задавать какие-то общие вещи, например по умолчанию хост для БД localhost, а group_vars/host_vars вы можете задавать нужные хосты для соответствующих окружений.

Файл с обработчиками событий

# file: deploy/handlers/main.yml
---
- name: reload uwsgi
  service: name=uwsgi state=reloaded
- name: flush cache
  shell: redis-cli flushall
...

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

Файл с описанием тасков

# file: deploy/tasks/main.yml
---
- include: deploy.yml
  tags:
    - common
    - deploy
- include: migrations.yml
  tags:
    - common
    - migrations
- include: pytasks.yml
  when: "'app-python-servers' in group_names"
  tags:
    - common
    - pytasks
- include: tests.yml
  tags:
    - tests
...

Можно задавать жесткие условия для выполнения некоторых тасков. В примере выше я задал условие:
when: "'app-python-servers' in group_names"

что означает, что таск будет выполняться только для хостов которые состоят в группе app_python-servers.

Я практически всегда использую теги для декомпозирования крупных ролей. В примере роли выше я задал общий тег «common» для первых двух тасков и отдельный тег для tests для последнего таска. Если мне нужно выполнить только первые два таска (т.е. выкатить код) я запускаю плйбук с ключом --tags common (или --skip-tags tests):

ansible-playbook -i develop playbooks/deploy.yml --tags common

затем, можно запустить тесты отдельно (обычно они долго выполняются) просто задаю --tags tests:
ansible-playbook -i develop playbooks/deploy.yml --tags tests

Шаблоны
В качестве шаблонизатора Ansible использует jinja2 со всеми его прелестями. Пример простого конфига uwsgi в виде ini файла с переменными:
# {{ ansible_managed }}

[uwsgi]

chdir = {{ app_dir }}/{{ app_name }}
home = {{ app_dir }}/{{ app_name }}/env

master = True

module = {{ app_name }}.wsgi:application

chmod-socket = 660

uid = {{ web_user }}
gid = {{ web_user }}

processes = 8

listen = 256

max-requests = 100

buffer-size = 16384

vacuum = true

env DJANGO_SETTINGS_MODULE = {{ env }}.settings

{% if env is defined and env == DEV %}
# Enable logging 
req-logger = file:/{{ logs_dir }}/{{ app_name }}/reqlog
logger = file:/{{ logs_dir }}/{{ app_name }}/errlog
{% endif %}

Можно использовать любые приемы jinja2 в сочетании с переменными Ansible. Например:
 — Задавать строки только хост состоит в той или иной группе (либо задать строки только для определенного хоста) и при этом использовать loop:
# file: iptables.j2
...
{% if (groups['cdn'] is defined and inventory_hostname in groups['cdn']) %}
# Allows HTTP and HTTPS connections from anywhere
{% for port in webs_ports %}
-A INPUT -p tcp -m tcp --dport {{ port }} -j ACCEPT
{% endfor %}
{% endif %}
...

Зависимости

Под зависимостями здесь подразумеваются другие роли которые необходимо выполнить перед тем выполнять вашу роль, в формате — { role: role_name, var_name: value }, например:

# file: deploy/meta/main.yml
---
dependencies:
- { role: base, apt_repo: us }

Указав все необходимые зависимости для конечной роли (например app-servers) вы может создать всего один плейбук в котором достаточно будет указать целевую группу хостов и роль.  И этого будет достаточно чтобы поддерживать систему в консистентном состоянии, запуская например плейбук по крону. Если нужно обновить какие-то определенные конфиги по месту, то вам придут на помощь теги.

Если вы хотите делать периодические билды и контролировать их выполнение или просто дать «кнопку» разработчикам, могу порекомендовать плагин Ansible для Jenkins подключив который вы сможете задавать в тасках Jenkins пути до playbook, inventory, а также tags и extra-vars.

Что дальше?

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

Держите открытой вкладку с официальной документацией по Ansible, она постоянно дополняется по мере развития проекта. Я до сих пор нахожу в ней новые и интересные для себя вещи.

В этой статье я рассказал не все что хотел, есть мысли и идеи еще на одну-две заметки. Если вас интересуют определенные вопросы пишите их комментариях или мне на почту, я постараюсь ответить и возможно учесть ваши пожелания при написании следующей статьи.

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

  • 5 июля 2016 в 12:15

    0

    Недавно столкнулся с тем, что ansible, как-то странно понимает секции include и block в шабонах. Приходилось использовать?

    • 5 июля 2016 в 12:18

      0

      include вместе с block пока не использовал (block — относительно новая директива, в 2.0 появилась). А в чем странность?
      • 5 июля 2016 в 12:25 (комментарий был изменён)

        0

        Сразу скажу, не копал глубоко.
        Есть куча конфигов nginx с обшей частью, я попытался загнать общую часть в файлик, но не вышло. Что-то подобное в джанге было на урра.


        {% set upstream_name = item.name + "_upstream" %}
        {% set server_name = item.name + "-" + item.type + "-" + base_domain %}
        {% set root_dir = build_dest + "/" + item.type + "/" + item.name + "/htdocs;" %}
        • 5 июля 2016 в 12:31

          0

          Вы говорили про странности с include и block.
          Include нужен для того чтобы инклудить файлы с тасками в плебуках/ролях.
          Block нужен что объединять группы тасков в блоки с некими общими условиями (when: something).
          Тут вы пишите кусок jinja-шаблона. Теперь мне совсем не понятен контекст. :)
          • 5 июля 2016 в 12:33

            0

            Я про include/block в контексте jinja, а не yaml.

            • 5 июля 2016 в 12:36

              0

              Include/Block работают в контексте тасков плейбука/роли.
              • 5 июля 2016 в 12:37

                0

                Еще раз. Я не про include в коде yaml, а в коде шаблона. То есть если я приведенный выше кусок вынесу в инклюд, я ничего не получу.

                • 5 июля 2016 в 12:40

                  0

                  Я понял. :) Ответ выше. В jinja свои директивы, include/block там не будут работать.
  • 5 июля 2016 в 12:42

    0

    Ох, если бы я не знал уже Ансибла, меня бы эта статья только запутала. Ансибл очень очень простой, а вот написание приличного playbook’а на нём — груда best practices и приёмов, которые совершенно неочевидно вытекают из документации по ansible. И вот именно эту штуку «за пределами документации» никто не хочет рассказывать.
    • 5 июля 2016 в 12:50

      0

      Ну на самом деле за пределами документации сейчас уже не так и много всего.
      Есть определенные тонкости, но их понимание приходит со временем, после третьей, четвертой ревизии ваших ролей.
      Поэтому у каждого свои «best practices» их нельзя получить и усвоить разом из какой-нибудь статейки.

© Habrahabr.ru