本記事では、Pythonでssh接続を行い、コマンド実行をすることができるライブラリである、paramikoの使い方について解説します。
解説例では、AWSのCentOS(Linux)にSSH接続し、作業対象ディレクトリに移動、ファイル一覧を取得する処理を紹介しています。
また、遠隔サーバーにあるDockerをコマンドで操作するために必要なオプションや、Exception ignored inエラーの対処法についても記載しています。
paramikoとは
PythonでSSH接続するためのライブライのことです。公式:paramiko.org
SSHとは、ネットワークを経由して他のコンピュータに接続し、遠隔操作をする仕組み(プロトコル)のことです。
PythonのSSHライブライには、他にもpxssh(Pexpect)、fabric、sshtunelなどがあります。
ssh接続先で対話式のコマンドを実行したい場合はpxssh(Pexpect)を使います。
ただ、対話式のコマンドを実行しないのであれば、個人的にはparamikoの方が使いやすいと思います。
paramikoのインストール
以下コマンドを実行することで、paramikoをインストールすることができます。
pip install paramiko
paramikoの使い方
下記はAWSのCentOS7にSSH接続し、作業対象ディレクトリに移動 → lsでファイル一覧を取得するコードです。
コードの詳細について、順番に説明していきます。
# ----------------------------------------------------------
# サーバーへの接続情報を設定
IP_ADDRESS = '51.61.211.166'
USER_NAME = 'centos'
KEY_FILENAME = '/Users/macuser/.ssh/aws.pem'
# サーバー上で実行するコマンドを設定
CMD = 'cd /home/user1/dir1 ; ls -l'
# ----------------------------------------------------------
# paramikoのインポート
import paramiko
# sshクライアントの作成
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.WarningPolicy()) # またはparamiko.AutoAddPolicy()
# 上記で設定したIPアドレス、ユーザー名、キーファイルを渡す
client.connect(IP_ADDRESS,
username=USER_NAME,
key_filename=KEY_FILENAME,
timeout=5.0)
# コマンドの実行
stdin, stdout, stderr = client.exec_command(CMD)
# コマンド実行結果を変数に格納
cmd_result = ''
for line in stdout:
cmd_result += line
# 実行結果を出力
print(cmd_result)
# ssh接続断
client.close()
del client, stdin, stdout, stderr
total 0
-rw-rw-r--. 1 centos centos 0 May 17 21:44 hoge
-rw-rw-r--. 1 centos centos 0 May 17 21:44 hoge1
-rw-rw-r--. 1 centos centos 0 May 17 21:45 hoge2
コード解説
上記で記述したコードの内容について解説します。
# ----------------------------------------------------------
# サーバーへの接続情報を設定
IP_ADDRESS = '51.61.211.166'
USER_NAME = 'centos'
KEY_FILENAME = '/Users/macuser/.ssh/aws.pem'
# サーバー上で実行するコマンドを設定
CMD = 'cd /home/user1/dir1 ; ls -l'
# ----------------------------------------------------------
この部分でサーバーにssh接続するための情報を記述しています。
IP_ADDRESS
にはサーバーのIPアドレスを設定。
USER_NAME
にはサーバーのユーザー名を設定。
KEY_FILENAME
にはAWSサーバーを作成時に発行されるpemキーを設定します。
pemキーはローカル端末に配置したものを使用します。
ここで記述した接続情報は、後述するconnect()関数で使用します。
CMD
にはサーバーで実行したいコマンドを設定します。
ここでは、サーバーにログインしたあと、dir1ディレクトリに移動し、ls -l コマンドを実行するように設定しています。
# paramikoのインポート
import paramiko
paramikoを使用するために、importを行なっています。
client = paramiko.SSHClient()
sshクライアントを作成している部分。
client.set_missing_host_key_policy(paramiko.WarningPolicy())
初めて接続するサーバー(※)に対しての動作を引数で指定します。
※ホストキーが登録されていないサーバー(参考)
paramiko.WarningPolicy()
: 警告をログに記録してから接続を行う
paramiko.RejectPolicy()
:デフォルト設定。不明なホスト名とキーを自動的に拒否する
paramiko.AutoAddPolicy()
: 新しいホストキーをローカルのHostKeysオブジェクトに自動的に保存して接続を行う
client.connect(IP_ADDRESS,
username=USER_NAME,
key_filename=KEY_FILENAME,
timeout=5.0)
冒頭で設定したIP_ADDRESS、USER_NAME、KEY_FILENAMEをここに渡すことで、サーバーにssh接続を行います。
timeoutはサーバーに接続できなかった場合、何秒で接続処理を中断するかを設定しています。
今回は、timeout=5.0 としており、5秒でタイムアウトするようになっています。
設定しておかないと接続先がなくても処理が中断されないので、設定しておきましょう。
また、パスワードで認証を行いたい場合はclient.connect
の引数にpassword="ログインパスワード"を設定することで行えます。
client.connect
に設定できる引数については後述します。
stdin, stdout, stderr = client.exec_command(CMD)
client.exec_command()の引数にコマンドを設定することで、サーバーに対してコマンドを実行します。
変数CMD には冒頭で設定した 'cd /home/user1/dir1 ; ls -l' が入ります。
client.exec_command(CMD)の実行結果は stdin, stdout, stderr に渡す仕様となっています。参考:Paramiko Client
余談1 - Dockerへの接続
サーバーに接続した後、そのサーバー中のDockerへ接続 → コマンドを実行する場合には、引数にget_pty=Trueを設定する必要があります。
stdin, stdout, stderr = client.exec_command(CMD, get_pty=True)
get_ptyは、サーバーに疑似端末を要求する設定となります。(デフォルトはFalse)
exec_command()に設定できる他の引数についても後述します。
余談2- 踏み台サーバーを経由する
踏み台サーバーにアクセスした後、実際に作業するサーバーにssh接続してコマンドを実行する場合は次のように記述します。
client.connect(<踏み台IP>,
username=<踏み台ユーザー名>,
key_filename=<踏み台キーファイル>,
timeout=5.0)
### 中略 ###
CMD = f'ssh -i ~/.ssh/{作業先キーファイル} {作業先ユーザー名}@{作業先IP}'
stdin, stdout, stderr = ssh.exec_command(CMD, get_pty=True)
stdin.write('ls -l\n')
stdin.write('exit\n')
前提として、踏み台サーバーに作業先サーバーのキーファイルを配置しておく必要があります。
鍵の配置はローカル端末から以下のコマンドを実行することで行えます。
scp -i ~/.ssh/<作業先サーバーのキーファイル> ~/.ssh/<作業先サーバーのキーファイル> <踏み台ユーザー名>@<踏み台_IP>:~/.ssh
ただ、セキュリティ的に踏み台サーバーにキーファイルを置きたく無いという場合は、sshコマンドの-oProxyCommandオプションを使用したシェルスクリプトを作成し、Pythonのsubprocess
モジュールなり手動なりで実行するという方法もあります。(paramikoでもproxyCommandを使用する方法がありますが、私の場合はどうしても上手くいかなかった)
閑話休題
# コマンド実行結果を変数に格納
cmd_result = ''
for line in stdout:
cmd_result += line
コマンドの実行結果は、stdout に格納されます。
この処理では、for文を実行することでその結果を一行ずつ取り出しています。
# 実行結果を出力
print(cmd_result)
ここでコマンドの実行結果を出力しています。
total 0
-rw-rw-r--. 1 centos centos 0 May 17 21:44 hoge
-rw-rw-r--. 1 centos centos 0 May 17 21:44 hoge1
-rw-rw-r--. 1 centos centos 0 May 17 21:45 hoge2
# ssh接続断
client.close()
del client, stdin, stdout, stderr
client.close()でsshの接続を閉じています。
最後の del client, stdin, stdout, stderr でガーベージコレクションの削除を行なっています。
このソースを記述しないと以下のようなエラーが散発的に発生するので記述しておきます。
Exception ignored in: <function BufferedFile.__del__ at 0x7f85eb87c1f0>
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/paramiko/file.py", line 66, in __del__
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/paramiko/channel.py", line 1392, in close
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/paramiko/channel.py", line 991, in shutdown_write
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/paramiko/channel.py", line 967, in shutdown
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/paramiko/transport.py", line 1846, in _send_user_message
AttributeError: 'NoneType' object has no attribute 'time'
上記エラーについての参考: https://github.com/paramiko/paramiko/issues/1078
ソースに関する解説は以上です。
connect()の引数
下記はサーバーにssh接続するために使用するconnect()関数の引数一覧です。
引数名 | 型 | 説明 |
---|---|---|
hostname | str | 接続するサーバーを指定する。 |
port | int | 接続するサーバーのポートを指定する。 |
username | str | 認証するユーザー名を指定する。(デフォルトは現在のローカルユーザー名) |
password | str | パスワード認証に使用する。指定した passphrase がない場合は、秘密鍵の復号化にも使用されます。 |
passphrase | str | 秘密鍵の復号化に使用する。 |
pkey | PKey | 認証に使用するオプションの秘密鍵 。 |
key_filename | str | 認証を試行するオプションの秘密鍵または証明書のファイル名、ファイル名のリストを指定する。 |
timeout | float | TCP接続のタイムアウト(秒単位)を指定する。 |
allow_agent | bool | SSHエージェントへの接続を無効にするにはFalseに設定する。 |
look_for_keys | bool | Falseに設定すると、~/.sshで検出可能な秘密鍵ファイルの検索が無効になります。設定すると認証に失敗した際のエラーメッセージがより正確になるらしい。 |
compress | bool | 圧縮を有効にするにはTrueを設定する。 |
sock | socket | 対象ホストとの通信に使用するオープンソケットまたはソケットのようなオブジェクトを指定する。 |
gss_auth | bool | GSS -API認証を使用する場合はTrueに設定する。 |
gss_kex | bool | GSS -APIキー交換とユーザー認証を実行する場合はTrueに設定する。 |
gss_deleg_creds | bool | GSS -APIクライアントの資格情報を委任するかどうかを指定する。 |
gss_host | str | Kerberosデータベースのターゲット名を指定する。デフォルト:ホスト名 |
gss_trust_dns | bool | 接続されているホストの名前を安全に正規化するためにDNSが信頼されているかどうかを設定します。(デフォルト True)。 |
Banner_timeout | float | SSHバナーが表示されるのを待つ時間を指定する(秒単位)。 |
auth_timeout | float | 認証応答時に待機する時間を指定する(秒単位)。 |
disabled_algorithms | dict | 直接渡されるオプションのdictTransportと同じ名前のそのキーワード引数。 |
exec_command()の引数
下記はサーバーに対してコマンドを実行するための関数である、 exec_command()の引数一覧です。
引数名 | 型 | 説明 |
---|---|---|
command | str | 実行するコマンド |
bufsize | int | Pythonの組み込み関数file()と同じように使用する。 |
timeout | int | コマンドのチャネルタイムアウトを設定する。詳細 |
get_pty | bool | サーバーに疑似端末を要求する(デフォルトではFalse)。詳細 |
environment | dict | リモートでコマンドが実行された際に、デフォルト環境にマージするシェルの環境変数を指定する。 |