63

There is a list of IP addresses in a .txt file, ex.:

1.1.1.1
2.2.2.2
3.3.3.3

Behind every IP address there is a server, and on every server there is an sshd running on port 22. Not every server is in the known_hosts list (on my PC, Ubuntu 10.04 LTS/bash).

How can I run commands on these servers, and collect the output?

Ideally, I'd like to run the commands in parallel on all the servers.

I'll be using public key authentication on all the servers.

Here are some potential pitfalls:

  • The ssh prompts me to put the given servers ssh key to my known_hosts file.
  • The given commands might return a nonzero exit code, indicating that the output is potentially invalid. I need to recognize that.
  • A connection might fail to be established to a given server, for example because of a network error.
  • There should be a timeout, in case the command runs for longer than expected or the server goes down while running the command.

The servers are AIX/ksh (but I think that doesn't really matter.

3
  • possible duplicate of How can I reproduce commands run on one machine on another machine?
    – Caleb
    Aug 19, 2011 at 12:51
  • 2
    I thinks it's not a duplicate, because the link you mention doesn't even contain SSH. Aug 19, 2011 at 13:06
  • 4
    If you have not done so, you should set up ssh servers on all of the machines, make a private/public key pair on the machine you work from and copy the public key to the accounts on the server to prevent further password hassles. That applies for my answer, and for @demure's as well.
    – Anthon
    May 23, 2013 at 4:49

16 Answers 16

76

There are several tools out there that allow you to log in to and execute series of commands on multiple machines at the same time. Here are a couple:

3
  • 1
    I found clusterssh fairly intuitive to use. Just my 2 cents...
    – josinalvo
    Apr 13, 2015 at 22:50
  • 1
    I use pssh frequently. It works very well, although I do wish I could tell it that certain remote commands will have a non-zero exit code, and that does not mean an error. That's purely cosmetic though.
    – acjca2
    Jul 30, 2017 at 14:50
  • 1
    @AnthonyClark you could append something like "|| true" to those commands.
    – exic
    Jul 2, 2018 at 12:43
22

If you're into Python scripting more than bash scripting, then Fabric might be the tool for you.

From the Fabric home page:

Fabric is a Python (2.5 or higher) library and command-line tool for streamlining the use of SSH for application deployment or systems administration tasks.

It provides a basic suite of operations for executing local or remote shell commands (normally or via sudo) and uploading/downloading files, as well as auxiliary functionality such as prompting the running user for input, or aborting execution.

Typical use involves creating a Python module containing one or more functions, then executing them via the fab command-line tool.

1
  • 3
    Fabric v1 was amazing. Unfortunately, Fabric v2 removed most of the features that made it useful for this use case -- would not recommend currently (June 2018).
    – Meekohi
    Jun 28, 2018 at 15:42
17

Assuming that you are not able to get pssh or others installed, you could do something similar to:

tmpdir=${TMPDIR:-/tmp}/pssh.$$
mkdir -p $tmpdir
count=0
while IFS= read -r userhost; do
    ssh -n -o BatchMode=yes ${userhost} 'uname -a' > ${tmpdir}/${userhost} 2>&1 &
    count=`expr $count + 1`
done < userhost.lst
while [ $count -gt 0 ]; do
    wait $pids
    count=`expr $count - 1`
done
echo "Output for hosts are in $tmpdir"
7
  • 1
    what does exactly wait do in this script?? ty! Aug 19, 2011 at 15:43
  • what happens if a servers key is not in my knows_hosts file? Aug 19, 2011 at 15:50
  • 5
    putting scripts into the background creates child processes. When a child process exits, the process 'slot' and resources stay in the system until the parent process exits or the parent process 'waits' for the child. These 'terminated by still present' processes are called 'zombie' processes. It is good behavior to clean up after child proceses and reclaim resources.
    – Arcege
    Aug 19, 2011 at 19:38
  • 1
    If not known, then that might screw it up, but you can add -o StrictHostKeyChecking=no to avoid this. However it is better to scan all the servers from a command-line first so the host keys can be added.
    – Arcege
    Aug 19, 2011 at 19:40
  • 1
    As I mentioned, after the ssh program is put into the background and after it exits, resources are still kept. The wait command reclaims those resources, including the process' return code (how the processs exited). Then if later in the program, you put another process, lets say a compression, in the background and needs to wait for it, then without this loop, the wait will reclaim one of the completed ssh programs, not the compression - which may still be running. You would get an return status on the wrong child process. It is good to clean up after yourself.
    – Arcege
    Aug 28, 2011 at 18:23
15

I do use GNU parallel for that, most specifically you can use this recipe:

parallel --tag --nonall --slf your.txt command

With your.txt being the file with the server IP address/names.

10
  • isn't there any other way only using ssh Since I am using my company servers I don't want to install additional packages
    – Özzesh
    May 23, 2013 at 4:49
  • Sure, I used to do this before ssh existed as well, but you need some way to login to the other computers and those ways used to be less secure (like rsh). You don't describe what you are using now to get from machine to machine (telnet?, rsh?).
    – Anthon
    May 23, 2013 at 4:52
  • 1
    @Özzesh yes only on the controller
    – Anthon
    May 23, 2013 at 5:20
  • 2
    @Özzesh See if your reason for not installing GNU Parallel is covered on oletange.blogspot.dk/2013/04/why-not-install-gnu-parallel.html
    – Ole Tange
    May 23, 2013 at 8:07
  • 1
    @OleTange Just realised who commented on my answer %-). Thanks for this excellent piece of software.
    – Anthon
    May 23, 2013 at 8:18
