Comments 52
Ух. Много. Я попробую ответить на некоторые вопросы, но в целом хочу сказать, что все приключения с vagrant'ом связаны с тем, что vagrant — ужас. Не используйте его, у него слишком много полиси. Для начала освоения — поднимите стейджинг руками, настройте начального пользователя и юзайте его. Автоматизация того, что не понимаешь — это боль и отчаяние, потому что от тебя скрывают ошибки нижележащего уровня.
По сказанному: update_cache надо делать только в качестве параметра для установки пакетов. Отдельной таской — глупо (и не понятно, оно changed или нет, и даже если файлы поменялись, то нас это касается или нет?). Если очень хотите, то можно в эту таску написать changed_when: False, но я обычно делаю вот так:
- name: Install packages
apt: name={{item}} update_cache=yes cache_valid_time='{{apt_cache_valid_time}}'
with_items: '{{packages}}'
register: apt_result
retries: 10
delay: 10
until: apt_result|success
when: packages is defined
tags:
- apt
- install
При этом у меня есть playbook'а для update (которая устанавливает обновления софта) — она переопределяет дефолтное apt_cache_valid_time в 1. Фигня с retries — баг в lxc-транспорте, который я юзаю на локальной машине (он падает если в выводе больше 64кб текста).
Become для не-рута и sudo:
1) Пакет acl — на все хосты обязательно. Вся фигня возникла из-за security-дыры на многопользовательских машинах, которую решили залатать, даже если она не кусает однопользовательские.
2) все атрибуты become/become_user наследуются от уровня выше. become на tasklist (рядом с hosts) наследуется на все такски. Так же как и become_user. Моё собственное правило — минимизировать become, т.е. вешать его только на таски внутри ролей.
Насчёт начального появления питона на хосте.
Вот мой bootstrap.yaml, позволяющий настроить python и т.д. даже если хост был переустановлен:
---
- hosts: all
gather_facts: no
serial: 1
tasks:
- name: Remove old facts
meta: clear_facts
- name:
set_fact:
host_v1: '{{ansible_ssh_host}}'
host_v2: '{{ansible_host}}'
tags:
- always
- name: Forcefully drop fact cache for host
local_action: file path=.facts/{{inventory_hostname}} state=absent
- name: remove old server key
local_action: command ssh-keygen -R '{{host_v1}}'
failed_when: false
- name: remove old server IP key
local_action: shell ssh-keygen -R `host {{host_v1}}|awk '{print $4}'`
failed_when: false
- name: remove old server key
local_action: command ssh-keygen -R '{{host_v2}}'
failed_when: false
- name: remove old server IP key
local_action: shell ssh-keygen -R `host {{host_v2}}|awk '{print $4}'`
failed_when: false
- hosts: all
gather_facts: no
tasks:
- name: Remember sever key and install python
raw: test -x /usr/bin/python || sudo apt-get update && sudo apt-get -y install python
vars:
ansible_ssh_extra_args: '-o StrictHostKeyChecking=no'
- hosts: all
become: true
strategy: free
pre_tasks:
- apt: update_cache=yes upgrade=dist
roles:
- mivok0.users
- Stouts.sudo
- george.shuklin.reboot-if-needed-for-upgrade
В этом скрипте много боли и хаков, но он (почти) работает (предполагается, что хосты resolvable). Иногда на ребутах отваливается чуток (после того, как всё сделал), планирую поправить.
Остальное попробую прокомментировать чуть позже.
Проверка
when: packages is defined
не поможет. Как минимум: заменить with_items
на "{{ packages|default([]) }}"
Неа. is defined проверяет на то, определено или нет. Если нет, то блок skipped.
---
- hosts: all
gather_facts: no
tasks:
- debug: msg="{{foo}}"
when: foo is defined
with_items: '{{foo}}'
ansible-playbook -i localhost, 1.yaml
______________
< TASK [debug] >
--------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
skipping: [localhost]
---
- hosts: all
gather_facts: no
tasks:
- set_fact:
foo: "Hello"
- debug: msg="{{item}}"
when: foo is not string
with_items: '{{foo}}'
Такой упадет. Мне кажется в первом варианте просто какой-то частный случай, который явно проверяется в ансибле, у них куча таких хаков в коде. Но спорить не буду, я эту часть не особо разбирал, надо будет посмотреть.
is not string
очевидно упадёт и без всякого with_items
, потому что чтобы сделать type(foo)
нужно иметь foo
определённым.
with_items
не упадет, будет skipped. Только с циклом падает---
- hosts: all
gather_facts: no
tasks:
- debug: msg="{{foo}}"
when: foo is not string
ansible-playbook -i localhost, 1.yaml
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'foo' is undefined\n\nThe error appears to have been in '/tmp/1.yaml': line 5, column 6, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n — debug: msg=\"{{foo}}\"\n ^ here\nWe could be wrong, but this one looks like it might be an issue with\nmissing quotes. Always quote template expression brackets when they\nstart a value. For instance:\n\n with_items:\n — {{ foo }}\n\nShould be written as:\n\n with_items:\n — \"{{ foo }}\"\n\nexception type: <class 'ansible.errors.AnsibleUndefinedVariable'>\nexception: 'foo' is undefined"}
ЧЯДНТ?
Мораль: is not string не катит для несуществующих переменных.
set_fact
:)Если мы делаем
foo: "Hello"
, то when: foo is not string
будет False, а значит по идее блок debug должен быть пропущен, и это так, но только если нет цикла. Если же цикл есть, то блок валится с ошибкой.Короче, что я хочу сказать: when + loop действует не так, как многие предпогалают, поэтому тут надо быть внимательнее
- debug: msg="{{item}}"
when: foo is defined
with_items: "{{foo}}"
- set_fact:
foo: "Hello"
- debug: msg="{{item}}"
when: foo is not string
with_items: "{{foo}}"
В обоих случаях выражение в when равно False, но при этом первый работает, а второй валится
1) Что значит «поднять стейджинг руками» — оплатить дроплет и поднимать на нём? Но написание плейбука занимает несколько недель, не очень хочется тратить деньги, когда есть бесплатная, пусть и чуть более глючная альтернатива.
2) Насчёт таска apt: ты его держишь в отдельном файле и импортишь каждый раз, когда нужно что-то установить? По одному на роль, или где-то рядом с корнем? Как это вообще работает?
3) «update_cache надо делать только в качестве параметра для установки пакетов» — но при этом ты его вызываешь в конце bootstrap.yaml. Почему?
Ещё, 4) при установке munin нужно делать
munin-node-configure --sh | sh
. Можно ли обойтись без модуля shell?И 5) где обычно хранят большие файлы, необходимые для настройки — дампы БД, бинарники, файлопомойки? Распихивают по облакам, или есть какой-то общий рецепт? Не в репу же их загружать.
1. Правильно иметь тестовую машину такой же, как машину, на которую будете устанавливать роль. По этой причине плохо подходит вагрант — многовато отличий. Если у вас linux — поставьте один раз ubuntu в kvm и потом просто делайте с нее копии/снапшоты и на них тестируйте. Если у вас мак — один раз поставьте руками virtualbox и настройте в нем машину с сетью, потом копируйте ее и тестируйте на копиях. Это, конечно, не так просто, как сказать vagrant up, но зато вы не будете потом иметь неясного происхождения геморрой с запуском плейбуков.
2. Это зависит от того, что и как вам нравится устанавливать. Можно в pre_tasks его держать, можно в отдельном файле и импортировать.
3. Потому что до этого apt вызывается только если нужно устанавливать python и при этом явно зовется apt update. Его и не нужно раньше звать просто.
4. Можно не вызывать --sh, а зафиксировать версию munin, которую ставите и сделать все ln -sf, которые он вызывает, через модуль file. Это правильнее, потому что избежите ненужных changed, да и вообще неопределенного поведения, если вдруг версия munin обновится внезапно.
5. Дампы БД очень изменчивы. Обычно они нужны, если вы мигрируете с одного сервера на другой. На практике это довольно редкое занятие и я, например, делаю такие вещи руками — настолько нечасто это нужно делать. Бинарники — тут кто во что горазд. Для Go становится модно тянуть собранный бинарник с гитхаба, например. Мне этот подход не очень нравится, но это лучше, чем положить блоб в репу. В идеале, конечно, бинарь должен быть упакован в пакет под нужную ОС, но это в идеализированном мире девопсов с понями так происходит, а на практике делают костыли.
Я с некоторыми мелкими глюками использую LXC для быстрой разработки (если использовать LXC как виртуалку, т.е. по SSH, то глюков нет, я говорю про lxc-transport, который позволяет работать с контейнерами без ssh). В принципе, можно поднять любую виртуализацию руками — тот же вагрант вам всего лишь фигачит виртуалки в virtualbox'е. Если у вас локальный линукс — то lxc или libvirt + kvm (virtualbox — ужасен). На других платформах выбирайте сами, но идея состоит в том, что не надо пытаться автоматизировать всё сразу. Поднимите виртуалки старыми методами (install from ISO), работайте с ними как с серверами.
Насчёт стейджинга — обычно у людей есть доступ к вычислительным рерсурсам работодателя. Если нет, то неприятно, да. Хотя я когда xen отлаживал, то я просто старый компьютер юзал для этого.
2) apt слишком простой, чтобы делать его playbook'ой или ролью. У меня есть роль apt_install, которая вызывается из common.yaml и ставит туда стандартный набор пакетов, но это ерунда и не надо об этом заморачиваться. Таска и таска.
3) Я вызываю не update_cache, а dist=true, что означает "сделай мне apt-get update && apt-get dist-upgrade" — ставит все апдейты на сервер в процессе "bootstrap".
4) munin-node-configure --sh | sh
— ужасная строчка. В таком виде что shell, что модуль — всё равно не ясно будет что там происходит внутри. Я бы попробовал расковырять и сделать руками (всё что там делается), но если трудно — обычного shell будет достаточно.
5) Вопрос хранения больших файлов — очень, очень, очень обширный топик. Вариантов много: приватный репозиторий, swift (cloud), иногда — submodule в git'е. В репе бинарники лучше не хранить. Какие-то мелкие скрипты — может быть, блобы — однозначно нет.
Но у компании должен быть workflow для работы с блобами. Это большая задача и её нельзя "обойти".
Модули в репозитории с проектом — это нормально.
Насчёт модуля, кстати, у меня та же мысль была. Но я не разобрался, можно ли, и как, запихать модуль в роль. В доках их советуют куда-то в чулан файловой системы пихать отдельно. Подскажи, пожалуйста, как их хранить вместе?
You can enable a custom action plugin by either dropping it into the action_plugins directory adjacent to your play, inside a role, or by putting it in one of the action plugin directory sources configured in ansible.cfg.
docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#embedding-modules-and-plugins-in-roles
Лично я во многом с автором согласен. Например, в том, что ansible не так прост, как про него рассказывают — это вот совершенно в точку. И про yaml — вообще не понимаю восторгов по поводу этого формата. Хотя это, возможно, уже вкусовщина.
Читать удобно. Много строк, но разобраться в чужой плейбуке, если там выкрутасов на сайд-эффектах не делали, чаще всего очень легко. А ещё из-за того, что yaml, то очень легко получаются вот такие конструкции:
- name: Configure settings
become: yes
copy:
dest: /etc/default/foo
content: |
# managed by ansible
PORT={{iinternal_port}}
ADDRESS={{internal_ip}}
Даже питоновские тройные кавычки не катят — |
позволяет иметь правильное многострочное форматирование с сохранением отступов. Более того, я не знаю ни одного другого формата энкапсуляции, который бы такое позволял.
Или вот в твоем примере — строка
# managed by ansible
— она ведь будет с нулевым отступом. А здесь — шесть пробелов. Неочевидно.Или то, насколько по-разному оформляются списки и словари в однострочном и многострочном формате…
Конечно, когда ко всему этому привыкнешь — оно все понятным и логичным выглядит. Я никак не привыкну, для меня yaml-синтаксис — даже незнаю, как описать. Он не сложный, нет, он «неправильный» какой-то.
Почему не очевидно? Табуляция отрезается по первому элементу. Если хочется с пробелами в начале, есть |+4
и т.д.
yaml надо изучить, но когда изучишь, лучше языка (для написания данных) трудно придумать. Именно потому, что в нём есть ответы на все вопросы.
Насчёт опечаток — есть линтеры для yaml'а и ансибла, они ошибки подсвечивают.
Я в целом про то, что:
процесс не smooth, и что там постоянно кулибинство
Вот у ansible — что ни версия, то революция. То им loop вместо with_ захотелось, то им with надо с кавычками (а раньше без), то у них теперь dynamic includes которые глючат и бибикают и перезаписывают default'ами роли set_facts, и т.д.
К ансиблу тут претензий куча. Но yaml за что гнобить-то?
Насчёт опечаток — есть линтеры для yaml'а и ансибла, они ошибки подсвечивают.
Посоветуй, чем сам пользуешься?
loop
вместо with_*
. Оказалось, между 2.4 и 2.5 все циклы переделали, документацию переписали с нуля. Я немного в шоке от таких «минорных» изменений.Я планирую миграцию на 2.5 после следующего минорного релиза. Пока только changelog глянул и всё.
Это не брюзжание. Это непрерывное ощущение, что процесс не smooth, и что там постоянно кулибинство. К сожалению, более адекватной системы нет.
Группу повторяющихся тасков с похожими параметрами выносите в отдельный файл и вызывайте через include_role
Потенциально очень багоемкая директива, рекомендую во всех возможных случаях юзать import_* или вообще объявлять роли списком в теле плейбука (1.4-стайл). Разница между статической и динамической загрузкой тут: docs.ansible.com/ansible/2.4/playbooks_reuse_includes.html
сделал playbook для установки всех ролей
А зачем?
Playbook для Galaxy — чтобы не вспоминать параметры команды. Я его почти целиком скопировал из какой-то статьи типа «пять полезных штук для удобства».
Самая большая проблема у них с багами. Их очень много. Достаточно посмотреть issues на гитхабе. Скорее всего это связано с очень активной разработкой, они так активно пилят фичи, что просто не в силах всё контролировать и тестировать. Ну и правила версионирования и обратная совместимость у них очень странная. Постоянно что-то меняют, короче со стабильностью у них есть проблемы и очень большие.
Для простых статических плейбуков всё обычно работает, но стоит чуть усложнить — подключить переменные, динамические загрузки тасков и ролей, и можно получить очень много сюрпризов. Из недавнего вспоминаю баг, когда в роли переменные не передавались. Я всё так красивенько на ролях сделал, и тут такой облом :) Я был в таком шоке, что запилил себе свою собственную систему «ролей», используя
include_tasks
. Сейчас они роли уже пофиксили, но я пока их использую очень осторожно.Пока у меня еще остались сложности с переменными в ансибле. Я пока не до конца понимаю механизм их работы, а именно области видимости. Как-то они начинают рекурсивно раскручиваться в момент использования в таске похоже. И тут можно получить совсем неожиданные результаты, т.к. ты думаешь, что ты уже присвоил где-то значение переменной, а оно каким-то образом заменяется в другом месте)) Одним словом, на императивность рассчитывать особо не стоит. Ну и разные приоритеты set_fact, include_vars и прочее. В общем, с переменными надо быть настороже.
И первый сюрприз: в коробке Ubuntu 16.04 нет python по умолчанию!
Если речь идет именно об Ubuntu 16.04, то там там уже python3 по умолчанию. В самом ansible это можно поправить, например, указав у нужного хоста переменную вот таким образом:
hostname01 ansible_host=192.168.1.10 ansible_python_interpreter=/usr/bin/python3
Сколько использую, пока не встретил проблем с этим.
ansible_python_interpreter
можно в ansible.cfg
засунуть, чтобы на всех хостах было одинаково.Сколько использую, пока не встретил проблем с этим.
И все же, если это не какая-то исключительная ситуация, лучше пока устанавливать python 2.x, как и сделал автор поста.
Из faq последней версии ansible:
Python 3 support is being worked on but some Ansible modules are not yet ported to run under Python 3.0. This is not a problem though as you can just install Python 2 also on a managed host.
И в документации поддержка python 3.x все еще помечена как technology preview feature.
Для тех кто захочет на opensuse 42.3 minimal заюзать ансибл, из коробки не будет пакета python-xml. К чему это приведёт, ну модули package и zypper работать не будут. Догадаетесь, какие есть варианты установки пакета? Всё тот же command или shell.
Но самое вкусное, это когда вы захотите подключить репозитории внешние, например nginx или zabbix: во время попытки установить нджинкс из его стейбл или мейнлайн репы, вместо репы ОС, у вас ничего не выйдет без тех же command или shell. А если вы обновляете заббикс с 2.2 из реп ОС до 3.4 то опять же работать можно только через command и shell. Собственно вопрос, будете ли вы пилить модуль 2-10 дней для фикса багов или вставите костыль за 30 минут… Проблемы есть конечно всегда, но со всеми suse линуксами ансибл придётся костылять в 10 раз больше, чаще дешевле переехать на другой дистр. Из всех здесь описанных багов ансибл я не встретил ничего кроме багов в zypper модуле.
по поводу правки конфигурационных файлов, lineinfile конечно годен, но только при одном изменении в файле, про blockinfile никогда не слышал и даже страшно стало что вы там с ним за хаки делаете: несколько тасков да в один файл. В ansible есть templates, именно их надо использовать для правки конфигурационных файлов. Это один из плюсов использования roles, одна role, если это конечно хорошо написаная role, а не говно мамонта 2014 года :) обычно выполняет не только установку пакета, но и устанавливает в нужные места templates, которые содержат variables, которые в свою очередь можно выставлять на уровне playbook'a
Те же самые ощущения помешали мне в своё время всё автоматизировать с помощью ansible/vagrant. Брал даже готовые сборки с надеждой поменять какие-то мелочи (puphpet внезапно потребовал меньше всего напильника).
В конечном итоге, за неделю сделал три варианта деплоя (на разных стеках), в каждом из которых были мелкие, но очень неприятные проблемы, требующие какого-нибудь простого костыля.
Но это было два года назад. Как сейчас обстоят дела с готовыми «тематическими» сборками? Что-то вроде уже настроенных разбирающимися людьми ubuntu/nginx/php-fpm-7x/symfony/pgsql/xdebug/metrics/...? Для использования в простых проектах.
Ну и описанное не помешало: я уже автоматизировал примерно две трети одного сервера, сейчас затык с собственными криво написанными скриптами, нужно будет переписать для порядка.
Ansible не так прост