Raspberry Pi に軽量 Kubernetes を導入し高可用な自宅サーバーを実現する

# Kubernetes on Raspberry Pi のセットアップ

自宅に持て余している Raspberry Pi を高可用な自宅サーバーもとい「おうちKubernetes」(n番煎じ)として活用するため、まずはKubernetesを導入するところから始める。

## 背景

- 学生の頃のインターンシップや、社会人になってからイベントで頂いたRaspberry Piが何台か余っている
- これまでデスクトップPCにサーバーを構築して、セルフホストしたアプリを家族や仲間内に公開してきたが、メンテナンス時に使えなくなることがあった
- デスクトップPCを常時稼働させると電気代がツライし発火するかもしれない

## 目的

- ちょうど複数台余った Raspberry Pi で冗長構成を取り、サービスを止めずにメンテナンスできるようにする
- 個人的な Kubernetes の勉強環境を構築する
- デスクトップPCに比較して省電力・安全安心にする(多分)

## やらないこと

- 完璧な可用性の担保
    - UPSとか必要そう
    - そもそも自宅サーバーなので停電なり災害なりあったら強制サ終

## クラスター構成

今回は以下の通り Master/Worker ともに冗長構成とする。

未だに Raspberry Pi 2 だと 32bit OS を使う羽目になるが、まだサポートしてくれているコンテナが少ないせいでアプリの立ち上げには苦戦する…。また別の記事にする。今からクラスターを作るなら Raspberry Pi 4 only の構成がおすすめ。

- Master
    - Raspberry Pi 4 (4GB RAM) × 2機
        - Ubuntu 22.04.3 64bit
    - Raspberry Pi 3 Rev1.2 (1GB RAM) × 1機
        - Ubuntu 22.04.3 64bit
- Worker
    - Raspberry Pi 2 Rev1.1 (1GB RAM) × 3機
        - Ubuntu 22.04.3 32bit

## SDカードの準備

