raspberrypi5上にdocker構築してその上でWebサーバとdbサーバをたててみる

raspberrypi上にdockerを構築してその上にWebサーバとdbサーバをたててみました。

# raspberrypi5のセットアップ

### インストール
Raspberry Pi Imager を https://www.raspberrypi.com/software からダウンロードし、PC にインストールする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3955514/ac499932-8463-23b8-f405-15be6f33f238.png)

### インストール設定
Raspberry Pi Imager を実行し、OS ボタンを押し、Raspberry Pi OS (64bit)を選択する。Choose Storage から micro-SD を選択し、進む。

「Would you like apply OS customization settings?」が表示されたら、Edit ボタンを押し、ID とパスワードを入力し、ホスト名も設定する。

さらに、「set locale settings」で国とタイムゾーンを選択し、「Save」をクリックする。

micro-SD へのインストールが完了したら、「continue」ボタンを押して Imager を閉じる。

ここで国の設定をしないとWI-FIの規格の違いで繋がらないことがある。
日本に住んでいる人はJPを選ぼう。

### raspberrypi設定

有線マウスと有線キーボードを用意しておくとよい。

microSD をラズベリーパイに挿入し、電源ボタンを押す。
(type-cをさすと勝手に起動する)

一応raspiの設定(左上)を確認しておこう。国や言語が初期設定に戻っている場合がある。

Imager で設定行っても、再度設定するように求められることがある。

国、言語、タイムゾーンを選択する。

日本を選択した場合、言語は日本語になるが、その下に英語を使用するチェックボックスもある。
ネットワーク接続のIDとパスワードを入力し、ホスト名も設定する。

### アップデートを実行

#### 最新のパッケージをインストール
```
sudo apt-get update
sudo apt-get upgrade
```
OSのアップデート
```
sudo apt-get dist-upgrade
```

ラズベリーパイのファームウェアのアップデート
```
sudo rpi-update
```


設定終了後に再起動ボタンを押す。
これで Raspberry Pi 5 のセットアップは終了。

# 同一LAN内でssh接続出来るようにしよう

このraspiは将来サーバになる予定なので他のpcから操作できるようssh接続できるようにしたい。

### raspberrypi側
userの確認
```
whoami
```
sudo(管理者権限)を与える
```
sudo gpasswd -a [ユーザー名] sudo
```
パスワードの変更をし、覚えておく

sshの有効化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3955514/dea717db-a3bf-9abd-1944-48ae987fd577.png)

RaspberryPiの設定または、RaspberryPiConfigurationから

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3955514/e0fce5d9-db9a-1812-f37b-bc66eeeec0ec.png)

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

ufwのインストール
```
sudo apt-get install ufw
```

22番と80番の解放
理由としては
sshのポート番号が22番

後にwebで公開したいので80か443を空ける
httpでのWeb公開のポート番号が80番
もし証明書を発行してhttpsにする場合は443番をあける
```
sudo ufw allow 22
sudo ufw allow 80
sudo ufw reload
```
ファイヤーウォールの有効化
```
sudo ufw enable
```
確認
```
sudo ufw status
```
22と80だけ空いてればOK。

subnetを確認する
```
ifconfig
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3955514/3f95fcbb-6d7a-b901-f634-24c4b506cd7a.png)

このような画面が出るので192.168.hoge.hogeの部分を覚える

これにより同じLAN内でのssh接続が可能になる

### 接続元PCでの操作
```
ssh [ユーザー名]@[ipアドレス]
```

アカウントのパスワードを打つと、Are you sure you want to continue connecting (yes/no)?と聞かれるのでyes。

これによりssh接続が完了する

# 公開鍵認証を利用しよう

### 接続元PCでの操作

#### 鍵作成

rsaは遅いし今後が心配なのでed25519を使用

```
ssh-keygen -t ed25519
```
鍵が生成される場所
```
C:\Users\hoge\.ssh
```
この「id_ed25519.pub」が公開鍵、「id_ed25519」が秘密鍵。

秘密鍵であるid_ed25519は絶対公開しないようにしよう

先ほどraspiにssh接続できるようになったので、ここからはlinuxコマンドで記述する

### raspberrypi側での操作

.sshフォルダを作成する
```
mkdir .ssh
```

.フォルダのlsでの確認は
```
ls -a
```
でおこなう。

先ほど送った”id_ed25519.pub"を”authorized_keys"という名前に変更しつつ".ssh"に移動する。
```
mv id_ed25519.pub .ssh/authorized_keys
```
“.ssh”と”authorized_kyes”のパーミッションを変更する。
```
chmod 700 .ssh
chmod 600 .ssh/authorized_keys
```
600は-rw------で管理者のみ読み取り書き込みが可能で、700はxrw------で管理者の実行読み取り書き込みが可能。

#### 公開鍵認証で接続
最後にsshd_configを修正して、ssh接続を有効にします。
```
nano /etc/ssh/sshd_config
```
以下の行のコメントアウトを削除して公開鍵認証を有効化し、パスワード認証を無効化する(後に外部接続するため、セキュリティ強化)。
```
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
---------------------
PasswordAuthentication no
```
これで公開鍵認証での接続が可能になる。

### 接続元PCでの操作

PC側から以下のコマンドで、公開鍵認証でssh接続を確認。
```
ssh [ユーザ名]@[RaspberryPiのIP] -i 「C:\Users\[ユーザ名]\.ssh\id_ed25519」
```

## configで接続を簡単にしよう

### 接続元PCでの操作

.ssh内にconfigを作成
```
cd C:\Users\hoge\.ssh
touch config
```
手動でもよい

記述方法
```
Host [好きな名前]
	HostName [RaspberryPiのIP]
	User [ユーザ名]
	Port 22
	IdentityFile 「C:\Users\[ユーザ名]\.ssh\id_ed25519」
	ServerAliveInterval 60
