Pure Ruby な SSH クライアントライブラリ「Net::SSH」
本日は Ruby スクリプトから簡単にリモートホストのコマンドを実行できる Net::SSH ライブラリをご紹介します。完全に Ruby で書かれた SSH クライアントで、単純な SSH 接続はもちろんのこと、公開鍵認証やポートフォワーディングなど、 SSH のほとんどの機能が利用できる、優れたライブラリです。
とくにデータセンターにサーバーを設置している場合、ほとんどの管理作業は SSH 経由になると思います。 Net::SSH は、それらのタスクの自動化に最適なライブラリです。ぜひ活用して、面倒な管理作業の効率化に役立ててください!
インストール
まずはライブラリのインストールです。 Gem を使って簡単にインストールできます。
gem install net-ssh
動作には OpenSSL のライブラリと Ruby バインドが必要なので、それらはあらかじめインストールしておいてください。もっとも、たいていのシステムではデフォルトで入っていると思いますが。
使ってみる
それでは、さっそく使ってみましょう。もっとも基本的な使い方は、 Net::SSH.start メソッドでコネクションを確立して exec! メソッドでコマンドを実行するというものです。
require 'rubygems' require 'net/ssh' Net::SSH.start('host', 'user', :password => 'password') do |ssh| print(ssh.exec!('ls ~')) end
なんと、これだけでスクリプトからリモートコマンドを実行できるのです! exec! メソッドの返り値は標準出力と標準エラー出力をミックスした文字列になりますので、上記のコードを実行すると、リモートホストのホームディレクトリの内容が表示されます。
このほかにも多彩な指定が可能ですので、詳細はリファレンスをご参照ください。以降では、代表的な機能(というか、私が試したもの^^;)の使い方をご紹介します。
公開鍵認証を使う
start メソッドの :keys パラメータに OpenSSH で生成した秘密鍵のファイル名を配列で指定することで、公開鍵認証が使えます。 OpenSSH による秘密鍵の生成方法はこちらの記事をご参照ください。秘密鍵にパスフレーズが設定されている場合はプロンプトを表示して入力を促しますが、 :passphrase パラメータに指定することも可能です。
Net::SSH.start('host', 'user', :keys => ['/path/to/private_key'], :passphrase => 'pass') do |ssh| # ... end
標準出力と標準エラー出力を別々に取得
標準出力と標準エラー出力がミックスされているのは不都合な場合もあるでしょう。そんなときは exec! メソッドにブロックを渡すことで、それらを別々に取得できます。
stdout = '' stderr = '' ssh.exec!('ls ~') do |channel, stream, data| if stream == :stdout stdout += data elsif stream == :stderr stderr += data end end print "==== 標準出力 ====¥n" print stdout + "¥n" print "==== 標準エラー出力 ====¥n" print stderr + "¥n"
標準入力にデータを流し込む
これはちょっと面倒です。新しいチャンネルを開いて、 send_data メソッドでデータを送信します。
ssh.open_channel do |channel| channel.exec('cat') do |ch, success| raise 'コマンドが実行できません。' unless success channel.on_data do |ch, data| puts data end channel.on_process do |ch| channel.eof! if channel.output.empty? end channel.send_data("標準入力に送信するデータ\n") end end ssh.loop
いきなり exec! 以外のメソッドが多数でてきたので、簡単に説明しておきますね。私もあまり詳しくないのですが、 SSH プロトコルではサーバーとクライアントの間に複数のチャンネルを設けることが可能で、それぞれのチャンネルでコマンドを同時に実行できます。これを正しくハンドリングするため、 Net::SSH では以下の手順で処理を行います。
- 新しいチャンネルを開く(open_channel メソッド)
- コマンドを実行する(exec メソッド)
- チャンネルを通して標準出力などのデータをやり取りしつつ(on_data, on_process などのコールバック)、実行が終了するまで待つ(loop メソッド)
- チャンネルを閉じる(自動で処理されます)
これまで使っていた exec! コマンドは、このプロセスをカプセル化したものだったわけですね。しかし、 exec! では標準入力を扱うことができないため、上記のコードではそれらを明示的に呼び出しているのです。なお、コールバックは他にもいくつかあるので、詳細はこちらのページをご参照ください。
ちなみに、本来は send_data の直後に eof! を呼ぶのが自然な気がするのですが、なぜか eof! を呼んだとたんにチャンネルが閉じられてしまいます(バグ?)。仕方がないので、 on_process 内で出力バッファが空になったのを確認して eof! を呼ぶようにしました。正しい方法かどうか分かりませんが、 Net::SSH のソースを覗いてみた限り、これが最も無難だと思います。
ポートフォワーディングを利用する
なんと、 Net::SSH はポートフォワーディングを処理することも可能です。以下は localhost:8080 を webos-goodies.jp:80 にフォワードする例です。
Net::SSH.start(...) do |ssh| ssh.forward.local(8080, 'webos-goodies.jp', 80) # ポートフォワーディングを使った処理 end
ポートフォワードは start メソッドのブロックを出るまで有効です。 Firewall の内側にあるデータベースサーバーの内容を定期的にバックアップなんてことも、これなら簡単ですね。
Tips
- 環境変数の設定
- リモート側の環境変数を設定する機能も存在するようですが、セキュリティー上の理由でほとんどの SSH サーバーでは無効にされているようです。代わりに export コマンドを使って exec!("export PATH=$PATH:~/bin ; myscript") のようにするのが良いと思います。
- 複数のコマンドの順次実行
- 前述の「標準入力にデータを流し込む」のように明示的にチャンネルを開いてコマンドを実行する場合は、一回のコマンド実行ごとに open_channel を実行する必要があります。ひとつの open_channel のブロック中で複数回 exec を呼ぶことはできないようです。
- open_channel をブロックなしで呼び出す
- open_channel はブロックを使って呼び出したほうが無難です。ブロックなしで呼び出した場合は接続が確立するのを待たずに戻るので、 exec などを呼び出しても失敗します。
以上、本日は Ruby の Net::SSH ライブラリをご紹介しました。こんなに簡単に SSH が使えるなんて、素晴らしいですね。このほかにも、 SCP や SFTP などの関連ライブラリが同じ作者さんにより公開されていて、ファイル転送なども自由自在。なんと open-uri で SCP が使える等、いずれも非常によくできています。 Ruby 使いの方は、ぜひお試しください!
詳しくはこちらの記事をどうぞ!
この記事にコメントする