У меня есть три сервера, но я не профессиональный сисадмин. Это означает, что несмотря на четыре базы данных и стопяцот приложений, бэкапы нигде не ведутся, к любой проблеме на сервере я подхожу, шумно вздохнув и бросив тарелку в стену, а операционные системы там достигли EOL два года назад. Я бы рад обновить, но на это нужно выделить, наверное, неделю, чтобы всё забэкапить и переставить. Проще забыть про yum update и apt-get upgrade.

Конечно, это неправильно. Я давно присматривался к chef и Puppet, которые, как я думал, решат все мои проблемы. Но я смотрел на конфиги знакомых проектов и откладывал. Это же нужно изучать, разбираться с ruby, бороться с многочисленными, по отзывам, косяками и ограничениями. Две недели назад статья Георгия amarao стала животворящим пинком. Даже не сама статья, а перечисление систем управления конфигурацией. После чтения комментариев и лёгкого гугления решил: возьму Ansible. Потому что питон, и на проблемы никто не жалуется.



Что ж, тогда я первым буду.

Сначала я нарыл кучу документации и учебников по Ansible, начиная с бесполезного видеоролика Quick Start на официальном сайте. Их, конечно, много, сделаны для разных задач и написаны разными людьми, но объединяет их одно: учебники делали для людей, которые уже понимают Ansible. Для людей со сферическим сервером в вакууме, которым достаточно подсказать, что бывают роли, модули и таски. Но я пришёл с clean slate и собрал все грабли, какие нашёл. Надеюсь, эта заметка поможет вам их обойти.

От систем управления конфигурациями я ждал чудес, вроде автоматически обновляемых приложений из git. Но оказалось, что Ansible — это лишь способ сохранить последовательность действий при настройке нового сервера. Вы сможете сделать в Ansible только то, что умеете делать из консоли самостоятельно. Чудес нет.

Начало. Vagrant


Задача: не делаю новый хост, потому что хочу сохранить ip. То есть, очищу дроплет через контрольную панель, затем инициализирую с помощью Ansible. План: написать playbook и отладить его на Vagrant.

Начать очень сложно. Все учебники по Ansible начинаются с описания inventory, куда нужно прописывать адрес сервера. Но какой ip у вагранта? Чёрт его знает. В документации по Ansible есть инструкция, как запустить playbook в Vagrant; в документации по Vagrant есть инструкция по подключению Ansible, и они не то чтобы идентичны. В итоге, забил на поиск ip и взял общее: минимальный Vagrantfile, который запускает playbook.

Vagrant.configure(2) do |config|
  config.vm.box = "ubuntu/xenial64"
  config.vm.network "forwarded_port", guest: 80, host: 8080
  # так и не знаю, зачем это:
  config.ssh.insert_key = false

  config.vm.provision "ansible" do |ansible|
    ansible.verbose = "v"
    ansible.playbook = "playbook.yml"
  end
end

Набросал черновик playbook-а, создал заготовки ролей и запустил vagrant up. Не взлетело. Поскольку официальный образ xenial — только для VirtualBox, а в Fedora Linux виртуализация через libvirt. Долго вспоминал правильную команду: vagrant up --provider virtualbox. Затем правил синтаксические ошибки в yaml (зачем там в начале обязательные три дефиса?). Помним, что после запуска коробки для перезапуска Ansible пишем vagrant provision.

И первый сюрприз: в коробке Ubuntu 16.04 нет python по умолчанию! Дикость для федоры, где пакетный менеджер написан на питоне. Ansible, как я узнал, загружает свои модули на сервер и выполняет их там. Идём на StackOverflow, находим волшебный таск (точнее, десять вариаций одного таска и непонятно, как лучше):

- name: Install python for Ansible 
  become: yes
  raw: test -e /usr/bin/python || (apt -qy update && apt install -y python-minimal)
  register: output
  changed_when: output.stdout

Суперпользователь, become!


