S3にファイルが置かれたらStep Functions+Glueで処理したい（リソースは1つ）

# はじめに
皆さん、イベント駆動処理していますか？
スケジュールを前もって決められない場合などに便利ですよね。
ただ、イベントが増えすぎると、それに伴い処理するリソースが増えていってしまいます。

例えば、S3にファイルが置かれたら、EventBridgeで発火、Step Functionsを起動してそのステートマシンでGlueを実行する処理を考えます。
以下は、ファイル数が3つの場合です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/e16ab50a-e66e-8316-b248-f0b491449a57.png)

このように、ファイルに応じてリソースを増やしていくと、管理が大変になります。

一方で、このように一本化できると、リソース管理が楽になりますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/94627eb6-dea3-fbfb-8760-e77be8c499e1.png)

今回は、この構成で、S3バケットに置かれたファイルのバケット名とプレフィックスをGlueでログに出力する処理を実行してみます。

# やってみた
上記の図で左にいるものは右のリソースに依存しているので、一番右のリソースから作成していきます。
また、今回重要なのは、S3 bucketのバケット名とプレフィックスを**EventBridgeからStep Functions、Step FunctionsからGlueへとバケツリレーしていくこと**です。
バケツを受け渡すときに、どのように指定するのかがポイントになります。
その渡し方を確認するために、一度Step Functionsは仮で作成して、お試し実行してみます。そうすることで、EventBridgeからどんなパラメータが渡ってくるかを確認できます。

## Glueジョブを作る
今回はパラメータが渡ってくるバケツリレーを確認できれば良いので、ファイルが置かれたS3のバケット名とプレフィックスをログに出力するジョブを作成します。

```python
import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job

## @params: [JOB_NAME]
args = getResolvedOptions(sys.argv, ['JOB_NAME', 's3_bucket', 's3_key'])

sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args['JOB_NAME'], args)

s3_bucket = args['s3_bucket']
s3_key = args['s3_key']

print("s3_bucket")
print(s3_bucket)
print("s3_key")
print(s3_key)

job.commit()
```

ジョブパラメータとして`--s3_bucket`, `--s3_key`を定義して、それを使ってStep Functionsから値を受け取ります。

:::note warn
Job detailsでジョブパラメータを定義できますが、動的にジョブパラメータを指定する際はそこで定義しないでもOKです。
:::

また、Job detailsで`Maximum concurrency`の値を10くらいに変更しておきます。これは、このジョブをいくつ並行して実行できるかを指定する値です。デフォルトは1ですが、そのままだとS3に複数ファイルが一気に置かれた場合、1つしかジョブが実行できません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/306ab75b-8694-3a9e-d0a9-caf331083aeb.png)

## Step Functionsのステートマシンを仮で作る
Glueを呼び出すステートマシンを作ります。ただし、EventBridgeからどんなイベントが渡ってくるのかよくわからないので、とりあえず仮作成して実行してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/ef426e96-ce5d-813f-e4eb-35c6c3a945de.png)

APIパラメータは以下の通りで、先ほど作成したGlueジョブの名前を指定します。
```json
{
  "JobName": "20241007_event_job"
}
```

これだとただGlueジョブを実行するだけで、ジョブに何も渡しませんが、一旦これで進みます。

## EventBridgeを作る
以下のS3パスに、ファイルを更新していくことを考えます。

- `s3://bucket-name/test/input_1`
- `s3://bucket-name/test/input_2`
- `s3://bucket-name/test/input_3`

上記のバケット、プレフィックスをイベントパターンに指定します。
```json
{
  "source": ["aws.s3"],
  "detail-type": ["Object Created"],
  "detail": {
    "bucket": {
      "name": ["bucket-name"]
    },
    "object": {
      "key": [{
        "prefix": "test/"
      }]
    }
  }
}
```

`"prefix": "test/"`と指定することで、test配下の全てのフォルダが更新されたことを検知してくれます。

ターゲットには、先ほど作成したステートマシンを選択します。

## 試しに実行してみる
試しに、S3に適当なファイルを置いてみます。
EventBridgeが発火し、ステートマシン、Glueが実行されますが…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/433e3860-1795-78a0-f348-090d566b5ff5.png)

Glueは失敗しています。ステートマシンで、ジョブパラメータを設定していないためです。

一方、ステートマシンは成功していますが、重要なのはどんなパラメータが入力されているかどうかです。
実行結果の、Glue StartJobRunの入力タブを見てみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/7a8351ef-06f0-6b4f-35ea-08f77c620175.png)

このようなパラメータが渡ってきていますね。今回ほしいのは、オレンジで一部の情報を隠した部分になります。

## ステートマシンを修正する
入力パラメータと、どの部分が欲しいかが分かったので、それを修正します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/80c09ea4-c3f9-ee3d-7704-3e06323336be.png)

APIパラメータを以下のように設定することで、EventBridgeから渡ってきたパラメータを、Glueのジョブパラメータとして受け渡します。この指定の方法がこの記事の全てと言っても過言ではありません。

```json
{
  "JobName": "20241007_event_job",
  "Arguments": {
    "--s3_bucket.$": "$.detail.bucket.name",
    "--s3_key.$": "$.detail.object.key"
  }
}
```

## 実行してみる
S3に、ファイルを3つおいてみました。

ステートマシンは、3回起動が成功しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/4ccc6503-2290-87d5-8b0e-2ad833347ca8.png)

同じくGlueも3回起動が成功しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2949357/5047a8f3-1ba1-f570-b5fa-be2eb095f6c5.png)

それぞれのログを見てみると、想定通り以下のように出力されていました。

```
s3_bucket
bucket-name
s3_key
test/input_1/test1.txt
```

```
s3_bucket
bucket-name
s3_key
test/input_2/test2.txt
```

```
s3_bucket
bucket-name
s3_key
test/input_2/test2.txt
```

# おわりに
S3のイベントの情報がそのままStep Functionsに渡されてくるので、それをGlueのジョブパラメータとして渡してあげるところがポイントでしたね。逆にそれが分かれば結構簡単にできたなという印象です。
EventBridge側でパラメータを変換することもできるので、それができるとステートマシン側の変数指定が少し楽になりそうです。そちらも今度試してみたいですね。