14

Very basic setup:

for host in $(cat hosts.txt); do ssh "$host" "$command" >"output.$host"; done

Authenticating with name/password is really no good idea. You should set up a private key for this:

ssh-keygen && for host in $(cat hosts.txt); do ssh-copy-id $host; done
1
4

I suggest Ansible.cc. It's a configuration manager and command dispatcher.

2

I think you're looking for pssh and the related parallel versions of the usual scp, rsync, etc..

0
2

I built an open-source tool called Overcast to make this sort of thing easier.

First you define your servers:

overcast import vm.01 --ip 1.1.1.1 --ssh-key /path/to/key
overcast import vm.02 --ip 2.2.2.2 --ssh-key /path/to/key

Then you can run multiple commands and script files across them, either sequentially or in parallel, like so:

overcast run vm.* uptime "free -m" /path/to/script --parallel
2

The Hypertable project has recently added a multi-host ssh tool. This tool is built with libssh and establishes connections and issues commands asynchronously and in parallel for maximum parallelism. See Multi-Host SSH Tool for complete documentation. To run a command on a set of hosts, you would run it as follows:

$ ht ssh 1.1.1.1,2.2.2.2,3.3.3.3 uptime

You can also specify a host name or IP pattern, for example:

$ ht ssh 1.1.1.[1-99] uptime
$ ht ssh host[00-99] uptime

It also supports a --random-start-delay <millis> option that will delay the start of the command on each host by a random time interval between 0 and <millis> milliseconds. This option can be used to avoid thundering herd problems when the command being run accesses a central resource.

1

Just a headsup for a really nice question:

Best solution I've found is, not-so-surprisingly, tmux.

You can do Ctrl-B : setw synchronize-panes in tmux for amazing resut. You must have all of your ssh connections opened, in different panes of 1 window of Tmux for this.

0

Maybe something like this works, to run command on multiple hosts? suppose all hosts are setup in .ssh/config for password-less login.

$ < hosts.txt xargs -I {} ssh {} command

0

Create a file /etc/sxx/hosts

populate like so:

[grp_ips]
1.1.1.1
2.2.2.2
3.3.3.3

share ssh key on all machines.

Install sxx from package:

https://github.com/ericcurtin/sxx/releases

Then run command like so:

sxx username@grp_ips "whatever bash command"
1
  • Where is the output written?  How are non-zero exit statuses handled?  Please do not respond in comments; edit your answer to make it clearer and more complete. Mar 20, 2018 at 23:38
0

Use Paramiko http://www.paramiko.org/ and Thread-based parallelism https://docs.python.org/3/library/threading.html .below code allows to execute multiple commands on each server in parallel!

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.load_system_host_keys()
ssh.connect(hostname, port, username, password)
t = threading.Thread(target=tasks[index], args=(ssh, box_ip,))
t.start()

Note: repeat above instructions for each server.

0

I think "expect" is what you need.
Many people does not even know it exists. It's a tcl based program that will made question and possible answers on your behalf. Have a look to https://wiki.tcl-lang.org/page/Expect

1
  • 1
    Also, you could use the autoexpect script to record one sample interaction and then edit the resulting file to adjust as needed.
    – Fjor
    Apr 28, 2022 at 16:01
0

Use pdsh. Here's a useful example:

pdsh -w 'something.example.com,node-[1-5].example.com' -R ssh 'uptime'
1
  • seems not easy to input password for sudo run (hava to call sudo --stdin by user)
    – yurenchen
    Sep 23, 2022 at 0:55
0

I would suggest to use Ansible ad hoc commands for this job. Ansible uses ssh and anyway it is a nice tool to manage multiple hosts, but now I am just focusing on the topic to run a single command on multiple hosts.

Requirement

your user has an ssh key already installed on the remote hosts (and for most command you want passordless sudo rights there as well). So the following command should work without asking any password on any of your managed hosts (running uptime command on the remote server):

ssh server1.mydomain.com uptime

or even better (to check passwordless sudo):

ssh server1.mydomain.com sudo uptime

Configure ansible

As the ssh works fine you can create an inventory file for ansible: inventory.yml (yes, it's in YAML format, so whitespace matters):

all:
  hosts:
    server1.mydomain.com:
    server2.mydomain.com:
    server3.mydomain.com:

Run command on multiple hosts

and now run ansible to use uptime command on all of your hosts in the inventory:

ansible -i inventory.yml --one-line all -a uptime 

output:

server1.mydomain.com | CHANGED | rc=0 | (stdout)  14:48:24 up 5 days,  9:18,  1 user,  load average: 0.16, 0.04, 0.01
server2.mydomain.com | CHANGED | rc=0 | (stdout)  12:48:24 up 6 days,  7:17,  3 users,  load average: 0.02, 0.12, 0.17
server3.mydomain.com | CHANGED | rc=0 | (stdout)  14:48:24 up 5 days,  9:18,  1 user,  load average: 0.08, 0.02, 0.01

The --one-line option is not a must, just gives you nicer output if you have many servers and the output really just one line.

If the user on the remote server is not the same as your local user you can define that in the inventory or in the command line as well:

ansible -i inventory.yml -u remoteuser all -a uptime 

If you need arguments for the command use quotation marks:

ansible -i inventory.yml all -a "ls -lh /etc"

And if you have to use pipe (|) than you need the shell module:

ansible -i inventory.yml all -m shell -a "cat /etc/passwd | grep root"

And finally for the original question, use this command before ansible to accept any ssh host keys:

export ANSIBLE_HOST_KEY_CHECKING=False
2

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .