【AWS ECS】データ補完処理をローカル処理からECSに変えたら、「実行時間：16日間→2日間」という爆速で処理が完了した話

## 経緯
今まで、ローカルPCでSAMを起動し、EC2踏み台サーバーを通して、プライベートサブネットのRDSに接続、SQLコマンドをPythonで叩いて、データ補完をしていました。
処理時間は、1日分の処理につき、実行時間が約1時間半かかるデータ補完処理です。

ローカルでDockerを起動するので、EC2インスタンス起動代だけなので、
お金はかかりませんが、「今後もデータ補完するときに課題になるな、、」と感じてました。

## AWSのサポート様に問い合わせる
AWSのサポート様に、
「データ補完処理で困ってるんですが、良いサービスはありませんかね・・・？」と聞いてみました。
```
(AWSサポート様の回答) ECS使うと良いですよー。
```
なるほど。ECSですか。
良い機会なので、検証も兼ねて試してみることに。


## 爆速でデータ補完できた
|実行環境|ECS(Fargate)<br>vCPU: 1vCPU<br>メモリ:3 GB|(従来)<br>ローカルPCでの実行(SAM)|
|:--:|:--:|:-:|
|データ補完にかかる日数|**1.7日**|16.0日|
|ローカルPC実行との削減率|**89%**|-|
|データ補完にかかる料金|**374.4円**|0円|

[内訳]
ECSを1時間稼働させた時の料金: 9.0円
稼働時間: 41.6時間
データ補完にかかる料金: 9.0円 * 41.6時間 = 374.4円

ローカルPC実行(SAM)と、ECSを使った際の比較です。

驚異の89%の削減率！
料金も想定よりずっと安い！
処理時間も大幅に削減できました！

※料金について
AWS公式の料金表と、「ざっくりAWS様」にて算出しました。

https://aws.amazon.com/jp/fargate/pricing/

https://aws-rough.cc/fargate/

## 大幅に短縮できた要因
処理時間が大幅に短縮できた要因としては、以下が考えられます。 
※あくまで考察です。
```
1. ECSとRDSを同じプライベートサブネットに配置しているから 
(※ローカルPC実行のように、EC2踏み台サーバーを経由する必要がなくなったから)  
2. AWSの処理リソースである、Fargate自体が演算処理のリソースとして優秀だから
```

## 今回のAWS構成図
![10_ECS_structure.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/5aa83f73-6d71-1080-f0f7-ce184389859a.png)

今回は、上記の構成で検証しました。
本記事では、サブネットの作成やRDSの配置・作成などは省略します。
※AWS Secrets Managerは、「シークレット名：postgres」で設定しています。
以下の3点に絞って、記事を投稿します。
```
1. DockerイメージをECRにPushする
2. ECS関係の設定をする
3. ECSを実行する
```

##  今回の記事にて、関係するAWSリソース
```
- Amazon ECS
- Amazon ECR
- AWS Fargate
- AWS Secrets Manager
- Amazon RDS for PostgreSQL
```

### Amazon ECS
- AWSでDockerコンテナ実行する際のオーケストレーションサービス。(いわゆる、コンテナを管理するもの)
- このサービスは管理がメインなので、コンテナ実行は、AWS Fargateで行います。

https://aws.amazon.com/jp/ecs/

https://qiita.com/leomaro7/items/deb114e5ec209a044e5d

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/a49d9c94-1075-c1e7-4bc3-0cdca9b5c1ec.png)

### Amazon ECR
- コンテナイメージをリポジトリとして管理するサービス。
- ECSは、このECRのリポジトリをPullしてきて、実行するようにします。

https://aws.amazon.com/jp/ecr/

https://qiita.com/miyuki_samitani/items/efe3f8a1bd16b4c0c4f1

### AWS Fargate
- サーバーレスで演算実行できるサービス。
- 従量課金制だが、実行環境のvCPUとメモリの設定を適切に設定(オートスケーリングなど)をすれば、金額は抑えられる。

https://aws.amazon.com/jp/fargate/

https://qiita.com/leomaro7/items/a3edb49d9929dd42cc0d

### AWS Secrets Manager
- データベース認証情報や API キーなどの機密情報を管理できるサービス。

https://aws.amazon.com/jp/secrets-manager/

https://qiita.com/mm-Genqiita/items/f93285a6058c64b39f23

### Amazon RDS for PostgreSQL
- RDS。AWSでPostgreSQLを使えるサービス。

https://aws.amazon.com/jp/rds/postgresql/