```
ServerAliveInterval 60は60秒ごとにサーバ側へセッションを送る。

こうすることですぐセッションタイムアウトが起きなくなる。

# 外部からssh接続出来るようにしよう

### ルーターの設定

ラズパイでなくてもいいので同じネットワーク内のPCからブラウザにルーターのipを入力してルーターの設定画面を表示。
```
route
```
によってゲートウェイ（ルーター）のipアドレスを表示させる。

デフォルトゲートウェイとはpcにローカルipを振る役割をしている場所
windowsの場合
```
ifconfig
```
または
unix系の場合
```
ipconfig
```
から、192.168.hoge.hoge辺りを探してもよい。だいたい192.168.1.1とか

これをブラウザで検索

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3955514/73bc5d1e-968a-75fe-5b9f-32a0a3620950.png)

このような画面を開くことが出来る。

ユーザー名とパスワードが求められることがある。

ここにメーカー別の初期パスワードがまとめられている。
https://0017.org/1491.html#toc3

softbankはuser userであった。

ポートを触れるところがあるはずなので設定

### 設定項目

| 項目                         | 記入事項                       |
|------------------------------|--------------------------------|
| インターネット(WAN)側のポート   | 49152～65535の任意の番号         |
| プロトコル                     | TCP                            |
| 返還後のLAN側のアドレス        | raspberrypiのipアドレス       |
| 返還後のLAN側のポート          | 22        |

---

ブラウザでHTTPプロトコルで通信する場合は80番ポート(HTTPSなら443)を使う。

---

### 通信設定（HTTP）

| 項目                         | 記入事項                       |
|------------------------------|--------------------------------|
| インターネット(WAN)側のポート   | 80                             |
| プロトコル                     | TCP                            |
| 返還後のLAN側のアドレス        | raspberrypiのipアドレス       |
| 返還後のLAN側のポート          | 80                             |


0～1023：ウェルノウンポート (Well-Known Ports) – HTTP(80), HTTPS(443), SSH(22) などの主要プロトコルが使用。

1024～49151：登録済みポート (Registered Ports) – アプリケーションやサービスごとに登録されているポート。

49152～65535：動的ポートは、これらと競合しない使えるポート。

以下のように設定した
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3955514/976c6f4b-1a99-cd62-670b-5ec448c51a0c.png)

50000-50000というのは50000から50000番の範囲のポートを使うという意味である。

私の場合はraspberrypiのipアドレスが192.168.3.39であるためこう設定

### 外部からSSHが通るか確認

まず以下のページを開く
https://www.cman.jp/network/support/go_access.cgi

大きくグローバルIPアドレスが出てくるので覚える

```
ssh -p [上で決めたポート番号] [ラズパイのアカウント名]@[グローバルip]
```
[上で決めたポート番号]とは、自分の場合は50000番

家にいて外部から接続の試し方が分からないという人はスマホのデザリングからするのをおすすめする。
```
$ [アカウント名]@raspberrypi:~ $
```
と表示されたら外部から接続は完了である。

#### configに書きたい方へ

先ほど書いたconfigの続きに
```
Host [好きな名前]
	HostName [グローバルip]
	User [ユーザ名]
	Port [上で決めたポート番号]
	ServerAliveInterval 60
