ecspresso + ecschedule + Actionsでスケジュールタスクを自動更新

## はじめに
スケジュールタスクの管理・運用に便利な「ecschedule」について、別の記事で基本操作をご紹介しました。前回の記事では、ローカル環境で操作しタスクスケジュールを更新する方法を説明しました。

しかし、ecscheduleにはタスク定義を更新する機能がないため、実運用では別の方法でタスク定義を更新する必要があります。

そこで、ecspressoのタスク定義更新機能と組み合わせることで、タスクスケジュールの更新を自動化できるのではないかと考えました。
今回は、ecscheduleに加え、ecspressoとGitHub Actionsを組み合わせて、タスクスケジュールを自動更新する方法について書いていきます。



https://qiita.com/kento0805-naka/items/159c7433231e6dd2789d

## 書くこと
- ecspresso + ecschedule + Actionsでスケジュールタスクを自動更新する方法


## 前提
- AWS Fargate上でApache　httpdイメージからコンテナを起動する環境を用意
- ecspressoで使用する設定ファイルの用意
- AWSのnetwork周りをTerraformで管理

#### ディレクトリ構成
```
.

├── .github
│   └── workflows
│       └── deploy.yml
├── .gitignore
├── deploy
│   ├── ecs-task-def.json
│   ├── ecschedule.yaml
│   └── ecspresso.yml
└── terraform
    └── env
        └── prod
            ├── .terraform-version
            ├── .terraform.lock.hcl
            ├── main.tf
            ├── network.tf
            └── variable.tf
```

## Actionsの作成
Github Actionsを使ってスケジュールタスクの自動更新ワークフローを作成していきます。
大まかな処理の流れは以下の通りです。

1. ecspressoを使ってタスク定義の更新
2. 最新のタスク定義のリビジョンを取得
3. ecscheduleでスケジュールタスクを更新

```deploy.yml
name: 'deploy'

on:
  push:
    branches:
      - master

jobs:
  register-task-definition:
    runs-on: ubuntu-latest
    steps:
    - name: "checkoutする"
      uses: actions/checkout@v4
    - name: "ecspressoのインストール"
      uses: kayac/ecspresso@v2
      with:
        version: latest
    - name: "タスク定義の登録"
      run: |
        ecspresso register --config ecspresso.yml
      shell:
        bash
      working-directory: ./deploy
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_PROD }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_PROD }}
        AWS_DEFAULT_REGION: ap-northeast-1
  
  update-ecs-schedule:
    needs: ["register-task-definition"]
    runs-on: ubuntu-latest
    steps:
    - name: "checkoutする"
      uses: actions/checkout@v4
    - name: "ecscheduleのインストール"
      uses: Songmu/ecschedule@main
      with:
        version: latest
    - name: configure aws credentials
      uses: aws-actions/configure-aws-credentials@v2
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_PROD }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_PROD }}
        aws-region: ap-northeast-1
    - name: "ECSのスケジュールを更新する"
      run: |
        ecschedule -conf ecschedule.yaml apply -all
      shell: bash
      working-directory: ./deploy
      env:
        S3_TFSTATE_URL: ${{ secrets.S3_TFSTATE_URL_PROD }}

```

### タスク定義の更新
はじめに`register-task-definition`ジョブでタスク定義を更新しています。
まずはタスク定義の更新に必要なecspressoをインストールし、その後`register`コマンドでタスク定義を更新しています。
deployディレクトリ以下にあるecspresso関連の設定ファイルの内容は以下のようになっています。

```ecspresso.yml
region: "ap-northeast-1"
cluster: ecshedule-test
service: ecschedule-test
task_definition: ecs-task-def.json
timeout: "10m0s"

```

```ecs-task-def.json
{
  "containerDefinitions": [
    {
      "command": [
        "sh",
        "-c",
        "echo 'Hello World.' \u003e index.html \u0026\u0026 httpd-foreground"
      ],
      "cpu": 0,
      "essential": true,
      "image": "httpd:latest",
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-create-group": "true",
          "awslogs-group": "/ecs/ecschedule-test",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "name": "apache",
      "portMappings": [
        {
          "appProtocol": "http",
          "containerPort": 80,
          "hostPort": 80,
          "name": "apache-80-tcp",
          "protocol": "tcp"
        }
      ],
      "workingDirectory": "/usr/local/apache2/htdocs/"
    }
  ],
  "cpu": "256",
  "executionRoleArn": "arn:aws:iam::XXXXXXXXXXX:role/ecsTaskExecutionRole",
  "family": "ecschedule-test",
  "memory": "512",
  "networkMode": "awsvpc",
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "runtimePlatform": {
    "cpuArchitecture": "X86_64",
    "operatingSystemFamily": "LINUX"
  },
  "taskRoleArn": "arn:aws:iam::XXXXXXXXXXX:role/ecsTaskExecutionRole"
}

```

