Avant de commencer : une liste d'hôtes et des connexions sans question
Toutes les méthodes ci-dessous lisent la même chose : un nom de serveur par ligne. Utilisez les noms de votre configuration SSH, pour que utilisateurs, ports et clés viennent de là et pas de chaque commande. Voir les exemples de fichier ssh config si vous n'en avez pas encore.
web1
web2
web3
Enregistrez-le sous hosts. Deux options rendent les lancements en série plus sages :
-o BatchMode=yeséchoue tout de suite au lieu d'attendre une demande de mot de passe ou de phrase de passe à laquelle personne ne répondra.-o ConnectTimeout=5empêche un serveur éteint de bloquer les autres.
Chargez d'abord votre clé dans l'agent avec ssh-add, pour qu'aucun lancement ne s'arrête pour la demander. Pour de nombreux lancements à la suite, le multiplexage SSH avec ControlMaster réutilise une connexion par serveur et fait démarrer chaque commande plus vite.
Un serveur après l'autre : une boucle
La boucle est la méthode la plus simple, et la sortie reste dans l'ordre. Celle-ci garde aussi la liste des serveurs où la commande a échoué :
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:
Le -n compte. Sans lui, ssh lit le reste de hosts comme sa propre entrée, et la boucle s'arrête après le premier serveur. Les règles de guillemets et les codes de sortie sont les mêmes que pour un seul serveur, voir comment exécuter une commande en SSH.
Avec beaucoup de serveurs, la boucle est lente : cinquante serveurs à deux secondes chacun prennent presque deux minutes.
En parallèle : tâches de fond, xargs et GNU parallel
Bash seul peut lancer chaque ssh en arrière-plan et attendre qu'ils finissent tous. Chaque serveur écrit dans son propre fichier, donc les sorties ne se mélangent pas :
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
Tout part en même temps. Pour limiter le nombre de lancements simultanés, utilisez xargs -P :
xargs -P 10 -I{} ssh -n -o BatchMode=yes {} 'echo "$(hostname): $(nproc) cpu"' < hosts
Les lignes arrivent dans l'ordre où les serveurs terminent, alors faites afficher le nom d'hôte par la commande.
GNU parallel fait les deux mieux : --tag met le nom du serveur devant chaque ligne, et --joblog écrit le code de sortie de chaque serveur dans un fichier :
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
Ici, web3 était éteint. parallel sort avec le nombre de tâches en échec, 1 dans ce cas, donc un script peut s'arrêter dessus.
parallel-ssh (pssh)
parallel-ssh est fait pour ce travail. Sous Debian et Ubuntu, c'est apt install pssh, et la commande s'appelle parallel-ssh. Sur d'autres systèmes, elle s'appelle 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
La commande vérifie qu'un dossier existe : il manque sur web1, et web3 est éteint. -i affiche la sortie de chaque serveur sous sa ligne d'état, et -t 10 abandonne un serveur après 10 secondes. Pour garder la sortie, écrivez plutôt un fichier par serveur :
parallel-ssh -h hosts -t 10 -o out -e err 'df -h /'
Le code de sortie n'est pas nul dès qu'un serveur a échoué. Ajoutez -p pour limiter le nombre de lancements simultanés.
Taper dans plusieurs terminaux à la fois : tmux
Parfois, il faut suivre chaque serveur, ou répondre à une question sur chacun. tmux peut ouvrir un panneau par serveur et envoyer chaque touche à tous :
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
Désactivez-le depuis tmux avec Ctrl-b : puis setw synchronize-panes off. Tant qu'il est actif, tout part partout, faute de frappe comprise. L'aide-mémoire tmux donne les touches pour passer d'un panneau à l'autre.
Avant une commande qui modifie quelque chose
Une erreur sur cinquante serveurs coûte cinquante fois plus. Quelques habitudes la gardent petite :
- Prévisualiser d'abord. Beaucoup d'outils ont un mode d'essai.
apt-get -s upgrade | grep -c ^Instcompte les paquets qui changeraient, sans rien changer. - Un serveur, puis les autres. Lancez la modification sur le premier serveur, vérifiez, puis sur les autres.
- Demander avant le lancement. Dans un script, affichez le nombre de serveurs et attendez une réponse.
- Lire chaque code de sortie. Un lancement réussi sur 49 serveurs et raté sur un seul est un échec tant que vous ne savez pas pourquoi.
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
Gardez la production dans un fichier d'hôtes à part, pour qu'une commande prévue pour la préproduction ne l'atteigne jamais par accident. D'autres habitudes sont dans comment éviter de lancer des commandes sur le mauvais serveur.
Quand Ansible convient mieux
Quand la même modification revient chaque semaine, un outil qui décrit le résultat vaut mieux qu'une commande shell. Le mode ad hoc d'Ansible lit la même 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
Il indique rc pour chaque serveur, et des modules comme apt ou copy peuvent montrer ce qu'ils changeraient avec --check. Pour un coup d'œil rapide à quelques serveurs, la boucle ou parallel-ssh demandent moins de travail. Voir comment gérer de nombreux serveurs SSH pour tenir la liste elle-même en ordre.
Questions
Pourquoi ma boucle ssh s'arrête-t-elle après le premier serveur ?
ssh lit l'entrée standard et emporte le reste de la liste des hôtes. Ajoutez -n à la commande ssh, ou utilisez une boucle for qui ne lit pas l'entrée standard.
Sur combien de serveurs puis-je lancer en même temps ?
Autant que votre machine et votre réseau le permettent. De dix à cinquante à la fois, c'est courant. Baissez ce nombre si la commande est lourde ou si les serveurs partagent une ressource comme une base de données.
Quelle différence entre parallel-ssh et Ansible ?
parallel-ssh lance une commande shell sur de nombreux serveurs. Ansible lance des modules qui décrivent le résultat voulu, peut vérifier avant de modifier, et garde inventaires et playbooks pour le travail répété.
Puis-je taper dans plusieurs sessions SSH à la fois ?
Oui. Dans tmux, mettez chaque session dans un panneau et activez synchronize-panes. Chaque touche va à tous les panneaux jusqu'à ce que vous le désactiviez.
Sources
- ssh(1), manuel OpenBSD
- Manuel de GNU parallel
- Projet parallel-ssh (pssh)
- tmux(1), synchronize-panes
- Introduction aux commandes ad hoc, documentation Ansible