Prima di cominciare: un elenco di host e accessi senza domande
Tutti i metodi qui sotto leggono la stessa cosa: un nome di server per riga. Usa i nomi della configurazione SSH, così utenti, porte e chiavi arrivano da lì e non da ogni comando. Se non ce l'hai ancora, vedi gli esempi di file ssh config.
web1
web2
web3
Salvalo come hosts. Due opzioni rendono ordinate le esecuzioni in serie:
-o BatchMode=yesfallisce subito invece di aspettare una password o una passphrase a cui nessuno risponderà.-o ConnectTimeout=5evita che un server spento blocchi gli altri.
Prima carica la chiave nell'agente con ssh-add, così nessuna esecuzione si ferma a chiederla. Per molte esecuzioni di fila, il multiplexing SSH con ControlMaster riusa una connessione per server e fa partire ogni comando più in fretta.
Un server dopo l'altro: un ciclo
Un ciclo è il modo più semplice, e l'output resta in ordine. Questo tiene anche l'elenco dei server dove il comando è fallito:
failed=()
while read -r host; do
printf '%s: ' "$host"
ssh -n -o BatchMode=yes -o ConnectTimeout=5 "$host" 'uptime -p' || failed+=("$host")
done < hosts
echo "failed: ${failed[*]}"
web1: up 4 days, 9 hours, 24 minutes
web2: up 4 days, 9 hours, 24 minutes
web3: up 4 days, 9 hours, 24 minutes
failed:
Il -n è importante. Senza, ssh legge il resto di hosts come proprio input e il ciclo finisce dopo il primo server. Virgolette e codici di uscita funzionano come con un solo server: vedi come eseguire un comando via SSH.
Con molti server un ciclo è lento: cinquanta server da due secondi l'uno richiedono quasi due minuti.
In parallelo: lavori in background, xargs e GNU parallel
Con bash puoi avviare ogni ssh in background e aspettarli tutti. Ogni server scrive nel proprio file, così gli output non si mescolano:
mkdir -p out
while read -r host; do
ssh -n -o BatchMode=yes "$host" 'hostname; uname -r' > "out/$host.log" 2>&1 &
done < hosts
wait
head out/*.log
Così partono tutti insieme. Per limitare quanti ne girano contemporaneamente, usa xargs -P:
xargs -P 10 -I{} ssh -n -o BatchMode=yes {} 'echo "$(hostname): $(nproc) cpu"' < hosts
Le righe arrivano nell'ordine in cui i server finiscono, quindi fai stampare al comando il nome del proprio host.
GNU parallel fa meglio entrambe le cose: --tag mette il nome del server davanti a ogni riga e --joblog scrive in un file il codice di uscita di ciascun server:
parallel -j 10 --tag --joblog run.log ssh -n -o BatchMode=yes -o ConnectTimeout=5 {} hostname :::: hosts
cut -f 7,9 run.log
Exitval Command
255 ssh -n -o BatchMode=yes -o ConnectTimeout=5 web3 hostname
0 ssh -n -o BatchMode=yes -o ConnectTimeout=5 web2 hostname
0 ssh -n -o BatchMode=yes -o ConnectTimeout=5 web1 hostname
Qui web3 era spento. parallel esce con il numero di lavori falliti, 1 in questo caso, così uno script può fermarsi.
parallel-ssh (pssh)
parallel-ssh è fatto proprio per questo. Su Debian e Ubuntu si installa con apt install pssh e il comando si chiama parallel-ssh. Su altri sistemi il comando si chiama pssh.
parallel-ssh -h hosts -i -t 10 'test -d /srv/app'
[1] 15:20:02 [FAILURE] web3 Exited with error code 255
Stderr: ssh: Could not resolve hostname web3: Name or service not known
[2] 15:20:02 [FAILURE] web1 Exited with error code 1
[3] 15:20:02 [SUCCESS] web2
Il comando controlla che una cartella esista: manca su web1 e web3 è spento. -i stampa l'output di ogni server sotto la sua riga di stato, e -t 10 rinuncia a un server dopo 10 secondi. Per conservare l'output, scrivi invece un file per server:
parallel-ssh -h hosts -t 10 -o out -e err 'df -h /'
Esce con un codice diverso da zero se un server qualsiasi è fallito. Aggiungi -p per limitare quanti ne girano insieme.
Scrivere in molti terminali insieme: tmux
A volte devi guardare ogni server, o rispondere a una domanda su ognuno. tmux può aprire un riquadro per server e mandare a tutti ogni tasto che premi:
tmux new-session -d -s multi 'ssh web1'
tmux split-window -t multi 'ssh web2'
tmux split-window -t multi 'ssh web3'
tmux select-layout -t multi tiled
tmux set-window-option -t multi synchronize-panes on
tmux attach -t multi
Per disattivarlo da dentro tmux usa Ctrl-b : e poi setw synchronize-panes off. Finché è attivo, tutto va dappertutto, errori di battitura compresi. Il promemoria di tmux elenca i tasti per spostarsi fra i riquadri.
Prima di un comando che modifica qualcosa
Un errore su cinquanta server costa cinquanta volte tanto. Poche abitudini lo tengono piccolo:
- Prima un'anteprima. Molti strumenti hanno una prova a vuoto.
apt-get -s upgrade | grep -c ^Instconta i pacchetti che cambierebbero, senza cambiarli. - Un server, poi gli altri. Esegui la modifica sul primo server, controlla, poi passa agli altri.
- Chiedi prima di partire. In uno script, mostra quanti server sono e aspetta una risposta.
- Leggi ogni codice di uscita. Un'esecuzione riuscita su 49 server e fallita su uno è fallita, finché non sai perché.
first=$(head -n 1 hosts)
ssh -n "$first" 'apt-get -s upgrade | grep -c ^Inst'
n=$(wc -l < hosts)
read -r -p "Run on $n servers? [y/N] " ok
[ "$ok" = y ] || exit 1
Tieni la produzione in un file di host separato, così un comando pensato per lo staging non ci arriva per sbaglio. Altre abitudini sono in come evitare di eseguire comandi sul server sbagliato.
Quando conviene Ansible
Quando la stessa modifica torna ogni settimana, uno strumento che descrive il risultato è meglio di un comando di shell. La modalità ad hoc di Ansible legge lo stesso elenco:
ansible all -i 'web1,web2,web3,' -m ansible.builtin.command -a 'uptime -p' --forks 10
web1 | CHANGED | rc=0 >>
up 4 days, 9 hours, 25 minutes
Riporta rc per ogni server, e moduli come apt o copy mostrano cosa cambierebbero con --check. Per un'occhiata veloce a pochi server, il ciclo o parallel-ssh richiedono meno lavoro. Per tenere in ordine l'elenco stesso, vedi come gestire molti server SSH.
Domande
Perché il mio ciclo ssh si ferma dopo il primo server?
ssh legge lo standard input e si porta via il resto dell'elenco dei server. Aggiungi -n al comando ssh, oppure usa un ciclo for che non legge dallo standard input.
Su quanti server posso eseguire un comando contemporaneamente?
Su quanti ne reggono il tuo computer e la rete. Da dieci a cinquanta insieme è normale. Abbassa il numero se il comando è pesante o se i server condividono una risorsa come un database.
Che differenza c'è fra parallel-ssh e Ansible?
parallel-ssh esegue un comando di shell su molti server. Ansible esegue moduli che descrivono il risultato voluto, può controllare prima di modificare e conserva inventari e playbook per il lavoro che si ripete.
Posso scrivere in più sessioni SSH insieme?
Sì. In tmux metti ogni sessione in un riquadro e attiva synchronize-panes. Ogni tasto va a tutti i riquadri finché non lo disattivi.
Fonti
- ssh(1), manuale OpenBSD
- Manuale di GNU parallel
- Progetto parallel-ssh (pssh)
- tmux(1), synchronize-panes
- Introduzione ai comandi ad hoc, documentazione di Ansible