自宅で簡単k3sクラスタ構築：「Raspberry Pi 5」2台から始めるKubernetes入門（作業メモ）

# はじめに
この記事では、2台のRaspberry Pi 5を使って、Ubuntu 24.04環境でk3sクラスタを構築する手順を共有します。クラウド上のKubernetes環境は主流ですが、「もっと手軽に自宅で試したい！」と思っている方（特に私w）に向けて、リーズナブルなハードウェアで、手軽にk3sクラスタを構築する方法をお伝えします（単なる作業メモです。。）。エッジコンピューティングやIoT分野での学習やプロジェクトの第一歩として、ぜひ参考にしてください。

# 前提条件
- 2台のRaspberry Pi 5（Rpi1とRpi2）
- 両方にUbuntu 24.04がインストール済み
- 両方がネットワークに接続されている

# 1. k3sのインストール
Raspberry Pi 2台を使用してk3sクラスタを構築する手順を解説します。最初に各デバイスのIPアドレスを確認し、ファイアウォールを設定します。その後、マスターノードとして機能するRpi1にk3sをインストールし、ワーカーノードであるRpi2をクラスタに参加させます。最後に、クラスタが正しく構成され、動作しているかを確認します。このガイドに従うことで、手軽にRaspberry Piでk3sクラスタを構築する方法を学べます。

## 1-1. IPアドレスの確認

両方のRaspberry Piで以下のコマンドを実行し、IPアドレスを確認します。

```sh
ip addr show
```

この例では、以下のIPアドレスを使用します。
- Rpi1 (マスターノード): 192.168.11.8
- Rpi2 (ワーカーノード): 192.168.11.5

## 1-2. ファイアウォールの設定

両方のRaspberry Piで以下のコマンドを実行し、必要なポートを開放します。

```sh
sudo ufw allow 22/tcp
sudo ufw allow 6443/tcp
sudo ufw allow 8472/udp
sudo ufw allow 10250/tcp
sudo ufw enable
```

## 1-3. Rpi1（マスターノード）の設定

Rpi1にSSH接続し、以下のコマンドを実行してk3sをインストールします。

```sh
curl -sfL https://get.k3s.io | sh -
```

k3sのステータスを確認します。
```sh
sudo systemctl status k3s
```

これにより、k3sサービスが正常に稼働しているかを確認できます。active (running) と表示されていれば、k3sが正常に動作していることを示します。
```sh
k3s.service - Lightweight Kubernetes
     Loaded: loaded (/etc/systemd/system/k3s.service; enabled; preset: >
     Active: active (running) since Sat 2024-08-10 22:25:33 JST; 16h ago
       Docs: https://k3s.io
    Process: 1272 ExecStartPre=/bin/sh -xc ! /usr/bin/systemctl is-enab>
    Process: 1274 ExecStartPre=/sbin/modprobe br_netfilter (code=exited>
    Process: 1277 ExecStartPre=/sbin/modprobe overlay (code=exited, sta>
   Main PID: 1279 (k3s-server)
      Tasks: 125
     Memory: 777.6M (peak: 973.0M)
        CPU: 55min 34.844s
     CGroup: /system.slice/k3s.ser
```

インストールが完了したら、ノードトークンを取得します。

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

このトークンをメモしておきます。

## 1-4. Rpi2（ワーカーノード）の設定

Rpi2にSSH接続し、以下のコマンドを実行してk3sエージェントをインストールします。<NODE_TOKEN>と<MASTER_IP>を適切な値に置き換えてください。

```sh
curl -sfL https://get.k3s.io | K3S_URL=https://<MASTER_IP>:6443 K3S_TOKEN=<NODE_TOKEN> sh -
```