```

# Dockerの構築

## dockerのセットアップ

以下のコマンドでdockerをインストールできる
```
curl -sSL https://get.docker.com | sh
```

バージョンの確認
```
docker -v
```
以下のようにdockerのインストールが確認できる。
```
Docker version 27.4.1, build b9d17ea
```

## docker環境の準備

作業用ディレクトリーの作成
raspi-dockerは任意のディレクトリー名に変えてよい
```
mkdir raspi-docker
```
ディレクトリーが作成できているかどうか確認
```
ls
```
ディレクトリーに移動
```
cd raspi-docker
```

## Webサーバとdbサーバの構築(Node.jsとMySQL)

### Dockerfileファイルの作成
```
touch Dockerfile
```
Dockerfileを開く
```
nano Dockerfile
```
Dockerfileの中身
```
FROM node:18

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD ["node", "app.js"]
```
### 中身の解説

- FROM node:18
FROM

ベースとなるDockerイメージを指定。
node:18は、Node.jsの公式イメージでバージョン18を使用。
バージョン固定の重要性

node:18のようにバージョンを固定することで、後で環境が変わっても同じ状態を再現できる。
node:latestのようにlatestを使うと、後で異なるバージョンが使われる可能性がある。

- WORKDIR /usr/src/app

コンテナ内で作業ディレクトリを設定。
以降のコマンド（COPYやRUNなど）は、このディレクトリを基準に実行される。
/usr/src/appは、アプリケーションのソースコードを配置するディレクトリとして一般的に使用される。
このパスは慣例的なもので、任意のパスでも可

- COPY package*.json ./

ホストマシン（ローカル）のpackage.jsonとpackage-lock.jsonを、コンテナ内のカレントディレクトリ（/usr/src/app）にコピーする。
package*.jsonはワイルドカードで、package.jsonとpackage-lock.jsonの両方を対象にする。
この形式を使うことで、どちらか一方しか存在しない場合でもエラーを回避できる。

- RUN npm install

コンテナのビルド時にコマンドを実行する。
ここではnpm installを実行し、package.jsonに記載された依存パッケージをインストールする。
インストールされたパッケージは/usr/src/app/node_modulesに格納される。
package.jsonが変更されない限り、このステップはキャッシュが使われる。

- COPY . .

ホストマシンのカレントディレクトリ（.）を、コンテナ内のカレントディレクトリ（/usr/src/app）にコピーする。
左側の.はローカルのカレントディレクトリ。
右側の.はコンテナ内のカレントディレクトリ（WORKDIRで指定した/usr/src/app)。

- EXPOSE 3000

コンテナがリッスンするポートを指定する。
この例では3000番ポートでアプリケーションが動作。

- CMD ["node", "app.js"]

コンテナ起動時に実行するデフォルトのコマンドを指定。
ここではnode app.jsを実行してアプリケーションを起動。

### docker-compose.ymlファイルの作成
```
touch docker-compose.yml
```
docker-compose.ymlを開く
```
nano docker-compose.yml
```
docker-compose.ymlの中身
```
version: '3.8'

