Discordの自作botを無料で一生動かしたい

Google Cloud Platform（GCP）でVMインスタンスを立ち上げ、Pythonファイルを常時稼働させる方法についてまとめました。半分以上は先輩から教えてもらったことの受け売りです。

# 状況
Discord上で動作するbotをPythonで作成しました。Jockey Musicなどと同様に、コマンドを入力するたびに何かしらのアクションを実行するタイプのbotなので、常時稼働させておく必要があります。ローカル環境で実行し続けるのは電気代が勿体ないので、以前はHerokuのような分かりやすいホスティングサービスを利用していました。そのHerokuが少し前に有料化してしまったので、今回はGoogle Cloud Platform（GCP）を使ってbotを動かすことにしました。

# Google Cloud Platformで仮想マシンを作成する

https://console.cloud.google.com/welcome

まずは上のリンクからGoogle Cloudのアカウント登録を行います。
諸々同意して、「無料で利用開始」のところからクレジットカード情報を登録します（請求されないので大丈夫です）。
![スクリーンショット 2024-10-15 033813.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/8ad46f04-910c-d707-e742-d67d0e0796ab.png)

登録が終わると、MyFirstProjectというプロジェクトが勝手に作成されていると思います。もしなければ適当な名前のプロジェクトを作成します。
「VMを作成」からVMインスタンスの作成に入ります。
![スクリーンショット 2024-10-15 033935.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/68878cd4-c33b-1c86-a2b4-0f796739aee0.png)

ここから仮想マシンの構成を決めていきます。（あまり無いと思いますが）無料枠の条件が変わってしまうこともあるかもしれないので、以下リンクから一応チェックしておくと良いかもしれません。

https://cloud.google.com/free/docs/free-cloud-features?hl=ja#compute

仮想マシンの名前は何でも良いです。リージョンを`us-west1 (オレゴン)`に設定し、マシンタイプシリーズは`E2`、マシンタイプは`e2-micro`にします。`us-central1 (アイオワ)`や`us-east1 (サウスカロライナ)`も無料枠の対象ですが、日本に近いオレゴンを選ぶのが一般的なようです。ゾーンは何でも良いです。

![スクリーンショット 2024-10-15 004335.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/67d67504-cd72-0002-1cd4-4e2b6e7a6f73.png)

ブートディスクの「変更」から、OS等の設定を行います。OSは`Ubuntu`、バージョンはLTS（長期サポート）のもののうち最新のものを選びます。2024年10月現在は`Ubuntu 24.04 LTS`です。`Arm64`よりは`x86/64`の方が良いと思います。ブートディスクの種類は`標準永続ディスク`、サイズは`30GB`とします。10GBのままでも良いですが、折角30GBまで無料なので。
![スクリーンショット 2024-10-15 004902.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/1c71d9c6-fd33-e59a-58d8-3e8297921fae.png)

HTTPトラフィック、HTTPSトラフィックを許可しておきます。これを許可しておくと、WEBサーバーとしての活用ができるようになります。その予定が無ければ許可しなくても大丈夫です。また、詳細オプションのネットワーキングからネットワークインターフェースの編集を行います。
![スクリーンショット 2024-10-15 010102.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/49d0097e-b4d8-4aee-6b43-bfb6c27d00b7.png)
![スクリーンショット 2024-10-15 010725.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/01f26f48-a753-35f3-bd70-f2568d14583f.png)

外部IPv4アドレスはデフォルトでエフェメラルになっていますが、静的外部IPアドレスを予約しておきます。名前・説明は適当で良いです。
![スクリーンショット 2024-10-15 011241.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/203006b4-9220-4865-2fd8-cfb0404f1f72.png)

ここまで設定できたら、作成を押下して完了です。
月間予測が$7.31となっていますが、無料枠を利用していれば課金されることはありません。
![スクリーンショット 2024-10-15 015356.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/35b46694-a9ae-a1c2-25c1-7aaeeed331a2.png)

なお、予約した外部IPアドレスは後で接続する際に使います。
![スクリーンショット 2024-10-15 015120.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/d03282cb-efb8-d448-641b-ec430ec71a70.png)


# 公開鍵と秘密鍵を作る
PuTTYgenを使って、RSA暗号の公開鍵・秘密鍵の生成を行います。インストールは以下リンクから。

https://www.puttygen.com/

PuTTY Key Generatorを立ち上げてGenerateを押します。
![スクリーンショット 2024-10-15 012502.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/ecb9124a-83b9-747d-61d0-1fe2db547385.png)

この画面になったら、マウスカーソルを適当に動かしまくります。その軌道を使って暗号を作っているらしいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/a888803e-f2c4-b8cd-3e37-93d55411e533.png)


生成が終わるとこの画面になるので、Public Keyをコピーします。
![スクリーンショット 2024-10-15 032844.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/b63fd789-0349-a26e-a1b5-62c8ca92ebda.png)

以下リンクから、メタデータの設定に移ります。

https://console.cloud.google.com/projectselector2/compute/metadata

SSH認証鍵1にペーストし、Enterを押下します。これで、MyFirstProject内のすべての仮想マシンがこの公開鍵を持つようになります。ユーザー名がKey commentの内容になっていることに注意（rsa-key-yyyymmddの形式）。
![スクリーンショット 2024-10-15 013544.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/3127cd70-5327-0ff1-9e6d-839269374655.png)