### スケジュールタスクの更新
`update-ecs-schedule`ジョブでは、最新のタスク定義リビジョンの取得とスケジュールタスクの更新を行います。ここで、`register-task-definition`ジョブで登録されたタスク定義のリビジョンを取得するために、`needs`キーワードを使って依存関係を作成しています。

~~最新のリビジョン番号を取得するには、AWS CLIの`aws ecs list-task-definitions`コマンドとシェルスクリプトを組み合わせています。取得したリビジョン番号はGitHub Actionsの環境変数`TASK_DEFINITION_REVISION`に追加され、後続のステップで利用可能になります。~~

~~今回は`--family-prefix ecschedule-test`で対象のタスク定義を指定していますが、皆さんの環境に合わせて適宜書き換えてください。~~

taskDefinitionにリビジョン番号を指定しなければ、自動的に最新のリビジョンを指定してくれるので上記の手順は必要ありませんでした。


`ecschedule.yaml`の設定は以下のようになっています。

```ecschedule.yaml
region: ap-northeast-1
cluster: ecshedule-test
rules:
- name: ecschedule-example
  scheduleExpression: rate(10 minutes)
  disabled: true
  targetId: test
  taskDefinition: ecschedule-test
  launch_type: FARGATE
  platform_version: LATEST
  network_configuration:
    aws_vpc_configuration:
      subnets:
      - {{ tfstate `aws_subnet.private_subnet_a.id` }}
      - {{ tfstate `aws_subnet.private_subnet_c.id` }}
      - {{ tfstate `aws_subnet.private_subnet_d.id` }}
      security_groups:
      - {{ tfstate `aws_security_group.default_sg.id` }}
      assign_public_ip: DISABLE
plugins:
- name: tfstate
  config:
    url: {{ must_env `S3_TFSTATE_URL` }}

```

#### 説明
##### タスク定義リビジョンの置き換え
- taskDefinitionのリビジョン番号部分を、最新のリビジョン番号である`TASK_DEFINITION_REVISION`に置き換えます

##### ネットワーク設定
- subnetsおよびsecurity_groupsの値は、tfstateから取得するように設定しています。これにより、TerraformのstateファイルからサブネットIDとセキュリティグループIDを動的に取得します

##### プラグイン設定
- tfstateプラグインを追加し、Terraformの状態ファイルが保管されているURLもしくはパスを指定する必要があります
- 今回はstateファイルをS3で管理しているのでs3のURLを入れています


この設定により、最新のタスク定義リビジョンを確実に取得し、それを使用してスケジュールタスクを更新することができます。サブネットとセキュリティグループの設定は、Terraformのstateファイルから動的に取得されるため、インフラストラクチャの変更や複数環境への反映に柔軟に対応できます。


## デプロイに必要な権限
上記Actionsを実行するために必要な権限は以下になります。
各用途毎にpolicyを分けています。

<details>
<summary>ecschedule-policy</summary>

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "events:TagResource",
                "events:PutTargets",
                "events:PutRule",
                "events:ListTargetsByRule",
                "events:ListRules"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
```
</details>

<details>
<summary>get-tfstate</summary>

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::hoge/terraform.tfstate"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::hoge" #tfstateを保管しているバケットを指定
            ]
        }
    ]
}
```
</details>

<details>
<summary>passrole</summary>

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "IAM",
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": [
                "arn:aws:iam::XXXXXXXXXXXX:role/ecsTaskExecutionRole",
                "arn:aws:iam::XXXXXXXXXXXX:role/ecsEventsRole"
            ]
        }
    ]
}
```
</details>

<details>
<summary>register-ecs-task-def</summary>

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ecs:RegisterTaskDefinition",
                "ecs:ListTaskDefinitions",
                "ecs:DescribeTaskDefinition"
            ],
            "Effect": "Allow",
            "Resource": "*",
            "Sid": "ECSRegisterTaskDefinition"
        }
    ]
}
```
</details>


## 実行結果
実際に動くか検証していきます。
タスク定義のcomanndを書き換えてapacheのバージョンを出力するように書き換えます。

```diff
command": [
-  "sh",
-  "-c",
-  "echo 'Hello World.' \u003e index.html \u0026\u0026 httpd-foreground"
+  "httpd",
+  "-v"
]
```

変更をコミットしてリポジトリにpush

![スクリーンショット 2024-06-23 20.10.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295060/0bfe4d34-631a-40fe-e721-824bf4b5195a.png)

ワークフローが実行完了したことを確認

![スクリーンショット 2024-06-24 1.47.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295060/a24f05e6-85c6-b106-8ae5-14f46ebe8c72.png)

Cloudwatch logsにApacheのバージョンが記録されているので、更新したスケジュールタスクの処理が完了している

## まとめ

今回は、ecschedule、ecspresso、そしてGitHub Actionsを使ってスケジュールタスクを自動更新する方法を紹介しました。
自動化することで、タスク定義を更新した際にスケジュールタスクの設定を変更し忘れるといったミスを防ぐことが期待できます。

皆さんの環境に合わせて設定を調整し、より効率的なスケジュールタスクの運用を目指していきましょう！！