AWSを使って生成AIを組み込んだカードゲームを開発する!

第2回DiscordからEC2へメッセージを送って⁠⁠⁠応答する仕組みを作成してみよう!

前回は作成するアプリケーションの全体像と、開発環境で使うDockerを紹介しました。今回はDiscordからメッセージを送信して、そのメッセージをEC2上で受け取るプログラムを作成します。

今回の概要とその準備

Discord上で使える!makeコマンドを作成し、コマンドと一緒にメッセージを送信します。具体的にはBotを通して、EC2上のPythonプログラムでそのメッセージを取得し、Discord上に応答が返ってくる仕組みを作ります。

今回使用するコードは前回ローカルマシン上に用意したgihyo-torecaディレクトリ内のchapter-2ディレクトリに用意してあります。

cd gihyo-toreca/chapter-2

Discordの開発者向けポータルサイトでの設定

まず最初に、Discordの開発者向けポータルサイトで新しいBotを作成し、Botのトークンを取得します。

Botを導入することで、Discord上のメッセージをAWSのEC2などの外部のサーバーで受信できるようになります。

Discordサーバーを用意する

Discord上でオリジルアプリを作成するには、管理権限のあるDiscordサーバーが必要なので、自分が作ったサーバーがまだない場合は事前に作成しておきましょう。

アプリをインストールしていない方はDiscord公式サイトからダウンロードしてインストールしてください。

サーバーの追加はDiscordアプリ内の「サーバーを追加」から作成が可能です。

図

「オリジナルの作成⁠⁠-⁠自分と友達のため」と進み、サーバー名を入力して「新規作成」を押して完了です。

BotユーザーとBotトークンを作成する

Botユーザーを作成します。Discordの開発者ポータルサイトを開いてログインします。次に「New Application」ボタンをクリックします。

ポップアップウィンドウが開くので、ボット名前を入力し、規約を確認しチェックを入れ、Createボタンをクリックします。

図

左側のメニューから「Bot」を選択して次に進みます。

図

「Reset Token」ボタンを押すと、Botのトークンが発行されます。

図

必ずこのトークンをコピーしておきましょう。なお、コピーできるのはボットを作成した直後だけです。トークンはパスワードのようなもので、また後で使いますので、メモ帳など、ローカル上の安全な場所に一旦保存しましょう。

図

最後に「Privileged Gateway Intents」欄の「MESSAGE CONTENT INTENT」を有効にしておきます。この設定は、メッセージに対する応答や、特定のキーワードに基づいたアクションに対して必要な許可となります。

図

BotをDiscordサーバーに追加する

次にBotをDiscordサーバーに追加します。左側のメニューから「OAuth2⁠⁠-⁠URLジェネレーター」を選択して次に進みます。

環境によってはメニューのナビゲーション表示が画像と違う時がありますが、特に気にしないで大丈夫です。

図

「SCOPES」欄の中にある「bot」にチェックを入れます。

図

ページ下の「GENERATED URL」にbot作成用のURLが生成されているので、コピーします。

図

これをブラウザのURL欄に貼り付け、エンターキーを押して遷移します。

図

認証画面に遷移しますので、管理権限のある追加したいサーバーを選択して「認証」をクリックします。

図

以上でBotが指定のDiscordサーバーに追加されました。Discordサーバーのメンバーリストには次のように表示されます。

図

Botアプリの作成

次に、Pythonを使ってとてもシンプルなBotを作ります。オリジナルのコマンド!makeを作成し、そのコマンドを通して、サーバー上でメッセージを取得します。取得後、返答として「ただいま作成中...」というメッセージをDiscord上に送信します。

この制御には、discord.pyライブラリを利用します。このライブラリの詳しい情報についてはdiscord.pyの公式サイトを参照してください。

Botのプログラム「app.py」

実際のBotのプログラムはapp.pyに記載されています。

app.py
import discord  # discord.pyライブラリをインポート。Discord Botの作成に必要です。
from discord.ext import commands  # commandsフレームワークをインポート。コマンドベースのBotを簡単に作成できます。
import logging  # loggingモジュールをインポート。ログ出力のために使用します。
import os  # osモジュールをインポート。環境変数へのアクセスに使用します。

# 環境変数からDiscord Botのトークンを取得
TOKEN = os.getenv('DISCORD_BOT_TOKEN')

# loggingの基本設定を行います。ログレベルをINFOに設定し、ログのフォーマットを指定します。
logging.basicConfig(level=logging.INFO, format='%(asctime)s:%(levelname)s: %(message)s')

