Masteries

技術的なことや仕事に関することを書いていきます.

「Mackerel User Group」を立ち上げることになりました

先週開催されたMackerel Meetupでも告知させて頂いたのですが, このたび「Mackerel User Group」を立ち上げることになりました!

スライドにも書いたのですが, 自分自身とあるサービスにMackerelを導入するにあたって, 多くのMackerelユーザーからアドバイスを頂くことができて, お陰でうまくMackerelを使いこなすことが出来た, という経験があります.

こういう「ユーザー間のコミュニケーション」によって, Mackerelの導入がもっと楽になる, というのはすごく良いと思っていて, Mackerel User Groupでそういう世界観を作り出せたらいいな, と思っています.

...とはいえ, 現時点では発起人を集めて動き出したばかりという状況なので, まだまだこれから, という感じです. 近々, Mackerel User GroupのSlackやブログなども公開される予定ですので, 今後の活動の詳細などはそちらでお伝え出来れば, と思います.

というわけで, Mackerel User Group, 宜しくお願いします.

「Walter」を試していたのでまとめた

Walterは, Goで書かれた「ビルドパイプライン」ツールです.

JenkinsなどでCIを行う場合, 複数のジョブを連携して, 一連の処理の流れを作ることがあります. これを「ビルドパイプライン」と呼び, Jenkinsにはビルドパイプラインを作る為のプラグインが幾つか提供されています.

Walterは, Jenkinsに依存しない形で, 「ビルドパイプライン」をよりシンプルに, かつコードで指定出来るようにしたツールです(Jenkinsで「ビルドパイプライン」を設定する場合, 大抵ブラウザでJenkinsをポチポチする必要があります...).

詳しくは,

などなど参考にしながら, 実際に手を動かしながら試してみるのが良さそうです.

インストール

というわけで早速インストールしましょう. WalterはGo製のツールであり, ワンバイナリで動作します.

バイナリは, Walterのリポジトリ配布されているので, ここからダウンロードするのが手っ取り早いでしょう. Linux系の64bit OSであれば, 「walter_x.x.x_linux_amd64.tar.gz」でOKです.

また, Homebrewでインストールすることも可能です:

$ brew tap higanworks/homebrew-walter-binary 
$ brew install walter

問題なくインストールできれば, walterというコマンドが利用可能になります. これで準備OKです.

設定ファイル: pipeline.yml

Walterは, pipeline.ymlという設定ファイルを利用します.

pipeline:
  - name: command_stage_1
    type: command
    command: echo "hello, world"

pipeline.ymlを設置したディレクトリで, walterコマンドを実行すると, この設定に従って処理を実行してくれます(-cオプションで, 任意のYAMLファイルを利用することもできます).

$ walter
11:34:30  INFO Pipeline file path: "./pipeline.yml"
11:34:30  INFO not found "require" block
11:34:30  INFO not found "service" block
11:34:30  INFO local client was created
11:34:30  INFO not found messenger block
11:34:30  INFO not found cleanup block in the input file
11:34:30  INFO running Walter
11:34:30  INFO Starting Walter in local mode
11:34:30  INFO Preparing to run pipeline process...
11:34:30  INFO [command] exec: command_stage_1
11:34:30  INFO [command_stage_1][command] exec output: hello, world
11:34:30  INFO Finished running pipeline process...
11:34:30  INFO Preparing to run cleanup process...
11:34:30  INFO Finished running cleanup process...
11:34:30  INFO succeded to finish Walter

pipeline.ymlで指定したコマンド(単なるechoですが...)が, ここで実行されていることがわかります.

11:34:30  INFO [command] exec: command_stage_1
11:34:30  INFO [command_stage_1][command] exec output: hello, world

処理の逐次実行

次のように記述すれば, 処理を逐次(上から順番に)実行することができます.

pipeline:
  - name: command_stage_1
    type: command
    command: echo "hello, world"
  - name: command_stage_2
    type: command
    command: echo "こんにちは世界"

実行すると, 次のようになります:

