ようこそゲストさん

Super Neurotic Junction

2009/12/24(木) rubyで複数のrsyncを、パスワード入力とともに実行。最後にcrontabに登録

2009/12/24 23:27 研究課題snjx
なんかすげーひさびさの更新が、こんな味も素っ気もない記事ですな。
…最近なんかイベントがあったはずなんだけど、よくわかりませんな。中止になったんじゃないですか?

外部コマンドを実行するとパスワード入力を求められちゃう

さて、ある事情があって、異なるサーバ間でデータの同期を取る必要がありいろいろ細工してみた。
rsyncコマンドで対象ファイルを指定すればいいんだけど、その際に厄介なことは、パスワードの入力が求められること。
一回限りだったらそれでも別にいいんだけど、できればcronに突っ込んで1時間ごとに実行したい。
rsyncコマンドを実行する際にパスワード入力をさせない方法は幾つかあるようだけど、どれも面倒そう。こういう運用は2ヶ月ほどの予定で、それ以後は同期元のサーバは停止するので、わざわざrsyncデーモンを走らせるとかの処置は、やってもいいけどちょっとモチベーションがわかない。
本格的なバックアップサーバを用意するわけではないので、このままrubyで外部コマンドを実行する簡単なスクリプトを組んでおきたい。

で、スクリプトを組む上でのポイントは
  • 複数のrsyncコマンドを実行する
  • パスワードを入力する
  • 成功失敗に関わらず、実行結果をログに出す
  • ひとつのコマンドが失敗しても次のコマンドを実施する
ほんとうだったらここで、スクリプト中のパスワードも隠蔽したいけど、まあ、同期先も元も俺ひとりが管理しているし、コードの中に平文でもいいか。
こういう対話的な処理をrubyで実現する場合、ptyとexpectを利用するのが定番らしい。

rsyncの動き方

その前にrsyncコマンドの動き方をちょびっと調べてみた。

まず、正常系。
[root@vps ~]# rsync -avz -e ssh /hogehoge/hagehage1 root@newserver:/hogehoge/hagehage1
root@newserver's password: 
building file list ... done
hagehage1

sent 93 bytes  received 42 bytes  24.55 bytes/sec
total size is 0  speedup is 0.00
なるほど、「password:」と表示されたらパスワードを入力し、「done」と来たら正常に終わるのね。

次、異常系。
[root@vps ~]# rsync -avz -e ssh --timeout=10 /hogehoge/hagehage1 root@newserver:/hogehoge/hagehage1
io timeout after 10 seconds -- exiting
rsync error: timeout in data send/receive (code 30) at io.c(171) [sender=2.6.8]
つまり、「rsync error」が来たら何かエラーが来てるってことね。その続きの文字列を取得できれば、エラーの内容もわかるわけね。それで、パスワード入力を求められる前にエラーが来ることもあるってことね。
これを、同期をとりたいファイル/ディレクトリの数だけ実施するようにすればいいわけか。

こんなん出ましたけど

んで、でっち上げたのが以下のコード。
たぶん、似たような需要は結構あるような気がするので、ここに掲載する。
#!/usr/bin/ruby
require 'pty'
require 'expect'
require 'syslog'

# sshで暗号化して、タイムアウトを10秒設定で3箇所の同期をとります。
rsynccmd = [
"rsync -avz -e ssh --timeout=10 /hogehoge/hagehage1 root@newserver:/hogehoge/hagehage1",
"rsync -avz -e ssh --timeout=10 /hogehoge/dir1/ root@newserver:/hogehoge/dir1/",
"rsync -avz -e ssh --timeout=10 /foo/bar root@newserver:/foo/bar" 
]

# 実行結果をsyslogに出します。
syslog = Syslog.open("rsyncdata",Syslog::LOG_NDELAY )

rsynccmd.each {|cmdstr|
        warnmsg = nil # 警告メッセージ初期化
        begin
                PTY.spawn(cmdstr) do |r,w| # rsyncコマンドを実行 r にrsyncからの出力。 w にrsyncへの入力。
                        w.sync = true # 入力と同時にflushしなさいというおまじない
                        # まずrsyncコマンド出力、最初の処理。
                        # 基本的には最初にパスワードの入力を求められるけど、場合によってはエラーが帰って来ることもあり得るので
                        # 「password:」か「rsync erro」を待つ。
                        r.expect(/password:|rsync error/,10){ |line| 
                                # このブロックは、「password:」「rsync erro」という出力があった場合に実行される。
                                # それか、タイムアウト(10秒)が来たらlineにnilが入った状態で呼び出される。ちなみに
                                # line==「password:」正常な処理であれば、これが来るはず。
                                # line==「rsync erro」同期先が落ちてるとかだと、これが来るかも。
                                # line==nil nilだとタイムアウトだけどこの例に限って、ここではタイムアウトはこないはず。
                                if line.to_s.include?("password") then
                                        w.puts "newpassword" # rsyncコマンドへパスワード入力
                                else
                                        # こっちに来るときは rsync error に引っかかっているはずなので
                                        warnmsg = r.gets # rsyncでエラーが帰ってきたら続きの出力を保持。エラーメッセージ突っ込んでおく
                                end
                        }
                        r.expect(/rsync error|done/,10) { |line|# エラーか終了のメッセージを待つ
                                # パスワード入力後の処理を待つ。
                                # パスワードが無事入力できたら、あとはrsyncがうまく終了するか何かでエラーを返すかどっちか。
                                # なので、「rsync error」「done」を待っていればいい。
                                if line.to_s.include?("rsync error") then
                                        warnmsg = r.gets # rsyncでエラーが帰ってきたらエラーメッセージ突っ込んでおく
                                else
                                        # doneで受けている場合は処理が成功したとみなす。
                                        warnmsg = nil
                                end
                        }
                end
        rescue PTY::ChildExited => e # PTYでキックしたプロセスが終了したときの処理
                syslog.warning("PTY::ChildExited[" + e.status + "]")
                exit 1
        rescue => e # それ以外のエラーの処理。まぁsyslogに記録するだけだけど。
                syslog.warning(e.class.to_s + "[" + e.message + "]")
                exit 2
        end
        
        # 実行結果をsyslogに記録
        if warnmsg == nil then
                syslog.info("OK[" + cmdstr + "]")
        else
                syslog.warning("NG[" + cmdstr + "]")
                syslog.warning("NG[" + warnmsg +"]")
                warnmsg = nil
        end
}
exit 0

1: snjx URL 2016年01月14日(木) 午前10時09分

参考までに

2: snjx 2016年01月14日(木) 午前10時09分

↑URLを参照のこと


名前:  非公開コメント   

E-Mail(任意/非公開):
URL(任意):
  • TB-URL  http://snjx.info/diary/snjx/036/tb/