【自動化】Pythonで複数機器に連続リモートSSH接続【pexpect】
・大量のルーターやサーバーをまとめて自動設定したい。
・一人で沢山のNW機器を管理するのは面倒すぎる。
・テラタームマクロよりも自由なスクリプトが使いたい。
インフラエンジニアとして、こんな悩みにぶつかったことが何度もありました。
サービス規模の拡大につれて、ルーターやサーバーの台数も多くなります。
Pythonによる作業自動化なら、自由に処理内容が記述できて便利です。
本記事では、複数端末に対するSSH接続作業をPythonで自動化する方法をまとめました。
目次
ご注意事項
動作環境
- 本記事における自動SSH接続プログラムの実行環境は、LinuxOS上での動作を前提としています。
- WindowsOS上では基本的に動作しません。
- WindowsOSで実行する場合は、「WSL」や「VitualBox」や「VMwareWorkstation」などを用いて、LinuxOS環境をご用意下さい。
前提知識
前提知識として、「Pythonのpexpectライブラリの基礎知識」と「SSH接続自動化の基本」について、以下の記事をご覧ください。
複数機器に自動連続SSH接続
本記事では、「pexpectライブラリとループ処理で複数機器に自動でSSH接続し、対話的にコマンドを投入するプログラム」を作成します。
プログラム名は「ssh_loop_multi_node.py」とします。
フローチャート
処理内容をまとめたフローチャートは以下の様になります。
分かりやすい様に、関数定義部分とループ処理部分の2つに分けました。
フローチャート①で定義した「ssh_work」関数をフローチャート②のループ内で呼び出して、目的の作業を繰り返し実行する流れです。
コード内容をまとめたフローチャートは以下の様になります。
なお、出力表示に関する記述は、煩雑さを避けるために敢えて割愛しています。
フローチャート②では「ループ」と「定義済み処理」の記号を使用。
この2つを利用することで、複数機器に対する連続自動SSHログインを効率良く実現できます。
コード内容
フローチャートを元に、ssh_loop_multi_node.pyのコードを記述します。
# pexpectライブラリからpxsshモジュールをインポート
from pexpect import pxssh
# timeモジュールをインポート
import time
# 作業内容をssh_work関数として定義
def ssh_work(ip_address, username, password):
# ログイン情報を設定しSSHサーバーにログイン
ssh = pxssh.pxssh()
ssh.login(ip_address, username, password)
print(ssh.after.decode(encoding='utf-8'), flush=True)
# ログイン先のグローバルIPを表示
ssh.sendline("curl inet-ip.info")
ssh.expect(r"\[.*\]\$ ")
print(ssh.before.decode(encoding='utf-8'), flush=True)
print(ssh.after.decode(encoding='utf-8'), flush=True)
time.sleep(1)
# ログイン先のプライベートIPを表示
ssh.sendline("ip -4 a")
ssh.expect(r"\[.*\]\$ ")
print(ssh.before.decode(encoding='utf-8'), flush=True)
print(ssh.after.decode(encoding='utf-8'), flush=True)
time.sleep(1)
# ログイン先のユーザー情報を表示
ssh.sendline("id")
ssh.expect(r"\[.*\]\$ ")
print(ssh.before.decode(encoding='utf-8'), flush=True)
print(ssh.after.decode(encoding='utf-8'), flush=True)
time.sleep(1)
# テキストファイル「test.txt」を作成
ssh.sendline("touch test.txt")
ssh.expect(r"\[.*\]\$ ")
print(ssh.before.decode(encoding='utf-8'), flush=True)
print(ssh.after.decode(encoding='utf-8'), flush=True)
time.sleep(1)
# カレントディレクトリのファイルを表示
ssh.sendline("ls -l")
ssh.expect(r"\[.*\]\$ ")
print(ssh.before.decode(encoding='utf-8'), flush=True)
print(ssh.after.decode(encoding='utf-8'), flush=True)
time.sleep(1)
# SSHサーバーからログアウト
ssh.logout()
print("Logged out \n")
# 「各リストからのログイン情報読み込み」と「ssh_work関数の実行」をループ処理
with open('ip_list.txt') as ip_list, \
open('user_list.txt') as user_list, \
open('pw_list.txt') as pw_list:
for ip, user, pw in zip(ip_list, user_list, pw_list):
ssh_work(ip, user, pw)
コード内容の解説
次に、具体的なコード内容を解説していきます。
def ssh_work(ip_address, username, password):
まず始めに、作業内容をssh_work関数として定義します。
仮引数にはSSH接続先のログイン情報として、以下の3つを指定しています。
- ip_address
- username
- password
実引数や具体的なデータの参照元は55行目以降で指定しており、詳細は後述します。
ssh.login(ip_address, username, password)
ssh_work関数から実引数で渡されるログイン情報を用いて、作業対象のSSHサーバーに接続します。
ssh.sendline(“curl inet-ip.info")
「curl inet-ip.info」は「ノード自身のグローバル IP アドレス」を表示させるコマンドです。
「ssh.sendline(“curl inet-ip.info")」により、「現在SSHで接続しているノードのグローバルIP」を表示させるコマンドを送信しています。
これは、接続中の作業対象のグローバルIPに間違いないかを確認する意図となります。
※プライベートIPで接続する場合は特段不要なコマンドです。
ssh.sendline(“ip -4 a")
「ip -4 a」コマンドにより、SSHで接続したノードのプライベートIPを表示させます。
「ip a」だけでは表示される情報がやや多いので、「-4」オプションを付加してIPv4の情報だけを表示させます。
これは、接続中の作業対象のプライベートIPが間違いないかを確認する意図となります。
※グローバルIPで接続する場合は特段不要なコマンドです。
ssh.sendline(“id")
コマンドを実行したユーザーのIDやユーザー名を表示します。
接続中のユーザーが間違いないかを確認する意図となります。
with open('ip_list.txt’) as ip_list, \
open('user_list.txt’) as user_list, ¥
open('pw_list.txt’) as pw_list:
※「¥」はバックスラッシュと読み替え下さい。
ログイン情報を3つのテキストファイルから読み込むため、with構文とopen関数を使います。
55行目でいうと、openで「ip_list.txt」ファイルを開き、asでそのファイルを「ip_list」という名前で変数として使えるようにしています。
with構文により、本来必要なファイルを閉じる処理の記述が不要となるので、不用意なエラーが回避しつつコードの簡略化が可能です。
カンマとバックスラッシュで区切ることで、複数のファイルを同時並行でopenできます。
なお事前準備として、上記コードのカッコ内の3ファイルをPythonの実行ファイルと同じディレクトリに作成して下さい。
各ファイルには、作業対象の各ログイン情報が同じ行で対応するように、IPとユーザー名とパスワードを記述します。
ip_list.txtには、各SSHサーバーのIPアドレスを記述します。複数台の作業ということで、IPアドレスは2つです。
ここでは伏字としますが、グローバルIPという想定です。実際には正しいIPアドレスを記述してください。
x.x.x.x
y.y.y.y
user_list.txtには各SSHサーバーのユーザー名を記述します。
もしSSHサーバー側でユーザーが未作成の場合は、事前に作成しておきましょう。
ここでは仮にtestuser1とtestuser2としていますが、実際には正しいユーザー名を記述してください。
testuser1
testuser2
pw_list.txtには各SSHサーバーのパスワードを記述します。
ここでは伏字としますが、実際には正しいパスワードを記述して下さい。
XXXXXXXX
YYYYYYYY
for ip, user, pw in zip(ip_list, user_list, pw_list):
前項の処理で変数化された3つのログイン情報をfor文で1つずつ取り出します。
複数の変数を1行のfor文でまとめて取得するために、zip関数を使います。
zip()のカッコ内の3つの要素から、ipとuserとpwを1セット取り出すようなイメージです。
各テキストファイルの上から順番に1セットずつ取り出すので、接続先の機器間でログイン情報の食い違い等は発生しません。
ssh_work(ip, user, pw)
7行目で定義したssh_work関数をfor文のブロックの中で実行してループさせます。
ループは処理は、3つのテキストファイルの最後の行を読み込むまで実行されます。
ssh_work関数のカッコ内には実引数を設定します。
実引数には、直前のfor文で取り出したログイン情報のセットである、以下の3つの変数を設定してください。
- ip
- user
- pw
この3つの実引数が、プログラム7行目の仮引数に渡され、プログラム11行目のlogin関数でSSH接続が実施されます。
プログラムの実行結果
「ssh_loop_multi_node.py」 を実行すると、以下のような出力が返ります。
[PEXPECT]$
curl inet-ip.info
x.x.x.x #グローバルIPが表示
[PEXPECT]$
ip -4 a
1: lo:~ #プライベートIPが表示
2: eth0:~ #プライベートIPが表示
[PEXPECT]$
id
uid=1003(testuser1) gid=1003(testuser1) groups=1003(testuser1) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[PEXPECT]$
touch test.txt
[PEXPECT]$
ls -l
total 0
-rw-rw-r--. 1 testuser1 testuser1 0 Dec 6 13:51 test.txt
[PEXPECT]$
Logged out
[PEXPECT]$
curl inet-ip.info
y.y.y.y
[PEXPECT]$
ip -4 a
1: lo: #中略
2: eth0: #中略
[PEXPECT]$
id
uid=1001(testuser2) gid=1001(testuser2) groups=1001(testuser2) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[PEXPECT]$
touch test.txt
[PEXPECT]$
ls -l
total 0
-rw-rw-r--. 1 testuser2 testuser2 0 Dec 6 13:51 test.txt
[PEXPECT]$
Logged out
まず、グローバルIPがx.x.x.xのtestuser1のサーバーにログインできていることが、各確認コマンドから分かります。
テキストファイルの作成が、testuser1を所有者として出来ていることも確認できます。
次に、グローバルIPがy.y.y.yのtestuser2のサーバーにログインできています。
こちらも同様に、testuser2を所有者としてテキストファイルの作成が出来ています。
目的とする動作が問題なく出来ていることが確認出来ました。
実務では、作業を要するノードの数だけ、ログイン情報のテキストファイルに情報を記載して下さい。
作業内容は多岐に渡ると推察されますが、pexpectの各種関数を組み合わせれば大抵の処理は可能です。
もし行き詰まったら…
この記事では、Pythonを使っての複数ノードに対する自動ログインと設定投入の方法を解説しました。
ここまでの解説で、ご期待通りの処理は実現されましたでしょうか?
もし、この記事が一助になれたとしたら幸いです。
しかしながら、現場の業務要件やシステムは複雑で千差万別なもの。
どこかで見聞きした内容を、そっくりそのまま転用できるとは限りません。
それらしい情報をネットや書籍からかき集めて、思いつく限りの方法で散々試行錯誤したとしても、期待通りに動かず行き詰まったことはありませんか?
私も最初からスムーズに動かせたことは滅多に無く、トライアンドエラーを延々と繰り返してようやく形にできています。
では、何度試してもうまくいかず心が折れそうな時に、どうすれば最後まで諦めずに完遂できるのでしょうか?
そこで役立ったものの一つが、以下の一冊です。
「ですから、目標達成のために、まずするべきことは『意志力には限りがある』という事実を受け入れることです。」
-「やり抜く人の9つの習慣 コロンビア大学の成功の科学」第8章より引用-
この一文にはハッとさせられました。
本書によると、多くの人は自分の意志力を過大評価しており、「嫌なことから逃げて楽がしたい」という誘惑に自力で勝てると思い込んでいます。
確かに自分も、何かが上手くいかないと、いつの間にかテレビやネットやゲームに逃げていたことは数えきれないほどあります。
成功する人は、逃げ道や誘惑をなるべく排除することで、目標に集中できる「環境」を作る努力をしているのです。
本書で得た大きな知見は、意志力とは消耗することもあれば強化もできるため、その分コントロールも可能であるということ。
この概念が、目標に対する取り組み方をより楽にさせてくれました。
もし現在行き詰まっている方は、本書で何かしら打開のヒントを得ることが出来ます。
なお本記事に関するご質問やご意見等は、下記のコメント欄かTwitterのDMをご利用ください。