システム部の福原と申します。
前回に引き続き、あーだこーだしている日常を並べていきます。
ご笑覧あれー。
プロローグ:バッチをECSで動かしたい
同僚氏:というわけで、このプロジェクトはPythonのバッチをDockerで動かしたいです。環境の方よろしくおねがいします。
、、、ふむっ。バッチか。。。
REST APIをSpring Bootで立てるってのはもうやったことあるが、これは定期的単発実行の処理だ。
どうしよう?
うちらはAmazon Elastic Container Service (ECS) を使っている。
バッチ実行はこれ↓に従えばよさそうだ。
クラシック Amazon ECS コンソールを使用してスタンドアロンタスクを実行する方法
そういえば弊社の場合バッチ処理は、Hinemosに実行管理させる決まりだ。
HinemosからECSタスクを起動するのどうしよう?
うーむ。なんだか色々ありそうだ。
<<しばらく試行錯誤。ドタバターン。>> =☆
よし、こんなのでどうか。
全体の構成
Hinemosからタスク実行管理シェルを起動する。
シェルの動作概要は以下の通り。
- Hinemosで、シェルを定時実行(
start-ECS.sh
とします。) start-ECS.sh
は、AWSコマンドを使ってECSタスクを起動する。起動だけしたら次の処理へ行く。aws ecs run-task
コマンドを使う。- ECSの実行環境がECRからイメージをpullする。
- バッチとしてやりたい処理を実行する.
start-ECS.sh
は、バッチの終了を待つ。具体的にはaws ecs describe-tasks
コマンドを無限ループ内で終わるまで呼び出す。バッチが終了したらループを抜ける。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
例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
cluster: 'fukuhara-test' count: 1 enableECSManagedTags: true enableExecuteCommand: true launchType: FARGATE networkConfiguration: awsvpcConfiguration: subnets: - 'subnet-1234dummysubnet' securityGroups: - 'sg-1234dummysg' assignPublicIp: DISABLED platformVersion: '1.4.0' propagateTags: TASK_DEFINITION taskDefinition: '__TASK_DEF__' # ここには実際のタスク定義の名前が入るよ! |
む、 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
ブランチにマージされたら動作する。
動作概要は以下の通り。
- GitHubで、developブランチにマージ。これきっかけでCodePipelineが動作する。
- CodePipelineで、CodeBuildが動作する。以下の処理を行う。
- イメージのビルド。コミットハッシュでタグ付け
- ECRへイメージをpush
- イメージのタグが決まるので、それを使ってタスク更新
- タスク更新でタスク定義の新しいバージョンが決まる。それを使ってタスク実行のyamlを作成
- ECS のタスク起動をするシェルスクリプトとタスク実行のyamlを纏めて次ステージに回す
- CodePipelineで、CodeDeployが動作する。以下の処理を行う。
- 受け取ったファイル群の中身をバッチ実行サーバにデプロイする。
仕組みがややこしいが、これでHimemosとつなげることができる。
グルーコード感あるが今のバッチ実行の仕組み的には必要なものだ。
CodeBuild ですること
コンテナイメージのビルドと、タスク定義の更新と、バッチサーバにリリースするファイル群を作成する。
ビルド
イメージのビルドは docker build
で実行。コミットハッシュが $CODEBUILD_RESOLVED_SOURCE_VERSION
から取得できるので、
先頭7桁を切り出してイメージのタグに付与する。
buildspec.yml
から一部抜粋。
1 2 3 |
- TAG_COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) - REPOSITORY_URI=1234dummy5678.dkr.ecr.ap-northeast-1.amazonaws.com/nantoka-batch-image - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$TAG_COMMIT_HASH |
タスク定義更新
タスク定義更新はaws ecs register-task-definition
で実行する。
定義全体を register-task.yaml
なるyamlファイルに書いておいて、 --cli-input-yaml
オプションで渡す。
その際イメージにつけたコミットハッシュによるタグを register-task.yaml
に仕込むようにする。sed
で置換すればよろしい。register-task.yaml
も、git管理にしてしまおう。
register-task.yaml
例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
family: 'nantoka-task-name' taskRoleArn: 'ECSNantokaRole' executionRoleArn: 'ECSNantokaRole' networkMode: awsvpc containerDefinitions: - name: 'nantoka-container-name' image: '1234dummy5678.dkr.ecr.ap-northeast-1.amazonaws.com/nantoka-batch-image:__NEW_TAG__' # ここには実際のイメージの名前が入るよ! essential: true environment: - name: NANTOKA_SETTING value: hoihoihoi cpu: '256' memory: '512' |
buildspec.yml
から一部抜粋。
1 2 |
- sed -i -e "s/__NEW_TAG__/${TAG_COMMIT_HASH}/g" register-task.yaml # コミットハッシュ置換 - newTaskJson=$(aws ecs register-task-definition --cli-input-yaml file://register-task.yaml ) |
バッチサーバにリリースするファイル群の作成
で、次は aws ecs register-task-definition
の戻り値を jq
で処理してタスク定義を切り出して、タスク実行のyamlを作る。
1 2 3 |
- taskDefinitionArn=$(echo $newTaskJson | jq -r '.taskDefinition.taskDefinitionArn') - newRevision=$(echo $taskDefinitionArn | awk -F "/" '{print $2}') - sed -i -e "s/__TASK_DEF__/${newRevision}/g" release_to_batch_server/run-task.yaml |
zip作成は、appspec.yml
と、start-ECS.sh
と、run-task.yaml
などバッチサーバに必要なファイルをrelease_to_batch_server
ディレクトリに配置する。
で、buildspec.yml
で、こうしておけば、後は次ステージに回る。
1 2 3 4 |
artifacts: files: - '**/*' base-directory: release_to_batch_server |
後続の CodeDeploy
に処理させればよい。OKだ!
CodeDeploy ですること
今までの処理で作ったzipをバッチサーバに配布すれば良い。
バッチサーバは普通のEC2インスタンスだ。
エピローグ
よしこれでできた。develop
ブランチにマージされたらテスト環境にデプロイ。main
ブランチにマージされたら本番環境にデプロイするようにして、、、。完成だ!
同僚氏:ありがとうございます。。でもこれ、まだちょっと不便で、、、
えー。まだあるの?(続きは別の機会に。。。)