AWS EC2インスタンスにDocker環境を入れてDiscord Botを動かす(Python)

#導入
[Docker上でDiscordのBotを動かす](https://qiita.com/otuhs_d/items/679a429d64ad5166bddb) のアイデアを元に誰かがボイスチャットに入ったときに入場曲を流すBotを、勉強を兼ねてPython/Docker/AWS EC2の環境で作成してみました。
ローカルで実行したいだけの場合や、ローカルのDocker環境で実行したい場合はDockerのくだりやEC2のくだりを適宜省略すれば動かせます。

# 下準備
1. discordのBotを作成し、アクセストークンを控えておく。
参考: [簡単なDiscord Botの作り方（初心者向け）](https://note.com/bami55/n/ncc3a68652697)

2. EC2インスタンスを作成し、SSH接続できるようにしておく。今回は無料枠のAmazon Linux 2 t2.microを選びます。
参考:[【初心者向け】Amazon EC2にSSH接続する【Windows、Macintosh】] (https://dev.classmethod.jp/articles/aws-beginner-ec2-ssh/)

3. EC2インスタンス上にdocker環境を構築しておく。こちらの2,3をご参考に。
参考: [AWS EC2インスタンスにdockerとdocker-composeをインストールして簡単なWEBサービスを立ち上げる方法](https://qiita.com/y-do/items/e127211b32296d65803a) 

これで進捗90%です。

#Pythonコードの作成
[discord.py](https://discordpy.readthedocs.io/ja/latest/api.html) の使用を前提で作成します。
`discord.PCMVolumeTransformer(xxx, volume=0.04)`の部分で音量を絞っています。結構小さめですが、これくらいがちょうどよいです。
`if before.channel is None:`で、接続なしの状態からそのチャンネルに入った場合のみの再生に限定しています。
`sleep()`で時間を決めて退出していますが、もっといいやり方があるかも。
今回は複数の音源をランダムに再生するように決めています。確率はお好みで。人によって再生する音源を変えても良いです。

```entrancebot.py
import discord
import random
from time import sleep

# Botのアクセストークン
discord_token = 'XXXX0XXX0000XX00.XXX00XX0.x000XXX0-XXXXX00000XXX0X000X'

client = discord.Client()

@client.event
async def on_ready():
	print('Botを起動しました')

@client.event
async def on_voice_state_update(member, before, after):
	if member.bot: 
		return
	if before.channel is None and after.channel.id == 000000000000000000: # 接続するボイスチャンネルのID
		sleep(1)
		await member.voice.channel.connect()
		print('接続しました。')
		num = random.randint(0,5)
		if num == 0:
			member.guild.voice_client.play(discord.PCMVolumeTransformer(discord.FFmpegPCMAudio("makenai.mp3"), volume=0.04))
			sleep(22)
		elif 1 <= num <= 2:
			member.guild.voice_client.play(discord.PCMVolumeTransformer(discord.FFmpegPCMAudio("tigau.mp3"), volume=0.04))
			sleep(20)
		elif 3 <= num <= 4:
			member.guild.voice_client.play(discord.PCMVolumeTransformer(discord.FFmpegPCMAudio("arigatou.mp3"), volume=0.04))
			sleep(20)
		elif num == 5:
			member.guild.voice_client.play(discord.PCMVolumeTransformer(discord.FFmpegPCMAudio("ambitious.mp3"), volume=0.04))
			sleep(14)
		member.guild.voice_client.stop()
	await member.guild.voice_client.disconnect()
	print('接続解除しました。')
client.run(discord_token)


```

#Dockerfileの作成
dockerイメージの元となるDockerfileを作成します。拡張子のないただのテキストです。
今回は音声を扱うのでffmpegをインストールし、音声を扱えるdiscord.py[voice]を入れます。
ローカルで実行する場合もこれらをインストールしておけば動きます。

```:Dockerfile
FROM python:3.9.4-buster

WORKDIR /app
COPY . /app

RUN apt-get update && apt-get install -y ffmpeg

RUN python -m pip install \
--upgrade pip \
--upgrade setuptools \
discord.py[voice]

CMD ["python3.9", "entrancebot.py"]
```

#docker-compose.ymlファイルの作成
直接Dockerfileで構築する場合は不要ですが、使っておきます。
ほぼ`build: .`でDockerfileを指定しているだけです。

```docker-compose.yml
version: "3"
services:
  python-service:
    restart: always
    build: .
    container_name: "pythonContainer"
    working_dir: "/app"
    tty: true
```


#ディレクトリの作成
作った3ファイル+音源ソースファイルを同じディレクトリにぶち込んでおきます。今回は`entrancebot`ディレクトリとします。

#ディレクトリのコピー
下準備のssh接続完了後、`entrancebot`ディレクトリの上階層で下記を実行し、ローカルのディレクトリをEC2インスタンス上のディレクトリにコピーします。
指定するユーザー名はデフォルトではec2-user, IPアドレスはご確認を。

```
$ scp -r -i ~/.ssh/xxxx.pem entrancebot ec2-user@xx.xxx.xxx.xx:/home/ec2-user
```

#dockerイメージ・コンテナの作成
まずはEC2インスタンスの中に入ります。

```
$ ssh -i ~/.ssh/xxxx.pem ec2-user@xx.xxx.xxx.xx
```

入るとホームディレクトリ`/home/ec2-user`にいるはずです。
そこで

```
$ cd entrancebot
```
で、先程コピーしたディレクトリの中に移動します。
さらに、

```
$ docker-compose up -d --build
```
で、dockerイメージとコンテナが作成され、起動されるはずです。
これでPythonコード上で指定したボイスチャンネルに入場すれば、Botが歌ってくれます！

ちなみに

```
$ docker-compose exec python-service /bin/sh
```
でdockerコンテナの中に入ることもできます。

#片付け


```
$ docker container ls -a
CONTAINER ID   IMAGE                        COMMAND                  CREATED          STATUS          PORTS     NAMES
daad8f6d5c15   entrancebot_python-service   "python3.9 entranceb…"   21 seconds ago   Up 20 seconds             pythonContainer
```
で表示されたIDを使って、

```
$ docker container stop daad8f6d5c15
$ docker container rm daad8f6d5c15
```

コンテナを停止して、削除。さらに、

```
$ docker image ls
REPOSITORY                   TAG            IMAGE ID       CREATED         SIZE
entrancebot_python-service   latest         e235b4decbcd   4 minutes ago   1.15GB
python                       3.9.4-buster   8f3a05c42ee5   32 hours ago    886MB
```

で表示されたIDを使って、

```
$ docker image rm e235b4decbcd
$ docker image rm 8f3a05c42ee5
```

イメージを削除。
あるいはコンテナ・イメージ削除はdocker-composeコマンドでまとめてできます。

```
$ docker-compose down --rmi all
```

また、`cd ../`でentrancebotディレクトリの上階層に移動して

```
$ rm -r entrancebot
```
でディレクトリを削除。最後に、


```
$ exit
```

EC2を抜けて、ローカルに戻れます。お疲れさまでした。
実行内容を変えたい場合は[リファレンス](https://discordpy.readthedocs.io/ja/latest/api.html)をご参考に。


#不明点・うまく行かなかったところ
1. ローカル(macOS)のdocker環境で実施したときは生成されるimageは`entrancebot_python-service`1つのみで、`python`が別に先に持ってこられることはなかったのだが、EC2上ではpythonイメージが別にある。この違いはなに...？どちらも空の状態から実行したけれど。。理解が足りていない。。

2. ローカルのdocker環境では問題はなかったが、EC2インスタンス上ではentrancebot.pyで`connect()`前に`sleep()`等で間隔開けないとうまく再生されなかった。性能の問題...？

Python/Docker/EC2 すべてが初めて触るものだったので、特定できず。。
詳しい方、コメントいただければと思いますm(_ _)m