11:37:53  INFO Pipeline file path: "./pipeline.yml"
11:37:53  INFO not found "require" block
11:37:53  INFO not found "service" block
11:37:53  INFO local client was created
11:37:53  INFO not found messenger block
11:37:53  INFO not found cleanup block in the input file
11:37:53  INFO running Walter
11:37:53  INFO Starting Walter in local mode
11:37:53  INFO Preparing to run pipeline process...
11:37:53  INFO [command] exec: command_stage_1
11:37:53  INFO [command_stage_1][command] exec output: hello, world
11:37:53  INFO [command] exec: command_stage_2
11:37:53  INFO [command_stage_2][command] exec output: こんにちは世界
11:37:53  INFO Finished running pipeline process...
11:37:53  INFO Preparing to run cleanup process...
11:37:53  INFO Finished running cleanup process...
11:37:53  INFO succeded to finish Walter

command_stage_1の後に, command_stage_2が実行されていることがわかります.

処理の並列実行

次のように記述すると, 幾つかの処理を並列に実行することができます.

pipeline:
  - name: parallel command
    parallel:
      - name: parallel 1
        type: command
        command: sleep 1
      - name: parallel 2
        type: command
        command: sleep 2
      - name: parallel 3
        type: command
        command: sleep 3

こうすると, parallel下に設定した3つの処理が, 同時に実行されます. もし, これを逐次実行した場合は6秒(1 + 2 + 3)のスリープになりますが, 並列に実行した場合は3秒で終了するはずです. というわけで, timeコマンドを使って処理時間を確認してみます.

$ time walter
11:39:04  INFO Pipeline file path: "./pipeline.yml"
11:39:04  INFO not found "require" block
11:39:04  INFO not found "service" block
11:39:04  INFO local client was created
11:39:04  INFO not found messenger block
11:39:04  INFO not found cleanup block in the input file
11:39:04  INFO running Walter
11:39:04  INFO Starting Walter in local mode
11:39:04  INFO Preparing to run pipeline process...
11:39:04  INFO [command] exec: parallel command
11:39:04  INFO [command] exec: parallel 1
11:39:04  INFO [command] exec: parallel 2
11:39:04  INFO [command] exec: parallel 3
11:39:07  INFO Finished running pipeline process...
11:39:07  INFO Preparing to run cleanup process...
11:39:07  INFO Finished running cleanup process...
11:39:07  INFO succeded to finish Walter
walter  0.01s user 0.03s system 1% cpu 3.043 total

3.043 totalということで, 確かに3秒で処理が終わっていることがわかります.

この並列処理の利用シーンですが, 例えば, carton installによるCPANモジュールのインストールと, npm installによるJavaScriptのライブラリのインストールを並列実行して, ビルドにかかる時間を節約する, といった使い方が出来ると思います.

コマンド実行時のオプション

1つの処理(Walterでは「ステージ」と言うそうです)の設定には, 次のようにオプションを指定することができます:

pipeline:
  - name: command_stage_1
    type: command
    only_if: test -e file.txt
    directory: dir
    command: cat file.txt

directoryは, そのステージで設定されたコマンドを実行するディレクトリを, only_ifはそのステージで設定されたコマンドを実行する条件(前提条件)を意味します.

例えばこの場合, only_if及びcommandで設定されたコマンドは, directoryで指定したdirというディレクトリをカレントディレクトリとして実行されます.

そして, only_iftest -e file.txtをしているので, このタスクはdirディレクトリ内にfile.txtが存在する場合のみ実行されるようになります.

まず, 失敗例から.

$ walter
11:46:49  INFO Pipeline file path: "./pipeline.yml"
11:46:49  INFO not found "require" block
11:46:49  INFO not found "service" block
11:46:49  INFO local client was created
11:46:49  INFO not found messenger block
11:46:49  INFO not found cleanup block in the input file
11:46:49  INFO running Walter
11:46:49  INFO Starting Walter in local mode
11:46:49  INFO Preparing to run pipeline process...
11:46:49  INFO [command] only_if: found "only_if" attribute in stage "command_stage_1"
11:46:49  INFO [command] only_if: command_stage_1
11:46:49  WARN [command] only_if err: exit status 1
11:46:49  WARN [command] exec: skipped this stage "command_stage_1", since only_if condition failed
11:46:49  INFO Finished running pipeline process...
11:46:49  INFO Preparing to run cleanup process...
11:46:49  INFO Finished running cleanup process...
11:46:49  INFO succeded to finish Walter

