tkuchikiの日記

新ブログ https://blog.tkuchiki.net

Github Webhook を受けて任意のスクリプトを実行するツール

ghooks-cmd-runner という、
Github の Webhook を受けて任意のスクリプトを実行する Go 製のツールを書きました。

Installation

https://github.com/tkuchiki/ghooks-cmd-runner/releases にバイナリがあるので、ダウンロードして解答してください。

Usage

$ ./ghooks-cmd-runner --help
usage: ghooks-cmd-runner --config=CONFIG [<flags>]

Receives Github webhooks and runs commands

Flags:
      --help              Show context-sensitive help (also try --help-long and --help-man).
  -c, --config=CONFIG     config file location
  -p, --port=18889        listen port
      --host="127.0.0.1"  listen host
  -l, --logfile=LOGFILE   log file location
      --pidfile=PIDFILE   pid file location
      --version           Show application version.

config ファイルが必要なので、以下の様な TOML ファイルを用意します。

# port = 18889 (default: 18889)
# host = "0.0.0.0 (default: 127.0.0.1)"
# secret = "your webhook secret"
# logfile = "path to logfile (default: stdout)"
# pidfile = "path to pidfile"

[[hook]]
event = "push"
command = "/path/to/script"

[[hook]]
event = "pull_request"
command = "/path/to/script"

[[hook]]eventcommand を必要な分記述して、スクリプトを用意すれば完了です。

ghooks-cmd-runnerスクリプトを実行する際、GITHUB_WEBHOOK_PAYLOAD という環境変数に payload の JSON テキストを base64エンコードした文字列を格納しますので、環境変数を読んで base64 でデコードすればお好きな言語で payload を受け取とることができます。
シェルスクリプトで書くと以下のようになると思います。

echo ${GITHUB_WEBHOOK_PAYLOAD} | base64 -d
...

daemonize の機能はないので各自で行ってください。

追記

〜 2016-05-18 17:00

以下のバージョンをリリースしています。

  • v0.1.2
    • コマンドの標準(エラー)出力を1行ずつログに書き出すように修正
  • v0.1.3
    • hook[].branch オプション追加
      • イベント毎に、正規表現にマッチした branch のみでコマンドを実行可能に
  • v0.2.0
    • イベント毎に、コマンドを直列実行するように修正

時刻文字列をいい感じに parse する Go の package

parsetime という時刻文字列をいい感じに parse する Go のライブラリを書きました。

Example

使用例です。

package main

import (
    "fmt"
    "github.com/tkuchiki/parsetime"
    "log"
)

func main() {
    p, err := parsetime.NewParseTime()
    if err != nil {
        log.Fatal(err)
    }

    t, err2 := p.Parse("2016-01-02T03:04:05")

    if err2 != nil {
        log.Fatal(err)
    }


    // Local timezone: JST
    // 2016-01-02 03:04:05 +0900 JST
    fmt.Println(t)
}

以下、NewParseTimeParse の説明です。

NewParseTime

NewParseTime は 0 ~ 2 の引数を指定可能です。
引数を指定しなければ local の *time.Location をセットした状態の time.Time を返します。
第一引数には *time.LocationAsia/TokyoJST のような文字列を指定できます。
Asia/Tokyo のような文字列を指定した場合は、
time.LoadLocation して *time.Location をセットします。
JST のような文字列を指定した場合は、
その文字列から offset を取得して time.FixedZone を実行します。
第一、二引数に JST のような文字列、offset を指定した場合も time.FixedZone を実行します。

ParseTime 構造体には、SetLocation というメソッドを用意しているので、あとから変更することも可能です。

Parse

Parse に時刻文字列を指定すると、time.Timeerror を返します。
Parse は、time.Parse ではなく、正規表現でキャプチャして time.Date を使う実装になっています。
最初に実装した時は、time.Parse の format を大量に用意しておいて、
parse できるまでループで回していたのですが、正規表現を使ったほうが柔軟に対応できそうだったので実装しなおしました。
15:04 のように、時、分だけ指定した場合、実行した年月日を設定した time.Time を返すようになっています
(time.Parse をループで回していたときはこれができていませんでした)。
対応している文字列は、ISO8601、RFC 3339、RFC822, RFC850, RFC1123 などです。
01/02/2006 15:04:0511:04 PMJan 2, 2006 at 3:04am (MST) のような形式にも対応しています。

parse 可能な時刻文字列の例は https://github.com/tkuchiki/parsetime#examples を参考にしてください。

