ossh: параллельное выполнение команд на многих серверах

Иногда бывает нужно запустить патч Бармина какую-то команду на многих серверах и желательно не ждать слишком долго результатов выполнения. Для этого я написал ossh (One SSH to rule them all). Вот пример его работы:

$ wc -l /tmp/ossh.ips
21418 /tmp/ossh.ips
$ time ossh -n -h /tmp/ossh.ips -c uptime -p 1000 >/tmp/ossh.out

real    3m10.310s
user    0m30.970s
sys     0m19.282s
$ grep 'load average' /tmp/ossh.out | sort -n -k5 | tail -n1
10.23.91.97   [1]  13:37:55 up 828 days,  2:34,  0 users,  load average: 8.29, 4.45, 3.90
$

В данном примере в файле /tmp/ossh.ips находится 21418 ip адресов машин. -n означает, что не нужно делать реверс запросы, чтобы определить имя по адресу. -c uptime задает команду, которую я хочу выполнить. -p 1000 позволяет использовать до 1000 соединений одновременно. Как видно из вывода отработала команда достаточно быстро.

Что еще умеет ossh?

$ ossh -?
Usage: ossh [-?AinPv] [-c COMMAND] [-C COMMAND_FILE] [-H HOST_STRING] [-h HOST_FILE] [-I FILTER] [-k PRIVATE_KEY] [-l USER] [-o PORT] [-p PARALLELISM] [-T TIMEOUT] [-t TIMEOUT] [parameters ...]
 -?, --help        Show help
 -A, --askpass     Prompt for a password for ssh connects
 -c, --command=COMMAND
                   Command to run
 -C, --command-file=COMMAND_FILE
                   file with commands to run
 -H, --host=HOST_STRING
                   Add the given HOST_STRING to the list of hosts
 -h, --hosts=HOST_FILE
                   Read hosts from file
 -i, --ignore-failures
                   Ignore connection failures in the preconnect mode
 -I, --inventory=FILTER
                   Use FILTER expression to select hosts from inventory
 -k, --key=PRIVATE_KEY
                   Use this private key
 -l, --user=USER   Username for connections [$LOGNAME]
 -n, --showip      In the output show ips instead of names
 -o, --port=PORT   Port to connect to [22]
 -p, --par=PARALLELISM
                   How many hosts to run simultaneously [512]
 -P, --preconnect  Connect to all hosts before running command
 -T, --connect-timeout=TIMEOUT
                   Connect timeout in seconds [60]
 -t, --timeout=TIMEOUT
                   Run timeout in seconds
 -v, --verbose     Verbose output
$

Список хостов можно задать либо прямо в командной строке через опцию -H (в случае нескольких хостов их надо перечислить через пробел, а весь список взять в кавычки как в примерах ниже) либо загрузить из файла при помощи опции -h. Строки в файле, начинающиеся с #, игнорируются. Адрес может содержать порт: my.host:2222. Можно использовать brace expansion: «host{1,3..5}.com» превратится в «host1.com host3.com host4.com host5.com». И -H и -h можно использовать многократно.

Для авторизации будут использованы


Именно в таком порядке.

Иногда бывает нужно убедиться, что вы можете залогиниться на все машины прежде чем выполнить команду. Для этого есть опция -P. По умолчанию если хоть одна машина будет недоступна ossh завершится с ошибкой. Если вы хотите игнорировать неудачные соединения используйте опцию -i.

Ossh может использовать вашу систему инвентаризации. Для этого в путях должна находится команда ossh-inventory, которой будут переданы параметры опции -I. Эта опция может быть использована многократно. Команда ossh-inventory должна выдавать на стандартный вывод строки в формате:

имя_машины адрес_машины

Где адрес_машины может быть как днс именем, так и ip адресом.

Команды для выполнения задаются опциями -C (читать из файла) или -c (брать из командной строки). Эти опции могут использоваться многократно. При наличии и -C и -c сперва выполнятся команды из файлов, потом из командной строки.

Помимо просто выполнения команд при помощи ossh можно стримить логи в реальном времени:

$ ossh -H "web05 web06" -c "tail -f -c 0 /var/log/nginx/access.log|grep --line-buffered Wget"
web05 192.168.1.23 - - [22/Jun/2016:12:24:02 -0700] "GET / HTTP/1.1" 200 1532 "-" "Wget/1.15 (linux-gnu)"
web05 192.168.1.49 - - [22/Jun/2016:12:24:07 -0700] "GET / HTTP/1.1" 200 1532 "-" "Wget/1.15 (linux-gnu)"
web06 192.168.1.117 - - [22/Jun/2016:12:24:23 -0700] "GET / HTTP/1.1" 200 1532 "-" "Wget/1.15 (linux-gnu)"
web05 192.168.1.29 - - [22/Jun/2016:12:24:30 -0700] "GET / HTTP/1.1" 200 1532 "-" "Wget/1.15 (linux-gnu)"
...

