▼ 2009/12/24(木) rubyで複数のrsyncを、パスワード入力とともに実行。最後にcrontabに登録
2009/12/24 23:27 【研究課題】
なんかすげーひさびさの更新が、こんな味も素っ気もない記事ですな。
…最近なんかイベントがあったはずなんだけど、よくわかりませんな。中止になったんじゃないですか?
…最近なんかイベントがあったはずなんだけど、よくわかりませんな。中止になったんじゃないですか?
■ 外部コマンドを実行するとパスワード入力を求められちゃう
さて、ある事情があって、異なるサーバ間でデータの同期を取る必要がありいろいろ細工してみた。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
- TB-URL http://snjx.info/diary/adiary.cgi/snjx/036/tb/
1: snjx URL 2016年01月14日(木) 午前10時09分
参考までに
2: snjx 2016年01月14日(木) 午前10時09分
↑URLを参照のこと