Masteries

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

処理に時間がかかるスクリプトが終わった時にEC2インスタンスを止めるには

...という質問を, 会社の同期から受けました.

状況としては, 夜退社する前にEC2インスタンス上で処理に時間がかかるスプリクトを動かしていて, 朝に結果を確認しているのだけれど, スクリプトの処理が終わってから, 朝確認するまで無駄にEC2インスタンスが動いていて, もったいないので何とか出来ないか? とのこと.

ソリューション

こういう場合, awsコマンドとか, 或いはSDKを使って, プログラムが終わったタイミングで自分が動いているEC2インスタンスを止めてしまう, というのが1つの手だと思います.

awsコマンドであれば, EC2インスタンスのインスタンスIDがi-xxxxxxxxの場合,

$ aws ec2 stop-instances --instance-ids i-xxxxxxxx

のように止められるので, 同じインスタンスを使い回すのであれば, 処理に時間がかかるスクリプトの最後で, このコマンドを実行するようにしておけば良さそうです.

インスタンスIDについては,

$ curl http://169.254.169.254/latest/meta-data/instance-id
i-xxxxxxxx

このようにすれば取れるので,

$ aws ec2 stop-instances --instance-ids $(curl http://169.254.169.254/latest/meta-data/instance-id)

こういう感じにしてやれば, どのEC2インスタンスでスクリプトを動かしても, 処理が終わったタイミングで適切に自分自身のEC2インスタンスを停止してくれる, と思います.

まとめ

処理に時間がかかるスクリプトが終わった時にEC2インスタンスを止める方法について書きました. もっと良い方法をご存知の方は, 是非教えていただけると嬉しいです!

Node.jsのWebアプリケーションでGraceful Restart

いろいろあって, Expressを使って開発されたNode.jsのWebアプリケーションでGraceful Restartする必要が出てきました. Perlであれば, 単純にServer::Starterなどを使って素直に構築できるのですが, Node.jsでGraceful Restartは今回が始めての経験.

Googleでさっくり調べてみた感じ, PerlでのServer::Starterのような「王道!」という構成やツールが見つからず苦労していました. 藁にもすがる思いでGotanda.jsのSlackで質問してみたところ, 「pm2というパッケージを使ってみてはどうですか?」という提案を受けたので, 今回はこれを使ってGraceful Restartしてみました.

インストール

まずはpm2をインストールします.

$ npm install -g pm2

簡単ですね!

設定ファイルの用意

[{
  "name":      "myapp",
  "instances": 0,
  "script":    "myapp.js",
  "exec_mode": "cluster_mode",
  "env": {
    "NODE_ENV": "production"
  }
}]

今回は, こんな感じで設定ファイルを用意してみました. scriptで起動するスクリプト(今回はmyapp.js)を, envで環境変数を設定できます. また, instancesは起動するプロセス数ですが, 0にした場合, コア数分のインスタンスを起動するようにしてくれます.

プロセスの起動

あとは, 先ほど用意したJSONをこのように読み込むと, myapp.jsのWebアプリケーションが立ち上がります.

$ pm2 start processes.json

このとき, Graceful Restartをするのであれば,

$ pm2 gracefulReload processes.json

でOK.

更に, 「プロセスが起動していない場合start, 既に起動している場合はgracefulReloadしたい」という場合は, 次のようにすればOKです.

$ pm2 startOrGracefulReload processes.json

これで, Node.jsで書かれたWebアプリケーションをうまくGraceful Restartできそうです.

まとめ

pm2を使って, Node.jsのWebアプリケーションをGraceful Restart出来るようにしてみました. この辺り, より良いベストプラクティスがあれば非常に興味があるので, ご存知の方は教えて頂けると嬉しいです.

「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をうまく起動することが出来ませんでした.

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