ECS環境で動作しているRuby on Railsの環境に、EventBridge Ruleでバッチ処理を組み込む

# 概要
表題の構成でバッチ処理を構築したので、その構築方法について記載します。

# 前提条件
:::note warn
前提条件一覧
- `ECS`環境で`Ruby on Rails`のアプリが動作している
- `Terraform`がインストールされている
- `ECS`や　`Network`関連(`ALB`、`IAM`、セキュリティーグループ等)が`Terraform`で構築されている
- `Ruby on Rails`のバージョンは、7.0以上
:::

# 作成手順
## Ruby on Rails
### 初期設定
- `EventBridge Rule`から`rails runner`(以下、`r`に省略)でコマンド叩く際、`path`の入力を行わないで済む様にする為、下記を追加した
```config/application.rb
config.eager_load_paths << Rails.root.join("lib", "batches")
```
上記の設定をした事で、ローカル環境では、下記のコマンドでバッチ処理の確認が可能
```sh
rails r "testBatch.execute"
# or 
bundle exec rails r "testBatch.execute"
```

#### メリット
- 大規模なアプリケーションになった際、パフォーマンスの向上に繋がる
- 特定のディレクトリ内のすべてのファイルが自動的に読み込まれる為、コードの整理が容易

#### 補足
- `config.eager_load_paths`の記述を書かない場合は、`lib`からの`path`を指定してする事で、作成したバッチ処理のコードを実行する事が可能
```sh
rails r lib/batches/my_script.rb
```

### バッチ処理の作成
- `lib`内に`batches`ディレクトリーを作成する
(バッチ処理のディレクチリー名は、任意の名前の設定でも良い)
- batchesディレクトリー内に、バッチ処理を行いたいファイルを記載する
```sh
# ディレクトリー作成
mkdir lib/batches

# ファイル作成
touch lib/batches/test.rb
```

- `require "logger"`を追加して、ログを出力する
- `ActiveRecord::Base.connection.execute('test')`は、ActiveRecordを通じて直接SQLを実行する
- 今回の処理では、何らかのバッチ処理を動作させた仮定して、`ActiveRecord::Base.connection.execute`の処理を実装した
```rb
require "logger"

class TestBatch
  def self.execute
    logger = Logger.new(STDOUT)
    logger.info("----- batch を実行します。 -----")
    ActiveRecord::Base.connection.execute('test')
    logger.info("----- batch を実行しました。 -----")  
  end
end
```

## Terraform
- 各表題で記載されている内容を`tf`ファイルに記載する
- コメントアウトで注意点や設定内容について自身の見解を述べている

### ディレクトリ構成
- 下記の構成で組んでいます。必要に応じて変更して下さい
- `main.tf`、`variables.tf`の内容に関しては、各々の書き方があるので、開発手法に応じて臨機応変に対応して下さい
```sh
.
├── main.tf
├── variables.tf
├── # その他のファイルは省略
├── ecs/
│   ├── task.tf
│   ├── cluster.tf
│   ├── service.tf
│   └── variables.tf
├── eventbridge/
│   ├── eventBridgeRule.tf
│   ├── variables.tf　
│   └── output.tf
└── iam/
    ├── iam.tf
    ├── variables.tf
    └── output.tf
```


### ロールの作成
```iam.tf
#------------------------------------------------------------------------------
# CLOUDWATCH EVENT ROLE
#------------------------------------------------------------------------------
resource "aws_iam_role" "scheduled_task_cw_event_role" {
  name = "batch-st-cw-role" 
  
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "events.amazonaws.com"
        }
      }
    ]
  })
}

# ECSのポリシーをアタッチ
resource "aws_iam_role_policy_attachment" "scheduled_task_cw_event_role_cloudwatch_policy" {
  # 付与したいロールを記載
  role       = aws_iam_role.scheduled_task_cw_event_role.name
  # AWSのポリシーのarnを記載している(独自のポリシーを付与する事も可能)
  policy_arn = "arn:aws:iam::aws:policy/AmazonECS_FullAccess"
}

```
- `name` : 任意の名前で良い。変更する場合は、リソース識別子を合わせて下さい
- `assume_role_policy` : EventBridge Ruleに操作権限を持たせる為、権限を一時的に移譲する。これにより、他のAWSサービス（ここではEventBridge）がこのロールを使用して他のAWSリソースに対して、アクションを実行する事が可能となる