例：
```sh
curl -sfL https://get.k3s.io | K3S_URL=https://192.168.11.8:6443 K3S_TOKEN=K10cf3cf691b31e613c5b7e69e81edb1994b8351538c375f3a59af515a58725d37f::server:980d9f3d5f3fc7e6fb8d086ce4cec5a4 sh -
```
インストールの進行状況は以下のように確認できます。
```sh
[INFO]  Finding release for channel stable
[INFO]  Using v1.30.3+k3s1 as release
[INFO]  Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.30.3+k3s1/sha256sum-arm64.txt
[INFO]  Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.30.3+k3s1/k3s-arm64
[INFO]  Verifying binary download
[INFO]  Installing k3s to /usr/local/bin/k3s
[INFO]  Skipping installation of SELinux RPM
[INFO]  Skipping /usr/local/bin/kubectl symlink to k3s, command exists in PATH at /usr/bin/kubectl
[INFO]  Skipping /usr/local/bin/crictl symlink to k3s, command exists in PATH at /usr/bin/crictl
[INFO]  Skipping /usr/local/bin/ctr symlink to k3s, already exists
[INFO]  Creating killall script /usr/local/bin/k3s-killall.sh
[INFO]  Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh
[INFO]  env: Creating environment file /etc/systemd/system/k3s-agent.service.env
[INFO]  systemd: Creating service file /etc/systemd/system/k3s-agent.service
[INFO]  systemd: Enabling k3s-agent unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s-agent.service → /etc/systemd/system/k3s-agent.service.
[INFO]  systemd: Starting k3s-agent
```

## 1-5. クラスタの確認

Rpi1（マスターノード）で以下のコマンドを実行し、ノードの状態を確認します。

```sh
sudo k3s kubectl get nodes
```

両方のノードが表示され、STATUSが"Ready"になっていれば、クラスタの構築は完了です。
```sh
NAME   STATUS   ROLES                  AGE   VERSION
Rpi1   Ready    control-plane,master   17h   v1.30.3+k3s1
Rpi2   Ready    <none>                 15h   v1.30.3+k3s1
```

# 2. サンプルアプリケーションのデプロイ
クラスタの動作を確認するために、サンプルのFlaskアプリケーションをデプロイします。

## 2-1. アプリケーションの準備
Rpi1で以下の手順を実行します
1. アプリケーションディレクトリの作成：
    ```sh
    mkdir flask-app
    cd flask-app
    ```

2. `app.py`の作成：
    k3sにデプロイ時、os.getenv('HOSTNAME')を使用してPod名を取得できます。Podが切り替わることを確認します。
    ```python
    from flask import Flask, jsonify
    import os
    
    app = Flask(__name__)
    
    @app.route('/api', methods=['GET'])
    def get_api():
        return jsonify(message="Hello from Flask!", pod_name=os.getenv('HOSTNAME'))
    
    @app.route('/api2', methods=['GET'])
    def get_api2():
        return jsonify(message="Hello from API2", pod_name=os.getenv('HOSTNAME'))
    
    
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=5000)
    ```

3. `Dockerfile`の作成：
    ```dockerfile
    FROM python:3.9-slim
    
    WORKDIR /app
    
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    
    COPY app.py .
    
    EXPOSE 5000
    
    CMD ["python", "-u", "app.py"]
    ```

4. `requirements.txt`の作成：
    ```
    Flask
    ```

## 2-2. Dockerイメージのビルドとプッシュ
Raspberry PiなどのARM64プラットフォームを使用するデバイスで動作させるために、ARM64向けにコンパイルされたバイナリを含むイメージをビルドします。
**"--platform linux/arm64"を指定すればmacでビルドしても問題なく動作しました**
```sh
docker build --platform linux/arm64 -t <your-dockerhub-username>/flask-app:v1.0.1 .
docker push <your-dockerhub-username>/flask-app:v1.0.1
```
## 2-3. マニフェストの作成
下記内容で、flask-app-deployment.yamlという名称のファイルを作成します。replicasに2を指定して、2つのPodを作成します。
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: flask-app
  template:
    metadata:
      labels:
        app: flask-app
    spec:
      containers:
      - name: flask-app
        image: <your-dockerhub-username>/flask-app:v1.0.1
        ports:
        - containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
  name: flask-app-service
spec:
  selector:
    app: flask-app
  ports:
  - port: 80
    targetPort: 5000
