$ 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
$
Neste exemplo, o arquivo /tmp/ossh.ips contém 21418 endereços IP de máquinas. -n significa que você não precisa fazer consultas reversas para determinar o nome por endereço. -c uptime define o comando que desejo executar. -p 1000 permite até 1000 conexões ao mesmo tempo. Como você pode ver na saída, o comando funcionou com bastante rapidez.
O que mais o ossh pode fazer?
$ 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
$
A lista de hosts pode ser especificada diretamente na linha de comando usando a opção -H (no caso de vários hosts, eles devem ser separados por um espaço e toda a lista deve ser colocada entre aspas, como nos exemplos abaixo) ou carregado de um arquivo usando a opção -h. As linhas que começam com # no arquivo são ignoradas. O endereço pode conter a porta: my.host:2222. Você pode usar a expansão de chaves: "host {1,3..5} .com" se tornará "host1.com host3.com host4.com host5.com". Ambos -H e -h podem ser usados várias vezes.
Para autorização será usado
- a senha que ossh pedirá ao usar a opção -A
- switch ssh especificado pela opção -k
- ssh-agent (neste caso, você deve ter a variável de ambiente SSH_AUTH_SOCK definida)
Naquela ordem.
Às vezes, você precisa se certificar de que pode efetuar login em todas as máquinas antes de executar um comando. Existe uma opção -P para isso. Por padrão, se pelo menos uma máquina estiver indisponível, o ossh falhará. Se você deseja ignorar conexões com falha, use a opção -i.
Ossh pode usar seu sistema de inventário. Para fazer isso, os caminhos devem conter o comando ossh-inventory, para o qual os parâmetros da opção -I serão passados. Esta opção pode ser usada várias vezes. O comando ossh-inventory deve imprimir linhas na saída padrão no formato:
_ _
Onde machine_address pode ser o nome dns ou o endereço IP.
Os comandos a serem executados são especificados pelas opções -C (ler do arquivo) ou -c (retirar da linha de comando). Essas opções podem ser usadas várias vezes. Se -C e -c estiverem presentes, os comandos dos arquivos serão executados primeiro e, em seguida, na linha de comando.
Além de simplesmente executar comandos usando ossh, você pode transmitir registros em tempo real:
$ 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)"
...
Aqui está uma simulação de implantação contínua:
$ 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
$
Percebe-se que os comandos são executados sequencialmente nas máquinas. Apenas uma máquina está envolvida por vez. Para uma implementação real, "sleep 10 && date" deve ser substituído por, por exemplo, "apt-get install -y your_package".
Foi para a implantação que a primeira versão do ossh foi escrita. Alguém vai perguntar por que eu não usei algum sistema de gerenciamento de configuração geralmente aceito? O fato é que foi em 2013 e usamos chef. Ficou claro que o chef não nos agradava em particular com a incerteza de quando exatamente as mudanças seriam aplicadas (o chef-cliente era executado a cada 30 minutos). Para implementar mudanças consistentemente em muitas máquinas, alguns desenvolvedores usaram um hack sujo: o chef-client não funcionava o tempo todo, mas era iniciado uma vez (via ssh) apenas no momento em que era necessário fazer a implantação. Já naquele momento, estava em andamento o trabalho de substituição do chef pelo sal, mas a transição não foi fácil e sua conclusão exigiu mais tempo. Estávamos desenvolvendo um novo serviço,que exigia implantações frequentes e foi implementado pelo único pacote Debian. Primeiro, usamos o utilitário faca do chef. Este utilitário permitia que você se conectasse via ssh aos servidores desejados e executasse comandos neles. Em algum momento, percebi que chef neste caso é um link extra e escrevi ossh.
É importante notar que ossh é uma ferramenta para resolver problemas de grande escala e fora do padrão. Se a necessidade de usar ossh surgir com frequência, esse é um motivo para pensar se você está se saindo bem com a infraestrutura e o gerenciamento de servidores. Aqui estão algumas situações em que ossh me ajudou pessoalmente:
- Uma vez eu arrumei /root/.ssh/authorized_keys em um grande número de servidores (havia cerca de 7.000 naquela época). Os desenvolvedores registraram suas chaves ali, em particular para os processos de atualização de seus serviços. Era necessário obter uma lista de todas as chaves usadas em todas as máquinas e certificar-se de que excluí-las não seria catastrófico.
- Para um segundo salto indolor
- Quando estávamos lutando com TCP SACK PANIC , as regras de iptables foram implementadas pelo sistema de gerenciamento de configuração. Para ter certeza de que está tudo bem, verifiquei as regras corretas com ossh. E não foi em vão, havia carros em que as regras não se aplicavam.
- Às vezes, preciso criar ambientes de teste com centenas (e às vezes milhares) de máquinas. Freqüentemente, essas máquinas são isoladas da rede de produção e não estão disponíveis para o sistema de gerenciamento de configuração padrão. Em situações como essa, a configuração da máquina pode ser feita usando ossh.
Prevejo a questão de por que não usei uma solução pronta. Como mencionei acima, a necessidade de executar comandos em milhares de máquinas ocorreu pela primeira vez em 2013. Naquela época, consegui encontrar apenas ssh paralelo, o que não me convinha com o seguinte:
- Não consegui elevar o paralelismo acima de 150, começaram a ocorrer erros ao conectar a servidores remotos
- O ssh paralelo acumulou todas as saídas e as despejou quando o comando terminou. O streaming de logs, por exemplo, era impossível com sua ajuda
- A saída ssh paralela foi (para mim pessoalmente) difícil de analisar
Originalmente, ossh foi escrito em ruby, para aumentar o desempenho, usei a máquina de eventos e depois a fibra. Recentemente, reescrevi ossh para ir. Eu ficaria muito grato se os especialistas em go (não estou no momento) dessem uma olhada no meu código e apontassem possíveis maneiras de melhorá-lo.