HerokuからRaspberry Piに乗り換えた話

# 概要
本投稿は、HerokuからRaspberry Piに乗り換えた経緯と、何を行ったかについてまとめたものである。

# 簡単な自己紹介
- プログラミングの個人事業を営んでいる電気電子系大学に通う大学３年生
- 主にアプリ、サービスの開発・運営をしている
- 得意な言語はPythonです

2年ほど前からご縁があり、趣味でやっていたプログラミングを仕事にしています。

# HerokuからRaspberry Pi に乗り換えた経緯
前述の通りアプリ、サービスの開発をしていまして、その際にHerokuをサーバーとして使用していました。

しかしながら、やはりクラウドサービスだと月額であったり、ファイルやデータベースをなるべく使用しないように（データベースなどを設置すると更に月額が掛かる）コードを組んだりと、少々制限があることに少々辟易としていました。

そこで色々と考えてみた結果、Raspberry Piをサーバーとして使用することに至りました。

# Raspberry Piのメリット・デメリット

ここからは、Raspberry Piをサーバーとすることのメリット・デメリットについて説明します（あくまでも私個人が調べたことであり、一個人の解釈と捉えてください）。

### 主なメリット
- 運用コストが低い
- 制限なく自由に環境を設定することが出来る
- USBにSSDやHDDを接続するだけで、自由に容量を増量できる
- Linuxの知識を得ることが出来る

最も大きく感じたメリットは**運用コストが低い**ことです。Herokuだと毎月数千円など掛かっていましたが、Raspberry Piに変更してからはたったの数十円から数百円程度で済んでいます。

