Pull to refresh

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). Иногда на ребутах отваливается чуток (после того, как всё сделал), планирую поправить.


Остальное попробую прокомментировать чуть позже.

Первый код должен упасть, если packages не определен.
Проверка 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]
Хм, странно. When проверяется не до выполнения цикла, а для каждый итерации. А цикл по undefined foo должен завершиться ошибкой. Возможно у них есть какая-то проверка для таких случаев.
Я думаю, это результат ленивых вычислений jinja2. item — это же foo|next, правильно?
---
- 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 действует не так, как многие предпогалают, поэтому тут надо быть внимательнее
У нас получается 2 случая:
- 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, но при этом первый работает, а второй валится
Не, мой косяк. С with_items работает и второй, но падает с loop. Хм :)

В моих тестах ваш второй пролетал успешно как skipped, но если убрать set_fact, падал. is defined видимо, специально обрабатывают.

Да, я тупанул :(
Уже на loop перешел, думал он сейчас аналогично with_items работает
Окей, после очередного глюка вагранта, когда после серии halt/up/resume всё равно не удаётся войти по ssh и ошибок нет, я готов сменить его на что-нибудь более вменяемое.

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 для работы с блобами. Это большая задача и её нельзя "обойти".

Да, ещё: если проект живёт и в command что-то, что не получается модулями, но нужно, то можно написать свой модуль. Модуль на питоне это лучше, чем bash + register с yaml'ом и jinja для парсинга вывода модуля.

Модули в репозитории с проектом — это нормально.
Спасибо за советы, жду ещё :)

Насчёт модуля, кстати, у меня та же мысль была. Но я не разобрался, можно ли, и как, запихать модуль в роль. В доках их советуют куда-то в чулан файловой системы пихать отдельно. Подскажи, пожалуйста, как их хранить вместе?
Полный список мест не знаю, я обычно в корне гита храню (в соотв. каталоге). Вот что документация говорит:
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.
Статья — брюзжание. А заголовок обещал что-то новое и интересное…
По-моему, не совсем. Ни разу я не пишу, что Ansible или Vagrant плохие. Когда я понял, что решил много проблем, с которыми могут столкнуться другие, я решил сэкономить им часы гугления, описав, как я эти проблемы решал. Ansible мне нравится, пилю сейчас playbook со всё возрастающей скоростью.
Кому как.
Лично я во многом с автором согласен. Например, в том, что 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, и что там постоянно кулибинство
Ну вот я yaml-то это не надо гнать. У yaml всё ровненько и хорошо. Выучил возможности (их конечное количество и они не меняются) и ладушки.

Вот у ansible — что ни версия, то революция. То им loop вместо with_ захотелось, то им with надо с кавычками (а раньше без), то у них теперь dynamic includes которые глючат и бибикают и перезаписывают default'ами роли set_facts, и т.д.

К ансиблу тут претензий куча. Но yaml за что гнобить-то?
Дак я и не гноблю. Сразу сказал, что это он для меня неудобен, а не вообще неудобен. Допускаю, что со временем пройдет. В равной степени допускаю, что есть другие такие, кому он неудобен (на начальных этапах, или вообще).
Насчёт опечаток — есть линтеры для yaml'а и ансибла, они ошибки подсвечивают.

Посоветуй, чем сам пользуешься?
ansible-lint, atom + linter-ansible-lint
Кстати да, я захотел проитерироваться по файлам, захожу в доку — а там какие-то loop вместо with_*. Оказалось, между 2.4 и 2.5 все циклы переделали, документацию переписали с нуля. Я немного в шоке от таких «минорных» изменений.
2.5 — мажорный релиз, а у ансибла довольно крутые изменения каждый мажорный релиз, так что нужно использовать выбранную версию. А между ними миграция класса «c postgres 9.3 на 10». Кстати, такая же фигня и у остальных систем — рядом отдел планирует переезд на новый chef уже пол-года.

Я планирую миграцию на 2.5 после следующего минорного релиза. Пока только changelog глянул и всё.

Это не брюзжание. Это непрерывное ощущение, что процесс не smooth, и что там постоянно кулибинство. К сожалению, более адекватной системы нет.