php-build が libphp[57].so を上書きしないようになりました

php-build が libphp5.so を上書きしないようにするパッチ で、
php-build が libphp5.so を上書きしてしまう問題を紹介しました。

2015-12-11 に Remove hard-patch code for APXS handling #353 が merge され、
libphp5.so(PHP 7 は libphp7.so なので、以降 libphp?.so) が上書きされないようになりました。

@tkak さんに Avoid overwriting apache module installed into user's libexec dir #204 を送って頂いていたのですが、方針と合わなかったのかこれ自体は Merge されませんでした。
送っていただいた Pull Request 自体が Merge されなかったのは残念ですが、
@tkak さんのお力添えで問題が解決されるまでに至りました。
ありがとうございました!

使い方

with_apxs2 という関数が追加されましたので、definitions に追記します。

# https://github.com/php-build/php-build/blob/master/bin/php-build#L491-L501
function with_apxs2() {
    local apxs="$1"
    shift

    if [ -z "$apxs" ]; then
        apxs="$PHP_BUILD_APXS"
    fi
    PHP_BUILD_APXS="$apxs"

    configure_option "--with-apxs2" "$apxs"
}
# https://github.com/php-build/php-build/blob/master/bin/php-build#L576-L580
# Use php-build prefix for the Apache libexec folder
if [ -n "$PHP_BUILD_APXS" ]; then
    apxs_libexecdir=$($PHP_BUILD_APXS -q LIBEXECDIR)
    sed -i"" -e "s|'\$(INSTALL_ROOT)${apxs_libexecdir}'|${PREFIX}${apxs_libexecdir}|g" ${source_path}/Makefile
fi

となっていますので、apxs に path が通っていれば with_apxs2
通っていなければ with_apxs2 /path/to/apxs と書けば libphp?.so の install path が変わります。
php-build には default_configure_options という、
全バージョン共通の configure のオプションを記述するファイルがあります。
こちらに --with-apxs2 /path/to/apxs を書いても、
libphp?.so の install path が変更されませんのでご注意ください。
また、definitions に configure_option "--with-apxs2" "/path/to/apxs" と書いても同様です。
しかし、異なるバージョンの php を install するごとに definitions を編集するのは大変です。
全部に追記したい場合は、以下のコマンドを実行すれば追記されます。

$ find /path/to/definitions -type f -print | xargs -I{} sh -c "grep -qs with_apxs2 {} || perl -p -i -e 'print \"with_apxs2 /usr/bin/apxs\n\" if $. == 1' {}"

追記済みの場合は何もしないので安心してお使いいただけるはずです。
上記のコマンドは色々な環境でも動くようにしたものですので、
GNU sed のある環境でしか使わない場合は以下のコマンドでも同じことができます。

$ find /path/to/definitions -type f -print | xargs -I{} sh -c "grep -qs with_apxs2 {} || sed -i '1i\with_apxs2 \"/usr/bin/apxs\"' {}"

まとめ

php-build が libphp?.so を上書きしてしまう問題への対応方法を紹介しました。
default_configure_options に書いたら libphp?.so の install path が変わったら楽そうだなと思いましたが、default_configure_options は全バージョン共通で使える configure option しか書かないと考えると、そういう方針なのだろうということで納得できました
(--with-apxs2 オプションは、現状 php-build が扱っている全バージョンで使えるようですが......)。
patch を当てる方法だとアップデートへの追従が大変なので、公式がサポートしてくれて良かったです。

jq で JSON を LTSV に変換する

結論

[
  {"foo": "bar1", "hoge": "piyo1"},
  {"foo": "bar2", "hoge": "piyo2"},
  {"foo": "bar3", "hoge": "piyo3"}
]

という json(test.json とする) があるときに、

$ cat test.json | jq -r '.[] | to_entries | map("\(.key):\(.value)") | join("\t")'

と実行すると、

foo:bar1        hoge:piyo1
foo:bar2        hoge:piyo2
foo:bar3        hoge:piyo3

と出力されます。

解説

分解して説明します。
to_entrieskey, value という key を持った hash の array に変換します。

$ cat test.json | jq -r '.[] | to_entries'
[
  {
    "key": "foo",
    "value": "bar1"
  },
  {
    "key": "hoge",
    "value": "piyo1"
  }
]
[
  {
    "key": "foo",
    "value": "bar2"
  },
  {
    "key": "hoge",
    "value": "piyo2"
  }
]
[
  {
    "key": "foo",
    "value": "bar3"
  },
  {
    "key": "hoge",
    "value": "piyo3"
  }
]

