AWS Batch × GitLab CI/CD × CDKでPythonスクリプトを自動実行

AWS BatchでPythonスクリプトを自動実行する処理をAWS CDKを使って、コードベースで書きます。

CDK・Batchの実行スクリプト共に言語は、`Python`

CDKドキュメントに記載されているPythonコード通りに書いても、デプロイ時にエラーが発生し、苦戦しました。

## **アーキテクチャ概要**

### **サービスの役割**
- **GitLab CI/CD**  
   - アプリケーションコードをGitLabリポジトリにプッシュすると、CI/CDパイプラインがDockerイメージをビルド
   - ビルドしたイメージをAmazon ECRにプッシュ

- **Amazon ECR (Elastic Container Registry)**  
   - コンテナイメージを保存するAWSのDockerレジストリ
   - イメージはAWS Batchで使用

- **AWS Batch**  
   - スケーラブルなバッチ処理環境
   - ジョブキューとコンピュート環境を使用してジョブを管理

- **AWS CDK (Cloud Development Kit)**  
   - AWSリソースをコードで定義・管理
   - BatchやEventBridgeの設定を自動化

- **Amazon EventBridge**  
   - スケジュールルールでBatchジョブをトリガー
   - ジョブを自動実行。（今回は、毎朝7時）

### **データフロー**

1. **GitLab CI/CD → Amazon ECR**
   - 開発者がコードをGitLabリポジトリにプッシュ
   - GitLabの`.gitlab-ci.yml`で以下のプロセスを実行:
     - Dockerイメージのビルド
     - AWS認証を行い、ビルドしたイメージをECRにプッシュ

2. **Amazon ECR → AWS Batch**
   - AWS BatchがECRからコンテナイメージを取得
   - ジョブ定義に基づき、コンピュートリソースを動的に割り当ててジョブを実行

3. **Amazon EventBridge → AWS Batch**
   - EventBridgeのスケジュールルールを使用して、毎朝7時にBatchジョブをトリガー
   - CRON式でスケジュールを柔軟に管理可能

4. **ジョブ実行結果の確認**
   - ジョブ実行時のログはCloudWatch Logsに保存
   - ジョブのステータス (成功/失敗) はAWS Management ConsoleまたはCloudWatchで確認


## **実装手順**

1. **ECRリポジトリの作成**

GitLab CI/CDがDockerイメージをプッシュするためのECRリポジトリを作成します。

以下は、AWS CDKを使用したリソース作成の例です。


```python
from aws_cdk import aws_ecr as ecr

repository = ecr.Repository(
    self, "dev-batch_app", #CloudFormationで割り当てられるリソース名
    repository_name="dev-batch_app",
    image_scan_on_push=True,
    removal_policy=RemovalPolicy.DESTROY, #スタック削除時に削除する
    empty_on_delete=True
)
```

1. **GitLab CI/CD の設定**
`.gitlab-ci.yml` を使用して以下のパイプラインを作成します。

これにより、コードの変更時にDockerイメージをECRに自動プッシュします。
環境ごとにリポジトリを分けることを想定してます。

`.gitlab-ci.yml`
```yaml
stages:
  - deploy
  - build

.batch_app-docker_build:
  stage: build
  image: docker:latest
  script:
    - docker build -t $ECR_REPOSITORY_NAME .
    - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin <your_account_id>.dkr.ecr.$AWS_REGION.amazonaws.com
    - docker tag $ECR_REPOSITORY_NAME:latest <your_account_id>.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY_NAME:latest
    - docker push <your_account_id>.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY_NAME:latest

dev-docker:
  extends: .batch_app-docker_build
  variables:
    ECRRepositoryName: dev-batch_app
  only:
    - develop
```

3. **AWS CDKでAWSリソースの構築**

```Python
from aws_cdk import 
    aws_batch as batch,
    aws_events as events,
    aws_events_targets as targets

# Fargate用Batch コンピューティング環境の作成
compute_environment = batch.ComputeEnvironment(
    self, "devFargateComputeEnvironment", #CloudFormationで割り当てられるリソース名
    compute_environment_name="FargateComputeEnvironment",
    service_role=batch_service_role,
    spot=False, #Fargate_spotをオフ
    maxv_cpus=4,
    vpc=vpc #VPC
)

# ジョブキューの作成
job_queue = batch.JobQueue(
    self, "devJobQueue", #CloudFormationで割り当てられるリソース名
    job_queue_name="devJobQueue",
    priority=1,
    compute_environment_order=[
        batch.CfnJobQueue.ComputeEnvironmentOrderProperty(
            compute_environment=compute_environment, #環境変数
            order=1
        )
    ]
)

# ジョブ定義の作成
job_definition = batch.CfnJobDefinition(
    self, "devJobDefinition", #CloudFormationで割り当てられるリソース名
    job_definition_name="devJobDefinition",
    type="container",
    retry_strategy={}, #ジョブ試行回数
    platform_capabilities=["FARGATE"], #オーケストレーションタイプ
    timeout=batch.CfnJobDefinition.TimeoutProperty(
        attempt_duration_seconds=1800 #実行タイムアウト
    ),
    container_properties=batch.CfnJobDefinition.ContainerPropertiesProperty(
        image=repository.repository_uri, #ECRリポジトリURI
        fargate_platform_configuration=batch.CfnJobDefinition.FargatePlatformConfigurationProperty(
            platform_version="LATEST"
        ),
        execution_role_arn=refresh_tri_dairy_batch_role.role_arn,
        job_role_arn=refresh_tri_dairy_batch_role.role_arn,
        resource_requirements=[
            {'value': '1.0', 'type': 'VCPU'},
            {'value': '2048', 'type': 'MEMORY'}
            ],
        environment=environment_variables, #環境変数
        network_configuration=batch.CfnJobDefinition.NetworkConfigurationProperty(
            assign_public_ip="ENABLED"
        ),
        runtime_platform=batch.CfnJobDefinition.RuntimePlatformProperty(
            operating_system_family="LINUX",
            cpu_architecture="X86_64"
        )
    )
)
job_definition.apply_removal_policy(policy=RemovalPolicy.DESTROY) #スタック削除時に削除する

# EventBridge ルールの作成
rule = events.Rule(
    self, 
    'devEventBridgeRuleName', #CloudFormationで割り当てられるリソース名
    rule_name="devEventBridgeRuleName",
    schedule=events.Schedule.cron(minute='0', hour='22'), #毎日7時実行
    targets=[targets.BatchJob(
        job_queue.attr_job_queue_arn, #ジョブキューのARN
        job_queue, #ジョブキュー定義
        f"arn:aws:batch:{self.region}:{self.account}:job-definition/devEcrRepository", #ジョブ定義のARN
        job_definition #ジョブ定義
        )],
    enabled=True
)
rule.apply_removal_policy(policy=RemovalPolicy.DESTROY) #スタック削除時に削除する
```

## **参考**
- [AWS BatchでPythonスクリプトを定期的に実行する by Qiita コンソールイメージあり](https://qiita.com/yacchin/items/f9ed4197bc23dfae6f64)
- [コンテナイメージのビルド by classmethod](https://dev.classmethod.jp/articles/codebuild-batch-image-and-job-definition/)
- [AWS CDK ドキュメント](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_batch.JobDefinitionProps.html)
- [AWS Batch ドキュメント](https://docs.aws.amazon.com/ja_jp/batch/latest/userguide/batch-cwe-target.html)