## コード・ツリー図
今回は、ECSからRDSへ接続できるかを確認できる、
コードを準備します。

コードは、以下の記事を参考にさせて頂きました。

https://qiita.com/kuromame1020611/items/8a19ae734f2925b42b20

```
.
└── repo-ecs/
    ├── main.py
    ├── Dockerfile
    └── requirements.txt
```

```main.py
from time import sleep

import json
import psycopg2
import boto3


try:
    print("RDS: 接続成功")
except Exception as e:
    print("RDS: 接続失敗")


def connect_rds():
    # SecretsManagerからRDSの認証情報を取得
    client = boto3.client(service_name='secretsmanager')
    get_secret_value_response = client.get_secret_value(
        SecretId='postgres')
    secret_info_rds = json.loads(get_secret_value_response['SecretString'])

    # DBの接続設定
    connection_config = {
        'host': secret_info_rds['host'],
        'port': secret_info_rds['port'],
        'database': secret_info_rds['dbname'],
        'user': secret_info_rds['username'],
        'password': secret_info_rds['password'],
        'connect_timeout': 3,
    }

    # 接続
    connection = psycopg2.connect(**connection_config)

    return connection


if __name__ == '__main__':
    # RDSへ接続
    con = connect_rds()
```

```requirements.txt
boto3
psycopg2
```

#### Dockerfile
```Dockerfile
# RUNはイメージをビルドする際に使用するコマンド
# WORKDIRは作業ディレクトリを指定するコマンド
# COPYはイメージにファイルやフォルダを追加するコマンド

# pythonの3.8をベースにする
FROM python:3.8-buster

# 作業ディレクトリを指定
WORKDIR /

# pipのアップグレード
RUN pip install --upgrade pip

# requirements.txtを配置
COPY requirements.txt /

# ライブラリをインストール
RUN pip install -r requirements.txt

# ソースコードの配置・実行
# Pythonはデフォルトで buffer をするようになっているため、"-u"で buffer をしないように設定する。
COPY main.py /
CMD [ "python", "-u", "/main.py" ]
```

## 設定手順

### 1. DockerイメージをECRにPushする
#### 1.1 ECRにアクセスして、「リポジトリを作成」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/35f3c6f7-5af5-04f1-53d6-fcb8e07e01ab.png)

#### 1.2 リポジトリ名を入力して、作成
今回は、「repo-ecs」とします。
![スクリーンショット 2023-02-20 192810.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/f6c63be3-f0b3-1059-61c9-72a268355b8c.png)

#### 1.3 リポジトリをPushする
作ったリポジトリを選択し、「プッシュコマンドの表示」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/f8cc9915-328d-3225-c009-8ff73df7dbd4.png)

プッシュコマンドが表示されるので、案内通りにコマンドを打ちます。
![スクリーンショット 2023-02-20 192812.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/eea614d6-2a86-36e4-1241-b94bc6a27e31.png)

コマンド実行後、ECRを確認すると、イメージがPushされたことが確認できます。

### 2. ECS関係の設定
次は、ECS関係の設定を行います。

#### 2.1 セキュリティグループを作成する
以下の3つのセキュリティグループを作成します。
EC2の管理画面から、「セキュリティグループ」を選択し、作成します。

```
1. ECS用のセキュリティグループ(作成名：ecs-sg)
    ※インバウンドルールには、HTTPSのTCPポート番号443を設定してください。
2. RDS用のセキュリティグループ(作成名：rds-sg)
3. SecretsManager用のセキュリティグループ(作成名：sm-sg)
```

それぞれを作成後、インバウンドルールの編集で以下のようにルールを追加します。
```
1. ecs-sgに、rds-sgを追加
2. rds-sgに、ecs-sgを追加
3. sm-sgに、ecs-sgとrds-sgを追加
```

#### 2.2 ECR用のエンドポイントを追加する

今回、Fargateの1.4.0を使うので、ECRのエンドポイントが必要になります。
EC2の管理画面から、「エンドポイント」を選択し、作成します。

https://dev.classmethod.jp/articles/vpc-endpoints-for-ecs-2022/


```
1. com.amazonaws.region.ecr.dkr (作成名：ecr-dkr-endpoint)
2. com.amazonaws.region.ecr.api (作成名：ecr-api-endpoint)
3. com.amazonaws.region.s3（ゲートウェイ型）(作成名：ecr-CloudWatchLogs-endpoint)
```

作成する際には、ECS用のセキュリティグループ(作成名：ecs-sg)を選択してください。

以上で、ECR用のエンドポイントの追加は完了です。


