cronでメール以外の方法で結果を通知する

cronって便利なんだけど、結果を確認するのがめんどくさい。

メールを全部見るのも今っぽくない。

でもRundeckとか他のジョブスケジューラを使い始めると、実行時に依存するものが増えるのでそれはそれで微妙。

じゃあ、メールで送っている内容と同じようなものを別のプログラムが取得できるか、ということ。

ソースコード確認

最初に考えたのがコマンドからパイプで繋いで最後に別のプログラムが情報をとる、というもの。

標準出力、標準エラー出力は取得可能。 実行したコマンドは、、、?

実行したコマンドはパイプで繋いでる数が少なくて時間がかかればプロセスリストから見つけられるけど、なんか違う。

じゃあCronがメールの件名につけている実行したコマンドはどうやって?ということでソース(とりあえずcronie)をおってみた。

cronie.git - Unnamed repository; edit this file to name it for gitweb.

コマンドを実行してその結果のメールを組み立てる部分。

cronie.git - Unnamed repository; edit this file to name it for gitweb.

entry->cmdという形で取得しているので、内部的にcronのファイルを構造化して、それを実行中に保持しているに過ぎないということ。

ちなみにentrystruct

cronie.git - Unnamed repository; edit this file to name it for gitweb.

外部のコマンドがうまーくやるにはちょっと厳し目か。

crond

次に考えたのがcrondコマンドの実行時オプション。何か良さげなものがないか?

% man crond

途中いっぱい省略するけど、-mオプションで実行可能なコマンドを渡すと、本来メール送信用に組み立てたテキストを、標準入力に渡すよ、という感じ。

OPTIONS -m This option allows you to specify a shell command to use for sending Cron mail output instead of using sendmail(8) This command must accept a fully formatted mail message (with headers) on standard input and send it as a mail message to the recipients specified in the mail headers. Specifying the string off (i.e., crond -m off) will disable the sending of mail.

実験

検証環境

% cat /etc/redhat-release
CentOS Linux release 7.2.1511 (Core)

% ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]

結果を受け取るプログラム

rubyで受け取ってみる。

/tmp/mail.rb

#!/home/admin/.rbenv/shims/ruby
text = STDIN.read
File.open "/tmp/stdin.mail.#{Time.now.to_i}", "w" do |writer|
  writer.puts text
end

とりあえずでこやつには実行権限も付与。

% chmod 775 /tmp/mail.rb

crondの起動時オプション

/etc/sysconfig/crondで起動時のオプションを指定できる。

# Settings for the CRON daemon.
# CRONDARGS= :  any extra command-line startup arguments for crond
CRONDARGS="-m /tmp/mail.rb"

cronのジョブ

とりあえず適当に、標準出力がちょっと長くなりそうなやつを。

/etc/cron.d/my.admin

MAILTO=sugilog@test.example
 * * * * * root /usr/bin/sar -P ALL

実行

まずはcrondを再起動

% systemctl restart crond
% ps aux | grep crond | grep -v grep

root       5948  0.0  0.0 126332  1712 ?        Ss   10:52   0:00 /usr/sbin/crond -n -m /tmp/mail.rb

cronの実行結果を確認してみる。

From: "(Cron Daemon)" <root>
To: sugilog@test.example
Subject: Cron <root@sugilog> /usr/bin/sar -P ALL
Content-Type: text/plain; charset=UTF-8
Auto-Submitted: auto-generated
Precedence: bulk
X-Cron-Env: <XDG_SESSION_ID=70>
X-Cron-Env: <XDG_RUNTIME_DIR=/run/user/0>
X-Cron-Env: <LANG=ja_JP.UTF-8>
X-Cron-Env: <MAILTO=sugilog@test.example>
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <HOME=/root>
X-Cron-Env: <PATH=/usr/bin:/bin>
X-Cron-Env: <LOGNAME=root>
X-Cron-Env: <USER=root>

Linux 3.10.0-327.22.2.el7.x86_64 (sugilog)      2016年12月20日  _x86_64_        (4 CPU)

07時37分20秒       LINUX RESTART

07時40分01秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
(以下省略)

その他

ちなみに、-mで渡したスクリプトがイケてなくと何かしら失敗していると、ログにそれっぽい出力がある。

Dec 20 10:47:01 sugilog CROND[5604]: (root) CMD (time /usr/bin/sar -P ALL)
Dec 20 10:47:01 sugilog CROND[5603]: (root) MAIL (mailed 8204 bytes of output but got status 0x0001#012)

あとは

cronから標準入力で渡されるテキストはメール形式なので、メールとしてパースすれば、構造化されたデータとして扱える。

例えばruby

require "rubygems"
require "mail"

text = STDIN.read

message = Mail.new(text)
message.header_fields.each do |field|
  puts "#{field.name} = #{field.value}"
end
puts message.body
From = "(Cron Daemon)" <root>
To = sugilog@test.example
Subject = Cron <root@sugilog> /usr/bin/sar -P ALL
Content-Type = text/plain; charset=UTF-8
Auto-Submitted = auto-generated
Precedence = bulk
X-Cron-Env = <XDG_SESSION_ID=70>
X-Cron-Env = <XDG_RUNTIME_DIR=/run/user/0>
X-Cron-Env = <LANG=ja_JP.UTF-8>
X-Cron-Env = <MAILTO=sugilog@test.example>
X-Cron-Env = <SHELL=/bin/sh>
X-Cron-Env = <HOME=/root>
X-Cron-Env = <PATH=/usr/bin:/bin>
X-Cron-Env = <LOGNAME=root>
X-Cron-Env = <USER=root>
Linux 3.10.0-327.22.2.el7.x86_64 (sugilog)      2016年12月20日  _x86_64_        (4 CPU)

07時37分20秒       LINUX RESTART

07時40分01秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
(以下省略)

参考

qiita.com