only_iferr: exit status 1になっており, これによってexec: skipped this stage "command_stage_1", since only_if condition failedとなっていることがわかります.

次に, 「Hello, walter!」という内容のファイルをdirディレクトリ内部にfile.txtとして設置して実行してみます.

$ walter
11:51:36  INFO Pipeline file path: "./pipeline.yml"
11:51:36  INFO not found "require" block
11:51:36  INFO not found "service" block
11:51:36  INFO local client was created
11:51:36  INFO not found messenger block
11:51:36  INFO not found cleanup block in the input file
11:51:36  INFO running Walter
11:51:36  INFO Starting Walter in local mode
11:51:36  INFO Preparing to run pipeline process...
11:51:36  INFO [command] only_if: found "only_if" attribute in stage "command_stage_1"
11:51:36  INFO [command] only_if: command_stage_1
11:51:36  INFO [command] exec: command_stage_1
11:51:36  INFO [command_stage_1][command] exec output: Hello, walter!
11:51:36  INFO Finished running pipeline process...
11:51:36  INFO Preparing to run cleanup process...
11:51:36  INFO Finished running cleanup process...
11:51:36  INFO succeded to finish Walter

先ほどと異なり, only_ifでエラーになっておらず, 正しくコマンド(cat file.txt)が実行され, それによってexec output: Hello, walter!になっていることがわかります.

「ステージ」が失敗した場合

もし, ステージで設定されたコマンドが失敗した場合, その時点で全ての処理が中断します.

例えば, 次のpipeline.ymlでは, 最初のステージで存在しないfailed_commandというコマンドを実行しようとしています.

pipeline:
  - name: command_stage_1
    type: command
    command: failed_command
  - name: command_stage_2
    type: command
    command: echo "hi!"
  - name: command_stage_3
    type: command
    command: echo "hi! hi!"

これを実行すると,

$ walter
11:55:09  INFO Pipeline file path: "./pipeline.yml"
11:55:09  INFO not found "require" block
11:55:09  INFO not found "service" block
11:55:09  INFO local client was created
11:55:09  INFO not found messenger block
11:55:09  INFO not found cleanup block in the input file
11:55:09  INFO running Walter
11:55:09  INFO Starting Walter in local mode
11:55:09  INFO Preparing to run pipeline process...
11:55:09  INFO [command] exec: command_stage_1
11:55:09  INFO [command_stage_1][command] exec output: sh: failed_command: command not found
11:55:09  WARN [command] exec err: exit status 127
11:55:09 ERROR [command] exec: failed stage "command_stage_1"
11:55:09  WARN Execution is skipped: command_stage_2
11:55:09  WARN Execution is skipped: command_stage_3
11:55:09  INFO Finished running pipeline process...
11:55:09  INFO Preparing to run cleanup process...
11:55:09  INFO Finished running cleanup process...
11:55:09  INFO more than one failures were detected running Walter

このように, exec output: sh: failed_command: command not foundでステージの実行が失敗していて, これによってExecution is skipped: command_stage_2そしてExecution is skipped: command_stage_3になっていることがわかります.

クリーンアップ

パイプラインの一番最後(処理が最後まで終了したか, そうでないかに関わらず)に行う処理は, cleanupで指定できます.

pipeline:
  - name: command_stage_1
    type: command
    command: touch file.txt
  - name: command_stage_2
    type: command
    command: ls
  - name: command_stage_3
    type: command
    command: echo "hi!"
cleanup:
  - name: cleanup
    command: rm file.txt

このように記述すると, command_stage_1で作成したfile.txtというファイルを, 一番最後に削除してくれます.

