А я тем временем продолжаю выискивать, чем бы ещё автоматизировать настройку хостов. До сих пор я пользовался связкой Vagrant + bash/PowerShell для линуксовых или виндовых хостов, но каким-то странным образом пропустил тулзу, которая подходит под это дело лучше, чем просто скриптовать всё подряд, — Ansible. Она существует уже лет пять, и, как я заметил, превратилась практически в синоним фразы «автоматическая конфигурация». Сегодня, наконец, я её пожмякаю. Посмотрим, так ли уж Ansible удобнее, чем старый добрый bash.
Что такое Ansible
Как я уже сказал, Ansible — это инструмент для автоматический конфигурации хостов. «Автоматической конфигурацией» может быть как одиночная команда, вроде reboot, отправленная стайке ничего не подозревающих хостов, так и целая коллекция задач — playbook, которая и сервис установит, и конфигурацию скопирует, и убедится, что всё запустилось. Количество хостов при этом не играет ни какой роли. Что один, что сто один — без разницы.
Ещё Ansible использует push подход. То есть когда хосту нужно доставить новые настройки, это не он запрашивает их у контроллера, а хост-контроллер явно отправляет их получателям. В качестве транспорта может идти как SSH для никсовых машин, так и PowerShell для оконных. Так как Ansible написан на Питоне, то того придётся устанавливать и на хосте-контроллере, и на настраиваемых серверах. Последнее нужно потому, что Ansible сначала копирует .py
файл с командой на сервер и потом запускает его уже там, а не командует локально.
Ну вроде со вступлением разобрались, будем чего-нибудь делать.
План
Я думаю переписать настройку Consul-сервера, которую делал в прошлых постах. В тот раз это был Vagrant и bash, но Vagrant + Ansible произносится заметно интереснее, так что должно получиться весело.
Навскидку, установка и настройка Consul-сервера на Ubuntu машину проходила так:
- Устанавливаем unzip.
- Скачиваем и распаковываем Consul.
- Даём ему права на запуск.
- Копируем
consul
в/usr/local/bin
— он же сервисом будет. - Копируем концигурацию Consul как systemd сервиса в
/etc/systemd/system/
. - Копируем просто конфигурацию Консула в
/etc/systemd/system/consul.d/
. - Запускаем сервис.
Вглядит как нечто, с чем Ansible должен справиться на ура.
Установка
Я всё ещё буду пользоваться Vagrant и VirtualBox для создания виртуального хоста, и устанавливать их, как всегда, — одно удовольствие. Но вот установить и запустить Ansible на маке оказалось сложнее, чем привычное скачать-и-запустить. Пришлось добывать питоновский pip
, потом им устанавливать ansible
, а затем ставить sshpass
, чтобы Ansible мог подключаться по SSH с логином и паролем. За кадром, кстати, осталось то, как я обновлял openssh и пересобирал Python, чтобы дать возможность Ansible скачивать файлы с мака по TLS 1.2. Не удалось, между прочим.
Шаг 0. Создаём и подготавливаем host
Вот люблю я создавать машины. Чтобы Ansible тоже был счастлив, хосту, с которым он будет общаться нужно: создать, собственно, хост, поставить на него Python и создать какого-нибудь пользователя для дружбы по SSH. C Vagrant это делать одно удовольствие.
Первым делом создаём Vagrantfile:
1 |
[pav@pav-macbookpro]$ vagrant init ubuntu/xenial64 --minimal |
Затем вешаем на него статическую айпишку, уже существующему пользователю ubuntu
меняем пароль на ubuntu
, и устанавливаем минимальный питоновский набор для Ansible:
1 2 3 4 5 6 7 8 9 |
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/xenial64" config.vm.define "consul-server" do |machine| machine.vm.network "private_network", ip: "192.168.99.100" machine.vm.provision "shell", inline: "echo ubuntu:ubuntu | chpasswd" machine.vm.provision "shell", inline: "apt-get update && apt-get install -y python-minimal" end end |
Легкотня. Теперь выполняем vagrant up
и можем начинать тестировать SSH соединение:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[pav@pav-macbookpro]$ vagrant up # Bringing machine 'consul-server' up with 'virtualbox' provider... # ... # ==> consul-server: Setting up python (2.7.11-1) ... [pav@pav-macbookpro]$ ssh ubuntu@192.168.99.100 # The authenticity of host '192.168.99.100 (192.168.99.100)' can't be established. # ECDSA key fingerprint is SHA256:Bzxp6dpLcdJPRUMyJKZUVzK8jLN6z4OI1iG1j4Iu77M. # Are you sure you want to continue connecting (yes/no)? yes # Warning: Permanently added '192.168.99.100' (ECDSA) to the list of known hosts. ubuntu@192.168.99.100's password: # ... ubuntu@ubuntu-xenial:~$ |
Соединение работает. Кроме этого радостного факта произошла ещё одна полезная вещь: ssh добавил fingerprint новой виртуальной машины в доверенные хосты (~/.ssh/known_hosts
). Без этого подключаться к ней можно было бы только убедив Ansible верить всем подряд (есть такая штука в настройках), что для нашей задачи уже перебор.
Гуд. Хост готов, пора его отансиблить©.
Шаг 1. Создаём inventory файл
Ansible хранит хосты, с которыми ему предстоит дружить, в инвентарных файлах. Это обычные INI файлы, в которых можно ложить айпишки, доменные имена, параметры подключения, группы и переменные… много чего можно ложить. Но, в принципе, даже одинокого IP адреса достаточно, чтобы inventory файл стал полноценным.
По умолчанию Ansible будет искать файл в /etc/ansible/hosts
, но так как я не хочу пачкать Mac очередным временным файлом, лучше уж я положу его рядом с Vagrantfile. Вот каким инвентарник получился у меня:
1 |
consul-server ansible_host=192.168.99.100 ansible_user=ubuntu ansible_ssh_pass=ubuntu |
Да-да-да, хранить SSH пароль открытым текстом в файле настроек, как пОшло. С другой стороны, виртуальной машины через пару часов не станет, так что почему бы и нет. Но в продакшене я бы определённо пользовался либо SSH сертификатами, либо Ansible Vault.
Ещё один момент. Хотя ansible_ssh_pass
параметр и считается deprecated, а я должен пользоваться свежим ansible_pass
, новый параметр работал у меня через раз. Не знаю, это проблемы мака, лунного затмения, или золотых рук, растущих из пятой точки, но старый параметр работает всегда, а новый — нет. Выбор был простым.
Имея на руках инвентарный файл, можно попробовать поотправлять команду-другую на новоиспечённый хост:
1 2 3 4 5 6 7 8 9 |
[pav@pav-macbookpro]$ ansible all -i hosts -m ping # consul-server | SUCCESS => { # "changed": false, # "ping": "pong" # } [pav@pav-macbookpro]$ ansible all -i hosts -m command -a "lsb_release -r" # consul-server | SUCCESS | rc=0 >> # Release: 16.04 |
Ansible определённо завёлся, это хорошо. Первая команда запустила ping
модуль (-m
) для всех (all)
хостов из inventory файла по имени hosts
(-i hosts
), а вторая отправила команду (lsb_release -r
) прямо в шелл и узнала, какой версии Убунта там запущена. Если бы в hosts
было сто машин, я бы получил сто версий.
all
, кстати, не единственный доступный параметр. Можно было бы использовать и имя группы, если бы у меня была хоть одна, и имя хоста, вроде consul-server
.
Шаг 2. Создаём playbook
Слать команды по одной — удивительный отстой (классный стих, правда?). Чтобы собрать что-то более сложное, нужно брать сразу много команд и ложить их в специальный файл — playbook.
Плейбук — это обычный YAML файл, в котором будут складироваться, в принципе, все те же вещи, что мы запускали из командой строки: хосты, модули. И не только. Читается этот файл потом как обычный текст. Чтобы это продемонстрировать, попробуем как сделать первый шаг установки консула — установить unzip
.
Шаг 2.1 Устанавливаем unzip
Вот такой плейбук сделает необходимую магию на Дебиан-подобных системах:
1 2 3 4 5 |
- hosts: consul-server tasks: - name: Install unzip apt: name=unzip state=present become: true |
В этот раз я явно указал имя хоста вместо all
: consul-server
. За ним идёт секция-коллекция tasks
, в которую, собственно, и ложатся индивидуальные команды-модули. Сейчас там только один apt
, которые умеет управлять убунтовскими apt пакетами и делать что-то похожее на apt-get install
. Вся четвёртая строка практически как обычный текст: «возьми apt, убедись что unzip есть».
Самый кайф именно в «есть». Не «установи», а просто есть. При такой формулировке нормально ожидать, что если unzip уже был, то его трогать не надо. Такое поведение называется идемпотентностью, возможностью повторять одно и то же действие много раз и получать один и тот же результат, и в посте с конфигурацией консула через bash её приходилось делать отдельно. А в Ansible она есть по умолчанию. Даже command
модуль, которым мы просто запускали шелл команды, можно запросто научить идемпотентности.
И да, почти забыл. Был ещё один параметр become: true
. Это эквивалент sudo
. Будучи простым смертным юзером ведь ничего толкового в наши дни не установишь.
Но хватит болтовни, будем запускать плейбук:
1 2 3 4 5 6 7 8 9 10 11 12 |
[pav@pav-macbookpro]$ ansible-playbook -i hosts consul.yml # #PLAY [consul-server] ****************************************************** # #TASK [Gathering Facts] **************************************************** #ok: [consul-server] # #TASK [Install unzip] ****************************************************** #changed: [consul-server] # #PLAY RECAP **************************************************************** #consul-server : ok=2 changed=1 unreachable=0 failed=0 |
На новой Убунте unzip
не было, поэтому задача «Install unzip» отчиталась, что что-то изменила. Если же запустить плейбук ещё раз (я пробовал), то во второй раз он скажет, что всё и так прекрасно.
Шаг 2.2 — 2.4 Качаем и устанавливаем Consul
То, что было аж тремя шагами в bash, станет одним шагом в Ansible. Модуль unarchive
может и скачать архив, и распаковать, и положить, куда нужно, и даже права доступа настроить.
Настроить его для скачивания консула можно вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
- hosts: consul-server vars: consul_version: 0.9.2 tasks: - name: Install unzip apt: name=unzip state=present become: true - name: Install Consul become: true unarchive: src: https://releases.hashicorp.com/consul/{{ consul_version }}/consul_{{ consul_version }}_linux_amd64.zip remote_src: yes dest: /usr/local/bin creates: /usr/local/bin/consul mode: 0555 |
Я добавил пару пустых строк для пущей читабельности (не ясно, правда, стало ли читабельнее) и использовал ещё одну новую фичу Ansible — переменные. Но вообще «Install Consul» задача получилась вполне понятной. Из особо интересного: creates
свойство, делающее задачу идемпотентной, и mode
, устанавливающее пермишены на consul
(r-x)
.
Перезапустив плейбук, можно проверить, что консул теперь действительно существует:
1 2 3 4 |
[pav@pav-macbookpro]$ ansible all -i hosts -m command -a "consul --version" # consul-server | SUCCESS | rc=0 >> # Consul v0.9.2 # Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents) |
… и, следовательно, мыслит.
Шаг 2.5 Делаем Consul systemd сервисом
Чтобы сделать Consul сервисом нужно просто скопировать уже готовый заголовок сервиса в папку к остальным. Ещё с прошлого поста про Консул у меня остался consul.service
, и абсолютно без изменений он пошёл и в этот. Ansible модуль же, который умеет копировать файлы, называется (сюрприз!) copy
. Ну никакой фантазии у буржуев.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[Unit] Description=consul agent Requires=network-online.target After=network-online.target [Service] EnvironmentFile=-/etc/sysconfig/consul Restart=on-failure ExecStart=/usr/local/bin/consul agent $CONSUL_FLAGS -config-dir=/etc/systemd/system/consul.d ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target |
1 2 3 4 5 6 7 8 9 |
#... tasks: - name: Make Consul a service become: true copy: src: consul.service dest: /etc/systemd/system/consul.service #... |
Шаг 2.6 Настраиваем Consul
Чтобы настроить Consul нужно скопировать ещё один файл. В этот раз JSON. В нашем случае это будет init.json,
телепортированный в
/etc/systemd/system/consul.d. Проблема в том, что в init.json
будет айпишка сервера, и я не хочу ещё жёстко туда прошивать. Лучше бы её хранить где-нить на уровне playbook, и передавать в конфигурацию как параметр для шаблона. Что приятно, Ansible понимает шаблоны прямо из коробки, так что этот трюк легко провести.
Прежде чем мы это сделаем, есть ещё один момент: consul.d
папки, в которую мы положим конфигурацию, может и не существовать, поэтому в плейбуке хорошо бы озаботиться и этим.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#... vars: consul_version: 0.9.2 consul_server_ip: 192.168.99.100 consul_config_dir: /etc/systemd/system/consul.d tasks: #... - name: Ensure config directory exists become: true file: path: "{{ consul_config_dir }}" state: directory - name: Deploy consul config become: true template: src: init.json.j2 dest: "{{consul_config_dir}}/init.json" #... |
Озабоченность принесла свои плоды, так что у нас теперь есть шаблон конфигурации init.json.j2
, где j2 означает язык шаблонов Jinja2, и два новых шага (плюс 2 переменных) в плейбуке.
1 2 3 4 5 6 7 8 |
{ "server": true, "ui": true, "advertise_addr": "{{ consul_server_ip }}", "client_addr": "{{ consul_server_ip }}", "data_dir": "/tmp/consul", "bootstrap_expect": 1 } |
Шаг 2.6 Запускаем сервис
Это будет одна из самых простых задач. В Ansible есть модуль по имени service
, который умеет делать с сервисами всё, что угодно. В том числе и запускать:
1 2 3 4 |
# ... - name: Ensure consul's running become: true service: name=consul state=started |
Если вы дочитали аж до сюда, и можете запустить плейбук целиком, то во вселенной станет на одну айпишку больше, и на её 8500 порту (192.168.99.100:8500
) будет вот что:
Да, это живой консул. Всячески кликабельный и полезный.
Шаг 3. Дружим Vagrant с Ansible
Есть ещё такой момент — Vagrant может использовать Ansible напрямую, так что запустить плейбук можно сразу из Vagrantfile:
1 2 3 4 5 6 7 8 9 |
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/xenial64" config.vm.define "consul-server" do |machine| machine.vm.network "private_network", ip: "192.168.99.100" machine.vm.provision "shell", inline: "apt-get update && apt-get install -y python-minimal" machine.vm.provision "ansible", playbook: "consul.yml" end end |
Я даже убрал строку, которая устанавливала пароль для ubuntu
, так как управление инвентарным файлом и паролями теперь головняк для Vagranta, не для меня.
Если теперь удалить существующую виртуалку через vagrant destroy -f
и запустить новую с vagrant up
, то полностью готовый хост с Консул получится ровно в одну команду.
1 2 3 4 5 6 7 8 |
[pav@pav-macbookpro]$ vagrant up # ... # ==> consul-server: Running provisioner: ansible... # consul-server: Running ansible-playbook... # # PLAY [consul-server] *********************************************************** # ... # consul-server : ok=7 changed=6 unreachable=0 failed=0 |
Мораль
Мне понравилось. Хотя установка Ansible на Mac м отличалась от тривиальной, конфигурация хоста модулями в YAML мне нравится больше, чем кодом в bash. Её проще читать, идемпотентность получается автоматически, и вместо того, чтобы изобретать каждый раз колесо, можно использовать армаду готовых модулей.
Кроме этого, мы совсем не затронули такую штуку в Ansible как роли — переиспользуемые куски плейбуков. Например, «ftp server». Более того, есть такая штука, как Ansible Galaxy — паблик хаб для таких ролей. Если в ней поискать готовую роль по имени «Consul service», то она-таки там будет. Много, много готовых Консул-ролей.
«Ansible сначала копирует .py файл с командой на сервер и потом запускает его уже там, а не командует локально.»
Не всегда такое возможно совершить, например на сетевом оборудовании не всегда есть Python, но оборудование конфигурится вполне спокойно конфигурится при помощи Ansible, для этого в playbook указывается
gather_facts: false
connection: local
Спасибо, буду знать.