ローカルとECS（Fargate）間のやりとりまとめ（ファイル転送も）

ECS（Fargate）を利用しいざ運用を進めておりましたが、どうしてもコンテナへ接続しデバッグが必要になるタイミングがありました。本記事ではいくつかの接続手法を紹介します。

# 前提

AWS CLIがローカルに設定済みであることを前提とします。使用するコマンドは以下の通りです。
権限エラーが発生した場合は、その内容に応じて必要最低限のポリシーを適用してください。

```
1. aws ecs describe-tasks ....
2. aws ssm start-session ...
3. aws s3 presign
```

# 「コンテナにセキュアに接続しデバッグしたい」

・「コンテナ内でrails c したい」
・「コンテナ内からRDSへ接続してSQL実行したい」

> Amazon ECS Exec を使用すれば、最初にホストコンテナのオペレーティングシステムとやり取りしたり、インバウンドポートを開いたり、SSH キーを管理したりすることなく、コンテナと直接やり取りできます。ECS Exec を使用して、Amazon EC2 インスタンスまたは AWS Fargate で実行されているコンテナでコマンドを実行したり、シェルを取得したりできます。これにより、診断情報を収集し、エラーを迅速にトラブルシューティングすることが容易になります。
>[ECS Exec を使用して Amazon ECS コンテナをモニタリングする](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-exec.html)

これについては山ほど記事がありますが、まとめ記事なのでやり方を記載します。
この辺のポリシーが実行元にIAMへ必要になります。

```
"Action": [
  "ecs:ExecuteCommand",
  "ecs:DescribeTasks",
  "ecs:UpdateTaskSet",
  "ecs:DescribeServices",
  "ecs:UpdateService"
],
```

まず、ECS内で動作しているタスクが「ecs exec」によるSSH接続が可能な状態であるかを確認します。以下のコマンドを実行してください。

```
aws ecs describe-services --cluster $CLUSTERNAME --services $SERVICENAME
```

$CLUSTERNAME と $SERVICENAME の部分は、実際のクラスタ名とサービス名に適宜置き換えてください。

```
...
...
            "propagateTags": "NONE",
            "enableExecuteCommand": false
        }
    ],
    "failures": []
}
```

上記レスポンスは`enableExecuteCommand": false` であり、これはECS execが無効な状態であることを示しています。この設定を有効にするために、次のコマンドを実行しましょう。

```
 aws ecs update-service --cluster $CLUSTERNAME --service $SERVICENAME --enable-execute-command
```

このコマンドを実行した後、レスポンスの箇所が変化し、"enableExecuteCommand": true となるはずです。

:::note warn
"enableExecuteCommand": true に設定した後は、タスクを再生成する必要があります。これは、サービスの設定が更新されたものの、新しい設定を反映したタスクがまだ作成されていないためです。更新後の設定を反映させるには、サービスで新たにタスクを作成する必要があります。
:::

その後、コマンドを準備し実行してください。

```
aws ecs execute-command \
--cluster $CLUSTERNAME \
--task arn:aws:ecs:ap-northeast-1:111111111:task/$CLUSTERNAME/xxxxxxxxxxxxxx \
--container $CONTAINERNAME \
--interactive \
--command "/bin/sh"
```

--task の箇所は以下からコピーでもOKです。

![スクリーンショット 2024-10-24 18.02.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26033/10a1022e-10fc-8e0b-8ad5-3478fbb17438.png)


再トライ...おめでとうございます👏

```
 % aws ecs execute-command \
--cluster $CLUSTERNAME \
--task arn:aws:ecs:ap-northeast-1:111111111:task/$CLUSTERNAME/xxxxxxxxxxxxxx \
--container $CONTAINERNAME \
--interactive \
--command "/bin/sh"

The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.

Starting session with SessionId: ecs-execute-command-abrzdnx283ka1dbnqfssieye

# 
```

### （補足）別の接続方法

:::note info
aws ssm start-session コマンドを利用したポートフォワード接続についても紹介
:::

[AWS > ドキュメント > AWS Systems Manager > ユーザーガイド](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html)

