2026, Jan 03 06:01
Почему Salt не стримит stdout в Python‑клиенте и что делать
Разбираемся, почему при запуске команд в Salt stdout не приходит в реальном времени в Python‑клиенте, что возвращает minion и как правильно получать результат.
Когда вы запускаете длительный циклический скрипт оболочки на миньоне Salt и пытаетесь читать его вывод из Python‑клиента, легко ожидать непрерывный поток строк в реальном времени. На деле ничего не появляется, пока задание не завершится. В этом материале объясняется, почему так происходит, что именно возвращает Salt и как настроить код и ожидания соответственно.
Как воспроизвести расхождение ожиданий
Следующий пример на Python запускает команду, которая в течение 50 секунд выводит по одной строке в секунду. Затем код сразу запрашивает результаты и пытается печатать их по мере поступления.
import salt.client
target = "test"
api = salt.client.LocalClient()
def launch_task(target_id):
return api.run_job(
target_id,
"cmd.run",
arg=['''for i in {1..50}; do echo "Log line $i at $(date '+%T')"; sleep 1; done''']
)
def read_results(job_info):
payload = api.get_cli_returns(
job_info["jid"],
job_info["minions"],
timeout=30,
verbose=True,
show_jid=True,
)
print(list(payload))
for item in payload:
print(f"Response: {item}")
if __name__ == "__main__":
print("Immediate check -- fails")
job_meta = launch_task(target)
print(job_meta)
read_results(job_meta)
Ожидается непрерывный вывод примерно такого вида:
Response: Log line 1 at 11:47:08
Response: Log line 2 at 11:47:09
Response: Log line 3 at 11:47:10
Response: Log line 4 at 11:47:11
Response: Log line 5 at 11:47:12
...
Что на самом деле происходит и почему
Salt — асинхронная система. Отправленная вами команда выполняется на миньоне. Эта команда пишет в консоль миньона, а не в ваш Salt‑клиент. Миньон не отправит ничего обратно мастеру, пока полностью не завершит запрошенную задачу. В нашем примере цикл делает паузу в одну секунду на каждой из 50 итераций, поэтому вы не увидите никакого ответа до его завершения. Лишь после этого миньон отправит результат, и только тогда вашему клиентскому коду будет что напечатать.
Через использованный выше Python‑API Salt не предоставляет живой поток stdout для запущенных задач. Вы можете узнать, выполняется ли задание, с помощью find_job — на этом наблюдаемость во время выполнения в данном контексте заканчивается. Когда задание завершится, будет отправлен результат, и только тогда get_cli_returns вернет данные.
Что изменить в клиентском коде
Решение — скорректировать ожидания и логику. Не пытайтесь «тэйлить» вывод в реальном времени. Либо подождите достаточно долго, чтобы задание завершилось, и только потом собирайте результат, либо отслеживайте, идет ли оно еще, и забирайте возврат по завершении. В примере ниже код просто ждет дольше, чем длится цикл, а потом получает результат. Потока по‑прежнему нет: ответ появляется лишь после окончания задачи.
import time
import salt.client
endpoint = "test"
session = salt.client.LocalClient()
def fire_job(match):
return session.run_job(
match,
"cmd.run",
arg=['''for i in {1..50}; do echo "Log line $i at $(date '+%T')"; sleep 1; done''']
)
def collect_when_finished(job_meta):
# Ждите дольше, чем длится задача; здесь нет потокового вывода
time.sleep(60)
result = session.get_cli_returns(
job_meta["jid"],
job_meta["minions"],
timeout=90,
verbose=True,
show_jid=True,
)
for entry in result:
print(f"Response: {entry}")
if __name__ == "__main__":
info = fire_job(endpoint)
collect_when_finished(info)
Если вам нужно знать, выполняется ли задание, используйте find_job, чтобы проверить его состояние, и запрашивайте результат только после завершения. Это подтверждает «живость», но не дает поэтапный stdout.
Почему это важно для веб‑интерфейсов
Если вы хотите показывать прогресс выполнения на веб‑странице, полагаться на Python‑API для потоковой передачи stdout с миньона нельзя. Проектируйте интерфейс с учетом этой модели: показывайте, что задание выполняется, при необходимости проверяйте через find_job, идет ли оно еще, и выводите результат после завершения. Ожидайте, что первый вывод появится только по окончании задачи.
Выводы
Salt выполняет команды на миньонах асинхронно. Вывод stdout от работающей команды остается локальным на миньоне до завершения. Python‑клиент получает данные только после окончания задания, поэтому потоковой выдачи строк не будет. Если нужна видимость во время выполнения, проверяйте с помощью find_job, идет ли задача, а затем забирайте и показывайте итоговый вывод. Для долгих операций убедитесь, что ваш клиент корректно ждет или сигнализирует о прогрессе, не пытаясь «тэйлить» вывод.