RSA暗号の仕組み上、公開鍵は外に漏れても問題ありません。仮想マシンに接続するためには、PuTTY Key Generatorで公開鍵と一緒に生成された秘密鍵が必要になります。
Save private keyを押して、ローカルに秘密鍵を保存します。
![スクリーンショット 2024-10-15 032844.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/20a5393d-117b-242b-76b4-8f30c2958ccc.png)


# SSH接続する
WinSCPを使って接続します。インストールは以下リンクから。

https://winscp.net/eng/download.php

ホスト名に先ほど立ち上げたVMインスタンスの外部IPを、ユーザー名にrsa-ssh-yyyymmdd形式のKey commentを入力します。入力したら「設定」を押します。
![スクリーンショット 2024-10-15 015608.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/748fbc69-9235-cb96-5c5a-c8b740d743b7.png)

設定を色々いじります。シェルは`sudo su -`とします。
![スクリーンショット 2024-10-15 015824.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/8890d52b-ae3a-64b4-c3db-1b971e4be329.png)

SSH -> 認証で、秘密鍵をセットします。拡張子は.ppkです。セットしたらOKを押し、ログインします。
![スクリーンショット 2024-10-15 020041.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/22a186de-7b8e-c2cd-4df2-eae0e806435d.png)

左がローカル、右が仮想マシンのディレクトリとなっています。Pythonファイルを動かすためには、まず左から所望のPythonファイルを探してきて右にドラッグ＆ドロップする必要があります。ただし、所有者がrootとなっているディレクトリは触れません。パーミッションがrwxr-xr-xとなっていて、我々はオーナーとしてではなくグループメンバとしてアクセスしているからです。
![スクリーンショット 2024-10-15 020351.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/f4a3b67c-dbd9-4b57-1fcd-379518b13291.png)

homeディレクトリの中に、ユーザー名のフォルダがあると思います。そこには書き込み可能（rwxrwxr-x）なので、discordという名前でディレクトリを作成し中にPythonファイルを放り込みます。

![スクリーンショット 2024-10-15 021758.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/3638fdf6-9429-c929-4d26-5f039d18d425.png)

# コマンドを打つ
あとはコマンドを打ってPythonファイルを動かすだけです。WinSCPでもコンソールが使えるのですが、永続的に動かすための必須コマンド`nohup`がうまく動作しません（何故か接続を切られてしまいます）。そこで今回はPuTTY.exeを使います。インストールは以下リンクから。

https://www.putty.org/

やることはWinSCPとほぼ同じです。Host Nameには仮想マシンの外部IPアドレスを入力します。
![スクリーンショット 2024-10-15 024712.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/1ff2f59f-a6b1-6ad3-feb4-4841791cdec5.png)

Connection -> DataのLogin detailsでAuto-login usernameを設定します。
![スクリーンショット 2024-10-15 025551.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/d8c2a8f6-fd3e-9026-4560-0ecd5dc7ded6.png)

Connection -> SSH -> AuthのPrivate key file for authenticationに秘密鍵をセットします。
![スクリーンショット 2024-10-15 025309.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/ffa1b8e7-8105-2b56-f7c1-4d6159c8692c.png)

以上の設定が終わったらOpenを押します。コマンドラインが起動します。
まず、以下のコマンドを実行してpythonが入っていることを確認します。Ubuntuの最新版をイメージに設定していれば入っているはずですが、万が一なければ`$ sudo apt install python3`を実行します。
```bash
$ python3 --version
```
Pythonファイルを実行するためには、色々モジュールをインストールしておく必要があります。特にDiscordのbotを動かす際には`$ pip install discord.py`が必須です。
以下のコマンドを実行して、pipを使えるようにします。
```bash
$ sudo apt update
$ sudo apt install python3-pip
```
しかしおそらく、そのまま`$ pip install discord.py`としても失敗します。これはroot権限でないと実行できないからです。システム全体にパッケージをインストールするのは何かと危ないので、それを阻止する設定になっています。
システム全体に影響を与えたくない場合は、Pythonの仮想環境を使ってパッケージをインストールできます。
以下は仮想環境を作成して有効化するコマンドです。
```shell
$ python3 -m venv venv
$ source venv/bin/activate
```
満を持して`$ pip install discord.py`を実行します。私の場合は以下のモジュールをインストールしました。
```shell
$ pip install discord.py
$ pip install aiohttp
```
さて、あとは`nohup`コマンドを打つだけです。プロセスは通常、仮想端末を閉じたりログアウトした瞬間にHUPシグナルを受け取って終了するようにできています。`nohup`はそのHUPシグナルを無視するように命令するものです。すなわち、指定したプロセスがシェルを閉じた後も継続して動作するようになるのです。
Pythonファイルが置かれてあるディレクトリに移動して、`nohup`コマンドを実行します。
```shell
$ cd discord
$ nohup python3 hogehoge.py &
```
最後に、実行できているかどうかを確かめてみます。以下は、python3で動いているプロセスを全て表示するコマンドです。
```shell
$ ps -ef | grep python3
```
以下のように目的のPythonファイルが動作していれば成功です。
```
rsa-key+    6794    6319  1 18:11 pts/0    00:00:00 python3 hogehoge.py
```
この結果から、プロセス識別子（PID）が`6794`であることも分かります。もうシェルを閉じようがPCを閉じようがPythonファイルは動き続けますが、実行を止めたいとなったら`$kill 6794`とします。

# ちなみに
こういうbotです。
![スクリーンショット 2024-10-15 035410.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636314/552664a5-6d96-6455-f468-75a719a18c09.png)
Nitroユーザーが他サーバーの絵文字を手軽にサーバーに追加できます。