Зашел почитать, как страшен Ansible, а прочел, как ужасен Vagrant + Virtualbox. Ну да ладно.
Группу повторяющихся тасков с похожими параметрами выносите в отдельный файл и вызывайте через include_role

Потенциально очень багоемкая директива, рекомендую во всех возможных случаях юзать import_* или вообще объявлять роли списком в теле плейбука (1.4-стайл). Разница между статической и динамической загрузкой тут: docs.ansible.com/ansible/2.4/playbooks_reuse_includes.html
сделал playbook для установки всех ролей

А зачем?
Роли я просто перечисляю, внутри них пару раз использовал `include_tasks` вместо `import_tasks`, потому что решил, что переменные во втором случае не передадутся. Это, конечно, развенчивает документация, спасибо.

Playbook для Galaxy — чтобы не вспоминать параметры команды. Я его почти целиком скопировал из какой-то статьи типа «пять полезных штук для удобства».
У ансибла в принципе нормальная документация для старта. Если делать всё последовательно, а не скакать по разделам. Сначала с adhoc поиграться локально, потом можно к плейбукам переходить на удаленных машинах. Но это если поставить задачу разобраться в инструменте, а не сделать всё за час.

Самая большая проблема у них с багами. Их очень много. Достаточно посмотреть 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 засунуть, чтобы на всех хостах было одинаково.
Скорей всего, да. Не указывал так, потому что у меня есть инстансы, где и python2.7 актуален. :)
А есть чит, чтоб ansible самостоятельно находил доступный интерпретатор?
Вот так навскидку не вспомню, нужно смотреть документацию.
Сколько использую, пока не встретил проблем с этим.

И все же, если это не какая-то исключительная ситуация, лучше пока устанавливать 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.
Да, в случае с 1-2 серверами этот подход окей. Но когда их уже становится даже больше 10, то идти на них и ставить python2.7 становится утомительно. :)
Так а зачем на них идти? Речь идет об использовании модуля raw для изначальной инсталляции python 2.x, если отсутствует /usr/bin/python. Т.е. добавляется дополнительный task в «bootstrap» playbook.
Спасибо полистал статью и комменты. Как всегда в комментах есть, что-то интересное.
Для тех кто захочет на 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 модуле.
Знатоки, подскажите, kubernates + docker решает схожие проблемы или это из разных областей?
Это из разных областей, но решает схожие проблемы :-)
по поводу синтaксиса yaml три дефиса в начале как shebang в баше и питоне, вы же не будете писать скрипт не выставив shebang?
по поводу правки конфигурационных файлов, lineinfile конечно годен, но только при одном изменении в файле, про blockinfile никогда не слышал и даже страшно стало что вы там с ним за хаки делаете: несколько тасков да в один файл. В ansible есть templates, именно их надо использовать для правки конфигурационных файлов. Это один из плюсов использования roles, одна role, если это конечно хорошо написаная role, а не говно мамонта 2014 года :) обычно выполняет не только установку пакета, но и устанавливает в нужные места templates, которые содержат variables, которые в свою очередь можно выставлять на уровне playbook'a
Хоть я и использовал ansible немного, но для более глубокого понимания я себе заказал книжку (прошу не считать рекламой). Вроде как довольно свежая и по актуальной версии…
Спасибо за пост и особенно за комментарии!

Те же самые ощущения помешали мне в своё время всё автоматизировать с помощью ansible/vagrant. Брал даже готовые сборки с надеждой поменять какие-то мелочи (puphpet внезапно потребовал меньше всего напильника).
В конечном итоге, за неделю сделал три варианта деплоя (на разных стеках), в каждом из которых были мелкие, но очень неприятные проблемы, требующие какого-нибудь простого костыля.

Но это было два года назад. Как сейчас обстоят дела с готовыми «тематическими» сборками? Что-то вроде уже настроенных разбирающимися людьми ubuntu/nginx/php-fpm-7x/symfony/pgsql/xdebug/metrics/...? Для использования в простых проектах.
Сейчас, я так понимаю, достаточно сделать playbook из готовых ролей Ansible Galaxy для всего этого, и базовый сервер будет настроен. Там даже есть всякие lamp.

Ну и описанное не помешало: я уже автоматизировал примерно две трети одного сервера, сейчас затык с собственными криво написанными скриптами, нужно будет переписать для порядка.
Sign up to leave a comment.

Articles