[そして別ページには以下](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-exec.html#ecs-exec-architecture)
> ECS Exec は、AWS Systems Manager (SSM) セッションマネージャーを使用して実行中のコンテナとの接続を確立し、AWS Identity and Access Management (IAM) ポリシーを使用して実行中のコンテナで実行中のコマンドへのアクセスを制御します。



### runtimeId の取得

次のコマンドを実行して、指定したECSクラスタ内のタスクの詳細情報を取得します。

```bash
aws ecs describe-tasks \
  --cluster $CLUSTERNAME \
  --task arn:aws:ecs:ap-northeast-1:111111111:task/$CLUSTERNAME/xxxxxxxxxxxxxx
```

レスポンスから `runtimeId` の値をコピーしてください。

```
            ...
            "containers": [
                {
                    ...
                    ...                    
                    "imageDigest": "sha256:xxxxxxxxxxx",
                    "runtimeId": "yyyyyyyyyy-yyyyyyyyyy",
                    ...
                    ...                    
```

### aws ssm コマンドの target オプション用の値を作成する

`ecs:` と `cluster_name` と `task_id` と先ほど取得した `runtimeId` をアンダースコア（_）で結合します。作成する値の形式は次のようになります。

`ecs:$CLUSTERNAME_xxxxxxxxxxxxxx_yyyyyyyyyy-yyyyyyyyyy` のような具合。

では、改めてコマンドを準備して実行。

```
% aws ssm start-session \
--target ecs:$CLUSTERNAME_xxxxxxxxxxxxxx_yyyyyyyyyy-yyyyyyyyyy
```

セッションが開始され、以下のようなメッセージが表示され、接続成功です👏

```
Starting session with SessionId: $CLUSTERNAME-nripo83pnbj2933nbm1qoie
#
```

ただし、この接続方法では aws ecs execute-command で接続するのと変わりません。SSM Session Managerを利用した接続は、ポートフォワード機能を有効に活用することで、その便利さを最大限に発揮します。たとえば、ローカルPC上のDB管理ツールを使用して接続したい場合などです。

以下のようなポートフォワーディング接続を開始することで、別途管理ツールを立ち上げ、localhost への接続が可能になります。

```
% aws ssm start-session \
--target ecs:$CLUSTERNAME_xxxxxxxxxxxxxx_yyyyyyyyyy-yyyyyyyyyy \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["your-rds-endpoint.xxxxxxxxx.ap-northeast-1.rds.amazonaws.com"],"portNumber":["3306"], "localPortNumber":["3306"]}'
```

上記のコマンドを実行すると、以下のようなメッセージが表示されます。

```
Starting session with SessionId: $CLUSTERNAME-nripo83pnbj2933nbm1qoie
Port 3306 opened for sessionId $CLUSTERNAME-nripo83pnbj2933nbm1qoie

Waiting for connections...
```

この状態になったら、DBeaverなどのデータベース管理ツールから localhost へのデータベース接続が成功するはずです。コンソールからSQL操作を行うよりも、より効率的かつ便利に操作できるというメリットがあります。

# 「コンテナにファイルを転送したい」

次に、ファイルをコンテナ内にコピーする方法について説明します。ここでは、以下の2つの方法（ファイルをリポジトリに含めてデプロイする方法やAWS CLIをコンテナ内で利用可能にする方法）は使用しない前提です。

やり方の概要は以下のとおりです

**「S3にファイルを設置して署名URLを発行し、コンテナ内からcurlで取得する」**

 [AWSドキュメント > S3 > ユーザーガイド > 署名付き URL を使用したオブジェクトの共有](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html)

早速S3にファイルを設置しローカルから署名付きURLを発行しましょう。（今回は有効期間60秒）

```
% aws s3 presign $S3_BUCKET_NAME/hoge.txt --expires-in 60 --region ap-northeast-1
```

発行されました。

```
https://$S3_BUCKET_NAME.s3.ap-northeast-1.amazonaws.com/hoge.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXXXX%2F20241029%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20241029T035056Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host&X-Amz-Signature=6bXXXXXXXa35dde9bXXXXXXXe9d3bc4fXXXXXXXd87d656bffee68
```

念の為、動作確認指定時間の間は、外部から接続可能（60秒以内）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26033/53b7b1b5-35ac-968a-03b3-736537127058.png)

指定時間が過ぎると Access Denied

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26033/abf30b5e-73bb-2c10-6cd6-f4294c8bcfa9.png)

ではコンテナ内から curl コマンドでダウンロードしましょう。

```
# curl -O 'https://$S3_BUCKET_NAME.s3.ap-northeast-1.amazonaws.com/hoge.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXXXX%2F20241029%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20241029T035056Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host&X-Amz-Signature=6bXXXXXXXa35dde9bXXXXXXXe9d3bc4fXXXXXXXd87d656bffee68'
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    12  100    12    0     0    115      0 --:--:-- --:--:-- --:--:--   116
```

中身をみてみましょう。

```
# cat hoge.txt
hogehogehoge# 
```

おめでとうございます👏


# 参考記事

- [ECS Fargateを踏み台にポートフォワードしてVPC内のRDSに接続する
](https://zenn.dev/quiver/articles/1458e453118254)
- [ECSとローカル間でファイルをコピーしたり、対話形式でrun-taskやexecできるデバッグ特化のCLIツールを作りました
](https://zenn.dev/yukiarrr/articles/b2d3f8392ff56a)