3台のRaspberry Pi 4でKubernetesクラスターを構築する(Ubuntu Server 20.04 LTS 64bit)

ここ数年、複数台のRaspberry Pi 3/4を使って、Kubernetesクラスターを構築している記事をよく見かけます。Raspberry Piは安価なため、3台構成のクラスターを3万円弱で作成できます。私も構築してみました。

Raspberry Pi×3台を用いたKubernetesクラスター


今回、3台のRaspberry Pi 4(4GB)に、Ubuntu Server 20.04 LTS(64bit)をインストールして構築しました(OSは32bit版より、64bit版にした方が、対応するバイナリ/dockerイメージが、若干多いようです)。

Raspberry Piの積層ケースについては、色々ある中、中国のAliExpress(52Pi Official Store)から、前面に12cmファンを備えた「Tower 4 Layer Acrylic Cluster Case」(送料込29.99USドル)を購入しました(コロナ禍による航空郵便の遅延により、届くまで1か月以上かかりました‥)。ファンが光るなど、派手な外観が特徴です。

Raspberry Pi 4は発熱が大きいため、安定運用のために、12cmファンで冷却しようと考えました。ファンは口径が大きい方が、同じ風量(=冷却性能)で、低回転(=低騒音)になります。ただ、このケース自体は、アクリル板の精度が悪くて組み立てしづらく、ツメが折れてしまったなど、使いづらいところがあります。また、写真にはファンガードがありますが、付属品ではなく、別途購入する必要がありました。

初期設定

最低限必要な初期設定として、ホスト名とタイムゾーンを設定しました。

1
2
$ sudo hostnamectl set-hostname raspi001
$ sudo timedatectl set-timezone Asia/Tokyo

※今回は、raspi001、raspi002、raspi003と設定しました。

OSを最新の状態に更新しておきます。

1
2
$ sudo apt update
$ sudo apt -y upgrade

ネットワークの設定

IPアドレスを固定するのに、ルーター側でMACアドレスと紐づけて設定しようとしましたが、Ubuntu 20.04の初期設定では正しく動作しませんでした。次のような設定ファイルで、DHCPの設定を修正する必要がありました。

1
$ sudo vi /etc/netplan/99_config.yaml
1
2
3
4
5
6
7
network:
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: true
dhcp-identifier: mac

設定ファイルを追加後、反映します(反映後、IPアドレスが変わる場合は、SSHの再接続が必要です)。

1
$ sudo netplan apply

※なお、直接、固定IPを設定する場合は次のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
network:
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: false
addresses:
- 192.168.1.201/24
gateway4: 192.168.1.1
nameservers:
addresses:
- 192.168.1.1

Raspberry Pi OSとは異なり、mDNSが無効だったため、有効にしました(有効化すると、Windows 10 PCやMacから、raspi001.localでアクセスできるようになります)。

1
$ sudo apt -y install avahi-daemon

Kubernetesの動作に必要な設定

まず、スワップが無効である必要があります。freeコマンドで確認したところ、Ubuntu Server 20.04の場合、初期設定でスワップは0でした。

1
2
3
4
$ free -h
total used free shared buff/cache available
Mem: 3.7Gi 1.0Gi 845Mi 5.0Mi 1.9Gi 2.8Gi
Swap: 0B 0B 0B

次に、cpusetとmemoryのcgroupが有効である必要があります。 /proc/cgroups を確認したところ、memoryのcgroupが無効でした。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 3 34 1
cpu 4 77 1
cpuacct 4 77 1
blkio 7 77 1
memory 10 146 0
devices 6 77 1
freezer 11 35 1
net_cls 5 34 1
perf_event 2 34 1
net_prio 5 34 1
pids 9 82 1
rdma 8 1 1

そこで /boot/firmware/cmdline.txt の最後に下記を追加し、再起動しました。 /proc/cgroups でも有効になったことを確認しました。

1
cgroup_enable=memory cgroup_memory=1

最後に、Kubernetesと互換性があるiptableに入れ替えます。

1
2
$ sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
$ sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy

Dockerのインストール

Dockerをインストールします。

1
2
3
4
5
$ sudo apt -y install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=arm64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
$ sudo apt update
$ sudo apt -y install docker-ce docker-ce-cli containerd.io

※Kubernetesの動作確認済バージョンを指定してインストールする場合は次の通りです。

1
$ sudo apt install containerd.io=1.2.13-2 docker-ce=5:19.03.11~3-0~ubuntu-focal docker-ce-cli=5:19.03.11~3-0~ubuntu-focal