# Botクライアントの初期化。コマンドのプレフィックスを'!'に設定し、Botが受信するイベントの設定します。
intents = discord.Intents.default()
intents.message_content = True  # メッセージ内容へのアクセスを有効にします。これにより、メッセージのテキストを読み取れるようになります。

bot = commands.Bot(command_prefix='!', intents=intents)

@bot.event
async def on_ready():
    # BotがDiscordに接続したときに実行されるイベント。Botのログインが完了したことをログに記録します。
    logging.info(f'Botが準備できました: {bot.user}')

@bot.command()
async def make(ctx, *, text: str):
    # '!make <text>'というコマンドに反応して実行される関数。
    # 受け取ったテキストメッセージをログに記録し、ユーザーに「ただいま作成中...」というメッセージを送信します。
    logging.info(f'受信したメッセージ: {text}')
    await ctx.send('ただいま作成中...')

bot.run(TOKEN)  # Botを起動します。この関数により、BotはDiscordサーバーに接続し、コマンドの待受を開始します。

このプログラムの中でTOKEN = os.getenv('DISCORD_BOT_TOKEN')と、Botのトークンを指定する箇所があります。しかしトークンはパスワードのようなものなので、プログラムに直接書くことは基本的にしません。

トークンを直接書かずに連携させる方法はいくつかありますが、今回は次のコマンドをターミナルで実行して、一時的に自分のPCにBotのトークンを環境変数として記憶してもらいます。

export DISCORD_BOT_TOKEN='ボット作成時にコピーしたトークン'

requirements.txtにdiscord.pyライブラリを追加

Pythonで必要になるライブラリをrequirements.txtに追加することで、ライブラリをインストールすることができます。今回は次のようにdiscord.pyを追加しています。

requirements.txt
discord.py # 第2回に追加

ローカル環境でのBotアプリの動作確認

作成したBotプログラムをDockerコンテナ内で実行して、ローカルで動作を確認します。

requirements.txtを追加、修正したときは、開発環境にライブラリを反映させるため、もう一度Dockerの開発環境をビルドし直してから起動させます。それでは、次のコマンドをターミナルからBotを起動してみましょう。必ずchapter-2ディレクトリに移動してから実行してください。

# requirements.txtを修正した場合は一度buildし直し直してから起動する
docker-compose build --no-cache

# アプリの起動
docker-compose up

Dockerコンテナが起動後、ターミナル上のログに「Botが準備できました」と表示されれば準備完了です。

Discord上で次のコマンドを送ってみましょう。

!make 石の巨人のモンスター

Discord上で「ただいま作成中...」とBotが返答し、ターミナル上のログに「石の巨人のモンスター」と表示されたら成功です!

図
図

このBotアプリの停止には、ターミナルでCtrl + Cを押します。

EC2でアプリの公開

ローカル開発環境からの動作が確認できたところで、今度はEC2上に公開しましょう。

本番環境への公開への流れは次のとおりです。

  1. AWSの本番環境をローカル上からCDKで構築する
  2. 作成したEC2へローカルからアクセスする
  3. EC2にGitHubからアプリをクローンする
  4. EC2内でDockerを起動させて、アプリを実行する

ローカルでは、PCを閉じてしまったらプログラムは停止しますが、クラウド上のサーバーであるEC2に公開すれば24時間動き続けてくれます。なお、EC2についての詳しい情報についてはEC2の公式サイトを参照してください。

AWS CDKを利用して本番環境を構築する

AWS CDKを使うことでローカル上からコードでインフラを設計し、自動で作成できます。コードによる管理は設定漏れを防ぎ、修正や変更履歴はGitで管理できるため、変更内容が分かりやすく、共有するのにも便利です。

CDKの連携に必要なアクセスキーの発行

CDKの連携に必要なアクセスキーの発行には、AWS Console「IAM」を検索してください。

図

IAMのダッシュボードより、⁠セキュリティに関するレコメンデーション」欄にある「アクセスキーを管理」ボタンを押します。

図

遷移先ページの中ほどにある「アクセスキー」欄の「アクセスキーを作成」ボタンを押します。

図

今回、手順を簡略化するためルートユーザーで作っているため次のように注意書きが表示されますが、このままアクセスキーを作成します。

図

アクセスキーとシークレットアクセスキーが作成できました。この2つのキーは後述のCDKの連携設定時に必要となりますのでメモしてください。また厳重に管理してください。