Вот симуляция rolling deployment:

$ ossh -p 1 -H "test0{1..3}" -c "sleep 10 && date"
test01 Wed Jun 22 12:38:24 PDT 2016
test02 Wed Jun 22 12:38:34 PDT 2016
test03 Wed Jun 22 12:38:44 PDT 2016
$

Видно, что команды выполняются на машинах последовательно. В каждый момент времени задействована только одна машина. Для настоящего деплоймента «sleep 10 && date» надо заменить на, к примеру, “apt-get install -y your_package”.

Именно для деплоймента была написана самая первая версия ossh. Кто-то спросит почему я не использовал какую-то общепринятую систему управления конфигурациями? Дело в том, что это было в далеком 2013-м году и мы использовали chef. Было ясно, что chef нас не устраивает в частности неопределенностью когда именно будут применены изменения (chef-client отрабатывал раз в 30 минут). Для того, чтобы согласованно выкатывать изменения на многих машинах некоторые разработчики применяли грязный хак: chef-client не работал постоянно, а однократно запускался (через ssh) только в тот момент, когда было нужно сделать деплоймент. Уже в тот момент шла работа по замене chef на salt, но переход был не простым и завершение его требовало дополнительного времени. Мы же разрабатывали новый сервис, который требовал частых деплойментов и раскатывался единственным дебиановским пакетом. Сперва мы использовали утилиту knife из состава chef. Эта утилита позволяла соединяться по ssh с нужными серверами и выполнять на них команды. В какой-то моент я понял, что chef в данном случае является лишним звеном и написал ossh.

Важно отметить, что ossh является инструментом для разрешения масштабных и нестандартных проблем. Если необходимость использовать ossh возникает часто это повод задуматься все ли у вас хорошо с инфраструктурой и управлением серверами. Вот некоторые ситуации, в которых ossh помог лично мне:


Предвижу вопрос почему я не использовал готовое решение. Как я упомянул выше необходимость в прогонах команд на тысячах машин первый раз возникла у меня в 2013-м году. На тот момент мне удалось найти только parallel ssh, который не устроил меня следующим:


Исходно ossh был написан на ruby, для увеличения производительности я задействовал event machine, а потом и fiber-ы. Относительно недавно я переписал ossh на go. Буду признателен если go-эксперты (я таковым на данный момент не являюсь) посмотрят на мой код и укажут на возможные способы улучшить его.
@kt97679
15.03.2021 05:42 UTC
Первоисточник

Комментарии

@orthanner
15.03.2021 04:26 UTC
+2

Неплохо. Но рекомендую взять готовый инструмент. В 2013 уже был, кстати. Очень удобная штука. Причём одним только удалённым выполнением команд не ограничивается. Тут и копирование файлов, и сложная логика, и локальное выполнение команд…
Сам пользовался для управления DNS-серверами. Информация на них была одна и та же, но вот версии сервера отличались и окружение тоже. А держать несколько копий данных мне не улыбалось. Тут-то меня Capistrano и выручил: небольшая правка скрипта — и вот уже в зависимости от хоста файлы исключаются из копирования или редактируются удалённом после приёма. Классная штука.

@kt97679
15.03.2021 15:15 UTC
0
Спасибо за наводку, capistrano прошел мимо меня. Правильно ли я понимаю, что на клиентской стороне достаточно шелла и базовых команд? В документации этот момент четко не прописан.
@
15.03.2021 05:49 UTC
0
НЛО прилетело и опубликовало эту надпись здесь
@kt97679
15.03.2021 15:17 UTC
0
В 2013-м я дотошно просмотрел все опции parallel ssh и ничего подобного не нашел. Может это все появилось позже? Параллельность у меня ограничена не была, просто при достижении определенного количества соединений начинали сыпаться ошибки.
@divanikus
15.03.2021 09:48 UTC
0
Parallel SSH это pdsh или я что-то упускаю? Аналога pdcp я полагаю не завезли.
@inkvizitor68sl
15.03.2021 11:35 UTC
-1
github.com/viert/xc
Там distribute
15.03.2021 20:49 UTC
0

Жалко что про эту штуку не знает никто кроме админов ya/ex-ya… Надо будет хоть самому затестить как-нибудь.

