Продолжаем прокачивать Ansible

76df2c5314ed4f5c1f7beb9748f6171c.jpg?v=1

Поводом для этой статьи послужил пост в чате @pro_ansible:

Vladislav? Shishkov, [17.02.21 20:59] Господа, есть два вопроса, касаются кастомной долгой операции, например, бекапа: 1. Можно ли через ансибл прикрутить прогрессбар выполнения кастомного баша? (если через плагин, то пните в какой-нибудь пример или документацию плиз) 2. Вроде хочется для этого баша написать плагин, но встает вопрос, как быть и как решать моменты выполнения, которые идемпотентны?

Беглый поиск по задворкам памяти ничего подходящего не подсказал. Тем не менее, я точно вспомнил, что код Ansible легко читаемый, и «искаропки» поддерживает расширение как плагинами, так и обычными Python-модулями. А раз так, то ничего не мешает в очередной раз раздвинуть границы возможного. Hold my beer!…

Понятно, что стандартный Ansible уже умеет делать оба шага, только вот полученный «выхлоп» собирается в единое целое и передаётся на управляющий хост уже по завершению процесса, а мы хотим это сделать в реальном времени. Следовательно, можно как минимум посмотреть на существующую реализацию, а как максимум — каким-то образом переиспользовать существующий код.

Исходный вопрос можно свести к двум простейшим шагам:

  1. Захватить stdout команды на целевом хосте

  2. Передать его на управляющий хост.

Передаём данные на управляющий хост

Предлагаю начать «с конца»: с организации дополнительного канала передачи на управляющий хост. Решение этого вопроса выглядит достаточно очевидным: вспоминаем, что Ansible работает поверх ssh, и используем функцию обратного проброса порта:

Код на Python
# добавляем куда-нибудь сюда:
# https://github.com/ansible/ansible/blob/5078a0baa26e0eb715e86c93ec32af6bc4022e45/lib/ansible/plugins/connection/ssh.py#L662
self._add_args(
    b_command,
    (b"-R", b"127.0.0.1:33333:" + to_bytes(self._play_context.remote_addr, errors='surrogate_or_strict', nonstring='simplerepr') + b":33335"),
    u"ANSIBLE_STREAMING/streaming set"
)

Как это работает? При сборке аргументов командной строки для установления ssh-соединения эта конструкция предоставит нам на целевом хосте порт 33333 по адресу 127.0.0.1, который будет туннелировать входящие соединения на контроллер — прямиком на порт 33335.

Для простоты используем netcat (ну правда, ну что за статья без котиков?): nc -lk 33335.

В этот момент, кстати, уже можно запустить Ansible и проверить, что туннель работает так, как следует: хотя пока по нему ничего и не передаётся, мы уже можем на целевом хосте зайти в консоль и выполнить nc 127.0.0.1 33333, введя какую-нибудь фразу и увидев её как результат работы команды выше.

Перехватываем stdout

Полдела сделано — идём дальше. Мы хотим перехватить stdout какой-то команды — по логике работы Ansible нам подойдёт модуль «shell». Забавной, что он оказался пустышкой — в нём ни строчки кода, кроме документации и примеров, зато находим в нём отсылку к модулю command. С ним всё оказалось хорошо, кроме того факта, что нужная функция в нём напрямую не описана, хотя и использована. Но это уже было почти попадание «в яблочко», потому что в итоге она нашлась в другом файле.

Под мысленное «просто добавь воды» просто добавляем щепотку своего кода:

Опять код
# в начале basic.py, рядом с прочими import'ами 
import socket

# в функции run_command - где-нибудь тут:
# https://github.com/ansible/ansible/blob/5078a0baa26e0eb715e86c93ec32af6bc4022e45/lib/ansible/module_utils/basic.py#L2447
clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
clientSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
clientSocket.connect(("127.0.0.1",33333));

# в функции run_command - где-нибудь тут:
# https://github.com/ansible/ansible/blob/5078a0baa26e0eb715e86c93ec32af6bc4022e45/lib/ansible/module_utils/basic.py#L2455
clientSocket.send(b_chunk);

# в функции run_command - где-нибудь тут
# https://github.com/ansible/ansible/blob/5078a0baa26e0eb715e86c93ec32af6bc4022e45/lib/ansible/module_utils/basic.py#L2481
clientSocket.close()

Собираем воедино и запускаем

Осталось сделать что? Правильно, определиться со способом подключения изменённых модулей в стоковый Ansible. На всякий случай напоминаю: мы поправили один connection plugin, и один модуль из стандартной библиотеки Ansible. Новичкам в этом деле могу рекомендовать статью хабраюзера chemtech с расшифровкой моего доклада на «Стачке-2019» (там как раз в том числе объясняется, какие Python-модули куда складывать), ну, а опытным бойцам эти пояснения вроде и не нужны :-)

Итак, время «Ч». Результат в виде статичной картинки не очень показателен, поэтому я настроил tmux и запустил запись скринкаста.

Для внимательных зрителей скринкаста

В анимации можете увидеть два полезных побочных эффекта:

  • Теперь мы видим stdout всех не-Python процессов, которые запускаются Ansible’ом на целевом хосте — например, тех, что запускаются при сборе фактов;

  • Настройки переиспользования ssh-соединений из другой моей статьи позволяют получать этот самый stdout от удалённой команды уже после отключения Ansible от хоста.

Хотите ко мне на тренинг по Ansible?

Раз уж вы здесь — значит, вам как минимум интересно то, что я пишу об Ansible. Так вот, у меня есть опыт ведения такого тренинга внутри компании для коллег.

В прошедшем году я всерьёз начал задумываться о том, чтобы начать вести свой собственный тренинг по этому инструменту для всех желающих, поэтому предлагаю пройти опрос.

© Habrahabr.ru