SSH Remote Control

Note

Last Updated on 2024-03-27

Read time: 13 minutes (3286 words)

As part of my research work, I maintain several small clusters.

Cluster Systems

Processor

Num

Cores

Intel-I5

4

8

Pi 3

4

16

Pi 4

7

14

Pi 5

4

20

That is a lot of machines, fortunately most of them were fairly cheap and acquired over several years of teaching. My problem is managing the setup and code on all of those critters!

All of these systems run some form of Linux. I have all set up for SSH access with no password required. My intent is to use these clusters as test beds, to experiment with parallel processing techniques. Logging in to a console on each machine using SSH is simple enough, but repeatedly login in and running commands on a bunch of machines is painful. I do use the Ansible tool to manage basic configurations on all of these machines, which significantly improves my life.

Developing for a cluster

In most cases the code that runs on a cluster is written in some high level language like C++ or modern Fortran. I use a MacBook Air as my primary laptop, and it is all set up to produce documents (like this one) and build Python applications. My goal is to use Python to create a simple automation technique that lets me send a list of commands to one or more machines and collect the console output for display on my management laptop. That output will be included in my documentation using the Sphine programoutput extension.

The inspiration for this experiment is Ansible, but I want to have more control over the command sequence and the collection of output from a remote task.

Time to improve the process.

Test Setup

I have a simple Raspberry Pi 3 running on the top of my office bookcase. That machine has been running for over a year and can be accessed using a Wifi connection.

In this example, I have set up an alias for the machine (mine is picam) that dets up the remote machine IP address. I have also added my SSH public key to the known_hosts list on the Pi. This allows logging in with no password.

Once all of this has been set up, this should work:

$ ssh picam 'uptime'
 18:28:46 up 247 days,  4:43,  2 users,  load average: 0.91, 0.91, 0.91

SSH supports running commands from a text file. Here is an example command file:

$ cat ex2.txt
uname -a
df -H
uptime

The normal SSH command to run this locally is:

ssh picam < commands.txt

Unfortunately, the Sphinx extension I use to include program output in my documents cannot handle file redirection like this. We could transfer the command file to the Pi and run it there but we would miss the individual command output results. A solution (maybe not the best) is to create a simple local Python program that reads our command file and runs the SSH commands for us. (Python is great for creating simple automation tasks like this (see Weigart [Wei07]).

Here is a basic Python snippet using this approach:

1import subprocess
2import sys
3
4subprocess.run(['ssh','picam', 'uptime'])

And here is the program output:

$ python code/ex3.py
 18:28:46 up 247 days,  4:43,  2 users,  load average: 0.91, 0.91, 0.91

This works! We now need to wrap this up in a loop that reads a line from the command file and creates the list of command parts. Python is up to this job!

1uname -a
2uptime
3df -H
4
$ python code/ex4.py code/ex1.txt
Darwin MBair-M1 23.4.0 Darwin Kernel Version 23.4.0: Fri Mar 15 00:12:41 PDT 2024; root:xnu-10063.101.17~1/RELEASE_ARM64_T8103 arm64
18:28  up  2:57, 2 users, load averages: 1.45 1.49 1.46
Filesystem        Size    Used   Avail Capacity iused ifree %iused  Mounted on
/dev/disk3s1s1    494G     10G    382G     3%    404k  3.7G    0%   /
devfs             203k    203k      0B   100%     686     0  100%   /dev
/dev/disk3s6      494G   2148M    382G     1%       2  3.7G    0%   /System/Volumes/VM
/dev/disk3s2      494G   6210M    382G     2%    1.4k  3.7G    0%   /System/Volumes/Preboot
/dev/disk3s4      494G     20M    382G     1%      72  3.7G    0%   /System/Volumes/Update
/dev/disk1s2      524M   6312k    506M     2%       3  4.9M    0%   /System/Volumes/xarts
/dev/disk1s1      524M   6459k    506M     2%      31  4.9M    0%   /System/Volumes/iSCPreboot
/dev/disk1s3      524M    549k    506M     1%      39  4.9M    0%   /System/Volumes/Hardware
/dev/disk3s5      494G     92G    382G    20%    1.4M  3.7G    0%   /System/Volumes/Data
map auto_home       0B      0B      0B   100%       0     0     -   /System/Volumes/Data/home
******************************
******************************
******************************

Generating this code was not too difficult, considering the need to read the command line, make sure the lines were not blank, then creating a list of final command parts to be proceeded by the subprocess.run code.

However, the output displayed here does not currently match what I see when running this code locally. It appears that output buffering by the Sphinx extension is not dealing with this well. A solution is to generate the output lines in the Python code and dump that output at the end of the code.

1uname -a
2uptime
3df -H
4