$ walter
11:57:30  INFO Pipeline file path: "./pipeline.yml"
11:57:30  INFO not found "require" block
11:57:30  INFO not found "service" block
11:57:30  INFO local client was created
11:57:30  INFO not found messenger block
11:57:30  INFO found cleanup block
11:57:30  INFO running Walter
11:57:30  INFO Starting Walter in local mode
11:57:30  INFO Preparing to run pipeline process...
11:57:30  INFO [command] exec: command_stage_1
11:57:30  INFO [command] exec: command_stage_1
11:57:30  INFO [command_stage_1][command] exec output: file.txt
pipeline.yml
11:57:30  INFO [command] exec: command_stage_2
11:57:30  INFO [command_stage_2][command] exec output: hi!
11:57:30  INFO [command] exec: command_stage_3
11:57:30  INFO [command_stage_3][command] exec output: hi! hi!
11:57:30  INFO Finished running pipeline process...
11:57:30  INFO Preparing to run cleanup process...
11:57:30  INFO [command] exec: cleanup
11:57:30  INFO Finished running cleanup process...
11:57:30  INFO succeded to finish Walter
$ ls
pipeline.yml

command_stage_1lsを実行したタイミングでは, file.txtが存在しますが, walterコマンドによる処理が終了した後にlsで確認すると, cleanupfile.txtを削除しているので, pipeline.ymlしか残っていないことがわかります.

まとめ

Walter, 他にもHipChatやSlack, Githubとの連携などなど, いろいろ機能があるのでこちらも試してみたいなーと思っています.

MackerelのAmazon Linux対応に対応していて軽くやらかした話

3月28日から, MackerelはAmazon Linuxの正式サポートを開始しました: Amazon Linuxの正式サポートについて - Mackerel ブログ #mackerelio

これに伴って, Amazon Linuxにおけるmackerel-agentのインストール方法が変更されたので, 某サービスでも早速対応していたのですが, そこでちょっと軽くやらかしたというか, 凡ミスをしたのでシェアします.

結論

mackerel-agentのインストールにおいて, 別途, /etc/mackerel-agent/mackerel-agent.confにAPIキーを書き込む処理をしているのであれば, 「sudo mackerel-agent init -apikey={{MACKEREL_APIKEY}}」は実行しなくてもよい.

詳細

先ほどのブログでは, 新規インストールの手順について以下のように紹介されています.

curl -fsSL https://mackerel.io/file/script/amznlinux/setup-yum.sh | sh
sudo yum install -y mackerel-agent
sudo mackerel-agent init -apikey={{MACKEREL_APIKEY}}
sudo /sbin/service mackerel-agent start
※`{{MACKEREL_APIKEY}}`はRead/Write権限のあるAPIキーを指定する

さて, 某サービスでは, mackerel-agentを含むミドルウェアなどのインストールは, Packerを使ってAMIを作る際に行う, という仕組みになっています. これまではmackerel-agentのインストールに関する処理を, 次のように書いていました:

# 従来のインストール方法で`mackerel-agent`をインストール
curl -fsSL https://mackerel.io/assets/files/scripts/setup-yum.sh | sh
yum install -y mackerel-agent
yum install -y mackerel-agent-plugins
yum install -y mackerel-check-plugins

# 設定ファイルの生成
mkdir /etc/mackerel-agent/conf.d
cat >> /etc/mackerel-agent/mackerel-agent.conf <<'EOF';
apikey = "MACKEREL_APIKEY"
EOF

というわけで, ブログ記事に従って次のように書き換えました:

# Install Mackerel
curl -fsSL https://mackerel.io/file/script/amznlinux/setup-yum.sh | sh
yum install -y mackerel-agent
mackerel-agent init -apikey={{MACKEREL_APIKEY}}
yum install -y mackerel-agent-plugins
yum install -y mackerel-check-plugins

mkdir /etc/mackerel-agent/conf.d
cat >> /etc/mackerel-agent/mackerel-agent.conf <<'EOF';
apikey = "MACKEREL_APIKEY"
EOF

...まあ, もうオチはお分かりだと思います.

mackerel-agent init -apikey={{MACKEREL_APIKEY}}」は/etc/mackerel-agent/mackerel-agent.confに指定したAPIキーを記載するコマンドなので, その後に手動でAPIキーを/etc/mackerel-agent/mackerel-agent.confに記述するようにしていると, /etc/mackerel-agent/mackerel-agent.confにAPIキーが(同一ではあるものの)2回現れることになり, mackrel-agentをうまく起動することが出来ませんでした.

...というわけで, 結論に至る次第です. ちょっとした凡ミスですが, 皆様もお気をつけ下さい.

