AWS Batchを使ってみた

# はじめに
Hubbleでバックエンドエンジニアをしている @yuto_1220 です。[Hubble Advent Calendar 2023](https://qiita.com/advent-calendar/2023/hubble)の6日目です。

私は最近、クラウド電子契約サービスに存在する契約書をdailyでHubbleに取り込むというプロジェクトに携わったのですが、その際にHubbleとして初のAWS Batchを導入してみたので、AWS Batchを使うための最低限の知識やHubbleでの使い方、自身がAWS Batchを使う上で困った点などを記していこうと思います。

# なぜAWS Batchを使用したのか
前提としてHubbleはIaaSとしてAWSを使用しています。
「_クラウド電子契約サービスに存在する契約書をdailyでHubbleに取り込む_」はまさに典型的なバッチ処理とも言うべき処理ですが、これをAWSのサービスで実現するにあたってCTOと相談した結果は以下のどちらかのアプローチでいこうという話になりました。

1. Amazon SQS + AWS Lambda
2. AWS Batch

最終的にはAWS Batchの2の方法を選択したわけですが、理由としては

1. AWS Lambdaの[15分制約](https://aws.amazon.com/jp/blogs/news/aws-lambda-timeout-15min/)の存在
1. HubbleではAWS Batchは使ったことがなかったので技術的な挑戦
1. ~~フルマネージドなのでスケールなどの面倒な一切合切を丸投げできそうという私の怠惰な考え方~~

で、AWS Batchを使用してみることしました。

ちなみに、AWS BatchではオーケストレーションタイプとしてFargate, Amazon EC2, Amazon EKSを選択することができますが、今回は**Fargate**を選択しました。
Amazon ECRの既存のイメージを使いまわせそうというところと、AWS Batchは特別な理由がない限りは[Fargateを推奨](https://docs.aws.amazon.com/ja_jp/batch/latest/userguide/fargate.html#when-to-use-fargate)しているのが理由です。

:::note warn
今回はオーケストレーションタイプとしては**Fargate**に焦点を当てた記事なのでAmazon EC2などに必ずしも当てはまらない可能性があることに留意ください
:::

# AWS Batchを使うための最低限の知識
AWS Batchを最短で動かすために最低限これだけ知っておけば良いであろうことを説明します。

## AWS Batchを動かす3つのリソース
AWS Batchは基本的には以下の3つのリソースの設定を行うことで動くようになります。

### ジョブ定義
ジョブに関する基本的な設定を行います。オーケストレーションタイプとしてここでFargate, Amazon EC2, Amazon EKSを選択することが可能です。今回のプロジェクトでは実行ロールを`ecsTaskExecutionRole`で、コンテナ設定のイメージの指定箇所では社内で使っているAmazon ECRのイメージを指定しました。

### コンピューティング環境
ジョブをどういった環境で実行するかの設定になります。コンピューティング環境の設定でもFargate, Amazon EC2, Amazon EKSを選択します。このコンピューティング環境の設定では主にVPC周りの設定を行います。

### ジョブキュー
AWS BatchにおけるAmazon SQSのようなものです。ここでもFargate, Amazon EC2, Amazon EKSを選択します。スケジューリングポリシーなどを設定しなければ一般的なFIFOになるようです。ここでは使用するコンピューティング環境を指定するのが主な設定項目のようです。なので、先にコンピューティング環境の設定を行なっておくと順番的に良さそうですね。

## AWS Batchはどう動いている？
[AWS Batchを動かす3つのリソース](#aws-batchを動かす3つのリソース)を説明しましたが、それがどう結びついて動いているのかを以下で説明します。

![Qiita.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599964/a3fd171a-0365-ea09-eb8d-1ab794391fc8.png)

:::note warn
上記画像については、ジョブキューに対してコンピューティング環境は複数設定できるので実際には**1:1ではなく1:N対応**になる可能性もありますが、画像では説明を簡易にするために1:1のように作成しています
:::

### ①ジョブ定義に従って新しいジョブが作成されて、指定のジョブキューにエンキューする
AWSのコンソール画面やCLI, SDKなどからも新規のジョブを作成することができ、指定のジョブキューにそれをエンキューします。この際には、ジョブごとにジョブ定義のパラメータを上書きすることができます。[後述](#ジョブ定義の上書き)しますが、今回のプロジェクトでも一部パラメータを上書きしてジョブを実行しています。

### ②キューに入っているジョブを取り出して実行する
[こちら](#ジョブキュー)でも記述していますが、スケジューリングポリシーを設定して発展的にジョブの実行順序を設定することも可能なようですが、デフォルトではキューとしては一般的なFIFOになります。

### ③各ジョブが実行される
デキューされたジョブが各自実行されるのですが、AWS Batchのダッシュボードではジョブの状態をリアルタイムに確認することが可能です。メモリやキューの設定によっては直列に実行されたり、並列に実行されるなど様々な実行順があるかと思います。[ジョブの依存関係](https://docs.aws.amazon.com/batch/latest/userguide/job_dependencies.html)も指定できるようです。

![console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599964/15e17abf-f6e2-4741-9367-0cf30b879114.png)

# Hubbleでの使い方
Hubbleでは主なコンピューティングリソースとしては上述のようにECSを用いています。
そして、ECSには[スケジュールされたタスク](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/scheduled_tasks.html)を設定することができます。このスケジュールされたタスクで1日1回にある時刻を設定して、Hubbleアプリケーション内のスクリプトを実行します。そのスクリプトはDBから必要な情報を抽出してきて、AWS Batchの指定のジョブキューに必要なジョブ群をエンキューさせるようにしています。

今回のプロジェクトではエンキューさせたジョブ群は順序は関係なく実行されて問題ないため、スケジュールポリシーなどは設定はしておらず、エンキューされたものからすぐに`RUNNABLE`のステータスになり実行されるようになっています。

# 自身が直面した困った点
## DataDogとの連携
Hubbleではログ監視ツールとしては主にDataDogを用いています。そして、ECSだとログの設定にはFireLensなどを用いてカスタム的にDataDogなどへログを流せるようになっていますが、AWS Batchだとその辺りがどうやら一筋縄ではできなさそうです。
何とかやる方法があるのかを検証し切れていないですが、ひとまずCloudWatchにはログが流れているのでそこで確認するようにしました。また、CloudWatchに流れているログをそのままDataDogに横流しする仕組みも社内にあるので、それで良いかなと言う結論に至りました。

## ポリシーのアタッチ
ECSのスケジュールされたタスクでスクリプトを実行していると[前述](#hubbleでの使い方)したのですが、そのスクリプト実行の際に、SDKで使用している`submit_job`メソッドの実行権限が無いと言うエラーが返ってくることがありました。実行ユーザーにポリシーがアタッチされて無かったのです。
初歩的ではありますが、こういった実行権限やセキュリティグループ、VPCなどの設定を見落としていると上手く動かないこともあるかと思うので注意したいところです。（この辺りはAWS Batchに限定された話ではないですが。）

## ジョブ定義の上書き
ジョブ定義には「コンテナ設定」という設定項目があるのですが、そこでジョブ実行時に実行するコマンドを指定できます。手探り状態でAWS Batchを使用していた時には「あれ？実行コマンドは固定なのか！？」と思って少し焦ってしまったのですが、よくよくドキュメントを見ると、`container_overrides`でジョブ定義を上書きできる旨がしっかりと記述されてありました。
実行コマンドをジョブごとに動的に指定したい場合は`submit_job`メソッドだと以下のように`container_overrides`の項目があるので、ここで実行するコマンドを以下のように動的に指定することができます。
```ruby:script.rb
    job_id = 1
    client.submit_job({
        # 省略
        container_overrides: {
            command: ["any", "job_#{job_id}", "command"]
        }
    })
```

# まとめ
私はHubbleにジョインしてからAWSを本格的に触り始めましたし、AWS Batchは社内でも使用したことが無かったので初めて尽くしのプロジェクトとなりました。

動かせるようになるまでは私は少し時間がかかりましたが、この記事を読んだことによって早くAWS Batchを意図した通りに動かせるようになった方が増えると幸いです。

まだ、AWS Batchは使い始めたばかりなので、より良いパフォーマンスを求めて色々と調整する余地は大いにありますが、それは追々やっていければと思います。例として今回はあまりメモリ配分の最適化などを考慮していないのでこの辺りを再考したり、AWS Batchの設定をTerraformなどで管理できるようになればと考えています。

次の記事はイスラム世界が産んださすらいのフロントエンドエンジニア@kareemさんです！

# 参考
AWS Batch ユーザーガイド：https://docs.aws.amazon.com/ja_jp/batch/latest/userguide/what-is-batch.html
AWS SDK Ruby：https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Batch.html