Fargateでpythonを実行するまでの記録

## はじめに
今回は既存のLambdaで実行していたpythonプログラムを、Fargateでも実行できるように試行錯誤した備忘録を記録する。前提として、ECRやECS、Task定義は設定済。ECRやECS、Task定義などの設定の記録については以下記事に記録してある。↓

https://qiita.com/kennyQiita/items/2e0d524f7f65f6c3bda3

また、今回最終的に構築したいのは以下のような構成だが、本記事ではとりあえずPublisher Fargateを手動で起動（Task Run）できたところまでを記録する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/82f58a2c-c34b-2570-7e34-9cda3ca34f90.png)

ちなみに、FargateではなくLambdaを用いた元々の構成を構築するまでの記録は以下記事に残してある。
（以下記事からさらに遡って追っていく必要があるのは申し訳ない。興味ある人だけ。）↓

https://qiita.com/kennyQiita/items/005da29e8e2ed2c2cc4e

今回は以下の順序で実施記録を整理していく。
1. 実行したいDockerイメージをECRにプッシュ
1. Task実行とエラー
1. エラーを解決して再度Task実行して動作確認

## 実行するpythonプログラムを含むDockerイメージをECRにプッシュ

### python
本来はAPIリクエストから受け取るメッセージ内容を`Message`にセットしたいが、とりあえず固定文字で進めていく。
```python:topic_publisher.py
import base64
import boto3
from datetime import datetime
import json

client = boto3.client('sns', region_name='ap-northeast-1')

def main():
    
    print('fargate started')
    
    params = {
        'TopicArn': 'arn:aws:sns:ap-northeast-1:123456789012:myFirstTopic_std',
        'Subject' : 'Published From: deliverTopicToSNS_Test01',
        'Message' : 'fargate message'
    }
    
    try:
        response = client.publish(
            TopicArn = params['TopicArn'],
            Subject = params['Subject'],
            Message = params['Message']
        )
        
        print(json.dumps(response))
        return {
            'statusCode': 200,
            'body': json.dumps('A new message has been published')
        }
        

    except Exception as e:
        print(e)
        raise e
    
main()
```
ちなみに`boto3.client()`としているところで、`region_name`を指定していないとFargateでの実行時に`NoRegionError`が発生した。Lambdaで記述していた時には不要なパラメータだったが、Fargateでは必要なのだろうか？

https://stackoverflow.com/questions/40377662/boto3-client-noregionerror-you-must-specify-a-region-error-only-sometimes

### Dockerfile
pythonは元々Lambdaで動かしていた3.8を指定。
`ENV PYTHONBUFFERED=1`としている。
`ENTORYPOINT ["python", "./topic_publisher.py"]`としている。
このように記述することでpythonプログラムの`print()`の内容がCloudWatch Logsで確認できるようになるらしい。（詳しい方その原理を教えてください。）

https://stackoverflow.com/questions/64158887/how-to-see-python-print-statements-from-running-fargate-ecs-task

```Dockerfile
FROM python:3.8

WORKDIR /app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY topic_publisher.py ./

ENV PYTHONUNBUFFERED=1
# CMD ["python", "./topic_publisher.py"]
ENTRYPOINT ["python", "./topic_publisher.py"]
```
ちなみにpythonのバージョンを指定していないと（恐らく）最新のバージョンが利用されるのだと思う。その場合に以下の記事にもある`Code ImportError`が発生した。今回はバージョンを3.8に指定することで解決した。

https://coronene.hatenablog.com/entry/python/importerror

### requirement.txt
以下のように利用boto3を指定
```requirement.txt
boto3==1.9.183
```

### DockerイメージをECRへプッシュ
ECRコンソールから確認できるプッシュ手順に従ってコマンドを打っていく。まずはECRへの認証。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/6828aac5-66cd-e488-829d-f328f5879638.png)

`docker build`でイメージのビルド
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/fff02d52-102c-4c78-d5d0-d3edeb44255f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/740773a6-6e53-46d0-cb6c-77a12087fa81.png)

イメージへタグ付けしてリポジトリへのプッシュ準備
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/83be94d1-b464-14d4-8458-fc9c2d44e909.png)

`docker push`でリポジトリへプッシュ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/5d4faf9f-21df-108c-0d1b-5a171163ba79.png)

## Taskを作成してRun
ECRにイメージがプッシュされて実行準備が整ったので、新規Taskをコンソールから作成して実行してみる。以下のTaskを実行（デプロイ）する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/554b8a50-c4bd-459b-d7f2-21e09de57bff.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/142ccf47-11f1-2ce0-340a-1b00ba35be1a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/abba3ff4-f0d9-c8ab-b612-f28f94573ae5.png)
このNetworkingの設定のところでインタネットに出るルーティングがされていないSubnetを選択していたり、Public IP付与をOFFにしていると`CannotPullContainerError`が発生する。

https://qiita.com/nosuk_11/items/99bba8a7625e2343ff19

### エラー
Exit code:1でエラーとなった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/815366f3-970d-34c3-d58a-65bc145d963a.png)

ログを見てみると`Unable to locate credentials`と出ている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/b386fa37-225d-b14a-6f81-36551e221107.png)
こちらの参考記事にある通り、このエラーはECSのTaskロールがうまく設定されていないと発生するっぽい。

https://zenn.dev/creationup2u/articles/785a39f2a37fc8

Task定義を確認してみると、確かにTaskロールの方は設定されていなかった。Task execution roleとは別に付与が必要らしい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/525db563-94da-5116-df9a-0b4067c8d1f1.png)

以下のようにTaskロールの方にも同じロールを付与し（本来は別々のロールが良いかと思うが、ここでは同じロールを使おうと思う）、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/a3bbe721-30fa-f1ac-14c6-8476bd939000.png)

当該ロールに対して、`AmazonSNSFullAccess`も付与しておく。SNSトピックを配信しているのでおそらく必要なポリシーだろう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/532486c1-074b-3ceb-ac72-3e3ee41ca622.png)

### 再実行
同じようにTaskを作成して再度Run。
今度は無事にTopicがpublishされた！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/0e4d51ac-9ef7-7e53-fc1f-84acac5041db.png)

後続のLambdaなども想定通り動き、DynamoDBに新規Itemが格納され、手元にメールが届いたことも確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/484097/10322e1e-d5a1-ff5f-dcf4-ca6621408f57.png)

## おわりに
所感として、ECS（Fargate）はLambdaよりも実行キャパが大きかったり時間制限がなかったりと、イベントドリブンでかつ処理が重いような機能の実行基盤としては向いていると思います。今回はコンソールから手動でTaskをRunしましたが、これをAPI Gateway経由で実行するようなところをこの後検証していこうと思います。

## その他参考サイト
https://qiita.com/moritalous/items/75ab963ec783d58cf685

https://dev.classmethod.jp/articles/s3event-trigger-ecstask-on-fargate-bia-lambda/