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 можно использовать многократно.

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

  • пароль, который ossh запросит при использовании опции -A
  • ssh ключ, заданный опцией -k
  • ssh-agent (в этом случае у вас должна быть определена переменная окружения SSH_AUTH_SOCK)

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

Иногда бывает нужно убедиться, что вы можете залогиниться на все машины прежде чем выполнить команду. Для этого есть опция -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 помог лично мне:

  • Однажды я наводил порядок в /root/.ssh/authorized_keys на большом количестве серверов (на тот момент их было около 7000). Разработчики прописали туда свои ключи, в частности для процессов обновления своих сервисов. Нужно было получить список всех ключей, использованных на всех машинах, и убедиться, что удаление этих ключей не приведет к катастрофическим последствиям.
  • Для безболезненного прохождения leap second
  • Когда мы боролись с TCP SACK PANIC правила для iptables выкатывались системой управления конфигураций. Чтобы убедиться, что все хорошо, я проверил наличие нужных правил при помощи ossh. И это было совсем не зря, обнаружились машины, на которых правила не применились.
  • Иногда мне приходится создавать тестовые среды состоящие из сотен (а иногда из тысяч) машин. Часто эти машины изолированы от production сети и не доступны для штатной системы управления конфигурациями. В подобных ситуациях конфигурирование машин можно проводить при помощи ossh.

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

  • Мне не удалось поднять параллелизм выше 150, начали возникать ошибки при соединении с удаленными серверами
  • parallel ssh накапливал весь вывод и выдавал его по завершении команды. Стримить логи, к примеру, с его помощью было невозможно
  • Вывод parallel ssh был (лично для меня) неудобен для парсинга

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

ссылка на оригинал статьи https://habr.com/ru/post/546998/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *