ChatGPT が回答する Discord Bot をほぼ0円運用できるように作った
こういう個人開発する時って限りなく0円に近い価格で運用したくありませんか?
特にDiscordBotは色々制約がある上意外と作るのが難しかったので、知見を共有します
あとChatGPTの話はあんまり出てきません
※この記事にはオーバーエンジニアリングを含みます
DiscordBotの制約を知っておく
結論
- WebSocketを常時Listenするのが一番簡単に作れるがサーバー費用がかさむ
- InteractionをHTTPで受け取るようにすればWebSocketほど自由度はないがFaaSの載せられる
- HTTPのInteractionは大体3秒以内に返答しないとタイムアウトになってしまうため、重めの処理は工夫する必要がある
作り方の制約
まず第一に、DiscordBotを作るならEC2なりVPSなりでサーバーを建ててそこで実行するのが一番簡単に作れます
これはDiscordの仕様によるもので、 チャットや通話、スタンプなどのイベントをWebSocketで受け取る のが一番自由で何でもできるからです
そのイベントを簡単に受け取れるライブラリとして、既に discord.py や discord.js がありとても便利です
ただし、WebSocketを受け取るということは、 サーバーを常時起動させておく ということになります
その場合はGCPやAWSの何かの無料枠などを使ったり、 render.com や railway などの激安PaaSを使えば1000円以内くらいでできると思います
別の選択肢としては、WebSocketとは別にDiscordからの Botコマンドのみ をHTTPで受け取れる Interaction
という方式もあります
ということは、常時起動してなくていいですしFaaSに載せられ、めちゃくちゃお財布に優しくなります
注意点としては、受け取るイベントがBotコマンドのみとなってしまうため細かい操作や、例えばVoiceチャンネルに誰か入ってきたみたいなイベントは取れません
Interactionの制約
HTTPのInteractionは大体3秒以内(体感)にリクエストを返答しないとDiscord側がタイムアウトとして判断して、エラーになってしまいます
今回のBotはOpenAIApiにリクエストを投げる必要があったため、試作段階の時点で9割方タイムアウトしてしまいました
ということは、Botへの返答はさっさとしておいて、メインの処理は非同期にするなりなんなりする必要があるということです
今回作ったアプリケーションの機能概要
-
/talk question:?
コマンドで質問を送信し、回答が表示される また、会話はキャッシュし継続して会話できるようにする -
/talk question:? voice:True
を質問と送信するとボイスチャンネルで回答を読み上げてくれる 上記と同様にキャッシュする -
/voice-talk
コマンドを送信するとBotと音声でインタラクティブに会話することができる
実装方針を決める
制約と機能概要がわかったことで、選択肢を絞ります
- お金をかけたくないのでできるだけFaaSで作る
- 音声の生成や加工をするとなると CloudflareWorkers でやるのは無理(ffmpeg使ったりするため)
- Dockerを2つ動かしたい
a. メインの処理をするサーバー
b. 音声合成をするサーバー(Voicevoxを使わせて頂きました) - 非同期の処理をうまく処理する必要がある
- キャッシュの機構を入れる必要がある
(CloudflareWorkersでDiscordBotを作る記事は過去に書いてるのでこちらもぜひ)
使った技術解説
結論
- インフラ
- AzureContainerApps
- API (Node.js)
- Voicevox
- AzureContainerApps
- PubSub
- AzureServiceBus
- Redis
- Upstash
- CDNEdge
- CloudflareWorkers
AzureContainerApps って何?
サーバーレスコンテナをいい感じにしてくれるサービスです
後ろではKubernetesが動いていますが、開発者はその存在を意識しなくても、コンテナオーケストレーションの旨味だけを感じられるようになっています
個人開発でKubernetesは流石にやりすぎだろ……ってなりますが、ContainerAppsなら全然そんなことないと思います
イメージ的にはGAEやAWSEBとかと同じような操作感で使えます かなり手軽に使えます
AzureContainerAppsを構成するDapr
このAzureContainerAppsを使う最大のメリットとしてDaprがビルトインしているという点があります
Daprは、Kubernetes上で動作するサイドカーランタイムです
主に各コンテナー間やPubSubやキャッシュなどのアプリケーションの外側の異なるサービスの通信をプロキシーしてくれます
これの何が嬉しいかというと、「アプリケーションからリクエストするのは1つのホストだけで良い」「外部のサービスが変更されたとしても、アプリケーションからリクエストするエンドポイントのインターフェースはDaprが策定した規格で統一されるため影響しない」というところです
例えばPubSubをトピックを1つ作りたいとしたら、以下のようにリクエストするだけで良いです
fetch(`http://localhost:3500/v1.0/pubsub/{コンポーネント名}/{トピック名}`, {
method: 'POST',
body: /* payload */
})
※コンポーネントとはDaprがプロキシする外部サービス1つのことを指します
Daprはたくさんのサービスに対応していますが、例えばPubSubだとCloud Pub/Subでも、AWSSQSでも、AzureServiceBusでも、RedisStreamでも↑のエンドポイントは一切代わりません
更にこの先の接続先情報はコンポーネントとして予め定義しておくことができます
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: {コンポーネント名}
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: {ホスト名}
- name: redisPassword
value: {パスワード}
AzureContainerAppsと併用する場合はAzureのダッシュボードから設定すればOKです
この様に色々なサービスとの通信をDaprがプロキシしてくれているため、今回作ったアプリケーションからは localhost:3500
しか叩いてません これは本番でも同様です
Daprはサイドーカーランタイムのため、必ずアプリケーションのDockerと同じホストのあるポートをHTTPまたはgRPCでListenしています
このエンドポイントにアクセスできれば何でもつながる、という世界になっています
ネットワークのどうのこうのとかIPがどうとかを考えなくて良いため、とても簡単にサービスメッシュを組むことができます
DaprはOSSのサイドカーランタイムなので、GKEなりにデプロイするにはKubernetesを意識する必要があります
ですが、AzureContainerAppsはDaprをビルトインしているため、DaprのPod管理などは全く気にしなくて大丈夫です
AzureServiceBus
PubSubに関しては正直何でも良かったですが、最初からAzureを使用することを想定していたためServiceBusを選びました
DaprはPubSubの接続先としてRedisStreamも選べるので、自分でRedisサーバーを建てるのも選択肢にありましたが、ServiceBusの方が料金が圧倒的に安そうだったのでこちらにしてます
Upstash
UpstashはサーバーレスRedisサービスです
とにかくクソ安くて便利で、何も考えずに使えます
今Redisをキャッシュとして使うなら、ここ一択な気がします
CloudflareWorkers
ContainerApps作ってるのにCloudflareWorkersも結局使っちゃってるんですが、
これはContainerAppsのコールドスタートを避けるために使っています
DiscordのInteraction先をContainerAppsに向けてしまうと、コールドスタートからの復帰中にDiscordがタイムアウトさせてしまいエラーが出ます
ですので、InteractionをCloudflareWorkersに作ったハンドラに向けて、そこからServiceBusにTopicを飛ばしています
Topicを飛ばしてしまえばあとはContainerAppsがSubscribeしてるので、勝手に処理してくれます
今回の気付きとして、こういったシンプルにリクエストを受け取ってキューを飛ばすだけみたいな処理にCloudflareWorkers向いてるな〜って感じました(FaaSだから当たり前か)
もっと非同期処理が当たり前な世界になってほしいです
結局いくらぐらいかかってるの?
1日200~300リクエストくらいしかこないし、自分と身内でしか使ってないのですが(公開はしてるけど使ってくれる人がいない)
1週間くらい使ってこんな感じです
UpstashとCloudflareWorkersは0円です
ContainerAppsは頻繁にコールドスリープしてくれてるため、無料枠の範囲内に収まっています コンピューティングリソースよりログ保管料金の方が高いってマジ……!?
一番高いのはOpenAIになっちゃいましたね それでも過疎Botなので無料枠内ですが……
おわりに
今回CotainerAppsやUpstashを初めて使ったのですが、自分の中での技術選択肢が増えました
個人的に、技術選定の選択肢のフローとしては1番にCloudflareWorkersが、EdgeFunctionが要件的にダルそうならContainerAppsを選ぶことになりそうです
もう終始本当に便利でした 意外とDaprが薄くて使いやすいです もしContainerAppsがダメでも、Daprだけは残してGKEに載せると思います
CloudRunとかも良いですがサービスの連携が面倒になりがちなので、ちょっとでも複雑なアプリケーションを作るならContainerAppsを最初に考えても良さそうです
あくまで個人開発なら、の前提付きですがこんな感じで色んなサービスを駆使することで安く運用できるサービスを作れました
みなさんも変なPaaS面白サービス便利OSS作ったアプリなどあればコメントやTwitterで教えて下さい
Discussion