```output.tf
output "scheduled_task_cw_event_role_arn" {
  value = aws_iam_role.scheduled_task_cw_event_role.arn
}
```

#### Terrform構築時の参考資料
- [[hashicorp] aws_iam_role_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment)
- [[hashicorp] aws_iam_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role)


### バッチ処理の作成
```eventBridgeRule.tf
#------------------------------------------------------------------------------
# CLOUDWATCH EVENT RULE
#------------------------------------------------------------------------------
resource "aws_cloudwatch_event_rule" "event_rule" {
  name = "test-batch"
  schedule_expression = "cron(30 18 * * ? *)"
  state               = "ENABLED" # ENABLEDにする事で、ルールが有効化される

  # 任意で記述
  tags = {
    Name  = "test-batch"
  }
}

#------------------------------------------------------------------------------
# CLOUDWATCH EVENT TARGET
# #------------------------------------------------------------------------------
resource "aws_cloudwatch_event_target" "ecs_scheduled_task" {
  target_id = "testBatch"
  rule      = aws_cloudwatch_event_rule.event_rule.name
  arn       = # ECSクラスタのarnを記述する
  role_arn  = # 上記で作成したiamロールのarnを記述する
  
  # nameとcommandの順番が入れ替わる為、ヒアドキュメントで記載
  input = <<EOF
  {
    "containerOverrides": [
      {
        "name": "test_rails_container",
        "command": ["bundle","exec","rails","runner","testBatch.execute"]
      }
    ]
  }
  EOF

  # ECSタスクを設定するためのブロック
  ecs_target {
    task_definition_arn = # ECSのタスク定義のARNを記述する
    group               = "production_batch" # 任意の名前で設定
    task_count          = "1"
    launch_type         = "FARGATE"
    platform_version    = "LATEST"

    # タスクのネットワーク設定を定義
    network_configuration {
      assign_public_ip = true # 必ずtrueにする
      security_groups  = ["sg-0123456789abcdef0", var.sg_id] # セキュリティーIDを記述する(複数記述可能。また、表記内容は、参考例を記述している)
      subnets          = ["subnet-0123456789abcdef0", var.public_subnet_a_id] # サブネットIDを記述する(複数記述可能。また、表記内容は、参考例を記述している)
    }
  }
}

```
- `schedule_expression` : `rate`と`cron`を使用して処理の実行期間を設定する
- `rate` : 定期実行したい場合に適している。シンプルな繰り返しタスクに向いている
- `cron` : 柔軟なスケジュールが必要な場合に使用する。特定の曜日や月など、複雑なスケジュール設定が可能
- `cron(30 18 * * ? *)` : # 左から[分、時、日、月、曜日、年]を指定する。曜日に関しては、`?`の設定で良い
- `assign_public_ip` : FargateタスクにパブリックIPを割り当てるための設定。ECRからイメージを取得できず、コンテナが起動しない為、必ず`true`にする

#### Terrform構築時の参考資料
- [[hashicorp] aws_cloudwatch_event_archive](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_archive)
- [[hashicorp] aws_cloudwatch_event_target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_event_target)
- [ヒアドキュメント](https://ja.wikipedia.org/wiki/%E3%83%92%E3%82%A2%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88)


# 参考資料
- [Amazon EventBridge ルール](https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-rules.html)
- [[公式] EventBridge IAM ロール](https://docs.aws.amazon.com/ja_jp/batch/latest/userguide/CWE_IAM_role.html)
- [[公式] ecs-fargate-scheduled-task](https://github.com/cn-terraform/terraform-aws-ecs-fargate-scheduled-task)


# まとめ
今回は、`Ruby on rails`と`EventBridge Rule`を使用したバッチ処理を作成しました。時間制御で行うバッチ処理に関しては、公式が推奨している[Amazon EventBridge Scheduler](https://docs.aws.amazon.com/ja_jp/scheduler/latest/UserGuide/what-is-scheduler.html)を使用した方が良かったんですが、会社の意向で`EventBridge Rule`を使用して作成しました。ただ、処理自体に大きな差はないので、`EventBridge Rule`でも問題はないです。機会があれば、`Amazon EventBridge Scheduler`で構築したバージョンも記事として載せたいと考えています。