
Ich habe ein Skript geschrieben, das Befehle auf über 1000 Servern im Hintergrund ausführt. Manchmal bleibt das Skript auf einem der Server hängen. Wenn ein Server beim Ausführen eines Skripts hängen bleibt (aufgrund hoher durchschnittlicher Auslastung), bleibt der Befehl möglicherweise auch auf diesem Server hängen. Gibt es eine Möglichkeit, diesen Host zu überspringen, damit das Skript zum nächsten Host wechseln und weiterlaufen kann?
Ich hebe die beiden Hauptfunktionen meines Skripts hervor, habe aber kein Glück mit der Angabe der Schlüsselwörter „ConnectTimeout“ und „wait“.
exec_ssh()
{
for i in `cat $file`
do
ssh -q -o "StrictHostKeyChecking no" -o "NumberOfPasswordPrompts 0" -o ConnectTimeout=2 $i $command 2>>/dev/null &
if wait $!; then
echo "" >> /dev/null
else
echo "$i is not reachable over SSH or passwordless authentication is not setup on the server" >> /tmp/not_reachable
fi
done >/tmp/output.csv &
run_command()
{
export -f exec_ssh
export command
nohup bash -c exec_ssh &>>$log_file &
}
Antwort1
Ihr Skript wie geschriebenwürdeFühren Sie alle Ihre Remote-Befehle gleichzeitig aus, aber wait
warten Sie bei Ihrer Verwendung ausdrücklich auf die Fertigstellung einer Hintergrundaufgabe. In dem von Ihnen beschriebenen Fall eines Servers mit hoher Auslastung bedeutet dies, dass Ihr ssh
Befehl nicht abläuft, sondern einfach lange braucht, um abgeschlossen zu werden, sodass das Skript genau das tut, was Sie von ihm verlangen. ConnectTimeout
ist hinfällig, wenn Sie die Verbindung erfolgreich herstellen können ssh
.
Wenn Sie diese Art von Skript anstelle eines Tools verwenden möchten, das für die verteilte Remote-Ausführung entwickelt wurde, wie z. B.Ansible, ich könnte Ihr Skript wie folgt ändern:
exec_ssh() {
while read file; do
if ! ssh -q -o BatchMode=yes -o ConnectTimeout=2 "$i" "$command" 2>>/dev/null & then
echo "$i is not reachable via non-interactive SSH or remote command threw error - exit code $?" >> /tmp/not_reachable
fi
done < "$file" > /tmp/output.csv &
}
run_command() {
export -f exec_ssh
export command
nohup bash -c exec_ssh &>> "$log_file" &
}
Es könnte auch sinnvoll sein, den Test „Kann ich per SSH auf den Host zugreifen“ vom Test „Kann ich den Auftrag abschließen“ zu trennen:
if ssh -q -o BatchMode=yes -o ConnectTimeout=2 "$host" true; then
# connection succeeded
if ! ssh -q -o BatchMode=yes -o ConnectTimeout=2 "$host" "$command" & then
echo "Remote command threw $?"
fi
else
echo "SSH threw $?"
fi
Antwort2
Wenn Ihre lokalen und Remote-Befehle komplexer werden, werden Sie schnell überfordert sein, wenn Sie versuchen, alles in ein zusammenhängendes Skript zu packen. Bei Hunderten oder Tausenden von Hintergrundprozessen werden Sie wahrscheinlich sogar bei einem leistungsstarken lokalen Computer auf Probleme mit Ressourcenkonflikten stoßen.
Mit bekommen Sie das in den Griff xargs -P
. Normalerweise teile ich solche Aufgaben in zwei Skripte auf.
lokal.sh
Im Allgemeinen hat dieses Skript ein einziges Argument, nämlich den Hostnamen, und führt alle notwendigen Validierungen, Pre-Flight-Aufgaben, Protokollierungen usw. durch. Beispiel:
#!/bin/bash
hostname=$1
# simple
cat remote.sh | ssh user@$hostname
# sudo the whole thing
cat remote.sh | ssh user@$hostname sudo
# log to files
cat remote.sh | ssh user@$hostname &> logs/$hostname.log
# or log to stdout with the hostname prefixed
cat remote.sh | ssh user@$hostname 2>&1 | sed "s/^/$hostname:/"
remote.sh
Sie möchten das Skript remote ausführen, müssen es jetzt aber nicht mehr in einen einzeiligen Text mit Anführungszeichen stopfen und sich mit der Hölle des Entfernens von Anführungszeichen herumschlagen.
Der eigentliche Befehl
cat host_list.txt | xargs -P 16 -n 1 -I {} bash local.sh {}
Wo:
-P 16
wird bis zu 16 Unterprozesse aufspalten-n 1
liefert genau ein Argument pro Befehl-I {}
ersetzt das Argument durch{}
[hier nicht notwendig, kann aber für die Konstruktion komplexerer xargs-Aufrufe nützlich sein.
Selbst wenn also eines Ihrer lokalen oder Remote-Skripte hängen bleibt, können die anderen 15 weiterhin ungehindert weiterarbeiten.