15.03.2021 22:55 UTC
0
Ну xc работает, чего его тестить — бери да пользуйся. Поудобнее xx даже местами.
А пользуется мало кто — так они уже не очень нужны. Полтора раза в год можно сделать for i in `cat file`; do ssh $i command; done и пойти кофе попить, остальное либо кубер, либо CMS делают.
15.03.2021 22:51 UTC
0
Да я собственно не ищу чем заменить, я пытаюсь понять с чем автор сравнивает. Использую pdsh уже лет эдак 10 и все до сих пор более-менее устраивало, да и есть наверное в любом дистрибутиве…
15.03.2021 22:52 UTC
0
Для pssh/pdsh/etc очень неудобно в гите списки хостов хранить, когда админов больше одного =) Для xc есть веб-сервис для inventory
@encyclopedist
15.03.2021 22:05 UTC
0

Я полагаю что вот это https://github.com/lilydjwg/pssh

16.03.2021 00:26 UTC
0
Огромное спасибо за ссылку! Мне кажется, что из-за того, что существует еще вот этот проект возникла некоторая путаница. Я начинал с pssh и думаю ограничения, в которые я уперся, в частности вытекают из того, что pssh под капотом просто запускает ssh. Судя по readme pssh существовал уже как минимум в 2009-м. Первый PR parallel-ssh был создан в октябре 2013-го года, т.е. примерно тогда, когда я начал работу над ossh.
16.03.2021 12:14 UTC
0
Самый ранний полагаю все-таки будет distributed shell (dsh) он же dancer's shell. Его еще в 2007м использовал. Сейчас чаще использую Parallel Distributed Shell или pdsh. Изначально они вообще были для использования с rsh.
@kiaplayer
15.03.2021 17:56 UTC
0

Из статьи не совсем понятно, почему не ansible? Вроде бы тоже все делает через ssh, клиентов на узлах не требует. Не говоря уже про идемпотетность сценариев и прочее…

@kt97679
15.03.2021 18:30 UTC
0
ansible это configuration management system. У нас уже был chef и мы уже были в процессе миграции на salt. Нам нужно было временное решение для деплоймента одного единственного пакета в этот промежуточный период. Если бы я предложил воспользоваться еще одной системой управления конфигурациями это как минимум вызвало бы непонимание.

ossh был сделан не для управления конфигурациями а именно для параллельного запуска команд. Из примеров, когда ossh мне помог, видно, что использовать для тех задач системы управления конфигурациями было бы затруднительно.
15.03.2021 18:43 UTC
0
$ ansible all -i '<ip-address-1>,<ip-address-2>' --user=admin --private-key=~/.ssh/id_rsa -m shell -a "cat /etc/os-release"

К примеру, эта команда вернет содержимое файла /etc/os-release на любом указанном количестве хостов. Это если не хотите пользоваться playbook'ами и создавать инвентари :)
Точно так же можно поставить пакет, создать каталог и т.д (через shell-команды или используя встроенные модули ansible).

У нас в компании сейчас идет фаза перехода с Chef на Ansible. Это оказалось гораздо менее болезненным, чем казалось.
15.03.2021 20:05 UTC
0
Какой максимальный параллелизм выдерживает ansible и насколько удобно парсить вывод?
15.03.2021 23:22 UTC
0

Параллелизм — сколько задашь в ansible.cfg, ну и сколько памяти сожрёт питон при запуске на большом числе машин — проверить стоит.


Дефолтный вывод парсить не слишком удобно, но есть callback plugins, например json.


Вроде такого:


 ANSIBLE_LOAD_CALLBACK_PLUGINS=1 ANSIBLE_STDOUT_CALLBACK=json ansible all -i 'ip1,ip2' --user=root --private-key=~/.ssh/id_rsa -m command -a '/bin/cat /etc/fstab'  | jq .plays[0].tasks[0].hosts
16.03.2021 00:29 UTC
0
Спрашиваю про параллелизм потому, что, к примеру, salt-ssh несколько лет назад не смог отработать на 500+ хостов. Тогда пришлось придумывать костыли с разбиением хостов на подгруппы.
@Rigidus
19.03.2021 19:23 UTC
0
Есть еще вот такой подход:

howardism.org/Technical/Emacs/literate-devops.html
@nad_oby
21.03.2021 18:36 UTC
0

В добавок к этому закину http://www.fabfile.org/
и http://www.paramiko.org/.
это если нужно на питоне sah параллельно запускать.
в сочетании с pyexpect, к примеру, позволяет делать довольно странные вещи.