Pull to refresh

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

Reading time 5 min
Views 3.8K
Иногда бывает нужно запустить патч Бармина какую-то команду на многих серверах и желательно не ждать слишком долго результатов выполнения. Для этого я написал 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-эксперты (я таковым на данный момент не являюсь) посмотрят на мой код и укажут на возможные способы улучшить его.
Tags:
Hubs:
+12
Comments 21
Comments Comments 21

Articles