Comments 44

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

include вместе с block пока не использовал (block — относительно новая директива, в 2.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;" %}
Вы говорили про странности с include и block.
Include нужен для того чтобы инклудить файлы с тасками в плебуках/ролях.
Block нужен что объединять группы тасков в блоки с некими общими условиями (when: something).
Тут вы пишите кусок jinja-шаблона. Теперь мне совсем не понятен контекст. :)

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

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

Именно. То есть получается, все плюшки jinja там толком не поиспользовать.

Разве? ну возможно. Хотя вроде jinja тянет в зависимостях. А потом обертывает в свои врапперы. Утверждать не буду, проверять лень. :)

мой пример include в шаблоне
github.com/le9i0nx/ansible-rspamd/blob/master/templates/etc/rspamd/conf.d/default.conf.j2
код#{{ ansible_managed }}
{% if item.data is mapping %}
{% for key, value in item.data|dictsort %}
{% set tabs = [] %}
{% for i in tabs %}{{i}}{% endfor %}{{ key }} {
{% include 'nested_structure.j2' %}
{% for i in tabs %}{{i}}{% endfor %}}
{% endfor %}
{% endif %}

а теперь магия инклуд самого себя
github.com/le9i0nx/ansible-rspamd/blob/master/templates/etc/rspamd/conf.d/nested_structure.j2
код{% if value is mapping %}
{% set tabs = tabs + [' '] %}
{% for key, value in value|dictsort %}
{% if key == 'raw' %}
{% for listes in value %}
{% for i in tabs %}{{i}}{% endfor %}{{ listes }}
{% endfor %}
{% elif value is number %}
{% for i in tabs %}{{i}}{% endfor %}{{ key }} = {{ value }};
{% elif value is string %}
{% for i in tabs %}{{i}}{% endfor %}{{ key }} = "{{ value }}";
{% elif value is iterable %}
{% if value is mapping %}

{% for i in tabs %}{{i}}{% endfor %}{{ key }} {
{% include 'nested_structure.j2' %}
{% for i in tabs %}{{i}}{% endfor %}}
{% else %}
{% for listes in value %}
{% if listes is mapping %}
{% set value = listes %}
{% for i in tabs %}{{i}}{% endfor %}{{ key }} {
{% include 'nested_structure.j2' %}
{% for i in tabs %}{{i}}{% endfor %}}
{% else %}
{% for i in tabs %}{{i}}{% endfor %}{{ key }} = "{{ listes }}«1;
{% endif %}
{% endfor %}
{% endif %}
{% else %}
{% for i in tabs %}{{i}}{% endfor %}{{ key }} = {{ value }};
{% endif %}
{% endfor %}
{% endif %}

вообще некоторые вещи в шаблонах работают не ожидаемо
с чем приходится считатся
Ох, если бы я не знал уже Ансибла, меня бы эта статья только запутала. Ансибл очень очень простой, а вот написание приличного playbook'а на нём — груда best practices и приёмов, которые совершенно неочевидно вытекают из документации по ansible. И вот именно эту штуку «за пределами документации» никто не хочет рассказывать.
Ну на самом деле за пределами документации сейчас уже не так и много всего.
Есть определенные тонкости, но их понимание приходит со временем, после третьей, четвертой ревизии ваших ролей.
Поэтому у каждого свои «best practices» их нельзя получить и усвоить разом из какой-нибудь статейки.
я незнаю ansible, и когда я вижу комментарий что мне нужно 3-4 раза переписать, я так подозреваю чуть ли не половину, и только после этого мне будет удобно и хорошо им пользоватся, то как то желание изучать его пропадает
«К сожалению», любой программный продукт нужно постоянно доделывать, допиливать и т.д.
Если вы не готовы, что либо поддерживать и дорабатывать, то вам нечего делать в ИТ.
За два года почти ежедневной работы с ним накопился нехилый опыт прикручивания костылей и использования хитрых способов. Я бы статью написал, только есть одна проблема — для меня все это теперь кажется очевидным и не понятно о чем есть интерес у других почитать.

К примеру, как делать не надо, потому что дебажить сложно, но это самый удобный способ передать в параметр servers нужные данные в том виде, которая она ожидает, но чтобы это была другая переменная:


haproxy_backends:
  - name: prometheus_api
    balance: leastconn
    mode: http
    servers: |
      [{% for host in prometheus_hosts %}
        {{ '{' }}
          'name': '{{ host.name }}',
          'ip': '{{ host.ip }}',
          'port': 9090,
          'params': {{ host.params }},
          'maxconn': 1000
        {{ '}' }}
        {% if not loop.last %},{% endif %}
      {% endfor %}]

