So führen Sie einen Befehl per SSH auf mehreren Servern aus

Schreiben Sie die Servernamen in eine Datei und durchlaufen Sie sie mit ssh -n -o BatchMode=yes "$host" 'Befehl'. So läuft ein Server nach dem anderen, und keiner hält die Schleife an. Schneller geht es parallel mit xargs -P, GNU parallel --tag oder parallel-ssh -h hosts -i, die jede Ausgabe mit ihrem Server kennzeichnen. Alles, was einen Server verändert, probieren Sie zuerst auf einem aus, und prüfen Sie jeden Exit-Code.

Keine Lust auf Hostlisten und Parallel-Optionen? Wardterm führt einen Befehl auf den gewählten Servern aus und zeigt das Ergebnis jedes einzelnen. Zeigen

Aktualisiert am . Alle Befehle liefen aus einem Debian-13-Container gegen drei Debian-13-Container mit OpenSSH 10.0, mit bash 5.2, GNU parallel 20240222, parallel-ssh 2.3.5, tmux 3.5a und ansible-core 2.19.

Vorbereitung: eine Hostliste und stille Anmeldungen

Alle Methoden unten lesen dasselbe: einen Servernamen pro Zeile. Verwenden Sie die Namen aus Ihrer SSH-Konfiguration, dann kommen Benutzer, Ports und Schlüssel von dort und nicht aus jedem Befehl. Falls Sie noch keine haben, sehen Sie sich die Beispiele für die SSH-Config-Datei an.

web1
web2
web3

Speichern Sie sie als hosts. Zwei Optionen sorgen dafür, dass Stapelläufe sauber laufen:

  • -o BatchMode=yes bricht sofort ab, statt an einer Passwort- oder Passphrase-Abfrage zu warten, die niemand beantwortet.
  • -o ConnectTimeout=5 verhindert, dass ein toter Server den Rest aufhält.

Laden Sie Ihren Schlüssel vorher mit ssh-add in den Agenten, damit kein Lauf danach fragt. Bei vielen Läufen hintereinander nutzt SSH-Multiplexing mit ControlMaster eine Verbindung pro Server weiter, und jeder Befehl startet schneller.

Ein Server nach dem anderen: eine Schleife

Eine Schleife ist der einfachste Weg, und die Ausgabe bleibt in Reihenfolge. Diese hier merkt sich außerdem die Server, auf denen der Befehl fehlschlug:

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: 

Das -n ist wichtig. Ohne es liest ssh den Rest von hosts als eigene Eingabe, und die Schleife endet nach dem ersten Server. Quoting und Exit-Codes funktionieren wie bei einem einzelnen Server, siehe wie Sie einen Befehl per SSH ausführen.

Bei vielen Servern ist eine Schleife langsam: Fünfzig Server zu je zwei Sekunden dauern fast zwei Minuten.

Parallel: Hintergrundjobs, xargs und GNU parallel

Reines bash kann jedes ssh im Hintergrund starten und auf alle warten. Jeder Server schreibt in eine eigene Datei, damit sich die Ausgaben nicht mischen:

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

Das startet alle auf einmal. Um zu begrenzen, wie viele gleichzeitig laufen, nutzen Sie xargs -P:

xargs -P 10 -I{} ssh -n -o BatchMode=yes {} 'echo "$(hostname): $(nproc) cpu"' < hosts

Die Zeilen kommen in der Reihenfolge zurück, in der die Server fertig werden. Lassen Sie den Befehl also seinen eigenen Hostnamen ausgeben.

GNU parallel kann beides besser: --tag setzt den Servernamen vor jede Zeile, und --joblog schreibt den Exit-Code jedes Servers in eine Datei:

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

Hier war web3 nicht erreichbar. parallel selbst endet mit der Zahl der fehlgeschlagenen Jobs, hier 1, sodass ein Skript darauf anhalten kann.

parallel-ssh (pssh)

parallel-ssh ist genau dafür gemacht. Unter Debian und Ubuntu installieren Sie es mit apt install pssh, der Befehl heißt dort parallel-ssh. Andere Systeme nennen ihn 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

