У меня есть три сервера, но я не профессиональный сисадмин. Это означает, что несмотря на четыре базы данных и стопяцот приложений, бэкапы нигде не ведутся, к любой проблеме на сервере я подхожу, шумно вздохнув и бросив тарелку в стену, а операционные системы там достигли EOL два года назад. Я бы рад обновить, но на это нужно выделить, наверное, неделю, чтобы всё забэкапить и переставить. Проще забыть про yum update
и apt-get upgrade
.
Конечно, это неправильно. Я давно присматривался к chef и Puppet, которые, как я думал, решат все мои проблемы. Но я смотрел на конфиги знакомых проектов и откладывал. Это же нужно изучать, разбираться с ruby, бороться с многочисленными, по отзывам, косяками и ограничениями. Две недели назад статья Георгия amarao стала животворящим пинком. Даже не сама статья, а перечисление систем управления конфигурацией. После чтения комментариев и лёгкого гугления решил: возьму Ansible. Потому что питон, и на проблемы никто не жалуется.
Что ж, тогда я первым буду.
Сначала я нарыл кучу документации и учебников по Ansible, начиная с бесполезного видеоролика Quick Start на официальном сайте. Их, конечно, много, сделаны для разных задач и написаны разными людьми, но объединяет их одно: учебники делали для людей, которые уже понимают Ansible. Для людей со сферическим сервером в вакууме, которым достаточно подсказать, что бывают роли, модули и таски. Но я пришёл с clean slate и собрал все грабли, какие нашёл. Надеюсь, эта заметка поможет вам их обойти.
От систем управления конфигурациями я ждал чудес, вроде автоматически обновляемых приложений из git. Но оказалось, что Ansible — это лишь способ сохранить последовательность действий при настройке нового сервера. Вы сможете сделать в Ansible только то, что умеете делать из консоли самостоятельно. Чудес нет.
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
vagrant up
. Не взлетело. Поскольку официальный образ xenial — только для VirtualBox, а в Fedora Linux виртуализация через libvirt. Долго вспоминал правильную команду: vagrant up --provider virtualbox
. Затем правил синтаксические ошибки в yaml (зачем там в начале обязательные три дефиса?). Помним, что после запуска коробки для перезапуска Ansible пишем vagrant provision
.- 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
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 и перешёл на него. Хорошо, когда есть выбор.update_cache=yes
хорошо бы сделать отдельной задачей. И эта задача, вот беда, всё время возвращает «changed». Оказалось, нужно прописать cache_valid_time=3600
, чтобы проверять обновления не чаще раза в час. Сначала подумал написать 86400 (сутки), но я же не в кроне буду Ansible вызывать, а раз в месяц — пусть живёт.become_user: postgres
. И тут коробка выдала странную ошибку: «Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user». Помните, как Ansible загружает модули на сервер и там запускает? Ну так вот, загружает он их от root или от другого суперпользователя, а потом у пользователя postgres нет к ним доступа. Вот незадача.ansible.cfg
и прописать внутрь pipelining=True
(а для решения какой-то другой возникшей проблемы я временно ставил pipelining=False
). Второй выход — буквально, «не делайте так». И третий самый простой: ставите пакет acl
и всё волшебным образом работает. Вернее, не работает другим способом: «sudo: a password is required». Ну что за дела, откуда здесь вообще пароли, я же с ключом захожу?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.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"
» и всё работает! Не очень красиво получилось, но ура!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
. Иначе все будут затирать чужие блоки.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
. Тут ещё должно быть про параметризацию ролей, но я ещё только учусь и роль у меня одна.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: автор.роль
vagrant destroy
и создайте коробку заново. Стопроцентно обнаружите несколько косяков: забытые sudo, пропущенные mode: 0755 для исполняемых файлов, недостающие пакеты (помогают dnf provides
или apt-file
, который нужно устанавливать). Наконец, самое главное: после второго запуска vagrant provision
должно быть «changed: 0».К сожалению, не доступен сервер mySQL