Поводом для этой статьи послужил пост в чате @pro_ansible:
Vladislav ? Shishkov, [17.02.21 20:59] Господа, есть два вопроса, касаются кастомной долгой операции, например, бекапа: 1. Можно ли через ансибл прикрутить прогрессбар выполнения кастомного баша? (если через плагин, то пните в какой-нибудь пример или документацию плиз) 2. Вроде хочется для этого баша написать плагин, но встает вопрос, как быть и как решать моменты выполнения, которые идемпотентны?
Беглый поиск по задворкам памяти ничего подходящего не подсказал. Тем не менее, я точно вспомнил, что код Ansible легко читаемый, и «искаропки» поддерживает расширение как плагинами, так и обычными Python-модулями. А раз так, то ничего не мешает в очередной раз раздвинуть границы возможного. Hold my beer!…
Понятно, что стандартный Ansible уже умеет делать оба шага, только вот полученный «выхлоп» собирается в единое целое и передаётся на управляющий хост уже по завершению процесса, а мы хотим это сделать в реальном времени. Следовательно, можно как минимум посмотреть на существующую реализацию, а как максимум — каким-то образом переиспользовать существующий код.
Исходный вопрос можно свести к двум простейшим шагам:
-
Захватить stdout команды на целевом хосте
-
Передать его на управляющий хост.
Передаём данные на управляющий хост
Предлагаю начать «с конца»: с организации дополнительного канала передачи на управляющий хост. Решение этого вопроса выглядит достаточно очевидным: вспоминаем, что 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. Так вот, у меня есть опыт ведения такого тренинга внутри компании для коллег.
В прошедшем году я всерьёз начал задумываться о том, чтобы начать вести свой собственный тренинг по этому инструменту для всех желающих, поэтому предлагаю пройти опрос.
ссылка на оригинал статьи https://habr.com/ru/post/543598/
Добавить комментарий