Der Befehl prüft, ob ein Ordner existiert: Auf web1 fehlt er, und web3 ist nicht erreichbar. -i gibt die Ausgabe jedes Servers unter seiner Statuszeile aus, und -t 10 gibt einen Server nach 10 Sekunden auf. Um die Ausgabe aufzubewahren, schreiben Sie stattdessen eine Datei pro Server:

parallel-ssh -h hosts -t 10 -o out -e err 'df -h /'

Der Exit-Code ist ungleich null, sobald ein Server fehlschlug. Mit -p begrenzen Sie, wie viele gleichzeitig laufen.

In viele Terminals zugleich tippen: tmux

Manchmal müssen Sie jeden Server beobachten oder auf jedem eine Abfrage beantworten. tmux kann einen Bereich pro Server öffnen und jede Taste an alle senden:

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

Ausschalten können Sie es in tmux mit Ctrl-b : und setw synchronize-panes off. Solange es an ist, geht alles überallhin, auch ein Tippfehler. Das tmux-Cheat-Sheet listet die Tasten zum Wechseln zwischen Bereichen.

Vor einem Befehl, der etwas ändert

Ein Fehler auf fünfzig Servern kostet fünfzigmal so viel. Ein paar Gewohnheiten halten ihn klein:

  • Erst die Vorschau. Viele Werkzeuge haben einen Probelauf. apt-get -s upgrade | grep -c ^Inst zählt die Pakete, die sich ändern würden, ohne sie zu ändern.
  • Ein Server, dann der Rest. Führen Sie die Änderung auf dem ersten Server aus, prüfen Sie sie, dann auf den anderen.
  • Vor dem Lauf fragen. Zeigen Sie in einem Skript die Anzahl und warten Sie auf eine Antwort.
  • Jeden Exit-Code lesen. Ein Lauf, der auf 49 Servern klappte und auf einem fehlschlug, ist fehlgeschlagen, bis Sie den Grund kennen.
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

Halten Sie Produktion in einer eigenen Hostdatei, damit ein Befehl für Staging sie nie aus Versehen erreicht. Weitere Gewohnheiten finden Sie unter wie Sie Befehle auf dem falschen Server vermeiden.

Wann Ansible besser passt

Wenn dieselbe Änderung jede Woche wiederkommt, ist ein Werkzeug, das das Ergebnis beschreibt, besser als ein Shell-Befehl. Der Ad-hoc-Modus von Ansible liest dieselbe Liste:

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

Es meldet rc für jeden Server, und Module wie apt oder copy zeigen mit --check, was sie ändern würden. Für einen kurzen Blick auf ein paar Server sind Schleife oder parallel-ssh weniger Aufwand. Wie Sie die Liste selbst in Ordnung halten, steht unter wie Sie viele SSH-Server verwalten.

Fragen

Warum stoppt meine ssh-Schleife nach dem ersten Server?

ssh liest die Standardeingabe und nimmt dabei den Rest der Hostliste mit. Fügen Sie dem ssh-Befehl -n hinzu, oder nutzen Sie eine for-Schleife, die nicht von der Standardeingabe liest.

Auf wie vielen Servern kann ich gleichzeitig arbeiten?

So vielen, wie Rechner und Netz schaffen. Zehn bis fünfzig gleichzeitig sind üblich. Gehen Sie tiefer, wenn der Befehl schwer ist oder die Server eine Ressource wie eine Datenbank teilen.

Was unterscheidet parallel-ssh von Ansible?

parallel-ssh führt einen Shell-Befehl auf vielen Servern aus. Ansible führt Module aus, die das gewünschte Ergebnis beschreiben, kann vor einer Änderung prüfen und hält Inventare und Playbooks für wiederkehrende Arbeit.

Kann ich in mehrere SSH-Sitzungen gleichzeitig tippen?

Ja. Legen Sie in tmux jede Sitzung in einen Bereich und schalten Sie synchronize-panes ein. Jede Taste geht an alle Bereiche, bis Sie es ausschalten.

Quellen

Mehr zu Sitzungen und tmux

Alle Anleitungen