Сама переменная:


prometheus_hosts:
  - name: prometheus1.example.com
    ip: 10.54.254.43
    params: "['check']"
  - name: prometheus2.example.com
    ip: 10.54.28.210
    params: "['check', 'backup']"

Лучше всего этот костыль использовать на данных из setup типа ansible_fqdn. Делаешь setup для всех бэкендов и подставляешь их адреса сюда, миную лишнюю переменную.

Ну или про многие фильтры народ вообще не знает, типа default(omit), mandatory или combine.

В основном не знают (если это так) те, кто начинал осваивать ansible с более ранних версий. Например, я сравнительно недавно узнал про default(omit) хотя он появился в 1.8, а combine появился в 2.0.
В общем надо почаще заглядывать в документацию она обновляется с каждым релизом.
Что касается combine, у меня по старой привычке с chef'а стоит

[defaults]
hash_behaviour = merge


Очень удобно.

Тоже перелазил с шефа и очень удивился стандартному поведению (replace). Прописал себе merge, но немного смущает документация:


We generally recommend not using this setting unless you think you have an absolute need for it, and playbooks in the official examples repos do not use this setting
Ну мало ли чего они там рекомендуют. Дефолтный merge облегчает жизнь сильно. А официальные плейбуки я не использую. Предпочитаю писать всё самому.
У меня с ansible было несколько организационных проблем и я не уверен, что решил их правильно.

1) rpm/deb-дистрибутивы имеют разные название пакетов, пути до конфигов

- include: {{ ansible_distribution }}.yml

Я выношу специфичные данные в отдельный файл.

2) разные сегменты сети имеют разные ntp, nameserver…

group_vars
  - all
  - dmz

# inventory
[dmz:children]
dmz_group1

А тут использую групповые переменные и inventory

Есть ли более красивое решение?
1) Нормально (хотя это не очень хорошо если вам приходится следить за зоопарком разных дистрибутивов).
2) Это тоже нормально, от этого не всегда можно уйти.
Установка/удаление ПО;
Конфигурирование ПО;
Деплой кода вашего ПО;

Stateless система под это дело совсем не подходит, поскольку нельзя делать откат ревизий, и, как следствие, нормальное A/B проходит мимо.
Кроме того, у них в гайдлайне написано, дескать: не делайте параметризированных ролей, лучше копипаста.
И получается, что сделать обычную роль для nginx с темплейтами и инклудами конфигов, которая в рамках одного inventory намазывается по разным серверам, — это целая, блин, задача с юнит-тестам, бл.

Создание/удаление контейнеров/виртуальных машин;

Создал, запустил, упал, перезапустил, упал. И так в цикле, пока apdex не просядит и мониторинг не начнёт орать, да? :-}

А стэктрейсы после запуска толстого плэйбука хотя бы на 50+ хостов смотреть? >____<

К примеру, когда в template неправильно указан аргумент src, а изменений много, и вносил их кто-то другой:
output -vvvv
An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/ansible/executor/task_executor.py", line 124, in run
    res = self._execute()
  File "/usr/lib/python2.7/dist-packages/ansible/executor/task_executor.py", line 446, in _execute
    result = self._handler.run(task_vars=variables)
  File "/usr/lib/python2.7/dist-packages/ansible/plugins/action/template.py", line 80, in run
    source = self._loader.path_dwim_relative(self._task._role._role_path, 'templates', source)
  File "/usr/lib/python2.7/dist-packages/ansible/parsing/dataloader.py", line 236, in path_dwim_relative
    if source.startswith('~') or source.startswith('/'):
AttributeError: 'NoneType' object has no attribute 'startswith'

fatal: [localhost]: FAILED! => {"failed": true, "msg": "Unexpected failure during module execution.", "stdout": ""}

спасибо за материал! насчет "-i " мысль интересная. сам пока использую --limit вместо этого

есть желание хранить плейбуки в репозитории прямо рядом с кодом и с ним же бранчеваться. есть опыт такого дела? может ли ansible-playbook выполнять плейбук из определенного бранча? может плагины есть для этого какие…