OS更新の際に、dockerのバージョンが上がって、Kubernetesとの互換性がなくなったりしないよう、dockerのバージョンを固定します。

1
$ sudo apt-mark hold docker-ce docker-ce-cli

kubernetesのドキュメントにあるように、cgroupの管理をcgroupfsではなく、systemdで行うように設定を追加します(動作が安定化するため)。

1
$ sudo vi /etc/docker/daemon.json
1
2
3
4
5
6
7
8
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
1
2
3
$ sudo mkdir -p /etc/systemd/system/docker.service.d
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

ユーザーにdockerの操作権限を与えるには、ユーザーを docker グループに追加します(再ログインが必要です)。

※ユーザー ubuntu に権限を与える場合

1
$ sudo usermod -aG docker ubuntu

バージョンを確認してみて、クライアント側・サーバー側双方のバージョンが表示されれば問題ありません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ docker version
Client: Docker Engine - Community
Version: 19.03.11
API version: 1.40
Go version: go1.13.10
Git commit: 42e35e6
Built: Mon Jun 1 09:13:07 2020
OS/Arch: linux/arm64
Experimental: false

Server: Docker Engine - Community
Engine:
Version: 19.03.11
(後略)

kubernetesのインストール

Kubernetesをインストールします。

1
2
3
4
5
6
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
> deb https://apt.kubernetes.io/ kubernetes-xenial main
> EOF
$ sudo apt update
$ sudo apt -y install kubelet kubeadm kubectl

OS更新の際に、Kubernetesのバージョンが上がらないように、固定します。

1
$ sudo apt-mark hold kubelet kubeadm kubectl

マスターのセットアップ

マスター(1台目)、ワーカー(2台目、3台目)それぞれを設定していきます。

まずは、マスターを設定します。

1
$ sudo kubeadm init

ワーカー追加時に必要なため、表示内容の最後の部分をコピペしておきます。

1
2
3
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.1.201:6443 --token r1j7o7.x7orcw1234567890 \
--discovery-token-ca-cert-hash sha256:f94e44006c8b45500328a11e2d5b4375b71467fcff8812345678901234567890

kubectlコマンドを利用できるよう、権限情報をコピーします。

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

バージョンを確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ kubectl version -o yaml
clientVersion:
buildDate: "2020-07-15T16:58:53Z"
compiler: gc
gitCommit: dff82dc0de47299ab66c83c626e08b245ab19037
gitTreeState: clean
gitVersion: v1.18.6
goVersion: go1.13.9
major: "1"
minor: "18"
platform: linux/arm64
serverVersion:
buildDate: "2020-07-15T16:51:04Z"
compiler: gc
gitCommit: dff82dc0de47299ab66c83c626e08b245ab19037
gitTreeState: clean
gitVersion: v1.18.6
(後略)

仮想ネットワークのインストール

コンテナ間の仮想ネットワークをインストールします。
ここでは、Calicoをインストールします(OSが32bit版の場合は、Calicoは対応していないため、Flannelなどをインストールします)。
マスター上で、以下のコマンドを実行します。

1
$ kubectl apply -f https://docs.projectcalico.org/v3.14/manifests/calico.yaml

ワーカーのセットアップ

つぎに、ワーカーの2台をセットアップします。

マスターのセットアップ時に控えた内容を、2台のワーカーそれぞれで実行します。

1
$ sudo kubeadm join 192.168.1.201:6443 --token r1j7o7.x7orcw1234567890 --discovery-token-ca-cert-hash sha256:f94e44006c8b45500328a11e2d5b4375b71467fcff8812345678901234567890

ワーカーでの作業は、以上で完了です。
ワーカーが追加されたか、マスター上で確認します。

1
2
3
4
5
$ kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION
raspi001 Ready master 6d3h v1.18.6
raspi002 Ready none 6d3h v1.18.6
raspi003 Ready none 6d3h v1.18.6

ワーカーのラベルが none なので worker というラベルをつけます。

1
2
$ kubectl label node raspi002 node-role.kubernetes.io/worker=worker
$ kubectl label node raspi003 node-role.kubernetes.io/worker=worker

MetalLBのインストール

Kubernetes上のアプリに、外部からアクセスする際に便利なため、MetalLBをインストールします。マスター上で実行します。

1
2
3
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/namespace.yaml
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml
$ kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"

MetalLBが使用するIPアドレスを設定します。ここでは、未使用の192.168.1.250~192.168.1.254を割り当てます。
下記の内容でファイルを作成し、適用します。

1
$ vi metallb.config.yaml
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.1.250-192.168.1.254
1
$ kubectl apply -f metallb.config.yaml

以上です。