図

CDK環境が用意されたDockerの準備

前回言及しましたが、AWS CDKがすでイントールされているDockerを用意しています。次のリポジトリをフォークしてください。

フォーク後、リポジトリをローカル上にクローンします。⁠あなたのアカウント名」の箇所は自分のGitHubのアカウント名に置き換えてください。

git clone https://github.com/あなたのアカウント名/cdk-gihyo-toreca.git

Dockerの起動

まずdockerが置いてあるディレクトリに移動します。PCの環境に合わせて次のどちらかのディレクトリに移動します。

# macでm1およびm2を使っている場合
cd mac-m1-m2

# 上記以外の環境の場合
cd cdk

前回の開発環境時とは違い、今回はコンテナ内に入って作業します。そのため、手軽にDocker環境のコンテナ入れるように、Docker Composeのrunコマンドを使います。その際に--rmオプションを付けて、コンテナ終了後の自動削除を実行するようにしておきます。

docker-compose run --rm cdk

コンテナに入るとターミナルの表示が変わるはずです。次のように表示されれば準備完了です。

root@cdk-container:/work$

ちなみにコンテナから出る時はexitを入力してください。

exit

CDK環境の初期設定

コンテナに入った状態で、AWSと連携できるようにCDKの初期設定を行います。それにはaws configureコマンドを使います。

この入力後に「AWS Access Key ID」「AWS Secret Access Key」に先ほど作成した、アクセスキーとシークレットキーアクセスキーを入力します。また、デフォルトのリージョンには東京ap-northeast-1⁠、出力形式にはjsonを指定しました。

aws configure

---
# AWS Access Key ID [None]: 自分のAWS AccessKeyID
# AWS Secret Access Key [None]: 自分のAWS SecretAccessKey
# Default region name [None]: ap-northeast-1
# Default output format [None]: json
---

この設定は一度登録されると、~/.awsディレクトリ内のcredentialsとconfigに保存されます。

なお、このDocker環境ではPC上の~/.awsにも同じ設定が残るようにしているため、コンテナを削除しても以降は設定が不要となります。

CDKの初回起動とそのコードの設定

CDKを起動します。初回時のみ、次のコマンドを実行します。これによりCDKに必要な設定がAWSに上に作成されます。

cdk bootstrap

AWSに構築する設定はapp.pyに記述します。

主にEC2の用意を実施し、これに伴い、EC2が配置される場所であるVPCとEC2へのアクセス権を管理するIAMを用意します。また、EC2作成時にDockerをインストールします。

デプロイ後はローカルからEC2に接続する必要があります。EC2との接続には、AWSのサービスのSSMを利用します。SSMはEC2のインスタンス名で簡単に接続できるようなるサービスで、一般的なSSHでの接続方法よりも簡単でセキュリティに強いのでオススメです。

デプロイ時のログにSSMの接続先情報を表示させるためにCfnOutputを利用しています。

なお、app.pyは全連載を通して同じもの使います。あらかじめ次回以降の必要なコードもすでに記載されていますが、各回に影響はありません。今回は説明のため第2回に必要なコードのみ本文に記載しています。

app.py
from aws_cdk import (
    App,
    Stack,
    aws_iam as iam,
    aws_ec2 as ec2,
    CfnOutput
)
from constructs import Construct

