報・連・相
TIME rest time current/total
TopicsPlaceHolder

報・連・相

Geeks Who Drink -monitoring-

Oct 30th, 2017

Profile

songmu

OSSで出しているGoプロダクト

horenso 報・連・相

cronなどのバッチジョブにおける課題

解法: ラッパーコマンドを作成する

% /path/to/wrapper /path/to/job ...

ラッパーコマンドが、実行コマンドを受け取り、そのコマンドを実行した後に、事後処理をおこなう

例1 cronlog

@kazuho さんによるPerlスクリプト

% cronlog -- ping -n 5 my-server 2>&1

ジョブが失敗した時だけ、出力を出す
→ cronに指定すると、失敗した時だけメールが飛ぶとかできる

cronlogの特性

例2 App::RunCron

拙作のPerl製ジョブ通知フレームワーク

例3 独自にラッパースクリプトを書く

% ./wrapper.(sh|pl) /path/to/job ...

そこでhorenso

インストール

https://github.com/Songmu/horenso/releases よりバイナリ取得可能

go get する手もあります

% go get github.com/Songmu/horenso/cmd/horenso

horensoの適用

$ /path/to/job args...

こういうジョブに対して

horensoの適用

$ horenso --reporter reporter.pl -- /path/to/job args...

このようにラップする。(reporter.plは任意の通知用スクリプト・後述)

reporter

reporterに渡されるJSONのサンプル

{
  "command": "perl -E 'say 1;warn \"$$\\n\";sleep 2'",
  "commandArgs": [
    "perl",
    "-E",
    "say 1;warn \"$$\\n\";sleep 2"
  ],
  "output": "57078\n1\n",
  "stdout": "1\n",
  "stderr": "57078\n",
  "exitCode": 0,
  "result": "command exited with code: 0",
  "hostname": "MatsukiMasayuki-no-MacBook-Pro.local",
  "pid": 57078,
  "startAt": "2016-02-16T01:27:08.946009881+09:00",
  "endAt": "2016-02-16T01:27:11.010627341+09:00",
  "systemTime": 0.03176,
  "userTime": 0.025954
}

例) reporter.pl

use JSON::PP;
my $report = decode_json <>; // 結果を取り出す
$report->{startAt};
...

例) reporter.go

import (
    "encoding/json"
    "os"
    "github.com/Songmu/horenso"
)
func main() {
    var d = horenso.Report{}
    json.NewDecoder(os.Stdin).Decode(&d)
    d.StartAt
    ...
}

ジョブ開始前に通知する(noticer)

% horenso --noticer 'ruby noticer.rb' \
    -r reporter.pl -- /path/to/job args...

時間のかかるジョブの実行前に通知させたい場合

reporterの複数指定

% horenso -r reporter.pl -r reporter2.py -- /path/to/job args...

複数のreporterを指定した場合、平行に実行される(Goっぽい)

horensoを使ってラッパーシェルを作る

#!/bin/sh
exec /path/to/horenso \
  -n /path/to/noticer.py         \
  -r /path/to/reporter.pl        \
  -r 'ruby /path/to/reporter.rb' \
  -- "$@"

これを例えば以下のようにcrontabに指定する

3 4 * * * /path/to/wrapper.sh /path/to/job --args... 2>&1 | logger -t myjob

できること一例

別にcronじゃなくても、バッチジョブ実行の仕組みに対して統一的に導入すると便利。

喜びの声

現在いただいている要望

実装上の工夫

標準出力、エラー出力を奪わずに、内容を変数に保持する

Goっぽさある。

// コマンドからパイプを取り出す
cmd := exec.Command(args[0], args[1:]...)
stdoutPipe, _ := cmd.StdoutPipe()
stderrPipe, _ := cmd.StderrPipe()

// 出力を格納する bytes.Buffer を宣言
var bufStdout bytes.Buffer
var bufStderr bytes.Buffer
var bufMerged bytes.Buffer

// pipeの書き出し先 io.TeeReader で追加
stdoutPipe2 := io.TeeReader(stdoutPipe, io.MultiWriter(&bufStdout, &bufMerged))
stderrPipe2 := io.TeeReader(stderrPipe, io.MultiWriter(&bufStderr, &bufMerged))

// コマンドの実行と io.Copy
cmd.Start()
go func() {
    defer stdoutPipe.Close()
    io.Copy(os.Stdout, stdoutPipe2)
}
go func() {
    defer stderrPipe.Close()
    io.Copy(os.Stderr, stderrPipe2)
}
cmd.Wait()

// 取り出す
out := bufStdout.String()
stderr := bufStderr.String()
merged := bufMerged.String()

コマンドからパイプを取り出す

cmd := exec.Command(args[0], args[1:]...)
stdoutPipe, _ := cmd.StdoutPipe()
stderrPipe, _ := cmd.StderrPipe()

出力を格納する bytes.Buffer を宣言

var bufStdout bytes.Buffer
var bufStderr bytes.Buffer
var bufMerged bytes.Buffer

pipeの書き出し先 io.TeeReader で追加

stdoutPipe2 := io.TeeReader(stdoutPipe, io.MultiWriter(&bufStdout, &bufMerged))
stderrPipe2 := io.TeeReader(stderrPipe, io.MultiWriter(&bufStderr, &bufMerged))

コマンドの実行と io.Copy

cmd.Start()
go func() {
    defer stdoutPipe.Close()
    io.Copy(os.Stdout, stdoutPipe2)
}
go func() {
    defer stderrPipe.Close()
    io.Copy(os.Stderr, stderrPipe2)
}
cmd.Wait()

出力を取り出す

out := bufStdout.String()
stderr := bufStderr.String()
merged := bufMerged.String()

実行プログラムの終了コードやシグナルを取得する

err := cmd.Run()
exitCode := 0
if e, ok := err.(*exec.ExitError); ok {
    if w, ok := e.Sys().(syscall.WaitStatus); ok {
        if w.Signaled() {
            // 無理やり感
            exitCode = int(w) | 0x80
        } else {
            exitCode = s.ExitStatus()
        }
    }
    exitCode = -1
}

https://github.com/Songmu/wrapcommander に切り出し

各行の出力をhookしてtimestampを付ける

以上

We are Hiring

hatena