【GMOリサーチではエンジニアを募集中です】採用情報はこちら!

バッチシステムをAmazon ECSで作った話

バッチシステムをAmazon ECSで作った話

システム部の福原と申します。
前回に引き続き、あーだこーだしている日常を並べていきます。
ご笑覧あれー。

プロローグ:バッチをECSで動かしたい

同僚氏:というわけで、このプロジェクトはPythonのバッチをDockerで動かしたいです。環境の方よろしくおねがいします。

、、、ふむっ。バッチか。。。
REST APIをSpring Bootで立てるってのはもうやったことあるが、これは定期的単発実行の処理だ。
どうしよう?

うちらはAmazon Elastic Container Service (ECS) を使っている。
バッチ実行はこれに従えばよさそうだ。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/ecs_run_task.html

そういえば弊社の場合バッチ処理は、Hinemosに実行管理させる決まりだ。
HinemosからECSタスクを起動するのどうしよう?

うーむ。なんだか色々ありそうだ。

<<しばらく試行錯誤。ドタバターン。>> =☆

よし、こんなのでどうか。

全体の構成

Hinemosからタスク実行管理シェルを起動する。
シェルの動作概要は以下の通り。

バッチ実行の流れ
  1. Hinemosで、シェルを定時実行(start-ECS.sh とします。)
  2. start-ECS.shは、AWSコマンドを使ってECSタスクを起動する。起動だけしたら次の処理へ行く。 aws ecs run-task コマンドを使う。
  3. ECSの実行環境がECRからイメージをpullする。
  4. バッチとしてやりたい処理を実行する.
  5. start-ECS.shは、バッチの終了を待つ。具体的には aws ecs describe-tasks コマンドを無限ループ内で終わるまで呼び出す。バッチが終了したらループを抜ける。
  6. start-ECS.shは、終了コードを取得する。その値をシェルの終了コードとすることでHinemosに返す。

こうすれば失敗時にHinemosが検出してくれる。
HinemosからSlack通知の仕組みは既にあるから、Hinemosに終了コードで 0 以外を返せば失敗が表現される。
0 なら成功だ。

バッチ処理の実行ログはFluentd経由でログ集約サーバに入れる仕組みが既にある。
それに乗せてしまおう。

よし、これで行こう。

タスクの実行(aws ecs run-task)

aws ecs run-task は、実行の引数をyaml化して、--cli-input-yaml オプションで渡せるようだ。
せっかくだからファイル化して、git管理にしてしまおう。
run-task.yaml とでもしようか。

だいたいこんなファイルだ。

run-task.yaml

む、 taskDefinition というのがあるな。
これはタスク定義の名前だからリリース毎に sed で書き換える処理が必要だな。
覚えておこう。

タスクの実行状況の確認(aws ecs describe-tasks)

aws ecs describe-tasks にはタスク実行のARNが要るな。
どうやら run-task の戻り値のJSONから取得できそうだ。
これは jq コマンドで切り出せばOK.

タスクが終わっていたら、コンテナの終了コードをしらべる。
戻り値のJSONに、コンテナの終了コードが入ってくるからこれも jq で切り出して、Hinemosに返せば良い。

よしできそう。早速作ろう。

デプロイメントパイプライン

デプロイをどうしよう? いままでやったREST APIとは違う点がいくつかあるぞ。

  • 稼働中のタスクをローリングアップデートする形ではなく、タスク定義を更新するだけでよい。
  • 別途、タスク(バッチの実際の処理)を起動する仕組みが必要
  • バッチ実行では弊社の決め事として、Hinemosに設定を管理させて実行をスケジューリングすることになっている。Hinemosに結果の確認やエラー時のslack発報をやらせることになっている。

これを実現するにはどうしたらいい?むむ?

<<しばらく試行錯誤。ドッギャァーン>> ==☆☆

よしこうしよう。

パイプライン概要

GitHubのリポジトリで、developブランチにマージされたら動作する。
動作概要は以下の通り。

  1. GitHubで、developブランチにマージ。これきっかけでCodePipelineが動作する。
  2. CodePipelineで、CodeBuildが動作する。以下の処理を行う。
  3. イメージのビルド。コミットハッシュでタグ付け
  4. ECRへイメージをpush
  5. イメージのタグが決まるので、それを使ってタスク更新
  6. タスク更新でタスク定義の新しいバージョンが決まる。それを使ってタスク実行のyamlを作成
  7. ECS のタスク起動をするシェルスクリプトとタスク実行のyamlを纏めて次ステージに回す
  8. CodePipelineで、CodeDeployが動作する。以下の処理を行う。
  9. 受け取ったファイル群の中身をバッチ実行サーバにデプロイする。

仕組みがややこしいが、これでHimemosとつなげることができる。
グルーコード感あるが今のバッチ実行の仕組み的には必要なものだ。

CodeBuild ですること

コンテナイメージのビルドと、タスク定義の更新と、バッチサーバにリリースするファイル群を作成する。

ビルド

イメージのビルドは docker buildで実行。コミットハッシュが $CODEBUILD_RESOLVED_SOURCE_VERSION から取得できるので、
先頭7桁を切り出してイメージのタグに付与する。

buildspec.yml から一部抜粋。

タスク定義更新

タスク定義更新はaws ecs register-task-definition で実行する。
定義全体を register-task.yaml なるyamlファイルに書いておいて、 --cli-input-yaml オプションで渡す。
その際イメージにつけたコミットハッシュによるタグを register-task.yaml に仕込むようにする。
sed で置換すればよろしい。
register-task.yaml も、git管理にしてしまおう。

register-task.yaml

buildspec.yml から一部抜粋。

バッチサーバにリリースするファイル群の作成

で、次は aws ecs register-task-definition の戻り値を jqで処理してタスク定義を切り出して、タスク実行のyamlを作る。

zip作成は、appspec.yml と、start-ECS.sh と、run-task.yaml などバッチサーバに必要なファイルをrelease_to_batch_server ディレクトリに配置する。
で、buildspec.yml で、こうしておけば、後は次ステージに回る。

後続の CodeDeploy に処理させればよい。OKだ!

CodeDeploy ですること

今までの処理で作ったzipをバッチサーバに配布すれば良い。
バッチサーバは普通のEC2インスタンスだ。

エピローグ

よしこれでできた。
develop ブランチにマージされたらテスト環境にデプロイ。
main ブランチにマージされたら本番環境にデプロイするようにして、、、。完成だ!

同僚氏:ありがとうございます。。でもこれ、まだちょっと不便で、、、

えー。まだあるの?(続きは別の機会に。。。)

未分類カテゴリの最新記事