Raspberry Pi 4を使ってKubernetesクラスタを構築する

# はじめに
エンジニアならきっと誰しもやりたくなる(?)ラズパイを使ったKubernetesクラスタ構築に手を出してみたので、その際のメモとなります。

完成品はこんな感じ。
![PXL_20220821_061200753.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2744609/38be7f99-1a43-0f65-cfdd-1f4bef1c553d.jpeg)

# ハードウェア準備
## 必要機材
| 機材 | 数量 | 備考 |
|:-|:-:|:-|
| Raspberry Pi 4 Model B / 4GB | 3台 ||
| microSDカード 32GB | 3枚 ||
| [Raspberry Pi クラスタケース](https://www.amazon.co.jp/GeeekPi%E6%96%B0%E3%81%97%E3%81%84Raspberry-Pi%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%BF%E3%82%B1%E3%83%BC%E3%82%B9%E3%80%81Raspberry-Pi%E3%83%A9%E3%83%83%E3%82%AF%E3%82%BF%E3%83%AF%E3%83%BC%E3%82%B1%E3%83%BC%E3%82%B9%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AB%E3%83%96%E3%83%AB%E3%82%B1%E3%83%BC%E3%82%B9%E3%80%81%E5%86%B7%E5%8D%B4%E3%83%95%E3%82%A1%E3%83%B3%E4%BB%98%E3%81%8D120mm-5V%E3%83%95%E3%82%A1%E3%83%B3%E3%80%81Raspberry-%E3%81%8A%E3%82%88%E3%81%B3Jetson/dp/B083FCLLQV/ref=asc_df_B083FCLLQV/?tag=jpgo-22&linkCode=df0&hvadid=342399834947&hvpos=&hvnetw=g&hvrand=8502986129615065815&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009418&hvtargid=pla-930990083188&th=1) | 1個 | |
| [スイッチングハブ (LSW6-GT-5ESL)](https://www.amazon.co.jp/gp/product/B09H37MYXB/ref=ppx_yo_dt_b_asin_image_o03_s00?ie=UTF8&psc=1) | 1台 | コンパクトにしたい場合はUSB給電できる機種を選択した方が良いかもしれません。　|
| [USB充電器 (4ポート 100W)](https://www.amazon.co.jp/UGREEN-%E6%96%B0%E7%94%9F%E4%BB%A3GaN%E6%8A%80%E8%A1%93-USB-C%E5%85%85%E9%9B%BB%E5%99%A8-4%E5%8F%B0%E5%90%8C%E6%99%82%E5%85%85%E9%9B%BB%EF%BC%88USB-C-PSE%E6%8A%80%E8%A1%93%E5%9F%BA%E6%BA%96%E9%81%A9%E5%90%88/dp/B0982VFYYJ/ref=sr_1_2_sspa?keywords=USB%E5%85%85%E9%9B%BB%E5%99%A8+UGREEN&qid=1661064551&sr=8-2-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUExU1ZQNEtMNkg0SzMwJmVuY3J5cHRlZElkPUEwMTE3Mjk2MjcySENKVUFQSllHOSZlbmNyeXB0ZWRBZElkPUFMSEFRNUZXQlgzUzUmd2lkZ2V0TmFtZT1zcF9hdGYmYWN0aW9uPWNsaWNrUmVkaXJlY3QmZG9Ob3RMb2dDbGljaz10cnVl) | 1台 | 3ポート以上かつ5V/3Aで出力できそうなものを選定 |
| USB Type-Cケーブル | 3本 | |
| 0.5m LANケーブル | 3本 | ラズパイとスイッチングハブの接続用 |
| 5m LANケーブル | 1本 | ルータとスイッチングハブの接続用 |

## ネットワーク構成
Raspberry Piはスイッチングハブを介して自宅のルータに接続しました。
![Raspberry Piクラスタネットワーク図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2744609/54d39658-6c6a-f30e-afbb-cf7a18cbead8.png)

# OSセットアップ
今回はRaspberry Pi OSを使用します。
ラズパイ3台全てに対して、セットアップを実施していきます。

microSDカードへのOS書き込み手順は色々あるので省略しますが、[Raspberry Pi Imager](https://www.raspberrypi.com/software/)を使用して書き込むのが楽なのでおすすめです。

## パッケージ更新やツールインストール
とりあえずaptの更新とエディタをインストールしておきます。
```shell
sudo apt update && sudo apt upgrade
sudo apt install -y vim
```

## IPアドレス固定
```/etc/dhcpcd.conf```を開いて設定を追記します。
```shell
sudo vim /etc/dhcpcd.conf
```

```ip_address```の値は各ラズパイ毎に変更する必要があります。
* master ... 192.168.1.111
* worker1 ... 192.168.1.112
* worker2 ... 192.168.1.113

以下はmasterの場合の例です。
```/etc/dhcpcd.conf
interface eth0
static ip_address=192.168.1.111/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1
```
設定したらラズパイを再起動し、```ip addr```コマンド等でIPアドレスが設定した値に変わっていることを確認します。

## ホスト名変更
初期設定ではホスト名がraspberrypiとなっているので変更していきます。
こちらも色々方法はありますが、```raspi-config```を使用した方法でやってみます。
```shell
sudo raspi-config
```

「1 System Options」→「S4 Hostname」を選択
![スクリーンショット 2022-08-16 16.42.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2744609/97f08916-3ffd-f0b6-98c1-6ddafb8f575b.png)

変更するホスト名を入力します。
今回はそれぞれ、master、worker1、worker2とします。
![スクリーンショット 2022-08-16 16.43.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2744609/b078a3aa-4083-5c34-e2e5-46a5837f84ce.png)

## hosts編集
```/etc/hosts```を編集して設定を追記します。
```shell
sudo vim /etc/hosts
```

```:/etc/hosts
192.168.1.111 master
192.168.1.112 worker1
192.168.1.113 worker2
```

## スワップの無効化
スワップが有効化されているとKubernetesを起動できないため、スワップを無効化します。
```shell
sudo  swapoff -a
sudo systemctl stop dphys-swapfile
sudo systemctl disable dphys-swapfile
systemctl status dphys-swapfile
```

ラズパイを再起動した後```free -h```コマンドを実行し、swapの値が0となっていれば大丈夫です。
```
pi@master:~ $ free -h
              total        used        free      shared  buff/cache   available
Mem:          3.7Gi       498Mi       1.4Gi       201Mi       1.8Gi       2.9Gi
Swap:            0B          0B          0B
```

## cgroupのメモリを有効化
こちらも設定しないと、Kubernetes起動時にエラーが発生する原因となります。
```/boot/cmdline.txt```を編集して設定を追記します。
```shell
sudo vim /boot/cmdline.txt
```
元々の記載の末尾に、下記設定を追記します。
```/boot/cmdline.txt
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
```

# Dockerインストール
以下のコマンドを実行することでdockerをインストールできます。
```shell
curl -fsSL https://get.docker.com/ | sh
```

インストールが完了したら現在のユーザをdockerグループに追加しておきます。
この作業を実施することで、root権限無しでdockerコマンドを実行できるようになります。
```shell
sudo gpasswd -a $(whoami) docker
exit
```

# Kubernetesインストール
```apt-transport-https```と```curl```が入っていない場合はインストールしておきます。
（Raspberry Pi OSには初めから入っているはずなので、恐らくこの作業は必要無いです。）
```shell
sudo apt update && sudo apt install -y apt-transport-https curl
```

GPGキーを追加します。
```shell
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
```

リポジトリ定義をsources.list.d配下に追加します。
```shell
cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
```

```kubelet```, ```kubeadm```, ```kubectl```のインストールとバージョン固定を実施します。
```shell
sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
```

# クラスタ構築の事前準備
この後のmasterノード設定やworkerノード追加の作業を実施前に、以下のコマンドでcontainerdを再起動しておきます。
（筆者の環境では、この作業を実施しておかないと後の作業でエラーが発生しました。）
```shell
sudo rm /etc/containerd/config.toml
sudo systemctl restart containerd
```

# masterノード設定
masterノードの初期化を実施していきます。
この作業はmasterノード用のラズパイ(master)のみ実施します。

## 初期化処理
```kubeadm init```コマンドを実行することでクラスタの初期化ができますが、まずは```--dry-run```オプションを付与して、動作確認をします。
```shell
sudo kubeadm init \
--apiserver-advertise-address=192.168.1.111 \
--pod-network-cidr=10.244.0.0/16 \
--token-ttl 0 \
--dry-run
```

ドライランの実行でエラーが出た場合は、設定を見直す必要があります。[公式の手順](https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/)にもいくつか前提条件が記載されているので参考にしてみてください。

ドライランが問題無く実行できた場合は、```--dry-run```オプションを外して実際にクラスタの初期化を実施します。
```shell
sudo kubeadm init \
--apiserver-advertise-address=192.168.1.111 \
--pod-network-cidr=10.244.0.0/16 \
--token-ttl 0
```

以下のようなメッセージが出力されていれば初期化処理は完了です。

```
...(省略)

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.1.111:6443 --token <認証トークン> \
    --discovery-token-ca-cert-hash <ハッシュ値> 
```
ここで表示された```kubeadm join```コマンドは、workerノード追加の際に必要なため、メモしておきます。

認証トークンの有効期限はデフォルトで24時間となっていますが、今回は```--token-ttl 0```のオプションを付与することで、無期限にしています。

## kubectlの設定
```kubeadm init```実行時の出力メッセージにもある通り、以下のコマンドを実行して```kubectl```を一般ユーザで実行できるように設定します。
```shell
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
```

ついでに```kubectl```の補完機能も有効化しておきます。
```shell
source <(kubectl completion bash)
echo "source <(kubectl completion bash)" >> ~/.bashrc
```

以下のように一般ユーザ権限で```kubectl```が実行できていればOKです。
```
pi@master:~ $ kubectl get node
NAME     STATUS     ROLES           AGE   VERSION
master   NotReady   control-plane   83s   v1.24.3
```

## flannelインストール
CNIプラグインとして広く利用されているflannelをインストールします。
```shell
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
```

# workerノードの追加
作成したクラスタにworkerノードを追加していきます。
この作業はworkerノード用のラズパイ(worker1, worker2)のみ実施します。

先ほどメモしておいた```kubeadm join```コマンドをworkerノードで実行します。
```shell
sudo kubeadm join 192.168.1.111:6443 --token <認証トークン> --discovery-token-ca-cert-hash <ハッシュ値> 
```

以下のようなメッセージが出力されていればOKです。
```
...(省略)

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
```

最後にmasterノードでworkerノードが正常に参加できていることを確認します。
STATUSがReadyになっていればOKです。

※ノード追加実施直後はSTATUSがNot Readyになっている可能性があります。その際は数分待ってから再度確認してみてください。
```
pi@master:~ $ kubectl get nodes
NAME      STATUS   ROLES           AGE    VERSION
master    Ready    control-plane   15m    v1.24.3
worker1   Ready    <none>          113s   v1.24.3
worker2   Ready    <none>          24s    v1.24.3
```

これでKubernetesクラスタ構築は完了です。お疲れ様でした！

# 参考資料
* https://qiita.com/sotoiwa/items/e350579d4c81c4a65260
* https://yun-craft.com/software-crafts/kube-raspi
* https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
* https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/