完成品
まずは完成したクラスターをご紹介します。
関連記事 2020/02/25 追記
- ラズパイk8s用の監視システム(Node Exporter + Prometheus + InfluxDB + Grafana)
- Raspberry Pi上のDockerで動くイメージのCIでのビルド方法
モチベーション
つい先日会社のサポート制度である「テックサポート制度」の対象にRaspberry Piが追加されているのを発見しました。
これは使わない手はないなと思い最上位モデルの4GBを3台購入し、長年の悲願であったKubernetes Cluster on Raspberry Piを構築することができました。
ちなみに、テックサポート制度に関しては会社の 開発者ブログ に詳しく書かれています。
(宣伝ノルマ達成)
これがやりたかっただけ
コンセプト
ラズパイk8sクラスターなんて先人達が幾度となく構築、記事に残しており、ぶっちゃけ枯れた分野です。
普通に作ってもつまらないので、以下のコンセプトで作成してみました。
せっかく金銭的にも余裕がでたことだし。
コンセプト1. ディスプレイ
見た目を良くしたい、なんとなくラズパイ用小型ディスプレイを試してみたい、といった理由から、ディスプレイを取り付ける方針としました。
最上段に上向きにつけると見づらいので、側面に取り付ける方向で検討しました。
コンセプト2. 電源
Raspberry Pi 4 の電力仕様である5V/3Aを供給することを目的としました。
(ラズパイ3時代はもっと少なくてよかった)
USB給電が少ない場合は2.5Aで十分と公式ドキュメントに書いてあります。
また、よくある構築例だとAnkerの1ポートあたり2.4AのUSB電源を利用している場合が多く、特にこれで問題は発生しないようです。
が、やはり仕様はきちんと満たしたいので、きっちり5V/3Aを供給できる電源を取り付ける方針としました。
コンセプト3. ネットワーク
ノード間はギガビットイーサ(1000BASE-T)で接続することをコンセプトとしました。
こちらもよくある構築例では、USB給電可能なメガビットイーサ(100BASE-T)対応のスイッチングハブが利用されています。
せっかくのギガビットイーサ対応Raspberry Piなので、意図的にボトルネックを作りたくないので、ギガビットイーサにこだわりました。
なお、頻繁にディスプレイを見たり、5V/3A供給が必要なほどデバイスを接続したり、ギガビットイーサを活かすほどのトラフィックを発生させる予定はまったくありません。完全にロマンです。
ここからはハードウェアとソフトウェアに分けて構成パーツや構築の流れを紹介します。
興味のある部分だけ見ていただく形がよいかと思います。
ハードウェア
まずはハードウェアのご紹介から。
構成パーツ一覧
最終的に構築に利用したパーツと料金、リンクを下記の表に示します。
分類 | 名前 | 値段 | リンク |
---|---|---|---|
Raspberry Pi本体 | Raspberry Pi 4 Model B 4GB * 3台 | ¥7315 * 3 | Amazon |
SDカード | SunDisk 32GB UHS-I Class10 * 3個 | ¥1009 * 3 | Amazon |
ケース | 積層ケース for Raspberry Pi 4 | ¥1889 | Amazon |
ディスプレイ | OSOYOO 3.5インチ LCD タッチスクリーン | ¥2380 | Amazon |
ジャンパー | ブレッドボード ジャンパーワイヤ メス-オス 20cm | ¥196 | Amazon |
電源 | Anniber QC3.0 ACアダプター * 3個 | ¥997 * 3 | Amazon |
電源ケーブル | USB type-c 【30cm3本】 | ¥690 | Amazon |
スイッチングハブ | エレコム スイッチングハブ EHC-G05PA-SB | ¥2351 | Amazon |
LANケーブル | エレコム LANケーブル 0.15m 2個入り * 2個 | ¥579 * 2 | Amazon |
LANケーブル | Aucas カテゴリ7 ウルトラフラット 1m | ¥898 | Amazon |
その他 | 金具、ネジ | ¥638 | N/A |
その他 | 基盤スタンド | ¥858 | Amazon |
合計 | ¥39030 |
ハードウェア詳細
Raspberry Pi本体
Raspberry Pi 4では、RAMが1GB、2GB、4GBのモデルが販売されています。
ベンチマーク結果を見て当初は2GBが最もコストパフォーマンスに優れていそうなので、これにしようかと思っていたのですが、金額を気にしなくて良くなったため4GBモデルを購入することにしました。
台数はクラスターを組むにあたり最低3台必要なので、とりあえずは3台としました。
microSD
Raspberry Pi 4では64GB以上のサイズも利用可能になりました。
しかし、64GB以上のmicroSDはフォーマットの仕方に癖があるようなので、今回は32GBとしました。
ディスプレイ
ディスプレイは上面に設置するタイプがほとんどなので、L字金具 + 基盤スタンドで側面に固定
GPIO端子は直付けではなく、ジャンパーを使って配線しています。
ディスプレイ選びですが、ちょうどよいサイズの3.5インチに関しては数社から販売されております。
価格/性能ともに特に差は見受けられなかったため、背面の基盤配置的にもっとも取り付けやすそうなものを購入しました。
画面に出力している内容に関しては後述します。
ケース
ラズパイクラスターといったら、やはり積層ケースでしょう。
いくつか探してみたのですが、結局は定番の以下のものに落ち着きました。
電源
Raspberry Pi 4の要求電源は5V/3Aです。
(USB Downstreamが500mA以下なら2.5Aで可)
当初は下の画像のような、複数のUSBポートを備えたUSB充電器を探していたのですが、複数ポートの場合は、どうしても「各ポート2.4Aまで」という制限がついています。
(いちおうこの程度の供給でも動作はするという報告は上がっているようです)
1つのUSB充電器で供給することは諦め、1ポートのUSB電源を3個購入しました。
クラスターのケースに取り付けることはできなくなりましたが、もともと可搬性は気にしていなかったので問題なしとしています。
スイッチングハブ
以下の2点を満たすものを探しました。
- USB給電可能
- 1000BASE-T対応
USB給電に関しては、ラズパイ側の都合で電源に複数ポート備えたUSB充電器を使わないので、実はなんでも良かったりします。
ただ、今後5V/3A対応の複数ポートのUSB充電器が発売されたらそちらに乗り換えたいため、USB給電可能なものを選択しました。
ソフトウェア
続いてソフトウェア面です。
構築手順
構築手順をざっと紹介します。
OSのセットアップ
Raspberry Pi用Debianの最新であるRaspbian Busterのheadless(X11なし)を採用しています。
公式の手順を見ながらインストールを行いました。
# デバイスを確認
$ lsblk -p
# microSDをunmount
$ umount /dev/sda1
# イメージの書き込み
$ sudo dd bs=4M if=2019-09-26-raspbian-buster-lite.img of=/dev/sda status=progress conv=fsync
$ sync
# microSDを取り外し、再度取り付ける
# 起動時にsshを有効にする設定
$ touch /media/reireias/boot/ssh
# Dockerを利用するために、cgroupに関する設定を追加します
$ vi /media/reireias/boot/cmdline.txt
# 下記を末尾に追加します
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
ここで一旦ラズパイを起動します。
# authorized_keysの設定
$ scp ~/.ssh/id_rsa.pub pi@ip:/tmp/authorized_keys
$ ssh pi@ip
$ mkdir ~/.ssh
$ cp /tmp/authorized_keys ~/.ssh/authorized_keys
# 初期パスワードを変更
$ sudo passwd pi
# swapを無効化
$ sudo systemctl disable dphys-swapfile.service
# ホスト名を設定
# 今回は pikube01 ~ pikube03 という名前にしました
$ sudo vi /etc/hosts
$ sudo vi /etc/hostname
# 何らかの手段でIPを固定化しておくと便利でしょう
# 私はルーターの機能で固定DHCPを設定しています
# packageの更新
$ sudo apt update
$ sudo apt upgrade
# もろもろを反映させるために再起動
$ sudo reboot
ディスプレイの設定
ディスプレイを接続し、公式のドキュメントに従い設定していきます。
$ sudo apt install git
$ git clone https://github.com/kedei/LCD_driver
$ chmod -R 777 LCD_driver
$ cd LCD_driver
$ ./LCD35_show
デフォルトのフォントは少し読みづらかったので変更します。
$ sudo vi /etc/default/console-setup
# 下記を設定
FONTFACE="Terminus"
FONTSIZE="8x16"
これで無事ディスプレイに表示されました。
あとはキーボードを接続し、ログインすれば任意の画面を表示できるはずです。
dockerのインストール
下記の公式ドキュメント通りに構築を行っています。
https://kubernetes.io/ja/docs/setup/production-environment/container-runtimes/
# dockerのインストール
# Debian busterのarm用パッケージはまだ配信されていないので、debファイルをダウンロードし、インストールしている
$ wget https://download.docker.com/linux/debian/dists/buster/pool/stable/armhf/containerd.io_1.2.6-3_armhf.deb
$ wget https://download.docker.com/linux/debian/dists/buster/pool/stable/armhf/docker-ce-cli_19.03.5~3-0~debian-buster_armhf.deb
$ wget https://download.docker.com/linux/debian/dists/buster/pool/stable/armhf/docker-ce_19.03.5~3-0~debian-buster_armhf.deb
$ sudo dpkg -i containerd.io_1.2.6-3_armhf.deb
$ sudo dpkg -i docker-ce-cli_19.03.5~3-0~debian-buster_armhf.deb
$ sudo dpkg -i docker-ce_19.03.5~3-0~debian-buster_armhf.deb
$ sudo usermod pi -aG docker
kubeadmとkubectlのインストール
Kubernetesクラスターの構築に利用するkubeadmin
と、Kubernetesを操作するkubectl
をインストールします。
公式の手順はこちら。
https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
$ sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
$ sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
$ sudo update-alternatives --set arptables /usr/sbin/arptables-legacy
$ sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy
$ sudo apt update && sudo apt install -y apt-transport-https curl
$ 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 install -y kubelet kubeadm kubectl
$ sudo apt-mark hold kubelet kubeadm kubectl
クラスターの構築
まずはマスターノードとするpikube01
上で下記を実行します。
# マスターノードの初期化
# IPは後述のflannelを使う都合でこれで固定
$ sudo kubeadm init --pod-network-cidr=10.244.0.0/16
# kubectlを実行できるように設定
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
# コンテナ用ネットワークファブリックのflannelを構築
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
# 作成できたか確認
$ kubectl get pod --all-namespaces
続いてワーカーにするpikube02
、pikube03
上で下記を実行します。
# master node作成時に出力されたコマンドを入力
$ kubeadm join 192.168.xx.yy:6443 --token xxxxxxxxxxxxxxxxxxx \
--discovery-token-ca-cert-hash sha256:zzzzzzzzzzzzzzzzz
master側でクラスターが作成できたか確認します。
$ kubectl get nodes
# 下記のように表示されればOK
NAME STATUS ROLES AGE VERSION
pikube01 Ready master 4m29s v1.17.2
pikube02 Ready <none> 47s v1.17.2
pikube03 Ready <none> 38s v1.17.2
# ROLESがnoneなので、labelをつける
$ kubectl label node pikube02 node-role.kubernetes.io/worker=
$ kubectl label node pikube03 node-role.kubernetes.io/worker=
# 再確認
# ROLESが意図通りになっている
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
pikube01 Ready master 8m38s v1.17.2
pikube02 Ready worker 4m56s v1.17.2
pikube03 Ready worker 4m47s v1.17.2
別マシンからkubectlコマンドを利用できるようにする
# master node(pikube01)上で下記を実行し、出力をコピー
$ kubectl config view --raw
# kubectlを実行できるようにしたいマシン上で下記を実施
$ vi ~/.kube/config
# 先程コピーした設定を書き込む
# 動作確認
$ kubectl get nodes
MetalLBのインストール
ベアメタル環境Kubernetes用のL2ロードバランサー機能を提供するMetalLBをクラスターへ追加します。
MetalLBを追加することで、指定したレンジでIPアドレスを取得し、そのIPをエンドポイントとしてLAN内にServiceを公開できるようになります。
# 全てのnode上で下記を実施し、MetalLBで利用する通信が疎通できるようにする
$ sudo sysctl net.ipv4.conf.all.forwarding=1
$ sudo iptables -P FORWARD ACCEPT
# MetalLBの構築
kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.8.3/manifests/metallb.yaml
下記内容でConfigMap用のyamlファイルを作成します。
adresses
にはDHCP等で取得できるIPアドレスのレンジを指定してください。
筆者の場合は、pikube01
〜pikube03
が192.168.99.100-102
となっているので、192.168.99.200-220
を割り当てています。
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.99.200-192.168.99.220
構築ができたら動作確認を行います。
みんな大好きNginxで簡単なServiceとDeploymentを作成し、MetalLBによってtype: LoadBalancer
なServiceにIPが割り振られることを確認します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
type: LoadBalancer
# 上記ファイルをapply
$ kubectl apply -f nginx.deployment.yml
# serviceを取得し、ExternalIPにIPが割り当てられていることを確認
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5d2h
nginx-service LoadBalancer 10.97.205.133 192.168.99.200 80:32198/TCP 4d2h
# curlで上記ExternalIPへアクセスし、Welcome to Nginx! が返ることを確認する
metrics-serverのクラスターへの追加
kubectl top
コマンドを利用できるようにするために、metrics-serverを追加します。
下記を参考にさせていただきました。
https://qiita.com/yyojiro/items/febfaeadabd2fe8eed08
下記リポジトリをcloneします。
https://github.com/kubernetes-sigs/metrics-server
deploy/1.8+/metrics-server-deployment.yaml
ファイルをarm1用に修正します。
差分は下記のようになります。
diff --git a/deploy/1.8+/metrics-server-deployment.yaml b/deploy/1.8+/metrics-server-deployment.yaml
index e4bfeaf..f7c72f1 100644
--- a/deploy/1.8+/metrics-server-deployment.yaml
+++ b/deploy/1.8+/metrics-server-deployment.yaml
@@ -29,10 +29,12 @@ spec:
emptyDir: {}
containers:
- name: metrics-server
- image: k8s.gcr.io/metrics-server-amd64:v0.3.6
+ image: k8s.gcr.io/metrics-server-arm:v0.3.6
args:
- --cert-dir=/tmp
- --secure-port=4443
+ - --kubelet-insecure-tls
+ - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
ports:
- name: main-port
containerPort: 4443
@@ -47,4 +49,4 @@ spec:
mountPath: /tmp
nodeSelector:
beta.kubernetes.io/os: linux
- kubernetes.io/arch: "amd64"
+ kubernetes.io/arch: "arm"
# ディレクトリ以下をapplyする
$ kubectl apply -f deploy/1.8+/
# コマンドの確認
$ kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
pikube01 385m 9% 649Mi 17%
pikube02 167m 4% 448Mi 11%
pikube03 175m 4% 435Mi 11%
samplerのビルド
これは、samplerというツールで表示をしているのですが、このツールがarm用ビルドに対応できないライブラリを利用しているため、一部コードを改変し、arm用にビルド可能にする必要があります。
それを実施したリポジトリがこちらです。
https://github.com/sqshq/sampler
# clone後、下記コマンドでarm[^1]用バイナリをビルド
GOOS=linux GOARCH=arm GOARM=7 go build
# scpでディスプレイを接続しているノード = マスターに転送
# パスが通っている場所へ移動
$ sudo mv /tmp/sampler /usr/bin
これでラズパイ上でsampler
コマンドが実行できるようになりました。
sampler
コマンドの設定ファイルは下記のように実装しています。
gauges:
- title: pikube01 CPU
position: [[0, 0], [40, 6]]
rate-ms: 30000
color: 10
percent-only: true
cur:
sample: cat /tmp/kube-node | grep pikube01 | awk '{print $3}' | tr -d "%"
max:
sample: echo 100
min:
sample: echo 0
- title: pikube02 CPU
position: [[0, 7], [40, 6]]
rate-ms: 30000
color: 13
percent-only: true
cur:
sample: cat /tmp/kube-node | grep pikube02 | awk '{print $3}' | tr -d "%"
max:
sample: echo 100
min:
sample: echo 0
- title: pikube03 CPU
position: [[0, 13], [40, 6]]
rate-ms: 30000
color: 14
percent-only: true
cur:
sample: cat /tmp/kube-node | grep pikube03 | awk '{print $3}' | tr -d "%"
max:
sample: echo 100
min:
sample: echo 0
- title: pikube01 Mem
position: [[40, 0], [40, 6]]
rate-ms: 30000
color: 10
cur:
sample: cat /tmp/kube-node | grep pikube01 | awk '{print $4}' | tr -d "Mi"
max:
sample: echo 4096
min:
sample: echo 0
- title: pikube02 Mem
position: [[40, 7], [40, 6]]
rate-ms: 30000
color: 13
cur:
sample: cat /tmp/kube-node | grep pikube02 | awk '{print $4}' | tr -d "Mi"
max:
sample: echo 4096
min:
sample: echo 0
- title: pikube03 Mem
position: [[40, 13], [40, 6]]
rate-ms: 30000
color: 14
cur:
sample: cat /tmp/kube-node | grep pikube03 | awk '{print $4}' | tr -d "Mi"
max:
sample: echo 4096
min:
sample: echo 0
textboxes:
- title: Status
position: [[0, 19], [80, 23]]
rate-ms: 30000
sample: >-
kubectl top node > /tmp/kube-node;
kubectl get all --all-namespaces > /tmp/kube-all;
echo "Pod:$(cat /tmp/kube-all | grep pod/ | grep 'Running' | wc -l)"
"Service:$(cat /tmp/kube-all | grep service/ | wc -l)"
"Daemonset:$(cat /tmp/kube-all | grep daemonset.apps/ | wc -l)"
"Deployment:$(cat /tmp/kube-all | grep deployment.apps/ | wc -l)"
"Replicaset:$(cat /tmp/kube-all | grep replicaset.apps/ | wc -l)";
echo "";
echo "Service";
kubectl get svc --no-headers | grep -v ClusterIP | awk '{print $1, $4, $5}' | column -t;
工夫点としては、同じコマンドはいったんファイルに保存し、catで読み込むことで、無駄にKubernetesクラスター側に負荷がかかるのを抑制している点です。
あとは、pikube01
ノードに接続したキーボードからsampler -c config.yml
を実行することで、下記がディスプレイに表示されるはずです。(※キャプチャの都合で、別マシンの同サイズウィンドウで撮影しています
所感
主にサイズ面で不要なパーツもいくつか購入したため、結局5万円くらい使ってしまいました。
この後は、もともと別のラズパイで動いていたサービスをk8sへ乗せ換えたり、監視周りを構築したり、いろいろやりたいことを試していこうと思います。
関連記事
- ラズパイk8s用の監視システム(Node Exporter + Prometheus + InfluxDB + Grafana)
- Raspberry Pi上のDockerで動くイメージのCIでのビルド方法
参考にさせていただいたリンク
-
CPUアーキテクチャの1種。ラズパイのCPUはこれ。よくあるPCの場合はamd64等。 ↩