map は配列に対して処理を行え、
"(\.KEY)" で文字列変数展開ができるので、
map("\(.key):\(.value)") として Label:Value の形式に変換します。

$ cat test.json | jq -r '.[] | to_entries | map("\(.key):\(.value)")'
[
  "foo:bar1",
  "hoge:piyo1"
]
[
  "foo:bar2",
  "hoge:piyo2"
]
[
  "foo:bar3",
  "hoge:piyo3"
]

最後に、join("\t") で array を Tab 区切りの文字列に変換して完了です。

謝辞

http://stackoverflow.com/a/25378171 をちょっといじっただけです。
とても助かりました。aioobe さん、ありがとうございます!

fluentd の out forward の secondary を S3 にして信頼性を向上する

オートスケールするサーバ(web、アプリ)に立てている fluentd(以下、sender) から、
ログ集約用の fluentd(以下、aggregator) にデータを送るとき、
aggregator がデータを受け付けられないと sender で buffer すると思います。
この状態でスケールイン(ディスクごとサーバを削除)すると、
buffer していてもデータをロストしてしまいます。
そこで、sendor から aggregator に送れない場合は Amazon S3 に送ることができたのなら、
信頼性を向上できるのではないかと考え実験しました。

検証環境

  • fluentd 0.12.22
  • fluent-plugin-s3 0.6.6
  • ruby 2.3.0

注意点

sender から aggregator にデータを送れない状況を再現するために、
server には fluentd を起動していないホストを指定します。
存在しないホストを指定すると fluentd の起動に失敗するので、存在するホストを指定してください。

file buffer

buffer したデータを比較するため、まず file buffer の実験を行います。
以下の様な設定ファイルで fluentd を起動します。

以下の様に fluent-cat でデータを送ります。

$ echo '{"foo": "bar"}' | fluent-cat debug.test

buffer は msgpack なようなので decode するスクリプトを用意しました。

decode すると以下の様になります。

$ ruby unpack.rb /path/to/forward.debug.test.xxx.log
[1459155557, {"foo"=>"bar"}]

s3 buffer

次は S3 に buffer できるかの実験です。
以下の様な設定ファイルで fluentd を起動します。
実験のため、retry_limitmax_retry_wait を非常に短くしています。

起動できたら file buffer の時と同じようにデータを送ります。

$ echo '{"foo": "bar"}' | fluent-cat debug.test

S3 を見るとデータが保存されていました。
送れていることが確認できたのならば、file buffer のときと同じく復元できるか確認します。

$ aws s3 cp s3://path/to/buffer/forward_debug.test.gz .
$ gunzip forward_debug.test.gz
$ ruby unpack.rb /path/to/forward.debug.test
[1459155737, {"foo"=>"bar"}]

復元できました。
タイムスタンプ以外は同じ結果になったので、
aggregator を復旧すれば buffer からデータを再送できると思います。

まとめ

out forward の secondary を S3 にすることができるか検証しました。
検証に使った設定ファイルのままでは使えないと思いますので調整する必要はあると思いますが、
buffer をロストしにくくできそうですね。
実際に使用する場合は、S3 の buffer からデータを送り直す手順を確認しておくと良いと思います。
primary と secondary の type が違うのは推奨していないようで、
type of secondary output should be same as primary output primary="Fluent::ForwardOutput" secondary="Fluent::S3Output" のような warn が出力されます。
secondary を S3 にした場合は問題なさそうな挙動でしたが、
他の plugin ではどうなるかわかりませんのでご注意ください。

プロセスのメモリ使用量とCPU使用率などが取れる mackerel-plugin-linux-proc-stats をリリースしました

プロセスのメモリ使用量と CPU 使用率などのメトリクスが取れる、
mackerel-plugin-linux-proc-stats をリリースしました。

Installation

https://github.com/tkuchiki/mackerel-plugin-linux-proc-stats/releases
からバイナリをダウンロードして解凍してください。

Usage

$ ./mackerel-plugin-linux-proc-stats --help
Usage of ./mackerel-plugin-linux-proc-stats:
  -follow-child-processes
        Follow child processes
  -metric-key-prefix string
        Metric key prefix
  -pid string
        PID
  -pidfile string
        PID file
  -tempfile string
        Temp file name
  -version
        Version

このプラグイン独自のオプションを説明すると、

  • pid : PID を指定
  • pidfile: PIDファイルの path
  • follow-child-processes: 子プロセスまで集計するか