services:
  node:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    depends_on:
      db:
        condition: service_healthy
    environment:
      - DB_HOST=db
      - DB_USER=${MYSQL_USER}
      - DB_PASSWORD=${MYSQL_PASSWORD}
      - DB_NAME=${MYSQL_DATABASE}

  db:
    image: mysql:8.0
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  db_data:
```
### 中身の解説
- version: '3.8'

docker-composeの仕様バージョンを指定している。

- services:

複数のコンテナを「サービス」として定義する。ここではnode（Node.jsアプリ）とdb（MySQLデータベース）の2つを定義。
docker-compose upでこれらのサービスが一括で起動。

-  node:

nodeはNode.jsアプリケーションを動作させるためのサービス名。
この名前は自由に変更可能

- build: .

コンテナはカレントディレクトリにあるDockerfileを使ってビルドされる。
. はカレントディレクトリを示し、そこにDockerfileがあることを前提としている。
もしDockerfileが別の場所にある場合は、build: ./path/to/dockerfileのように指定。

- ports:
      - "3000:3000"

ホストの3000番ポートをコンテナの3000番ポートにマッピング。
外部（ホスト側）からコンテナの3000番ポートにアクセスできるようになる。

- volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules

 .:/usr/src/app: ホストのカレントディレクトリ（.）をコンテナの/usr/src/appにマウント。
これにより、ローカルでコードを変更すると、コンテナ内にもリアルタイムで反映される（ホットリロード）。

-  depends_on:
      db:
        condition: service_healthy

このサービスはdb（MySQLサービス）が正常に起動した後に起動することを示す。
service_healthyを条件として、dbがhealthcheckで「正常」と判定されたらnodeサービスが起動。
これにより、データベースが完全に立ち上がる前にアプリケーションが起動することを防ぐ。

- environment:
      - DB_HOST=db
      - DB_USER={MYSQL_USER}
      - DB_PASSWORD={MYSQL_PASSWORD}
      - DB_NAME={MYSQL_DATABASE}

環境変数を設定する。
${}は.envファイルから値を取得。
DB_HOST=dbは、Node.jsアプリケーションがデータベースに接続する際のホスト名を指定。

- image: mysql:8.0

Docker Hubからmysql:8.0の公式イメージを取得して使用。
8.0はMySQLのバージョン。

- ports:
      - "3306:3306"

MySQLの標準ポート3306をホストとコンテナでマッピングする。
これにより、ホスト側からlocalhost:3306でMySQLに接続可能になる。

- volumes:
      - db_data:/var/lib/mysql

db_dataという名前のボリュームを作成し、MySQLのデータを/var/lib/mysqlに保存する。
コンテナを停止・削除しても、データはボリューム内に保持される。

- environment:
      - MYSQL_ROOT_PASSWORD={MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE={MYSQL_DATABASE}
      - MYSQL_USER={MYSQL_USER}
      - MYSQL_PASSWORD={MYSQL_PASSWORD}

MySQLの初期設定を行う。
ルートパスワードやデータベース名、ユーザー名、パスワードは環境変数から取得される。

- healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD"]
      interval: 10s
      timeout: 5s
      retries: 5

コンテナのヘルスチェックを設定する。
mysqladmin pingでMySQLが起動しているかを確認する。
interval: 10sは10秒ごとにチェックを行う。
timeout: 5sは応答を待つ時間。
retries: 5は失敗した場合のリトライ回数。
これはSQL接続のエラーが起きたためつけた。

- volumes:
  db_data:

db_dataという名前のボリュームを作成する。
dbサービスで使用され、MySQLのデータを保存する。

### app.jsファイルの作成
```
touch app.js
```
app.jsを開く
```
nano app.js
```
app.jsの中身
```
const mysql = require('mysql2');
const express = require('express');

const app = express();
const port = 3000;
const host = '192.168.x.x';  // 特定のIPアドレスで待ち受け

// MySQL接続設定
const connection = mysql.createConnection({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
});

// 接続確認
connection.connect(err => {
  if (err) {
    console.error('Error connecting to MySQL:', err);
    return;
  }
  console.log('Connected to MySQL database');
});

app.get('/', (req, res) => {
  res.send('Hello world!');
});

app.listen(port, () => {
  console.log(`App running on http://localhost:${port}`);
});
```

コードの説明
```
const mysql = require('mysql2');
const express = require('express');
```
mysql2をインポート
express：Node.jsのWebアプリケーションフレームワークをインポート
```
const app = express();
const port = 3000;
const host = '192.168.x.x';  // 特定のIPアドレスで待ち受け
```
app：Expressアプリケーションのインスタンスを生成。
port：サーバーがリッスンするポート番号を指定.。
host：サーバーの待ち受けIPアドレス。
```
const connection = mysql.createConnection({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
});
```
mysql.createConnection：MySQLへの接続を確立するための設定。
process.env：環境変数を参照して接続情報を取得。
```
connection.connect(err => {
  if (err) {
    console.error('Error connecting to MySQL:', err);
    return;
  }
  console.log('Connected to MySQL database');
});
```
connect：MySQLサーバーに接続
```
app.get('/', (req, res) => {
  res.send('Hello world!');
});
```
app.get：GETリクエストを受け取るルートを定義する。
ルート/にアクセスすると、Hello world!という文字列がブラウザに表示される。
```
app.listen(port, () => {
  console.log(`App running on http://localhost:${port}`);
});
```
listen：サーバーを指定したポートで起動。

## .envファイルの作成

ここではsqlの初期設定を行う
```
touch .env
```
```
nano .env
```
```
MYSQL_ROOT_PASSWORD=hogehoge
MYSQL_DATABASE=hogehoge
MYSQL_USER=hogehoge
MYSQL_PASSWORD=hogehoge
```

### npmパッケージとnode.jsのインストール

```
sudo apt update
sudo apt install nodejs npm
sudo node -v
```

### Node.jsでMySQLを使うためのパッケージをインストール

```
npm init -y
npm install express mysql2
```

### 起動
```
docker compose up --build
```

### エラーで権限がない場合
```
sudo usermod -aG docker $(whoami)
```
再起動する

ここで基本設定はおしまい