class DiscordBotEC2Stack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)

        # VPCの作成
        # AWSリソースを配置するための仮想ネットワークを作成します。ここでは最大2つのアベイラビリティーゾーンを使用します。
        vpc = ec2.Vpc(self, "VPC", max_azs=2)

        # IAMロールの作成
        # EC2インスタンスがAWSサービスとやり取りするための認証・認可を管理します。
        ec2_role = iam.Role(
            self, "EC2Role",
            assumed_by=iam.ServicePrincipal("ec2.amazonaws.com"),
        )

        # EC2セキュリティグループの定義
        # インバウンドおよびアウトバウンドのトラフィック制御を行うセキュリティグループを定義します。
        ec2_sg = ec2.SecurityGroup(
            self, "DiscordBotEC2-Sg",
            vpc=vpc,
            allow_all_outbound=True,
        )

        # ユーザーデータスクリプトの定義
        # EC2インスタンス起動時に自動的に実行されるスクリプトを定義します。ここではシステムの更新、GitとDockerのインストール、
        # Dockerサービスの設定を行います。
        user_data = ec2.UserData.for_linux()
        user_data.add_commands(
            "sudo dnf update -y",  # システムの更新
            "sudo dnf install -y git docker",  # GitとDockerのインストール
            "sudo systemctl start docker",  # Dockerサービスの開始
            "sudo systemctl enable docker",  # Dockerサービスの自動起動設定
            "sudo usermod -aG docker ec2-user",  # ec2-userをdockerグループに追加
        )

        # EC2インスタンスの定義
        # Discord Botを実行するためのEC2インスタンスを定義します。ここではインスタンスタイプ、マシンイメージ、
        # VPC、IAMロール、セキュリティグループ、ユーザーデータスクリプトを指定しています。
        ec2_instance = ec2.Instance(
            self, "EC2Instance",
            instance_type=ec2.InstanceType("t2.micro"),
            machine_image=ec2.GenericLinuxImage({'ap-northeast-1': 'ami-012261b9035f8f938'}),
            vpc=vpc,
            role=ec2_role,
            security_group=ec2_sg,
            user_data=user_data,
            ssm_session_permissions=True  # SSMセッションマネージャを通じたEC2インスタンスへのアクセスを許可
        )

        # SSMセッション開始コマンドの出力
        # AWS CLIを使用してSSMセッションマネージャを介してEC2インスタンスに接続するためのコマンドを出力します。
        CfnOutput(
            self,
            "StartSsmSessionCommand",
            value=f"aws ssm start-session --target {ec2_instance.instance_id}\n\n"
                "sudo su - ec2-user",
            description="上記のコマンドを実行してSSMを利用してEC2に接続してください",
        )

AWS本番環境の構築の実施

それでは起動しましょう。次のコマンドを実行するだけです。途中、構築を進めていいか確認されますが「y」を押して進めてください。

cdk deploy

実行後およそ5分ほどでAWSの作成が完了します。

作成したEC2へローカルからアクセスする

AWSが構築できましたので、早速EC2に接続してみましょう。

EC2との接続にはAWSで用意されているSSMを利用します。SSMの接続コマンドをデプロイ完了時にターミナルに表示されていますので、コピーして使いましょう。

図

ターミナルにコピーしたコマンドを貼り付けて実行します。

aws ssm start-session --target 作成したEC2のインスタンスID

接続後、EC2内のシステムユーザーをec2-userに切り替えます。

sudo su - ec2-user
図

[ec2-user@ip-xxx-xxx-xxx-xxx]$と表示されたら接続成功です。

EC2にGitHubからアプリをクローンする

EC2にGitHubからアプリをクローンします。もしオリジナルの変更を加えた場合はクローン前にGitHubのリポジトリへソースを反映させてからEC2へクローンしましょう。

git clone https://github.com/あなたのGitHubアカウント名/gihyo-toreca.git

cd gihyo-toreca

#実行したい各チャプターを指定
cd chapter-2

#READMEのマニュアルを参照
cat README.md

EC2内でDockerを起動させて⁠アプリを実行する

本番環境にはDocker Composeが用意されていないため、Dockerfileからイメージを作成して、そのイメージを起動します。

docker build --no-cache -t gihyo-toreca .

このDockerイメージを起動する際、開発時に利用したDISCORD_BOT_TOKENの値を直接、起動コマンド内で指定します。

# Dockerイメージを起動
docker run --rm -it --name app-container -v "$(pwd)":/app -e DISCORD_BOT_TOKEN='あなたのボットのトークン' gihyo-toreca

これでアプリが起動しましたので、Discord上から!make 石の巨人のモンスターと入力してみましょう。開発環境時と変わらずDiscord上からメッセージをターミナル上のログで表示できていたら成功です!

図

Discord上で「ただいま作成中...」の応答メッセージも確認できたらCtrl + Cを押してアプリを終了しましょう。

また、ローカルからEC2への接続を解除します。

exit

AWS本番環境の削除

SSMの接続解除までできたら、CDKによるAWS環境の削除をしてみましょう。途中、削除を進めていいか確認されますが「y」を押して進めてください。

cdk destroy

最後に、Dockerを終了します。

exit

以上でEC2へのデプロイから削除の方法まで終わりました。

これでいつでも気軽にAWSの構築と削除ができるようになりました!

次回⁠EC2とBedrockの連携

第3回ではAmazonの生成AIであるBedrockとEC2の連携に挑戦します。そしてBedrockでは文章生成を行います。お楽しみに!

おすすめ記事

記事・ニュース一覧