となっています。
follow-child-processes について説明するために実行例を示します。
nginx の master と worker プロセスが各1つずつ起動しているとします。
その状態で master プロセスの情報だけ取得する場合は、以下のように実行します。

$ ./mackerel-plugin-linux-proc-stats -pidfile /var/run/nginx.pid
nginx_process.cpu.usage 0.000000        1458808477
nginx_process.memory.vsize      107450368.000000        1458808477
nginx_process.memory.rss        3399680.000000  1458808477
nginx_process.num.running       0.000000        1458808477
nginx_process.num.processes     1.000000        1458808477
nginx_process.num.threads       1.000000        1458808477

master と worker プロセス両方の情報を合算して取得する場合は、以下のように実行します。

$ ./mackerel-plugin-linux-proc-stats -pidfile /var/run/nginx.pid -follow-child-processes
nginx_process.num.running       0.000000        1458808496
nginx_process.num.processes     2.000000        1458808496
nginx_process.num.threads       2.000000        1458808496
nginx_process.cpu.usage 0.000095        1458808496
nginx_process.memory.vsize      215269376.000000        1458808496
nginx_process.memory.rss        7966720.000000  1458808496

子プロセスまでしか追わないので、孫プロセスは対象外です(実装するか検討中)。

メトリクスについて

このプラグインは、/proc/PID/stat を parse しています。

  • num.running: 3番目が R かどうか
  • num.processes: 指定したプロセスのプロセス数(follow-child-processes をつけた場合は子プロセス含む)
  • num.threads: 20番目の値
  • cpu.usage: 15, 16 , 17, 22番目の値 とプロセスの起動時間、uptimegetconf CLK_TCK の値を使って計算
  • memory.vsize: 23番目の値
  • memory.rss: 24番目の値

num.running については、実行時間が長い処理を行わない限りは R にならないので、0 の場合がほとんどです。

/proc/PID/stat の例を載せておきます。

10440 (nginx) S 1 10440 10440 0 -1 4202816 71 0 0 0 0 0 0 0 20 0 1 0 364149088 107450368 830 18446744073709551615 1 1 0 0 0 0 0 1073746048 402745863 18446744073709551615 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0

IAM の Switch Role を捗らせる

AWS のクロスアカウントアクセスを利用すると、
IAM ユーザを AWS アカウントごとに作成しなくても良くなり管理が容易になるのですが、
Switch Role する アカウントが多いと、履歴が 5 件しかないのでアカウントとロールを再入力しなくてはならないことが増えてきます。

ロールを切り替えるユーザーアクセス権限の付与 を見ると、

https://signin.aws.amazon.com/switchrole?account=YourAccountIDorAliasHere&roleName=pathIfAny/YourRoleNameHere

とあります。
Alias は以下を例にすると Your_AWS_Account_ID です(参考: AWS アカウント ID とその別名)。

https://Your_AWS_Account_ID.signin.aws.amazon.com/console/

https://signin.aws.amazon.com/switchrole?YourAccount=hogehoge&roleName=YourRole にアクセスすると、
アカウントとロールが入力された状態でアクセスすることができます。

f:id:tkuchiki:20160222121321p:plain

また、ロールの切り替え(AWS マネジメントコンソール) を見ると、

https://signin.aws.amazon.com/switchrole?account=account_id_number&roleName=role_name&displayName=text_to_display

とあり、 displayName を指定すると表示名も入力した状態にできます。
また、color=カラーコード で色を指定することもできます。
指定できる色は左から順に、

  • F2B0A9 (何も指定しなくてもこれになります)
  • FBBF93
  • FAD791
  • B7CA9D
  • 99BCE3

です(右端の黒は指定方法がわかりませんでした)。
color についてはドキュメントに書いていないようでしたので、 変更される可能性があります。

最後に、account, roleName, displayName, color を全部指定した場合の例を示します。

https://signin.aws.amazon.com/switchrole?account=YourAccount&roleName=YourRole&displayName=YourDisplayName&color=99BCE3 にアクセスすると、

f:id:tkuchiki:20160222121332p:plain

すべて入力済みになります。
この URL をブックマークしておくと、各項目を入力しなおさなくても良くなるので便利です。

まとめ

Switch Role の URL の query string に、

  • account = alias または account ID
  • roleName = ロール名
  • displayName = 表示名
  • color = カラーコード

を指定すると、各項目を入力済みの状態にできるので便利です。