Amazon ECS で OpenAI Realtime API (WebSocket) を使った ChatBot をホスティングする

## はじめに

OpenAI Realtime API を利用した ChatBot を Amazon ECS（Fargate） にデプロイする方法を確認した。できるだけシンプルな構成で実装し、以下のステップを通じて実際に動作させる

1. ローカル環境で OpenAI Realtime API を使った ChatBot を動作させる
1. Amazon ECR にリポジトリを作成する
1. ChatBot の Docker イメージを Amazon ECRリポジトリにプッシュする
1. Amazon ECSで ChatBot の Docker を起動する
1. AWS にデプロイした ChatBot の動作確認

## 1. ローカル環境で OpenAI Realtime API を使った ChatBot を動作させる

ソースコードを取得する
```bash
git clone https://github.com/aRaikoFunakami/hello-cdk.git
```


Dockerコンテナの作成

```bash
cd hello-cdk/samples/
docker build -t my-realtime-chat .
```

コンテナを起動する

```bash
docker run --rm -p 3000:3000 --name chat-container --env OPENAI_API_KEY=$OPENAI_API_KEY my-realtime-chat
```

別のターミナルで **wscat** を使ってアプリの動作を確認する

```bash
wscat -c ws://localhost:3000/ws
```

出力例

```bash
Connected (press CTRL+C to quit)
> こんにちはChatGPT
< こんにちは！何かお手伝いできることはありますか？
> 私の名前はrairaiiです。あなたの名前は？
< 私の名前はChatGPTです。何かご質問やお話ししたいことがあれば、どうぞ教えてください。
> 私の名前わかりますか？
< あなたのお名前はrairaiiですね。どうぞよろしくお願いします。
> なぜおぼえているの？
< 会話の流れを理解し、適切に応答するために、先ほど教えていただいた情報を一時的に覚えています。会話が終了すると、情報は保持されませんのでご安心ください。
```

コンテナの終了

**Ctrl-C** で終了する。

Dockerプロセスが残っていないか確認

```
docker ps -a
```

明示的にKILL

```
docker kill chat-container
```


## 2. Amazon ECR にリポジトリを作成する

公式サイトを参考にした


https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/repository-create.html

Amazon ECR コンソール (https://console.aws.amazon.com/ecr/repositories) を開いて下記の作業を行う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/1d6897d9-0040-497e-bef8-7e3b9a0f4ad5.png)


1. **ナビゲーションバー** から、リポジトリを作成する **リージョン** を選択する
2. **[リポジトリ]** ページで、**[プライベートリポジトリ]** を選択し、**[リポジトリの作成]** を選択する
3. **[リポジトリ名]** に、リポジトリの一意の名前を入力する
4. **[Tag immutability] (タグの不変性)** で、このリポジトリのタグの変更可能性の設定を選択する
5. **[暗号化設定]** では、**[AES-256]** または **[AWS KMS]** のいずれかを選択する
6. **[Create] (作成)** を選択




##  3. ChatBot の Docker イメージを Amazon ECRリポジトリにプッシュする

**aws configure** での設定は終了しているものとする


### (1) Docker クライアントの認証

イメージをプッシュする先の Amazon ECRレジストリに対して Docker クライアントを認証する。
Amazon ECRレジストリに対して Docker を認証するには、 aws ecr get-login-password コマンドを実行する。

```
aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <aws_account_id>.dkr.ecr.<region>.amazonaws.com
```

### (2) プッシュするDockerイメージを確認

プッシュするイメージを確認するために、 **docker images** コマンドを実行し、システム上のコンテナイメージを一覧表示する

```bash
docker images
```

**出力例**

```bash
REPOSITORY                                                             TAG         IMAGE ID       CREATED        SIZE
my-realtime-chat                                                       latest      38ac3d587110   11 hours ago   437MB
```
### (3) プッシュするDockerイメージにタグ付け

Amazon ECRレジストリ、リポジトリ、およびオプションのイメージタグ名の組み合わせを使用してイメージにタグを付けする。レジストリ形式は **<aws_account_id>.dkr.ecr.<region>.amazonaws.com** とする。


```bash:例
docker tag <REPOSITORY>:<TAG> <aws_account_id>.dkr.ecr.<region>.amazonaws.com/<ECR_REPOSITORY>:<NEW_TAG>
```

次のパラメーターと値でタグ付けする

| パラメータ | 値 |
| --- | -- |
| REPOSITORY | my-realtime-chat |
| TAG | latest |
| aws_ account_id | xxxxxxxxxxxx | 
| ECR_REPOSITORY | my-realtime-chat |
| NEW_TAG | latest |

```bash
docker tag my-realtime-chat:latest xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/my-realtime-chat:latest
```

タグ付けの確認

```bash
docker images
```

出力例

```bash
REPOSITORY                                                             TAG         IMAGE ID       CREATED        SIZE
xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/my-realtime-chat          latest      38ac3d587110   12 hours ago   437MB
```

### (4) Docker イメージをECRにプッシュする

タグ付けされたイメージをECRにプッシュする

```bash
docker push xxxxxxxxxxxx.dkr.ecr.us-east-1.amazonaws.com/my-realtime-chat:latest
```

### (5) プッシュしたイメージを確認する

```bash
aws ecr list-images --repository-name my-realtime-chat --region us-east-1
```

**出力例**

```
imageIds:
- imageDigest: sha256:522cc5d1b874816fb23b6791cbfd2c1e497296416b3a2de2113b173285720939
  imageTag: latest
```
  
## 4. Amazon ECSで ChatBot の Docker を起動する

### (1) クラスターを作成

1. [Amazon ECS コンソール](https://us-east-1.console.aws.amazon.com/ecs/v2/clusters?region=us-east-1)を開く
1. **[クラスター]** のページで **[クラスターの作成]** を選択する
1. **[クラスター名]** を入力する
1. 画面下部の **[作成]** を選択しクラスターを作成する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/0708a4e6-36f6-4a2b-b388-d15e843d17f1.png)

### (2) タスク定義を作成

1. **[タスク定義]** のページで **[新しいタスク定義の作成]** を選択する
1. **[タスク定義ファミリー]** を入力する
1. **[オペレーティングシステム/アーキテクチャ]** で **[Linux/ARM64]** を選択する

ARM CPUを搭載したMacで作成したDockerを起動させるためには **Linux/ARM64** を選択する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/37c73a9a-6393-4bf6-bd1e-8be3f776fa47.png)