以下の条件下
- 1ヶ月を30日とする
- 1ヶ月の間、Raspberry Piは3A（推奨電源容量）、5V（定格電圧）で動作し続けるものとする
- （私が四国に住んでいるため）[四国電力](https://www.yonden.co.jp/customer/price/calculation_method.html)の2023年9月10日から2023年10月10日までの電力量料金である40円79銭（第3段料金）を1kwhあたりの電気代として計算する
- その他、Raspberry Pi以外で発生した電力は無視するものとする

でRaspberry Piの消費電力を計算してみると、

$$
[3\text{A} \times 5\text{V}] \div 1000 \times 24\text{h} \times 30\text{day} \times \text{40円79銭/kwh} = 440.532\text{円}
$$

このように約440円で一ヶ月運用できた計算となります。

更に、この計算は最大の電力消費を30日間続け、最も料金が高い第3段料金を1kwhあたりの電気代として計算しているため、実際にはこの値よりも小さくなります。

また、これはあくまでも個人的なものですが、**「Linuxの知識を得ることが出来る」** ことも大きなメリットかと思います。

研究室でもFedoraというLinuxを使用していますが、あくまでも大学側のPCであるため自分好みに変更することも出来ないため、サーバーを構築する際に色々触れるのは良い機会でした。

### 主なデメリット
- セキュリティリスクがある
- 火事のリスクが常にある
- 障害発生時に自分で対処する必要がある

最も大きいデメリットは**セキュリティリスク**でしょう。

万が一にもハッキングなどされ、内部のデータが流出すれば大問題に発展します。

クラウドで運用していたのならば、そのクラウド側に過失がある場合こちらが全ての責任を負うことはありませんが、自宅サーバーの場合は全ての責任を負うこととなるでしょう。

もう一つ個人的にリスクと感じるものは、**火事**のリスクです。Raspberry Piが出火した話は今のところ聞いたことはありませんが、電化製品である以上一定のリスクはあるものと考えています。

>Raspberry Piではありませんが、以下のようなノートパソコンがサーバーとして使用し出火した例があります。
>https://www.hotel-raj.si/hr

<br>
以上がRaspberry Piをサーバーとすることのメリット・デメリットですが、これらを踏まえた上でメリットがデメリットよりも大きいと感じたためRaspberry Piをサーバーとして運用することとなりました。

# ということでRaspberry Piでサーバーを構築してみた

長い前フリが終わり、ここからはRaspberry Piが届いてからHerokuのプログラムを移行するまでに行ったことを綴っていきます。

## そもそもどのモデルを買ったのか
今回買ったモデルは**Raspberry Pi 4 Model B 4GB**です。

2019年6月24日に発売されてから既に4年以上過ぎていますが、まだまだ現役で使用できる性能となっています。
以下は簡単なスペックです。
- CPU ： Broadcom BCM2711 quad-core Cortex-A72 (ARM v8) 64-bit SoC
- RAM : 4GB
- LAN : Gigabit Ethernet IEEE 802.11ac
- USB : USB 2.0×2、USB 3.0×2

とまあ、十分に使えるスペックです。

8GBモデルもありましたが、今回のサーバーは4GBでも問題なく動かせるため4GBモデルを購入しました。

## OSインストールからの初期設定
最終的にはSSDからブート出来るようにしますが、まずは慣習に従ってSDカードにOSをインストールします。

:::note info
Raspberry Pi 4からUSBにOSインストール済みのSSDを接続することでそのまま使用できますが、ロットによってブートローダーがUSBブートに対応していない場合があるため、そのためにも最初はSDカードからブートすることを推奨します。
:::


**1.** [Raspberry Pi公式サイト](https://www.raspberrypi.com/software/) からRaspberry Pi Imagerをダウンロード＆インストールする。

**2.** 画像のようにRaspberry Piデバイス、使用するOS、インストールするストレージを設定する。
<img width="400px" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/835335/96318cf9-c613-be6f-211c-3411400ca181.png">

**3.** 「次へ」をクリックする。「Would you like to apply OS customization settings?」(OSのカスタマイズ設定を適用しますか？)と聞かれますが、後で設定するため「いいえ」をクリックします。
<img width="400px" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/835335/74d6f0b6-58fa-e087-9692-eb98af3b3bd3.png">

**4.** 本当に実行するかを聞かれるため、問題がなければ「はい」をクリックします。
<img width="400px" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/835335/46ac1b33-47e7-21a4-a70e-fd947b28b447.png">

**5.** 書き込みが完了するまで待ち、完了後はパソコンからSDカードを抜き取る。

あとは、Raspberry PiのSDカード差し込み口に入れて完了です。

Raspberry Piに電源を入れ、少し待つとユーザーを登録する画面が出るため、任意のユーザ名とパスワードを入れて初期設定完了です。

## SSDからブートさせる

SDカードからブートさせましたが、一般的なSDカードは約1000回ほど書き込むと故障するためSSDからブート出来るように設定を変更していきます。

なお、今回は改めてSSDにOSをインストールしてそこからブートさせる方法を使用するため、SDカードのデータをコピーするやり方は紹介しません。（ここで結構トラブったから）

:::note info
今回はサーバーとして24時間動かし続けるためにSSDへ変更しますが、例えば電子工作として使用したり24時間動かしたりする必要がない場合には、Swapを制限したりするだけでもSDカードの寿命はぐんと伸びますのでこれからの工程は不要となります。
:::

ここで、使用したSSDとSATA変換ケーブルを紹介します。
- SSD : [Kingston A400 SATA SSD 120GB](https://www.kingston.com/jp/ssd/a400-solid-state-drive)
- ケーブル：[BENFEI SATA USB変換アダプター](https://amzn.asia/d/4O9Clpt)

まずは、SDカードと同様にSSDにOSをインストールしておきます。

次にRaspberry Piのブートローダーをアップデートします。
以下のコマンドを入力します。
```shell
sudo raspi-config
```
昔のBIOSみたいな画面が出力され、「6 Advanced Options」→「A5 Bootloader Version」→「E1 Latest Use the latest version boot ROM software」の順で選択し、「Yes」を選択すると最新のブートローダーに変更されます。

また、同様に「6 Advanced Options」→「A4 Boot Order」→「B2 USB Boot」の順で選択し、「Yes」を選択するとUSBからのブートが優先されるようになります。

ここで、
```shell
sudo reboot
```
で一旦Raspberry Piを再起動します。再起動したあとは
```shell
sudo shutdown -h now
```
で今度は完全にシャットダウンします。

Raspberry PiのLEDが赤のみ光っていればシャットダウンが完了した合図なので、そこで電源を落とします。

ここで、USBにSSDを接続しSDカードを抜きます。

そして、電源を入れてOS初回起動時と同じ画面が出力されれば完了です。
あとは、同様にユーザ名、パスワードを設定しましょう。

もし、ここで起動出来なかったり動作が不安定な場合、デバイスのドライバが**UASP**になっている可能性があります。

>UASPとは、コンピュータ本体と周辺機器の接続によく用いられるUSBの拡張仕様の一つで、ハードディスクなどのストレージ（外部記憶装置）のデータ転送を効率化して実効速度を高めるもの。USB 2.0以降で利用できる。
>https://e-words.jp/w/UASP.html

このUASPが少々曲者で、Raspberry Piでのドライバは不完全らしく相性問題が発生し易いようです。

私の場合、現在使用しているケーブルの前に別のものを使用していましたが、相性が悪く何回OSを再インストールしても起動出来ず、起動できてもOSが完全に立ち上がらず、約１日ほど潰れました。

対象法としては
- 別のケーブルに変える
- USAPを無効化する

といったことが挙げられます。

以下のサイトで「USAPを無効化する方法」が紹介されているため、参考にしてください。
- https://note.com/fumix/n/n0af975e5b339
- https://obel.hatenablog.jp/entry/20220503/1651566544
- https://signal-flag-z.blogspot.com/2020/03/raspberry-pi-4usb30ssd.html

## セキュリティ対策

SSDのブートが問題なく済んだため、ここからはセキュリティ対策をしていきます。

私が行った主に行ったセキュリティ対策は
- SSHポートの変更
- SSHを公開鍵認証に変更
- ファイアウォールの設定

になります。

本来はここにDNSサーバを設置したり仮想化したりする必要があったりしますが、私が運営しているサーバーはWEBサーバーでも何でもなく、ただPythonのプログラムを永延と実行し特定の処理を行うだけのものなので、今回は省きました。

### IPアドレスの固定

セキュリティ対策でも何でもありませんが、まずはIPアドレスを固定していきます。
```shell
sudo apt install dhcpcd5
```
でdhcpcd5をインストールし、
```shell
sudo nano /etc/dhcpcd.conf
```
でファイルを編集します。

一番下に以下のような内容を追記します。
```shell
interface eth0
static ip_address=192.168.0.10/24
static routers=192.168.0.1
static domain_name_servers=8.8.4.4 8.8.8.8
```

ここで、ip_addressは固定したいIPアドレス、routersはデフォルトゲートウェイのIPアドレス、static domain_name_serversはDNSサーバーのアドレスになります。

:::note info
8.8.4.4 8.8.8.8はgoogleのPublic DNSであり、通信速度向上や「temporary failure in name resolution」の対策になります。ただ、プライバシー関係の懸念点があり、ある意味セキュリティのリスクを負う要因になりかねない可能性があります。

一応GoogleのPublic DNSなので私は信頼して使用していますが、もし信用できない方は別のDNSサーバーないしは自分で建てられることを推奨します。
:::

### SSHポートの変更

SSHポートは初期設定で22番に設定されており、ここまで使用しているとセキュリティリスクとなるため、別のものに変更します。
まず
```shell
cd /boot
sudo mkdir ssh 
```
でSSHを有効化します。次に
```shell
sudo nano /etc/ssh/sshd_config
```
で設定ファイルを開き
```
#Port 22
```
となっているところを
```shell
Port 任意のポート番号
```
と#（コメントアウト）を外し任意のポート番号に変更します。

なるべく他のポートと重複しないように5000番以降にすることをオススメします。

```shell
sudo reboot
```
で再起動し、別端末から以下のようにssh接続して、問題ないことを確認しましょう。
```shell
ssh [ユーザ名]@[RaspberryPiのIPアドレス] -p [ポート番号]
```

これで、ポート番号の変更は完了です。



### SSHの公開鍵認証化

ポート番号を変えただけでは、まだ甘いため公開鍵認証を使用しないとSSH接続出来ないように変更します。

まず、接続に使う端末（今回はWindowsのPCから）で
```shell
ssh-keygen  -b 4096 -t rsa -C "hogehoge@gmail.com
```
を実行し、鍵を生成します。

RaspberryPi側に戻り、
```shell
mkdir ~/.ssh
```
で作成された公開鍵を受け取り格納するディレクトリを作ります。

接続に使う端末に戻り、
```shell
scp -P [ポート番号] id_rsa.pub [ユーザ名]@[RaspberryPiのIPアドレス]:/home/[ユーザ名]/.ssh
```
で公開鍵をRaspberryPiに送ります（ぶっちゃけUSBとかに入れて送る方法でも可能です）。

またRaspberryPi側に戻り、
```shell
cd ~/.ssh
cat id_rsa.pub >> authorized_keys
chmod 600 authorized_keys
chmod 700 ~/.ssh
rm ~/.ssh/id_rsa.pub
```
を実行します。

先程同様に、別端末から以下のようにssh接続して問題ないことを確認しましょう。
```shell
ssh [ユーザ名]@[RaspberryPiのIPアドレス] -p [ポート番号] -i [秘密鍵のパス]
```

接続出来ることを確認してから
```shell
sudo nano /etc/ssh/sshd_config
```
でまた設定ファイルを開き
```shell
#PermitRootLogin prohibit-password
#PasswordAuthentication yes
#PermitEmptyPasswords no
```
を
```shell
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
```
に変更しておきましょう。

>PermitRootLogin：rootによるSSH接続の可否
>PasswordAuthentication：パスワードによるSSH接続の可否
>PermitEmptyPasswords：パスワード入力、公開鍵認証なしのSSH接続の可否

これで公開鍵認証化が完了しました。

### ファイアウォールの設定

最後にファイアウォールを設定していきます。

以下のコマンドでufwをインストールします。
```shell
sudo apt install ufw -y
```

```shell
sudo ufw enable
```
でファイアウォールを有効化します。
なお、ポート開放するまでSSH接続が出来なくなるので注意してください。

SSH接続するため、ポート開放をしていきます。以下のコマンド
```shell
sudo ufw allow from 192.168.0.0/24 to any port [SSHのポート番号]
```
を実行します。これで、同じLAN内からしかアクセス出来なくなります。

```shell
sudo ufw status
```
これでファイアウォールの状態を確認することができ
```shell
To                         Action      From
--                         ------      ----
SSHのポート番号             ALLOW       192.168.0.0/24
```
恐らくこのようになっていると思います。

その他のファイアウォールの詳細は以下のページを参考にしてください。
[【パクろう】ラズパイでファイアーウォールを設定する方法](https://sukiburo.jp/raspberry-pi-firewall/)

これでセキュリティ対策はほぼ完璧でしょう。

## Pythonを実行

ここからは、Herokuで動かしていたPythonのプログラムをサービスとして登録し、永続的に実行が出来るようにします。

まずは、pipとvenvをインストールするため
```shell
sudo apt-get -y install python3-pip
sudo apt-get -y install python3-venv
```
を実行します。

:::note info
そのまま「pip install ライブラリ名」を実行すると
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.

    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx install xyz, which will manage a
    virtual environment for you. Make sure you have pipx installed.

    See /usr/share/doc/python3.11/README.venv for more information.

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

このように怒られるため、venvで仮想環境を作ってあげそこにライブラリをインストールをするにしましょう。
:::

任意のディレクトリ内にプログラムを配置し、そこでvenvで仮想環境を構築&ライブラリをインストールします。

今回は、  以下のような10秒ごとにDiscord webhookでメッセージ送るプログラムを配置したものとします。
```python:/home/roki/test/main.py
import time
import requests

WEBHOOK_URL = "webhook URL"

payload = {
    "username" : "テスト",
    "content" : "テスト通知です"
}

while True:
    res = requests.post(WEBHOOK_URL, json=payload)
    time.sleep(10)
```

次にサービスの追加をしていきます。
```shell
cd /etc/systemd/system/
```
でsystemフォルダに移動し、
```shell
sudo nano test.service
```
で.serviceファイルを作ります。
```shell
[Unit]
Description=Test
After=multi-user.target

[Service]
Restart=always
WorkingDirectory=/home/roki/test
ExecStart=/home/roki/test/venv/bin/python3 /home/roki/test/main.py

[Install]
WantedBy=multi-user.target
```
このように書き込んで保存します。あとは
```shell
sudo systemctl start test.service
sudo systemctl status test.service
```
でサービスを開始し、状態を確認しましょう。
問題なく起動していれば
```shell
● test.service - Test
     Loaded: loaded (/etc/systemd/system/test.service; enabled; preset: enabled)
     Active: active (running) since Sun 2023-10-29 19:40:52 JST; 3h 40min ago
   Main PID: 40132 (python3)
      Tasks: 6 (limit: 3919)
        CPU: 2h 9min 646ms
     CGroup: /system.slice/test.service
             └─40132 /home/roki/test/venv/bin/python3 /home/roki/test/main.py
```
と、「Active」のところが「active (running)」となっています。

RaspberryPiが再起動などをした後でも自動的に起動するように設定する際は
```shell
sudo systemctl enable test.service
```
で設定出来ます。

これでプログラムを永続的に実行し続けることが出来ます。

# 最後に
今回は「HerokuからRaspberry Piに乗り換えた話」と題しまして、Raspberry PiでPythonを動かす方法とセキュリティ対策について長々と書かせて頂きました。

ここからは完全に蛇足ですが、やっぱりRaspberry Piってスゴイですよね。

クレジットカードサイズなのに、4コアのCPUと4GBのRAMを載せてるって...もう最高。

そこにドライブを付けるだけで、いくらでもファイルを保存しまくれるって素晴らしいですね。

某おやおや卿並みに「素晴らしい　素晴らしい…！」ってずっと言ってました（~~今考えたらまあまあヤバい奴っすね~~）


皆様の参考になれば幸いです。それでは失礼します。