Даже с документацией и примерами многое непонятно. Не понимаю, например, почему Vagrant переопределяет remote_user, и как так получается, что в каждой коробке свой суперпользователь. Я же буду запускать playbook на чистом сервере, где будет только root, и нужно будет сделать своего суперпользователя. Но делать это под вагрантом нужно иначе, чем на чистом сервере, видимо. Вообще непонятно: получится два playbook, для стейджинга и для продакшена?

Или вот become и become_user: одно не подразумевает другого. Что из этого нужно указывать в корневом playbook, если для настройки сервера постоянно нужно включать рута? Я сначала поставил туда become: yes и в каждом втором таске писал become_user: root. Потом оказалось, что без become_user тоже всё работает от рута! Потому что root — это значение по умолчанию и я, по сути, с самого начала сделал sudo -i без возможности отпустить.

Где-то тут я вспомнил, что давно не обновлял систему на своём ноутбуке, и запустил dnf update. Продолжая колупаться с плейбуком. Vagrant работал, а dnf в соседней вкладке обновлял VirtualBox. Кажется, так делать не нужно, потому что очередной vagrant provision сказал: «всё сломалось и я не виноват». Ему не хватало VirtualBox, который «terminated unexpectedly during startup with exit code 1 (0x1)» — и хоть ты тресни. Команда vboxheadless -h (я не настоящий девопс, я гуглил) показала ошибку -1912. В интернете все как один отвечают: переустанови VirtualBox. Хрен там, не помогает. Отчаявшись, нашёл коробку xenial для libvirt и перешёл на него. Хорошо, когда есть выбор.

Из какого-то примера скопировал таск вызова apt с кучей параметров, а потом узнал, что update_cache=yes хорошо бы сделать отдельной задачей. И эта задача, вот беда, всё время возвращает «changed». Оказалось, нужно прописать cache_valid_time=3600, чтобы проверять обновления не чаще раза в час. Сначала подумал написать 86400 (сутки), но я же не в кроне буду Ansible вызывать, а раз в месяц — пусть живёт.

Развернём базу данных


Установка PostgreSQL — пять строчек в консоли или целая эпопея в Ansible. В определённый момент нужно сделать become_user: postgres. И тут коробка выдала странную ошибку: «Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user». Помните, как Ansible загружает модули на сервер и там запускает? Ну так вот, загружает он их от root или от другого суперпользователя, а потом у пользователя postgres нет к ним доступа. Вот незадача.

StackOverflow снова в помощь: оказывается, есть три выхода. Один из них — сделать ansible.cfg и прописать внутрь pipelining=True (а для решения какой-то другой возникшей проблемы я временно ставил pipelining=False). Второй выход — буквально, «не делайте так». И третий самый простой: ставите пакет acl и всё волшебным образом работает. Вернее, не работает другим способом: «sudo: a password is required». Ну что за дела, откуда здесь вообще пароли, я же с ключом захожу?

Оказалось, захожу в виртуалку без ключа, пользователем vagrant. Который был сделан до нас и за нас. Ansible при become_user, видимо, делает sudo -u postgres, а оно требует пароль пользователя vagrant. Пароля нет.

Начинаю перебирать варианты. become_method: su вылетает по таймауту, потому что сервер спрашивает пароль, а Ansible этого не понимает. Что он там делает — непонятно, потому что у меня sudo su postgres пароль не спрашивает. Есть вариант в файле /etc/sudoers.d/vagrant прописать «vagrant ALL=(ALL) ...», потому что слово в скобках позволит делать sudo -u без пароля. Но тогда playbook становится заточенным под Vagrant, а мне его ещё в проде запускать. Неаккуратненько.

От безысходности пробую вообще убрать become. Постгрес ожидаемо цедит: «Peer authentication failed for user „postgres“». Выкапываю стюардессу. Новый план: запускать роль под пользователем zverik, у которого есть все на свете права. Разбиваю playbook на два: в первом устанавливаю питон и делаю пользователя, вторым ставлю и настраиваю всё остальное с remote_user: zverik. Запускаю. И снова «sudo: a password is required». Почему? А, ну да, Vagrant передаёт значение remote_user и не даёт его поменять. Ну блин.

