同一ネットワーク上の複数ホストに対して,一斉実行命令を出す(on python3 paramiko)


私が普段学校で実験を行う際に,複数のホストで一斉にプログラムを動かしたいときが有ります.
人手があれば,1人1台張り付いて実行すれば良いですが,深夜に実験を回すこともあるので,1人で複数ホストに対して一斉に実行命令を出すことができれば効率が上がると考え,実装しました.
同様の環境を構築したい方は,参考にしていただけると幸いです.

実行環境と使用ツールのインストールなど

命令を送るPC:Debian11(test)
命令を受け取るホスト:ラズパイ(Debian)
今回はラズパイに向けて実行してますが,PCでも問題ないです.

命令を送るPCのPythonのバージョン
Python 3.9.2

環境構築に向けて,インストールしなければいけないものがあります.
それはparamikoというパッケージ?モジュール?です.

$ aptitude search paramiko
i   python3-paramiko      - Make ssh v2 connections (Python 3) 

もしくは

$ sudo apt-get install python3-paramiko -y

もしくは

$ pip3 search paramiko
# - - - - - - - - - - - - - - - - - - #
# pip3 search はうまく実行されないという記事が
# 投稿されているので,
# 素直にapt or aptitude で入れるのが
# スマートでしょう.
# - - - - - - - - - - - - - - - - - - #

プログラムを動かす前の準備

今回私が作成したプログラムは,同一ディレクトリ内に,host.txtというテキストファイルをおいておきます.
その内容は,記載してあるホスト名を読み込み,通信を行います.
host.txtは以下のようになっています.(一例)

host.txt
host1
host2
#host3
#host4
host5
#host6

上記のようにホスト名を記載します.
先頭に#が付いているホストはプログラム内部では通信対象ホストから外れます.
いちいち消して,通信するときに書いてもいいですが,めんどいのでコメントアウト的な感じにしておきました.
リモートホストの場合は不具合などで通信対象から外れることもあるかと思いますので,そのような仕様にしています.

host.txtに記載してあるホスト名は,/etc/hostsに記載しておいてください.
そうしないと,ホスト名がわかりませんというエラー吐きます.

体感速度ですが,動作時間は非常に短いので,host.txtに記載する順番は大きく影響しませんが,順番通りに書いたほうがわかりやすいと思います.

使用方法

host.txtに書いてあるホストに対して,forループで順番にコマンドを送りつけます.

paramiko_sample.py
#!/usr/bin/python3

import paramiko
import os
import time

user = "username"        # sshログイン時のユーザー名
passwd = "PASSWD"    # sshログイン時のパスワード
command = "python3 /home/username/paramiko/sample.py"
#commandには "ls" や "pwd" などターミナル上で打ち込むコマンドも使えます.
host_name = []     #

with open("host.txt") as f:
    s = f.readlines()

for i in range(len(s)):
    if s[i].startswith('#'):
        pass # startswithは先頭が#で始まるかどうかを判定しています.
    else:
        host_name.append(s[i].replace("\n",''))

for i in range(len(host_name)):
    target_host = host_name[i]
    with paramiko.SSHClient() as ssh:
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        # 初回ログイン時の(yes/no)?ってやつを飛ばしてくれます.非常に便利
        ssh.connect(target_host,username=user,password=passwd)
        # ここでリモートホストに対して接続を行います.
        stdin,stdout,stderr = ssh.exec_command(command)
        # ここで,コマンドを送りつけます.
        # stdin  -> 標準入力
        # stdout -> 標準出力
        # stderr -> エラー表示

これであらかたの説明は終わりました.
最後にちょこっと諸注意を述べておきます.

諸注意

勘の良い方は気づいたかと思いますが,commandのファイル名を絶対パスにしています.
それはなぜかといいますと,paramikoでログインした際の自分の居場所?(pwdコマンド)は/home/usernameにいます.
ホームディレクトリでプログラム量産している方は少数派だと思います.
必ず,どこかのディレクトリ内部にプログラムなどなどが配置されていると思います.
なので,絶対パスで記載してください.
確実にエラー吐きます.
file not found系のエラー出てきますよ.

更に感のいい人は気づいたかな?

リモートホストPCで実行する以下のプログラム
command = "python3 /home/username/paramiko/sample.py"
sample.pyの内部で,ファイルを読み書きするなど,ファイルパスを記載している方は更に注意が必要です.

仮にsample.pyの内部が以下のような内容だったとしましょう.

sample.py
FILE="./dir1/hogehoge.txt"
with open(FILE) as f:
    s = f.readlines()

通常のsshログインで動かす場合は,以下のようにcdコマンドでディレクトリを移動するので問題ありません

$ cd paramiko
$ pwd
/home/username/paramiko
$python3 sample.py

ですが,これがparamikoを使っての遠隔だと,cdコマンドを入力してないので,
自分の居場所はホームディレクトリです.
そこで,sample.pyを実行したところで,ホームディレクトリ下にdir1というディレクトリが存在しないのです.
もちろん実行できません.
なので,ファイルのパスを記載する場合は,必ず,絶対パスで記載したほうが,読み書き関係でエラーはなくなると思います.