сам пробовал гуглить, но все что вылезает касается модуля «git» который делает деплой уже непосредственно КОДА из какого-либо репо. а вот чтобы сам конфиг Ансибла… такого не нашел что то…
Я храню роли в отдельном репо. Смешивать с кодом (Python/PHP) я не стал бы, смысла нет (если только вы не упираетесь в какие-то ограничения).
Тут можно разные подходы использовать. Можно сделать плейбук который будет обновлять сам Ansible и обновлять репо с плейбуками/ролями — дергать его по крону или как pre-task в Jenkins или аналогах.

У меня сейчас миграция с puppet на ansible, и комбинация -i и --limit очень помогает.

В нашем случае:


  1. Обилие говнокода на puppet, разгребать смысла нет. В текущем состоянии идет постоянное нарастание.
  2. Наличие центрального сервера и агентов. Что вместе с п1 ведет к серьезным тормозам. Например: запуск агентов был сначала раз в 25 минут, потом раз в 45, потом раз в час, потом раз в 2 часа. Agentless у ansible, считаем это плюсом. А наличие CI позволяет вполне заменять центральный сервер.
  3. Осознание излищней сложности puppet. Docker+ansible позволяют выкинуть 60% кода без рефакторинга.
  4. Очень удобная декомпозиция ролей. Соответсвенно всегда можно удобно и быстро выполнить малую роль.
Stateless система под это дело совсем не подходит, поскольку нельзя делать откат ревизий, и, как следствие, нормальное A/B проходит мимо.

Обоснуйте.

Кроме того, у них в гайдлайне написано, дескать: не делайте параметризированных ролей, лучше копипаста.

Что-то не припомню такого. Тыкните в док.

Создал, запустил, упал, перезапустил, упал. И так в цикле, пока apdex не просядит и мониторинг не начнёт орать, да? :-}

О чем это? Неудачные опыты?

А стэктрейсы после запуска толстого плэйбука хотя бы на 50+ хостов смотреть? >____<

К примеру, когда в template неправильно указан аргумент src, а изменений много, и вносил их кто-то другой:

При наличии рук не растущих из плеч и отсутствии привычки тестировать свой код можно добиться и более впечатляющих результатов.
# {{ ansible_managed }}

Не советую использовать этот макрос, он включает в себя текущее время и поэтому ломает --check: при каждом прогоне task будет показываться как changed.
Это не совсем правда. Дата задается, но меняется только если шаблон изменился.
У меня при --check не было такой проблемы (ansible 2.0.1.0). Хотя в некоторых версиях думаю такое могло быть.
+ если результат шаблона изменился
но у этой строки есть частный случай
когда два разных шаблона накатываются на один файл на сервере тогда будет постоянный changed даже если результат их работы одинаковый
и результат этой строки можно подправить переопределив его в /etc/ansible/ansible.cfg
ansible_managed = Ansible managed: modified on %Y-%m-%d %H:%M:%S
Давно не проверял, хорошо, если починили. Хотя в документации по-прежнему пишут: «Note that if using this feature, and there is a date in the string, the template will be reported changed each time as the date is updated.»

Что нужно сделать что-бы работала следующая схема по переменным различающихся по stages?


Групповые (общие) переменные
  • group_vars/
    • all/
      • common
      • secret
    • dev/
      • common
      • secret
    • stage/
      • common
      • secret
    • prod/
      • common
      • secret

В Плейбуках пропивывате пути к файлам с переменными?

Эта схема будет работать автоматически, если в файле inventory (hosts) будут соответствующие группы (dev/stage/prod). В плейбуках при этом можно ничего специально не прописывать.
hosts
[dev]
host01
host02

[stage]
host01
host02

[prod]
host01
host02
Точно…

Вот теперь вспомнил почему я сделал по другому
я не хотел перемешивать окружения
и поэтому у меня выглядит так как описано тут:

http://toja.io/using-host-and-group-vars-files-in-ansible/

Очень важная фишка, зависимоти между ролями…
Надо использовать, тогда сами роли будут меньше и проще

Хорошая статья! Я изредка использую Ansible, когда настраиваю новый сервер/VPS. Но так как я использую его редко, то мой Ansible-проект сейчас похож на кашу. Статья помогла мне понять один из способов хорошей организации Ansible-проекта, теперь займусь рефакторингом :) Автору спасибо!

Спасибо за статью.
Еще один подход к разделению энвайроментов.


На счет тегов. Избыточные теги тоже зло. На мой взгляд (когда применяется бездумно) нарушает KISS и YAGNI.
По этому поводу можно подробней почитать здесь

Only those users with full accounts are able to leave comments. Log in, please.