#### 2.3 ECSの設定・タスク定義を行う
ECSのコンソール画面を開き、左側のメニューから「タスク定義」を選択します。
![スクリーンショット 2023-02-21 190634.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/7d6ebbc8-3f06-8c8e-9c57-9fe6a1121a26.png)

「新しいタスク定義の作成」を選択します。
![スクリーンショット 2023-02-21 190842.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/576ae075-aec9-c6bb-3439-cd33b5babd9e.jpeg)

「タスク定義とコンテナの設定」の設定を行います。
- タスク定義ファミリー：任意(今回：taskConnectionRDS)
- コンテナの詳細 
    - 名前：任意 (今回：containerConnectionRDS)
    - イメージ URI：ECRのURI名(:latestまで入れましょう)
    ※ECRのURI名は、ECRのリポジトリで確認できます。

![スクリーンショット 2023-02-21 191239.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/8f8daf22-da2a-08ab-2ef8-b1656f3d6b08.jpeg)

設定後、「次へ」を選択。

「環境、ストレージ、モニタリング、タグの設定」の設定 を行います。
- アプリケーション環境：Fargate 
- タスクサイズ：任意
    ※スペックによって、料金が変わるので注意。
    　私の場合、1vCPU・3GBの初期設定でも十分な性能でした。
- タスクロール、タスク実行ロール：ecsTaskExecutionRole(このロールは自動で作成されます。）
![スクリーンショット 2023-02-21 192057.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/a460f26a-0011-0923-b304-c5733924eb02.jpeg)


タスク定義が作成されました。
![スクリーンショット 2023-02-21 192347.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/721b7ab7-b62a-35b9-b7a9-77b8af3e594c.jpeg)


#### 2.4 ECSの設定・クラスター作成を行う
次は、クラスター作成を行います。

ECSのコンソール画面を開き、左側のメニューから「クラスター」を選択します。
![スクリーンショット 2023-02-21 192707.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/b2679281-b1c4-e67f-06e6-b7779150b23b.jpeg)


「クラスターの作成」を選択します。
![スクリーンショット 2023-02-21 193003.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/07508aa6-dbcd-e8de-2c30-36226d01577a.jpeg)

「クラスターの作成」の設定
- クラスター名：任意 (今回：clusterConnectionRDS) 
- ネットワーキング:
    - VPC：任意のVPC
    - サブネット：プライベートサブネットの２つ 
	※パブリックサブネットは選択しない点に注意。
![スクリーンショット 2023-02-21 193329.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/39bdad52-34e5-9a77-9d7a-6a748f82b4cf.jpeg)

上記を設定後、作成を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/b008533c-3f2f-0fa0-dbdd-f1296c564e78.png)

クラスターが作成されました。


#### 2.5 ecsTaskExecutionRoleロールに権限を付与する
IAMにて、ecsTaskExecutionRoleに以下の権限を付与しましょう。
```
- AmazonRDSFullAccess
- SecretsManagerReadWrite
- AmazonECSTaskExecutionRolePolicy
- EC2InstanceProfileForImageBuilderECRContainerBuilds
- AWSAppRunnerServicePolicyForECRAccess
```

ECSの設定は以上になります。

### 3. ECSを実行する
「タスク定義」を選択、作ったタスク定義を選択し、「デプロイ」、「タスクの実行」を選択します。
![スクリーンショット 2023-02-21 194231.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/c5fc075c-c502-aa64-a40e-33bcb6bfd75c.jpeg)

「作成」の設定を行います。
- 既存のクラスター：clusterConnectionRDSを選択
- ネットワーキング:
    - VPC：任意のVPC
    - サブネット：プライベートサブネットの２つ 
	※パブリックサブネットは選択しない点に注意。
- セキュリティグループ
    - 既存のセキュリティグループを使用　「ecs-sg」を選択
- パブリックIP：オン
![スクリーンショット 2023-02-21 194726.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/8f20f875-5774-cae5-97f2-346d9f3dd139.jpeg)

作成を押すと、タスク実行開始。 ECRのコードが実行されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/9949440b-be6b-b067-d63b-10e6f5923b29.png)

CloudWatchでログを確認すると、RDSに接続できることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3169191/0eab2043-24d2-37a7-bfd8-d33055273a7b.png)

## 終わりに
以上の設定で、ECSからRDSに接続し、SQLコマンドを使ってSELECTやINSERTなどができるようになります。
処理スピードが早すぎて驚いていました。
設定はあまり難しくはないと思うので、色々試してみると面白いかもしれません。
指摘事項などいただけると幸いです！