```
## 2-4. アプリケーションのデプロイ
Rpi1で下記コマンドを実行し、アプリケーションをデプロイします。
```bash
sudo k3s kubectl apply -f flask-app-deployment.yaml
```

## 2-5. デプロイメントの確認
下記コマンドを実行して、デプロイメントを確認します。
```bash
sudo kubectl get pods -o wide
```
実行結果：
```
NAME                         READY   STATUS    RESTARTS   AGE   IP           NODE   NOMINATED NODE   READINESS GATES
flask-app-698574456b-nb6xf   1/1     Running   0          15h   10.42.2.3    Rpi2   <none>           <none>
flask-app-698574456b-pldh8   1/1     Running   0          16h   10.42.0.15   Rpi1   <none>           <none>
```
flask-appの2つのPodが、それぞれRpi2（pod名=flask-app-698574456b-nb6xf）とRpi1（pod名=flask-app-698574456b-pldh8）のノード上で正常に稼働していることが確認できます。各Podは独自の内部IPアドレスを持ち、再起動もなく安定して稼働しています。

# 3. アプリケーションへのアクセス
この章では、デプロイしたFlaskアプリケーションへのアクセス方法を説明します。まず、k3sクラスタ内で使用されるポート番号を確認し、次に外部からアクセスできるように設定を行います。複数のリクエストを行うことで、クラスタ内でのラウンドロビン方式による負荷分散の効果を確認し、各Podのステータスをチェックします。

## 3-1. ポート番号の確認
ポート番号の確認：
```bash
sudo k3s kubectl get services
```

```sh
NAME                TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
flask-app-service   NodePort    10.43.206.71   <none>        80:30458/TCP   16h
kubernetes          ClusterIP   10.43.0.1      <none>        443/TCP        17h
```

## 3-2. 動作確認
1. 1回目
    - 正しく実行できることが確認できます。
    - pod_nameの値より、Rpi2上で動作しているPodにアクセスしたことがわかります。
    ```sh
    curl http://192.168.11.8:30458/api
    {"message":"Hello from Flask!","pod_name":"flask-app-698574456b-nb6xf"}
    ```
1. 2回目
    - 正しく実行できることが確認できます
    - pod_nameの値より、Rpi1上で動作しているPodにアクセスしたことがわかります。
    - 複数のサーバーをまたいでクラスタ化されていることがわかります。
    ```sh
    curl http://192.168.11.8:30458/api
    {"message":"Hello from Flask!","pod_name":"flask-app-698574456b-pldh8"}
    ```
1. 3回目
    - APIのエンドポイントを変えても正しく実行できることが確認できます。
    - pod_nameの値より、Rpi2上で動作しているPodにアクセスしたことがわかります。
    - ラウンドロビンで割り振られていることがわかります。
    ```sh
    curl http://192.168.11.8:30458/api2
    {"message":"Hello from API2","pod_name":"flask-app-698574456b-nb6xf"}
    ```
# 4. FAQ
## 4.1 sudoを使用せずに「kubectl get nodes」を実行する方法
1. KUBECONFIG 環境変数の永続化:
    KUBECONFIG 環境変数を永続的に設定するために、ユーザーのシェル設定ファイル（例えば .bashrc や .zshrc）に以下の行を追加します。これにより、再ログイン時や再起動時にも設定が引き継がれます。
    ```sh
    echo 'export KUBECONFIG=/etc/rancher/k3s/k3s.yaml' >> ~/.bashrc
    source ~/.bashrc
    ```

1. 設定ファイルのパーミッションの設定
    再起動後に設定ファイルのパーミッションが変更されていないか確認します。もし変更されていた場合、セキュリティポリシーや自動スクリプトが原因の可能性があります。この場合、以下のコマンドで再度パーミッションを設定して、状況を確認します。
    ```sh
    sudo chmod 644 /etc/rancher/k3s/k3s.yaml    
    ```
    **毎回起動時に実行する必要ありかもです**

# おわりに
ここまで読んでいただき、ありがとうございます！2台のRaspberry Pi 5を使ってk3sクラスタを構築する手順をざっくりまとめましたが、いかがでしたでしょうか？（私も手探りでやってますw）。クラスタを自分で構築してみると、Kubernetesの基礎を実際に体感できるので、学びが深まりますよ。次は、アプリケーションをスケールさせたり、異なるワークロードをデプロイしたりと、さらなる挑戦に進んでみてください！やっぱり実践あるのみですね。