[Raspberry Pi Imager](https://www.raspberrypi.com/software/) でOSを焼く。今回は Ubuntu 22.04.3 を選択。

最近の Imager は焼く前にホスト名やSSHのユーザー名・パスワード、Wi-Fi等が設定できる。すげぇ。

## 初期設定

OSを焼いたSDカードを Raspberry Pi に挿入し、起動する。起動後の各 Raspberry Pi 上で初期設定作業を繰り返し行う。

なお、 Raspberry Pi のIPアドレスは、macOS/Linuxなら `arp-scan` コマンド、Windowsなら `ping` と `arp` の組み合わせで推測できる。例として `arp-scan` コマンドでの検索方法は以下の通り。

```bash
$ sudo arp-scan -I [ネットワークインターフェース名] 192.168.xxx.0/24
```

### extraモジュールのインストール

クラスター内の通信のために vxlan モジュールが必要。

```bash
$ sudo apt install -y linux-modules-extra-raspi
```

### IP固定

netplan のファイルを追加する。

```bash
$ sudo bash -c 'cat << EOS > /etc/netplan/99-manual.yaml
network:
  version: 2
  ethernets:
    eth0:
      addresses:
      - 192.168.xxx.aaa/24  # ノードのIP
      gateway4: 192.168.xxx.ccc  # デフォルトルーターのIP
      nameservers:
        addresses:
        - 1.1.1.1
        - 1.0.0.1
      dhcp4: no
      optional: true
EOS'
```

### Linuxカーネルのパラメータ

```bash
$ sudo nano /boot/firmware/cmdline.txt

# 以下を末尾に追記 (改行不要)
cgroup_memory=1 cgroup_enable=memory cgroup_enable=cpuset
```

### SDカードの延命設定

そのまま Raspberry Pi を運用していると、SDカードに対して多数の読み書きが行われ、SDカードが早期に故障する可能性が高まる。

読み書きの多いディレクトリのRAMディスク化、および自動更新の無効化により、SDカードの寿命を長くすることができる(といっても運用し始めたばかりでまだ効果はわからず。。。)

#### RAMディスクの設定

ここでは、特に読み書きが多いと思われる以下のディレクトリを対象として、RAMディスク(メモリ上にファイルが書き込まれる)の設定を行う。

| ディレクトリパス | 内容 |
| ---- | ---- |
| /tmp | 一時ファイル |
| /var/tmp | 一時ファイル |
| /var/log | OS、ミドルウェアのログ |
| /var/spool | 一時ファイル |
| /var/cache | OS、ミドルウェアのキャッシュ |

ただし、`/var/cache/apt` については、比較的ファイルサイズが大きくなり、Raspberry Pi のメモリ容量では不足する可能性が高いため対象から除外する。

以下のコマンドでRAMディスクがマウントされるようにする。

```bash
$ sudo nano /etc/fstab

# 末尾に以下を追記
tmpfs   /tmp        tmpfs   defaults,size=64m,noatime,mode=1777 0   0
tmpfs   /var/tmp    tmpfs   defaults,size=16m,noatime,mode=0755 0   0
tmpfs   /var/log    tmpfs   defaults,size=64m,noatime,mode=0755 0   0
tmpfs   /var/spool  tmpfs   defaults,size=4m,noatime,mode=0755 0   0
tmpfs   /var/cache  tmpfs   defaults,size=32m,noatime,mode=0755 0   0
```

先に `/var/cache/apt` の退避先を作る。

```bash
$ sudo mkdir /var/cache.apt
```

RAM ディスクへの切り替えを行う。

:::note info
現在保存されているデータは全て破棄されるため、必要に応じてバックアップを行うこと。
:::

```bash
$ sudo bash -c 'rm -rf /tmp/* /var/tmp/* /var/log/* /var/spool/* /var/cache/* && mount -a && ln -sf /var/cache.apt /var/cache/apt'
```

以下のコマンドで切り替え完了を確認する。

```bash
$ df -h

# コマンドの表示結果に以下が含まれていることを確認する
tmpfs            64M     0   64M   0% /tmp
tmpfs            32M     0   32M   0% /var/cache
tmpfs            64M     0   64M   0% /var/log
tmpfs           4.0M     0  4.0M   0% /var/spool
tmpfs            16M     0   16M   0% /var/tmp
```

なお、次回起動時からは、自動的にRAMディスクがマウントされる。

:::note warn
/var/cache/apt のシンボリックリンクは、再起動後は削除されるため、 apt で作業を行う場合はもう一度シンボリックリンクを作成すること。
[folder2ram](https://pcvogel.sarakura.net/2020/12/21/32099) などのRAMディスクとストレージを同期して永続化を図る仕組みも存在するものの、今回は期待した動作にならず導入を見送る。
:::

:::note warn
/var/log に記録可能なログのサイズが制限されるため、必要に応じて、適切なログローテーションを設定すること。
:::

#### 自動更新の無効化

apt の設定ファイルを更新する。

```bash
$ sudo nano /etc/apt/apt.conf.d/20auto-upgrades

# 以下の通り更新して保存する
APT::Periodic::Update-Package-Lists "0";
APT::Periodic::Unattended-Upgrade "0";
```

### 再起動

設定を反映するために、 Raspberry Pi を再起動する。固定IPでアクセスできるかどうか確認すること。

## k3sの導入

IoTデバイスでも動作する軽量な Kubernetes ディストリビューションである [k3s](https://k3s.io) をインストールする。

### Master ノードの構築

1台目と2台目以降では手順が異なるので注意。

#### 1台目の Master ノードでの作業

##### k3s server のインストール

最初の1台目に以下のコマンドで k3s server をインストールする。 `K3S_KUBECONFIG_MODE` に `644` を指定すると、一般ユーザーでも `kubectl` コマンドを実行できるようになる。

```bash
$ curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE=644 sh -s - server --cluster-init --tls-san [ノードのIP] --bind-address [ノードのIP] --node-ip [ノードのIP]
```

##### vxlan モジュールの有効化

server のインストールに成功したあと、先刻インストールした vxlan モジュールを有効化する。

:::note info
本手順は2台目以降の Master ノード、 Worker ノードでも必要。
:::

```bash
$ sudo nano /etc/systemd/system/k3s.service

# ExecStart=/usr/local/bin/k3s の前の行に以下を追記する
ExecStartPre=-/sbin/modprobe vxlan

$ sudo systemctl daemon-reload
$ sudo systemctl restart k3s
```

まずは1台構成のクラスターが構築されたことを確認する。

```bash
$ kubectl get no
NAME               STATUS   ROLES                       AGE     VERSION
[1台目のホスト名]     Ready    control-plane,etcd,master   4d22h   v1.28.4+k3s2
```

##### server 連携トークンの取得

以下のコマンドで、2台目以降を連携するためのトークンを取得しておく。

```bash
$ sudo cat /var/lib/rancher/k3s/server/node-token
```

##### Master ノードへのコンテナデプロイ禁止

Master ノードへ任意のコンテナをデプロイさせなくするためには、以下のコマンドを実行する。本設定が不要ならSkipしても良い。2台目以降も同様に設定が必要となる。

```bash
$ kubectl taint nodes [Master ノードのホスト名] node-role.kubernetes.io/master:NoSchedule
```

#### 2台目以降の Master ノードでの作業

以下の手順を Master ノードとする台数分だけ繰り返す。

##### k3s server のインストール

以下のコマンドで k3s server をインストールする。

```bash
$ curl -sfL https://get.k3s.io | K3S_KUBECONFIG_MODE=644 K3S_TOKEN=[1台目で取得したトークン] sh -s - server --server https://[1台目のノードのIP]:6443 --tls-san [ノードのIP] --bind-address [ノードのIP] --node-ip [ノードのIP]
```

クラスターにノードが追加されたことを確認する。

```bash
$ kubectl get no
NAME               STATUS   ROLES                       AGE     VERSION
[1台目のホスト名]     Ready    control-plane,etcd,master   10m     v1.28.4+k3s2
[n台目のホスト名]     Ready    control-plane,etcd,master   1m      v1.28.4+k3s2
```

##### vxlan モジュールの有効化

[1台目の Master ノードと同様の手順](#vxlan-モジュールの有効化) で有効化しておく。

##### Master ノードへのコンテナデプロイ禁止

必要に応じて、 [1台目の Master ノードと同様の手順](#master-ノードへのコンテナデプロイ禁止) で設定しておく。

### Worker ノードの構築

Worker ノードでは手順は共通。

#### Woker ノードでの作業

以下の手順を Worker ノードとする台数分だけ繰り返す。

##### k3s agent のインストール

以下のコマンドで k3s agent をインストールする。

```bash
$ curl -sfL https://get.k3s.io | K3S_URL=https://[1台目の Master ノードのIP]:6443 K3S_TOKEN=[1台目の Master ノードで取得したトークン] sh -s - --node-ip [ノードのIP]
```

クラスターにノードが追加され、 `ROLES` が `worker` であることを確認する。

```bash
$ kubectl get no
NAME               STATUS   ROLES                       AGE     VERSION
[1台目のホスト名]     Ready    control-plane,etcd,master   20m     v1.28.4+k3s2
[2台目のホスト名]     Ready    control-plane,etcd,master   8m      v1.28.4+k3s2
[3台目のホスト名]     Ready    control-plane,etcd,master   5m      v1.28.4+k3s2
[n台目のホスト名]     Ready    worker                      1m      v1.28.4+k3s2
```

##### vxlan モジュールの有効化

[1台目の Master ノードと同様の手順](#vxlan-モジュールの有効化) で有効化しておく。