頼りがいのある(かもしれない)Database Migration Utility, Anegoを作っています

PerlでWebAppなどを作っている際, データベースのマイグレーションをするのであれば, 例えば@sugyanさんのブログに書かれているSQL::Translator::DiffでDBスキーマに追従させる方法のような手段を使ったり, 或いはこれと同じくSQL::Translator::Diffに基づいたGitDDLGitDDL::Migratorなどを使う手があると思います.

この辺りの方法を参考にしながら, Reactioなどでは独自のマイグレーションの仕組みを作って実践しているのですが, だいぶ安定して使えているし, そろそろ他のプロジェクトでも使いたくなってきたので, モジュールとして切り出してみることにしました.

なお名前は, Kansai.pmでAnegoのプロトタイプコードに様々なアドバイスを下さった@karupaneruraさんとそのORM「Aniki」に敬意を表して(?), 「Anego(姉御)」にしました.

使い方

AnegoはDBIx::Schema::DSLを使うようになっているので, まずはアプリケーションのスキーマをDBIx::Schema::DSLを使って記述します:

package MyApp::Schema;
use strict;
use warnings;
use utf8;

create_table 'user' => columns {
    integer 'id', primary_key, auto_increment;
    varchar 'name';
};

1;

あとは, このように操作することができます:

use strict;
use warnings;
use utf8;

use Anego;

my $anego = Anego->new(
    connect_info => [ ... ],
    schema_class => 'MyApp::Schema',
);

# スキーマのビルド
$anego->build;

# 最新のスキーマと現在のデータベースのスキーマとの差分を表示
$anego->diff;

# 最新のスキーマへのマイグレーションの実行
$anego->migrate;

次のように, Daikufileなどと組み合わせるといいと思います:

namespace db => sub {
    require Anego;

    my $anego = Anego->new(
        connect_info => [...],
        schema_class => 'MyApp::Schema',
    );

    task build => sub {
        $anego->build();
    };

    task diff => sub {
        $anego->diff($_[1]);
    };

    task migrate => sub {
        $anego->migrate($_[1]);
    };
};

buildをすると, .dbディレクトリにスキーマの情報が保存されていきます. diffmigrateをする際にバージョンとして指定することで, 現在のデータベースのスキーマと, 過去の任意のスキーマについて, 差分を表示したりマイグレーション(ロールバック)したりすることもできます.

$ ls .db
1458563116.sql
1458710980.sql

# 現在のデータベースのスキーマと, 1458563116.sqlにおけるスキーマの差分を表示する
$ daiku 'diff[1458563116]'

さっくり作った段階ですので, ご意見ご要望ご指摘などお待ちしております. よろしくお願い致します.

久々のKansai.pmでライブコーディングをしてきました

久々のKansai.pmである, Kansai Perl Mongers 第16回ミーティングが開催されたので参加してきました. 前回の開催が約3年前, 確かJPAの講師派遣制度で@yusukebeで来られた時だったと思うので, だいぶ久々だなあ, と思います.

当初は, 「PerlのWebApp開発におけるの新たなスタンダード〜Aniki+Amon2〜」というタイトルで, 最近気に入っているORM「Aniki」を使ったWebApp開発の話をしようと思っていて, 直前に開催されたキッカソンでデモアプリを作ろうとしていたのですが, WebAppのデータベースの準備をするあたりで「これまで書いてきたマイグレーション周りのコード, ライブラリ化してまとめたいな...」という気持ちが高ぶって来て, 最終的にはそのへんをライブコーディングで作っていくか, という所に落ち着きました.

今回の資料... というかライブコーディングの前説はこちら.

ちなみに, ライブコーディングしながら開発していたライブラリは, 当初は「Aniki::Migrator」という名前にしていましたが, Aniki専用という訳でもないので, 「頼りになるMigrator"Anego"」として作っていくことにしました.

...LTで, いいところを全部持って行った@karupaneruraさんにアドバイスいただいた所もちゃんと含まれています.

今回, 久々に関西地方にお住まいのPerl Mongerの方々とお会いできて楽しかったですし, 最後には「次のKansai.pmは8月までに!」という声も挙がったので, 次も是非参加したいですね!