I have a local machine which is supposed to make an SSH session to a remote master machine and then another inner SSH session from the master to each of some remote slaves, and then execute 2 commands i.e. to delete a specific directory and recreate it.
Note that the local machine has passwordless SSH to the master and the master has passwordless SSH to the slaves. Also all hostnames are known in .ssh/config of the local/master machines and the hostnames of the slaves are in slaves.txt locally and I read them from there.
So what I do and works is this:
username="ubuntu"
masterHostname="myMaster"
while read line
do
#Remove previous folders and create new ones.
ssh -n <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a581d0d6c0d7cbc4c8c0e5">[email protected]</a>$masterHostname "ssh -t -t <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="83a7f6f0e6f1ede2eee6c3">[email protected]</a>$line "rm -rf Input Output Partition""
ssh -n <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="cfebbabcaabda1aea2aa8f">[email protected]</a>$masterHostname "ssh -t -t <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="381c4d4b5d4a5659555d78">[email protected]</a>$line "mkdir -p EC2_WORKSPACE/$project Input Output Partition""
#Update changed files...
ssh -n <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="183c6d6b7d6a7679757d58">[email protected]</a>$masterHostname "ssh -t -t <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="6b4f1e180e19050a060e2b">[email protected]</a>$line "rsync --delete -avzh /EC2_NFS/$project/* EC2_WORKSPACE/$project""
done < slaves.txt
This cluster is on Amazon EC2 and I have noticed that there are 6 SSH sessions created at each iteration which induces a significant delay. I would like to combine these 3 commands into 1 to get fewer SSH connections. So I tried to combine the first 2 commands into
ssh -n <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f2d6878197809c939f97b2">[email protected]</a>$masterHostname "ssh -t -t <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="1135646274637f707c7451">[email protected]</a>$line "rm -rf Input Output Partition && mkdir -p EC2_WORKSPACE/$project Input Output Partition""
But it doesn’t work as expected. It seems to execute the first one (rm -rf Input Output Partition) and then exits the session and goes on. What can I do?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
Consider that && is a logical operator. It does not mean “also run this command” it means “run this command if the other succeeded”.
That means if the rm command fails (which will happen if any of the three directories don’t exist) then the mkdir won’t be executed. This does not sound like the behaviour you want; if the directories don’t exist, it’s probably fine to create them.
Use ;
The semicolon ; is used to separate commands. The commands are run sequentially, waiting for each before continuing onto the next, but their success or failure has no impact on each other.
Escape inner quotes
Quotes inside other quotes should be escaped, otherwise you’re creating an extra end point and start point. Your command:
ssh -n <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7551000610071b14181035">[email protected]</a>$masterHostname "ssh -t -t <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4367363026312d222e2603">[email protected]</a>$line "rm -rf Input Output Partition && mkdir -p EC2_WORKSPACE/$project Input Output Partition""
Becomes:
ssh -n <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="9abeefe9ffe8f4fbf7ffda">[email protected]</a>$masterHostname "ssh -t -t <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="381c4d4b5d4a5659555d78">[email protected]</a>$line "rm -rf Input Output Partition && mkdir -p EC2_WORKSPACE/$project Input OutputPartition""
Your current command, because of the lack of escaped quotes should be executing:
ssh -n <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="587c2d2b3d2a3639353d18">[email protected]</a>$masterHostname "ssh -t -t <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="8da9f8fee8ffe3ece0e8cd">[email protected]</a>$line "rm -rf Input Output Partition
if that succeeds:
mkdir -p EC2_WORKSPACE/$project Input Output Partition"" # runs on your local machine
You’ll notice the syntax highlighting shows the entire command as red on here, which means the whole command is the string being passed to ssh. Check your local machine; you may have the directories Input Output and Partition where you were running this.
Method 2
You can always define in your jumpbox Multiplexing in OpenSSH
Multiplexing is the ability to send more than one signal over a single
line or connection. With multiplexing, OpenSSH can re-use an existing
TCP connection for multiple concurrent SSH sessions rather than
creating a new one each time.An advantage with SSH multiplexing is that the overhead of creating
new TCP connections is eliminated. The overall number of connections
that a machine may accept is a finite resource and the limit is more
noticeable on some machines than on others, and varies greatly
depending on both load and usage. There is also significant delay when
opening a new connection. Activities that repeatedly open new
connections can be significantly sped up using multiplexing.
For that do in /etc/ssh/ssh_config:
ControlMaster auto ControlPath ~/.ssh/controlmasters/ssh_mux_%h_%p_%r ControlPersist 30m
In this way, any consecutive connections made to the same server in the following 30 minutes will be done reusing the previous ssh connection.
You can also define it for a machine or group of machines. Taken from the link provided.
Host machine1
HostName machine1.example.org
ControlPath ~/.ssh/controlmasters/%<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="97e5d7">[email protected]</a>%h:%p
ControlMaster auto
ControlPersist 10m
Method 3
You can put all your commands into a separate script on your “master” server.
Master Script
#!/bin/bash rm -rf "Input Output Partition" mkdir -p "EC2_WORKSPACE/$project Input Output Partition"
Then in your ssh script call it like this:
SSH Script
username="ubuntu" masterHostname="myMaster" while read line do ssh -n <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="36124345534458575b5376">[email protected]</a>$masterHostname "ssh -t -t <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="87a3f2f4e2f5e9e6eae2c7">[email protected]</a>$line < /path/to/masterscript.sh" ssh -n <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e5c1909680978b848880a5">[email protected]</a>$masterHostname "ssh -t -t <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f3d7868096819d929e96b3">[email protected]</a>$line "rsync --delete -avzh /EC2_NFS/$project/* EC2_WORKSPACE/$project"" done < slaves.txt
OR
if all files must be on the initial machine you could do something like this:
script1
script2="/path/to/script2" username="ubuntu" while read line; do cat $script2 | ssh -t -t <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f7d38284928599969a92b79b9e9992">[email protected]</a> done < slaves.txt
script2
#!/bin/bash rm -rf "Input Output Partition" mkdir -p "EC2_WORKSPACE/$project Input Output Partition" rsync --delete -avzh "/EC2_NFS/$project/* EC2_WORKSPACE/$project"
ssh script
script1="/path/to/script1" username="ubuntu" masterHostname="myMaster" cat $script1 | ssh -n <a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="81a5f4f2e4f3efe0ece4c1">[email protected]</a>$masterHostname
Method 4
Some time ago, I had occasion to use control sockets like the other answers recommend (this answer is essentially a combination of using control sockets like this answer and scripts like this answer).
The use case was a hack: The authorized_keys of the target user was overwritten periodically by a scheduled task and I wanted to quickly test things without going through red-tape needed to add something to that file. So I’d setup a while loop which added the key to that file as needed, run my test, and the cancel the loop. However, there would be a small window where the scheduled task would overwrite the file and my loop would still be sleeping. So, setting up a control socket at the start would let my script SSH later without problems:
#! /bin/bash -xe
. "${CONFIG_DIR}/scripts/setup-ssh.sh"
# Build and test
export TEST_LABEL="${_started_by}-${BUILD_TAG%-BUILD*}"
#...
xargs --arg-file test-list
--no-run-if-empty
--process-slot-var=NUM
--max-procs=${#SERVERS[@]}
--max-args="${BATCH_SIZE:-20}"
"${CONFIG_DIR}/scripts/run-test.sh"
Where setup-ssh.sh is:
export SSH_CONFIG="${CONFIG_DIR}/scripts/.ssh-config"
mapfile -t SERVERS < "${CONFIG_DIR}/scripts/hosts"
for SERVER in "${SERVERS[@]}"
do
while ! ssh -F "${SSH_CONFIG}" "${SERVER}" -fnN; do sleep 1; done
scp -F "${SSH_CONFIG}" "${CONFIG_DIR}/scripts/ssh-script.sh" "${SERVER}":"${TEST_LABEL}.sh"
done
And .ssh-config:
Host test-* User test StrictHostKeyChecking no ControlMaster auto ControlPath /tmp/ssh-%h-%p-%r
And run-test.sh:
mapfile -t TEST_SERVERS < "${CONFIG_DIR}/scripts/hosts"
ssh -F "${SSH_CONFIG}" "${TEST_SERVERS[$NUM]}" "./${TEST_LABEL}.sh"
The sequence goes like this:
- The main script (shown first) sources
setup-ssh.sh. setup-ssh.shbusy-loops the servers until all of them have a control socket setup. Thehostsfile simply lists the server hostnames one per line.- Since the configuration specifying the control socket is only in
${CONFIG_DIR}/scripts/.ssh-config, unless I specify that file using-F, SSH connections won’t use it. So this allows me to use the control socket only where I need them using theFoption. - The setup script also copies the test execution script to the servers. The execution script itself contains a bunch of commands, and because I copied the execution script, I don’t have to worry about an additional layer of quoting for SSH (and the additional cognitive overhead for figuring out what gets expanded when).
- Then the main script uses
xargsto distribute the workload over the servers, by starting new jobs as soon as running ones end.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0