Чтобы отвлечься, открыл текстовый редактор и начал писать эти заметки. К этому моменту я разбираюсь с Ansible вот уже неделю по полтора-два часа, а ещё даже базу данных в постгресе не создал. В учебниках это всё выглядит так просто… Посчитал вкладки, связанные с Ansible в фаерфоксе: 48 штук. Сорок восемь. Примерно одна шестая от общего числа.

Тут я отключил ansible.force_remote_user в Vagrantfile и перезапустил provision. Ура, новая ошибка! Напоминает, что вход пользователем zverik работает только по сертификату. Но у меня же есть сертификат, и vagrant ssh -p работает и впускает без пароля. Нагуглил решение: нужно указать путь к сертификату в ansible.cfg. Оно не сработает по той же причине, что и remote_user: Vagrant побеждает. На этот раз проще переопределить главную переменную: добавляем в playbook «ansible_ssh_private_key_file: "{{ lookup('env', 'HOME') }}/.ssh/id_rsa"» и всё работает! Не очень красиво получилось, но ура!

После того, как я разобрался с пользователями, написание ролей пошло как по маслу. Уже готова одна роль из шести, шестьдесят тасков. Но начать сложнее, чем кажется по учебникам.

Полезные штуки


Во время написания playbook-ов находишь или нагугливаешь много полезных мелочей. Какие-то описаны в документации, какие-то — в статьях (поищите «Ansible» на хабре). Вот несколько из них.

Для выполнения команд — только модули command или shell. Последний, как пишет документация, только в крайних случаях, поэтому забудьте про перенаправление вывода и &&. Результат всегда «changed», что плохо. Управляйте результатом либо параметром creates (удобнее — в блоке args, вместе с chdir), либо register и changed_when. Полезно проверить условия перед выполнением: сначала рекогносцировка command + register + changed_when: False, а затем с помощью when проверяем сохранённый stdout на необходимость запуска команды.

Чем меньше вызовов модуля command, тем лучше. Гуглите: почти всегда есть модуль. Например, я сначала сделал command: npm install -g {{ item }}, а потом обнаружил, что можно npm: name={{ item }} global=yes. Модуль всегда лучше, чем команда, потому что не нужно проверять конфигурацию и потому что результат работы будет не в строке stdout, а в удобной структуре.

Файлы конфигурации почти всегда правим через lineinfile, который ищет строчку по регулярному выражению и заменяет на другую. Модуль blockinfile добавляет целые блоки текста. С ним есть нюанс: если несколько тасков пишут в один файл, то нужно переопределять marker: # {mark} block name. Иначе все будут затирать чужие блоки.

Перед изменением таблиц PostgreSQL удобно проверять их состояние с помощью pg_tables. Например:

command: psql -A -t -d {{ gisdb }} -c "SELECT tableowner FROM pg_tables WHERE schemaname = 'public' AND tablename = 'spatial_ref_sys'"

Наследование — наше всё: если можно вместо двух почти одинаковых тасков написать один с условными выражениями и with_items, то делайте так. Группу повторяющихся тасков с похожими параметрами выносите в отдельный файл и вызывайте через include_role с vars. Тут ещё должно быть про параметризацию ролей, но я ещё только учусь и роль у меня одна.

В одной из статей нашёл совет не переизобретать велосипед, а искать подходящие роли в каталоге Ansible Galaxy. Действительно, php-fpm и postfix ставили тысячи людей до вас, и часто найдётся хорошо написанная роль с удобными значениями по умолчанию.

С другой стороны, какой смысл качать роль geerlingguy.apache, когда apt: pkg=apache2 решает все мои задачи? Или, вот, нашёл роль для установки osm2pgsql из исходников, а она 2014 года и там используется устаревший sudo: yes. То есть, я, конечно, записал roles_path = roles.galaxy:roles в ansible.cfg и сделал playbook для установки всех ролей, но ставить пока нечего. Вот как он выглядит:

- hosts: localhost 
  vars: 
    galaxy_path: roles.galaxy 
  tasks: 
    - name: Remove old galaxy roles 
      file: path={{ galaxy_path }} state=absent 
    - name: Install Ansible Galaxy roles 
      local_action: command ansible-galaxy install -r requirements.yml --roles-path {{ galaxy_path }}

И в requirements.yml пишете строчки для каждой роли из Galaxy:

- src: автор.роль

Написали playbook и он отработал в Vagrant до конца? Отлично, теперь сделайте vagrant destroy и создайте коробку заново. Стопроцентно обнаружите несколько косяков: забытые sudo, пропущенные mode: 0755 для исполняемых файлов, недостающие пакеты (помогают dnf provides или apt-file, который нужно устанавливать). Наконец, самое главное: после второго запуска vagrant provision должно быть «changed: 0».

***


Переводить серверы под систему управления конфигурациями сложно, какую бы систему вы ни выбрали. Но после начального поля граблей программирование playbook-а спорится. Главное — не забывать о цели, чтобы не перегореть: вон, сейчас у меня целевая операционка Ubuntu 16.04, а через месяц я без особых сложностей переведу сервер на 18.04. А удовольствие от полнофункционального сервера с нуля по одной команде в консоли поможет в пути.

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


  1. amarao
    02.04.2018 12:58
    +4

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


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


    1. shoomyst
      02.04.2018 14:21

      Первый код должен упасть, если packages не определен.
      Проверка when: packages is defined не поможет. Как минимум: заменить with_items на "{{ packages|default([]) }}"


      1. amarao
        02.04.2018 14:30

        Неа. 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]


        1. shoomyst
          02.04.2018 14:56

          Хм, странно. When проверяется не до выполнения цикла, а для каждый итерации. А цикл по undefined foo должен завершиться ошибкой. Возможно у них есть какая-то проверка для таких случаев.


          1. amarao
            02.04.2018 14:58

            Я думаю, это результат ленивых вычислений jinja2. item — это же foo|next, правильно?


            1. shoomyst
              02.04.2018 15:12

              ---
              - hosts: all
                gather_facts: no
                tasks:
                  - set_fact:
                      foo: "Hello"
                  - debug: msg="{{item}}"
                    when: foo is not string
                    with_items: '{{foo}}'
              

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


              1. amarao
                02.04.2018 15:16

                is not string очевидно упадёт и без всякого with_items, потому что чтобы сделать type(foo) нужно иметь foo определённым.


                1. shoomyst
                  02.04.2018 15:21

                  Без with_items не упадет, будет skipped. Только с циклом падает


                  1. amarao
                    02.04.2018 15:45

                    ---
                    - 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 не катит для несуществующих переменных.


                    1. shoomyst
                      02.04.2018 16:02

                      Не делаете set_fact :)
                      Если мы делаем foo: "Hello", то when: foo is not string будет False, а значит по идее блок debug должен быть пропущен, и это так, но только если нет цикла. Если же цикл есть, то блок валится с ошибкой.
                      Короче, что я хочу сказать: when + loop действует не так, как многие предпогалают, поэтому тут надо быть внимательнее


                    1. shoomyst
                      02.04.2018 16:14

                      У нас получается 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, но при этом первый работает, а второй валится


                      1. shoomyst
                        02.04.2018 16:31

                        Не, мой косяк. С with_items работает и второй, но падает с loop. Хм :)


                      1. amarao
                        02.04.2018 16:34

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


                        1. shoomyst
                          02.04.2018 16:37

                          Да, я тупанул :(
                          Уже на loop перешел, думал он сейчас аналогично with_items работает


    1. Zverik Автор
      03.04.2018 00:10

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

      1) Что значит «поднять стейджинг руками» — оплатить дроплет и поднимать на нём? Но написание плейбука занимает несколько недель, не очень хочется тратить деньги, когда есть бесплатная, пусть и чуть более глючная альтернатива.

      2) Насчёт таска apt: ты его держишь в отдельном файле и импортишь каждый раз, когда нужно что-то установить? По одному на роль, или где-то рядом с корнем? Как это вообще работает?

      3) «update_cache надо делать только в качестве параметра для установки пакетов» — но при этом ты его вызываешь в конце bootstrap.yaml. Почему?

      Ещё, 4) при установке munin нужно делать munin-node-configure --sh | sh. Можно ли обойтись без модуля shell?

      И 5) где обычно хранят большие файлы, необходимые для настройки — дампы БД, бинарники, файлопомойки? Распихивают по облакам, или есть какой-то общий рецепт? Не в репу же их загружать.


      1. raskal
        03.04.2018 01:37

        Давайте я вам отвечу, если не возражаете.

        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 становится модно тянуть собранный бинарник с гитхаба, например. Мне этот подход не очень нравится, но это лучше, чем положить блоб в репу. В идеале, конечно, бинарь должен быть упакован в пакет под нужную ОС, но это в идеализированном мире девопсов с понями так происходит, а на практике делают костыли.


      1. amarao
        03.04.2018 13:07
        +1

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


  1. amarao
    02.04.2018 13:45
    +1

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

    Модули в репозитории с проектом — это нормально.


    1. Zverik Автор
      02.04.2018 13:51

      Спасибо за советы, жду ещё :)

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


      1. amarao
        02.04.2018 13:53

        Полный список мест не знаю, я обычно в корне гита храню (в соотв. каталоге). Вот что документация говорит:

        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.


      1. raskal
        03.04.2018 01:38

        Создавать в роли каталог library и в него складывать модули. Вот полная дока:

        docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#embedding-modules-and-plugins-in-roles


  1. elve
    02.04.2018 13:56

    Статья — брюзжание. А заголовок обещал что-то новое и интересное…


    1. Zverik Автор
      02.04.2018 14:00

      По-моему, не совсем. Ни разу я не пишу, что Ansible или Vagrant плохие. Когда я понял, что решил много проблем, с которыми могут столкнуться другие, я решил сэкономить им часы гугления, описав, как я эти проблемы решал. Ansible мне нравится, пилю сейчас playbook со всё возрастающей скоростью.


    1. throttle
      02.04.2018 14:25
      +1

      Кому как.
      Лично я во многом с автором согласен. Например, в том, что ansible не так прост, как про него рассказывают — это вот совершенно в точку. И про yaml — вообще не понимаю восторгов по поводу этого формата. Хотя это, возможно, уже вкусовщина.


      1. amarao
        02.04.2018 14:32
        +1

        Читать удобно. Много строк, но разобраться в чужой плейбуке, если там выкрутасов на сайд-эффектах не делали, чаще всего очень легко. А ещё из-за того, что yaml, то очень легко получаются вот такие конструкции:


        - name: Configure settings
          become: yes
          copy:
            dest: /etc/default/foo
            content: |
              # managed by ansible
              PORT={{iinternal_port}}
              ADDRESS={{internal_ip}}

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


        1. throttle
          02.04.2018 14:54

          Не знаю, меня дико выбивает синтаксис. С одной стороны, все должно быть просто, а с другой — пропустил где-то пробел после дефиса — и все рушится. И не поймешь, где. Или отступы не так поставил. В этом смысле мне куда проще понять строгий синтаксис со скобками, точказапятыми, и вот этим вот всем.
          Или вот в твоем примере — строка # managed by ansible — она ведь будет с нулевым отступом. А здесь — шесть пробелов. Неочевидно.
          Или то, насколько по-разному оформляются списки и словари в однострочном и многострочном формате…
          Конечно, когда ко всему этому привыкнешь — оно все понятным и логичным выглядит. Я никак не привыкну, для меня yaml-синтаксис — даже незнаю, как описать. Он не сложный, нет, он «неправильный» какой-то.


          1. amarao
            02.04.2018 15:00

            Почему не очевидно? Табуляция отрезается по первому элементу. Если хочется с пробелами в начале, есть |+4 и т.д.


            yaml надо изучить, но когда изучишь, лучше языка (для написания данных) трудно придумать. Именно потому, что в нём есть ответы на все вопросы.


            Насчёт опечаток — есть линтеры для yaml'а и ансибла, они ошибки подсвечивают.


            1. throttle
              02.04.2018 15:19

              Мы об одном и том же, но разными словами. :)
              Я в целом про то, что:

              процесс не smooth, и что там постоянно кулибинство


              1. amarao
                02.04.2018 15:22
                +1

                Ну вот я yaml-то это не надо гнать. У yaml всё ровненько и хорошо. Выучил возможности (их конечное количество и они не меняются) и ладушки.

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

                К ансиблу тут претензий куча. Но yaml за что гнобить-то?


                1. throttle
                  02.04.2018 15:29

                  Дак я и не гноблю. Сразу сказал, что это он для меня неудобен, а не вообще неудобен. Допускаю, что со временем пройдет. В равной степени допускаю, что есть другие такие, кому он неудобен (на начальных этапах, или вообще).

                  Насчёт опечаток — есть линтеры для yaml'а и ансибла, они ошибки подсвечивают.

                  Посоветуй, чем сам пользуешься?


                  1. amarao
                    02.04.2018 15:50

                    ansible-lint, atom + linter-ansible-lint


                1. Zverik Автор
                  02.04.2018 15:29

                  Кстати да, я захотел проитерироваться по файлам, захожу в доку — а там какие-то loop вместо with_*. Оказалось, между 2.4 и 2.5 все циклы переделали, документацию переписали с нуля. Я немного в шоке от таких «минорных» изменений.


                  1. amarao
                    02.04.2018 15:52

                    2.5 — мажорный релиз, а у ансибла довольно крутые изменения каждый мажорный релиз, так что нужно использовать выбранную версию. А между ними миграция класса «c postgres 9.3 на 10». Кстати, такая же фигня и у остальных систем — рядом отдел планирует переезд на новый chef уже пол-года.

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


    1. amarao
      02.04.2018 14:31

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


  1. vkflare
    02.04.2018 14:37
    +1

    Зашел почитать, как страшен Ansible, а прочел, как ужасен Vagrant + Virtualbox. Ну да ладно.

    Группу повторяющихся тасков с похожими параметрами выносите в отдельный файл и вызывайте через include_role

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

    А зачем?


    1. Zverik Автор
      02.04.2018 14:41

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

      Playbook для Galaxy — чтобы не вспоминать параметры команды. Я его почти целиком скопировал из какой-то статьи типа «пять полезных штук для удобства».


  1. shoomyst
    02.04.2018 14:48

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

    Самая большая проблема у них с багами. Их очень много. Достаточно посмотреть issues на гитхабе. Скорее всего это связано с очень активной разработкой, они так активно пилят фичи, что просто не в силах всё контролировать и тестировать. Ну и правила версионирования и обратная совместимость у них очень странная. Постоянно что-то меняют, короче со стабильностью у них есть проблемы и очень большие.

    Для простых статических плейбуков всё обычно работает, но стоит чуть усложнить — подключить переменные, динамические загрузки тасков и ролей, и можно получить очень много сюрпризов. Из недавнего вспоминаю баг, когда в роли переменные не передавались. Я всё так красивенько на ролях сделал, и тут такой облом :) Я был в таком шоке, что запилил себе свою собственную систему «ролей», используя include_tasks. Сейчас они роли уже пофиксили, но я пока их использую очень осторожно.

    Пока у меня еще остались сложности с переменными в ансибле. Я пока не до конца понимаю механизм их работы, а именно области видимости. Как-то они начинают рекурсивно раскручиваться в момент использования в таске похоже. И тут можно получить совсем неожиданные результаты, т.к. ты думаешь, что ты уже присвоил где-то значение переменной, а оно каким-то образом заменяется в другом месте)) Одним словом, на императивность рассчитывать особо не стоит. Ну и разные приоритеты set_fact, include_vars и прочее. В общем, с переменными надо быть настороже.


  1. HeOS
    02.04.2018 18:45
    +1

    И первый сюрприз: в коробке Ubuntu 16.04 нет python по умолчанию!

    Если речь идет именно об Ubuntu 16.04, то там там уже python3 по умолчанию. В самом ansible это можно поправить, например, указав у нужного хоста переменную вот таким образом:
    hostname01 ansible_host=192.168.1.10 ansible_python_interpreter=/usr/bin/python3
    

    Сколько использую, пока не встретил проблем с этим.


    1. Zverik Автор
      02.04.2018 18:48

      О, не знал об этом, спасибо. Я так понимаю, ansible_python_interpreter можно в ansible.cfg засунуть, чтобы на всех хостах было одинаково.


      1. HeOS
        02.04.2018 18:52

        Скорей всего, да. Не указывал так, потому что у меня есть инстансы, где и python2.7 актуален. :)


    1. throttle
      02.04.2018 19:02

      А есть чит, чтоб ansible самостоятельно находил доступный интерпретатор?


      1. HeOS
        02.04.2018 19:23

        Вот так навскидку не вспомню, нужно смотреть документацию.


    1. bfuvx
      03.04.2018 09:28
      +1

      Сколько использую, пока не встретил проблем с этим.

      И все же, если это не какая-то исключительная ситуация, лучше пока устанавливать 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. HeOS
        03.04.2018 11:34

        Да, в случае с 1-2 серверами этот подход окей. Но когда их уже становится даже больше 10, то идти на них и ставить python2.7 становится утомительно. :)


        1. bfuvx
          03.04.2018 16:08

          Так а зачем на них идти? Речь идет об использовании модуля raw для изначальной инсталляции python 2.x, если отсутствует /usr/bin/python. Т.е. добавляется дополнительный task в «bootstrap» playbook.


  1. AlexGluck
    03.04.2018 03:08

    Спасибо полистал статью и комменты. Как всегда в комментах есть, что-то интересное.
    Для тех кто захочет на 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 модуле.


  1. Kazikus
    03.04.2018 11:27
    +1

    Знатоки, подскажите, kubernates + docker решает схожие проблемы или это из разных областей?


    1. Busla
      03.04.2018 12:42

      Это из разных областей, но решает схожие проблемы :-)


  1. Kylin
    03.04.2018 14:20
    +2

    по поводу синтaксиса yaml три дефиса в начале как shebang в баше и питоне, вы же не будете писать скрипт не выставив shebang?
    по поводу правки конфигурационных файлов, lineinfile конечно годен, но только при одном изменении в файле, про blockinfile никогда не слышал и даже страшно стало что вы там с ним за хаки делаете: несколько тасков да в один файл. В ansible есть templates, именно их надо использовать для правки конфигурационных файлов. Это один из плюсов использования roles, одна role, если это конечно хорошо написаная role, а не говно мамонта 2014 года :) обычно выполняет не только установку пакета, но и устанавливает в нужные места templates, которые содержат variables, которые в свою очередь можно выставлять на уровне playbook'a


  1. amadis
    03.04.2018 23:05

    Хоть я и использовал ansible немного, но для более глубокого понимания я себе заказал книжку (прошу не считать рекламой). Вроде как довольно свежая и по актуальной версии…


  1. TrogWarZ
    04.04.2018 01:29

    Спасибо за пост и особенно за комментарии!

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

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


    1. Zverik Автор
      04.04.2018 10:48

      Сейчас, я так понимаю, достаточно сделать playbook из готовых ролей Ansible Galaxy для всего этого, и базовый сервер будет настроен. Там даже есть всякие lamp.

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