自宅で簡単k3sクラスタ構築2：「Raspberry Pi 5」のk3sクラスターに永続ストレージを設定する方法（Flaskで構築したサンプルアプリあり）

# はじめに
このエントリでは、Raspberry Pi 5を使ってk3sクラスターを構築し、永続ストレージを設定する方法を紹介します。外付けSSDを活用し、Kubernetesの永続ボリューム（PV）と永続ボリュームクレーム（PVC）を使ってデータを安全に保存する環境を構築していきます。さらに、その環境上でFlaskアプリケーションをデプロイする手順も詳しく解説します。Raspberry Piを活用したホームサーバーや学習用プロジェクトに興味がある方に、ぜひ試していただきたい内容です。

# 前提条件
- 2台のRaspberry Pi 5（Rpi1とRpi2）
- 両方にUbuntu 24.04がインストール済み
- Rpi1をマスターノード、Rpi2をワーカーノードとしてk3sでクラスタを構築済み
- Rpi1の/mnt/my_ssd/に外付けSSDをマウント済み
- k3sの永続化領域として/mnt/my_ssd/k3s_storageを作成済み

# 1. 永続ボリューム（PV）と永続ボリュームクレーム（PVC）の作成
aspberry Pi 5でk3sクラスターを構築し、ファイルの永続化領域を作成するには、Kubernetesの永続ボリューム（PV）と永続ボリュームクレーム（PVC）を使用します。
## 1-1. 永続ボリューム（PV）の定義ファイルの作成（例：pv.yaml）
    ```yml
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: my-pv
    spec:
      capacity:
        storage: 100Gi
      accessModes:
        - ReadWriteOnce
      hostPath:
        path: /mnt/my_ssd/k3s_storage
      storageClassName: manual
    ```
## 1-2. 永続ボリュームクレーム（PVC）の定義ファイルの作成（例：pvc.yaml）
    ```yml
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: my-pvc
    spec:
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 100Gi
      storageClassName: manual
    ```
## 1-3. リソースの適用
    ```sh
    kubectl apply -f pv.yaml
    kubectl apply -f pvc.yaml
    ```
## 1-4. Podでの使用
    永続ボリュームクレームを使用するPodの定義ファイルを作成します。以下は、PVCをマウントしてファイルを保存するPodの例です。
    ```yml
    apiVersion: v1
    kind: Pod
    metadata:
      name: my-pod
    spec:
      containers:
        - name: my-container
          image: nginx
          volumeMounts:
            - mountPath: /usr/share/nginx/html
              name: my-storage
      volumes:
        - name: my-storage
          persistentVolumeClaim:
            claimName: my-pvc
    ```
## 1-5. Podの適用
    ```sh
    kubectl apply -f pod.yaml
    ```
    これで、Podが/mnt/my_ssd/k3s_storageに保存されたファイルにアクセスできるようになります。データは永続ボリュームに保存され、Podが削除されても保持されます。

# 2. 永続ボリュームにファイルを作成する
## 2-1. Flask アプリケーションのセットアップ
以下のPythonコードをapp.pyという名前で保存します。
```py
from flask import Flask, request, redirect, url_for, send_from_directory
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = '/usr/share/nginx/html/uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 最大アップロードサイズ16MB

# アップロードフォルダが存在しない場合は作成
if not os.path.exists(app.config['UPLOAD_FOLDER']):
    os.makedirs(app.config['UPLOAD_FOLDER'])

@app.route('/')
def index():
    return '''
    <!doctype html>
    <title>File Upload</title>
    <h1>Upload a File</h1>
    <form action="/upload" method="post" enctype="multipart/form-data">
      <input type="file" name="file">
      <input type="submit" value="Upload">
    </form>
    '''

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return redirect(request.url)
    file = request.files['file']
    if file.filename == '':
        return redirect(request.url)
    if file:
        filename = file.filename
        file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
        return redirect(url_for('uploaded_file',
                                filename=filename))

@app.route('/uploads/<filename>')
def uploaded_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'],
                               filename)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
```

## 2-1. Dockerfileの作成
Flaskアプリケーションをコンテナで動かすためのDockerfileを作成します。
```dockerfile
# Python 3.11をベースにする
FROM python:3.11-slim

# 必要なパッケージをインストール
RUN pip install flask

# アプリケーションコードをコンテナにコピー
COPY app.py /app/app.py

# 作業ディレクトリを設定
WORKDIR /app

# Flaskアプリケーションを実行
CMD ["python", "app.py"]
```
## 2-3. Kubernetes Deploymentの設定
FlaskアプリケーションをデプロイするためのKubernetes Deploymentの設定ファイル (flask-deployment.yaml) を作成します。
```yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask
  template:
    metadata:
      labels:
        app: flask
    spec:
      containers:
        - name: flask-container
          image: <your_docker_image> # デプロイするDockerイメージを指定
          ports:
            - containerPort: 5000
          volumeMounts:
            - name: flask-storage
              mountPath: /usr/share/nginx/html/uploads
      volumes:
        - name: flask-storage
          persistentVolumeClaim:
            claimName: my-pvc
```
## 2-4. Serviceの設定
FlaskアプリケーションにアクセスするためのServiceの設定ファイル (flask-service.yaml) を作成します。
```yml
apiVersion: v1
kind: Service
metadata:
  name: flask-service
spec:
  selector:
    app: flask
  ports:
    - protocol: TCP
      port: 80
      targetPort: 5000
  type: LoadBalancer

```
## 2-5. Kubernetesリソースの適用
以下のコマンドで、DeploymentとServiceをKubernetesに適用します。
```sh
kubectl apply -f flask-deployment.yaml
kubectl apply -f flask-service.yaml
```
## 2-6. 動作確認
ブラウザでサービスのIPアドレスにアクセスし、ファイルをアップロードして保存されることを確認します。ファイルは永続ボリュームに保存され、Podの再起動や再作成後も保持されます。

# おわりに
Raspberry Pi 5とk3sを組み合わせることで、低コストながらも強力なクラウドネイティブ環境を構築できることを実感していただけたと思います。今回の手順を応用すれば、さらに多くのアプリケーションをデプロイしたり、クラスタリングを活用したスケーラビリティの向上も可能です。

[FlaskだけでなくNode.jsやNginxのデプロイに興味がある方は合わせてこちらもご参考ください](https://kewton.blog/archives/2064)