1. **[コンテナ -1]** の **[ポートマッピングの設定]** を行う
    - **[コンテナポート]** : 3000
    - **[プロトコル]** : TCP
    - **[アプリケーションプロトコル]** : HTTP2


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/7f26246d-b163-45a2-bd77-4c1ca7ff37fc.png)



1. **[環境変数]** に **OPENAI_API_KEY** を追加

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/efce358e-4d87-4cd5-a102-1213be9f8eb1.png)


### (3) クラスタにサービスをデプロイ

1. **[クラスター]** のページで **[作成したクラスタ名]** を選択する
1. **[サービス]** の **[作成]** を選択する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/47623af7-5a02-4c86-bdc2-641244221c13.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/7d1ea7e7-62f4-43f2-8dbe-110725b02a96.png)

1. **[デプロイ設定]** の **[ファミリー]** で作成したタスクを設定する
1. **[デプロイ設定]** の **[サービス名]** を設定する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/dcc015c3-8472-43bd-8056-08a57ce6d897.png)


1. **[デプロイ設定]** の **[ネットワーキング]** の **[セキュリティグループ]** で **[新しいセキュリティグループの作成]** を選択する
1. **[セキュリティグループ名]** を設定する
1. **[セキュリティグループのインバウンドルール]** を設定する
    - タイプ : カスタムTCP
    - プロトコル：TCP
    - ポート範囲：3000
    - ソース : Anywhere
1. **[作成ボタン]** を設定してサービスを作成する
1. 数分かかるのでしばらく待つ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/1e5d6510-1c95-4629-9b86-291e2de543a2.png)





## 5. AWS にデプロイした ChatBot の動作確認

### (1) タスクの Public IP を取得
動作確認のためにデプロイして ChatBot が動作している Docker のIPアドレスを取得する

1. **[クラスター]** から作成した **[サービス]** を選択し、**[タスク]** タブを開く
1. 実行中の **[タスク]** を選択する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/04853c1c-18a8-472d-b437-f396c992507c.png)

1. **[ネットワーキング]** のタブを開く
1. **パブリック IP** のIPアドレスを確認する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1017489/322b9836-98fa-4232-9c3a-73b587a58636.png)

### (2) **wscat** で動作確認を行う

```bash
wscat -c ws://<パブリック IP>:3000/ws
```

**出力例**

```bash
Connected (press CTRL+C to quit)
> Hello, ChatGPT
< Hello! How can I assist you today?
> What's your name
< You can just call me ChatGPT. How can I assist you today?
> My name is rairaii.
< Nice to meet you, Rairaii! How can I assist you today?
> Do you know my name?
< Yes, you mentioned that your name is Rairaii. How can I help you today?
> Why do you remember me?
< I don't have memory of past interactions, so I only know what you tell me during our current conversation. How can I assist you now?
```

AWSにデプロイして動作することが確認できた


## トラブルシューティング

### macOS で docker pushが失敗する場合

```bash
Error saving credentials: error storing credentials - err: exec: "docker-credential-osxkeychain": executable file not found in $PATH, out: ``
```

docker-credential-helperをインストールする

```bash
brew install docker-credential-helper
```


## リファレンス


https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/docker-push-ecr-image.html


https://qiita.com/Xyeer/items/1f8836dd7801bd5134b8#1-ecr%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%AE%E4%BD%9C%E6%88%90-%E3%82%82%E3%81%97%E3%81%BE%E3%81%A0%E5%AD%98%E5%9C%A8%E3%81%97%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88


https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-best-practices.html


https://qiita.com/kimuni-i/items/cd39d82622f000dce28d