Raspberry Pi 4Bでkubernetesクラスタを立ち上げるまでのメモ

Table of Contents
Raspberry Pi 4Bを3台使って自宅kubernetesクラスタを構築する話。 kubernetes dashboardを起動できるようにするところまで。
モチベーション
自宅サーバを運用していて、色々なサービスをデプロイして遊ぶまでは良いのだけれど、規模が大きくなってくるとだんだんと環境が汚れたりして管理が面倒になってくる。かといって、物理サーバを複数台用意する資金力も、仮想サーバを建てまくるリソースの余裕もない。
これコンテナでやったら楽だよな→自宅にk8sクラスタほしいなという経緯。
ラズパイでk8sを構築する記事は日本語でも腐るほど出てくるので、随時参照されたし。
用意したもの
- Raspberry Pi 4 Model B RAM 8GB x3
- PoE+ HAT x3
- M.2 SSD 拡張ボード x3
- M.2 SSD (SATA) 512GB x3
- ケース(積層型の安いものでOK)
- LANケーブル(PoE対応のcat5e以上のもの)
- PoEスイッチ
2023/03/09現在ラズパイが入手困難なので、IntelのNUCとかも候補としてはいいかも。 自分は配線がごちゃごちゃするのが嫌だったので、PoE給電のオプションがあるラズパイにこだわってみた。
それにしたがい、PoE+で(1ポートで最大30Wまで)給電可能なPoEスイッチ(TL-SG105PE)も用意した。 オートリカバリー対応(ping疎通取れなくなると、自動で再起動する)なので、可用性も上がっていい感じだ。
また、ラズパイをサーバとして安定稼働させる上で1必須のSSDをコンパクトに纏めるために、M.2 SSD用の拡張ボードを取り付けた。
PoE+ HAT > ラズパイ > M.2SSDの3段構成にしていて、1ユニットでそれなりに高さが出るのでケースの組み立てに苦労した。 というのも、同梱されているスペーサだけだとちょうどよい高さにできなかったので、20mmのスペーサを追加で購入してなんとかサムネイル画像のようにまとめることができた。
前提
- Ubuntu Server 22.04 LTS 64bit
- kubernetes v1.26.2
control plane x1、worker node x2の構成。
手順
master/workerどちらで行う必要のある手順かを括弧内に記している。 共通の場合はすべてのノードで行う必要がある。
各ノードでの前提設定
OSのインストール(共通)
m.2 ssd to USBのssdケースをPCに挿して、rpi-imagerで直接OSを焼いた。
ケースがなければmicroSDで入れてからdd
しても、良いと思う。
swap無効化(共通)
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
各種カーネルモジュール有効化(共通)
sudo apt install linux-modules-extra-raspi && reboot # vxlanを有効化するために必要
sudo modprobe vxlan
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# 確認
lsmod | grep vxlan
lsmod | grep br_netfilter
lsmod | grep overlay
iptablesがブリッジを通過するトラフィックを処理できるようにする(共通)
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
# 確認
sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
コンテナランタイムのインストール(共通)
Kubernetes v1.20からDockerが非推奨になったので、containerdを入れる。
sudo apt install -y curl gnupg2 software-properties-common apt-transport-https ca-certificates
# docker repository有効化
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmour -o /etc/apt/trusted.gpg.d/docker.gpg
sudo add-apt-repository "deb [arch=arm64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt update
sudo apt install -y containerd.io
# containerdがcgroupを使うように
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null 2>&1
sudo sed -i 's/SystemdCgroup \= false/SystemdCgroup \= true/g' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl enable containerd
kubeadm,kubelet,kubectlのインストール(共通)
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmour -o /etc/apt/trusted.gpg.d/kubernetes-xenial.gpg
sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
cgroupメモリー有効化(共通)
以下を/boot/firmware/cmdline.txt
に追記してreboot
cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
スペース区切りで追記(改行はNG)。
省電力設定(Wi-Fi&Bluetooth無効化)
/etc/modprobe.d/blacklist.conf
に以下追記してreboot
# Disable Bluetooth
blacklist btbcm
blacklist hci_uart
# Disable Wi-Fi
blacklist brcmfmac
blacklist brcmutil
確認
ip a | grep wlan0 # 出力がなければOK
systemctl status bluetooth.service # inactiveであることを確認
kubernetes環境構築手順
kubeadm init(control planeのみ)
CNIプラグインとしてflannelを利用する場合--pod-network-cidr
は以下の値に固定する必要がある。
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
後でworkerノードを追加する際に必要になるので、kubeadm join...
で始まるコマンドをメモしておく。
root以外でもkubectlをできるように設定(control planeのみ)
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
source .bashrc
echo 'KUBECONFIG=$HOME/.kube/config' >> ~/.bashrc
kubectlの補完設定(control planeのみ)
bashの場合
source <(kubectl completion bash)
echo "source <(kubectl completion bash)" >> ~/.bashrc
zshの場合
source <(kubectl completion zsh)
echo "[[ $commands[kubectl] ]] && source <(kubectl completion zsh)" >> ~/.zshrc
flannelのデプロイ(control planeのみ)
CNI(Container Network Interface)としてflannel
を利用する。
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
他にもCalico,Terwayなど多くのCNIプラグインがある。
metalLB追加(control planeのみ)
ベアメタルでもLoadBalancer Serviceを使いたい場合、これ一択。
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.9/config/manifests/metallb-native.yaml
kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
config追加(control planeのみ)
MetalLB v0.13以降はConfigMapでの設定ができないので、CRDで設定を行う。
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default-pool
namespace: metallb-system
spec:
addresses:
- {任意のIP Pool}
autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default-advertisement
namespace: metallb-system
kubectl apply -f metallb-config.yaml
クラスターにjoin(worker nodeのみ)
kubeadm init
実行時にコピーしておいたkubectl join...
のコマンドでクラスターに参加。
ラベル付け(control planeのみ)
workerノードにラベル付けする(任意)。ラベル名は環境に応じて。
kubectl label node ubuntu2 node-role.kubernetes.io/worker=worker
kubectl label node ubuntu3 node-role.kubernetes.io/worker=worker
kubernetes dashboardを入れる(control planeのみ)
とりあえず動いていることを確認するためにkubernetes dashboard
をデプロイすることにした。
curl https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml -o dashboard.yaml
kubectl apply -f dashboard.yaml
dashboard用のアカウント作成(control planeのみ)
下記内容のファイルを作成
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: v1
kind: Secret
metadata:
name: kubernetes-dashboard-admin-user-secret
namespace: kubernetes-dashboard
annotations:
kubernetes.io/service-account.name: "admin-user"
type: kubernetes.io/service-account-token
適用
kubectl apply -f dashboard-adminuser.yaml
kubernetes v1.24.0以降だと、secretはデフォルトで作成されない2らしく、 Secretを明示しておく必要がある。
Service(LB)の作成(control planeのみ)
下記内容のファイルを作成
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: kubernetes-dashboard
name: dashboard-service-lb
namespace: kubernetes-dashboard
annotations:
metallb.universe.tf/address-pool: pool-ips
spec:
type: LoadBalancer
ports:
- name: dashboard-service-lb
protocol: TCP
port: 443 # serviceのIPでlistenするポート
nodePort: 30085 # nodeのIPでlistenするポート(30000-32767)
targetPort: 8443 # 転送先でlistenしているPort番号のポート
selector:
k8s-app: kubernetes-dashboard
適用
kubectl apply -f dashboard-svc.yaml
dashboardアクセス用のtoken取得(control planeのみ)
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}')
後で必要になるのでtokenをメモっておく。
metrics server追加
クラスタのリソース使用量を収集するコンポーネントであるmetrics server
を追加する。
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
下記のハイライト箇所を修正・追加。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: metrics-server
strategy:
rollingUpdate:
maxUnavailable: 0
template:
metadata:
labels:
k8s-app: metrics-server
spec:
containers:
- args:
- --cert-dir=/tmp
- --secure-port=4443
- --kubelet-preferred-address-types=InternalDNS,InternalIP,ExternalDNS,ExternalIP,Hostname #ここを編集
- --kubelet-use-node-status-port
- --metric-resolution=15s
- --kubelet-insecure-tls
# ...(省略)
nodeSelector:
kubernetes.io/os: linux
kubernetes.io/arch: "arm64"
priorityClassName: system-cluster-critical
serviceAccountName: metrics-server
volumes:
- emptyDir: {}
name: tmp-dir
適用
kubectl apply -f components.yaml
アクセス
kubectl get svc -n kubernetes-dashboard
でMetalLBが払い出すIPが確認できるので、そのIPにクラスタと同一のネットワーク内からhttpsアクセス。
tokenを求められるので、先程メモしたtokenを入力すれば、ダッシュボードにアクセスできるはず。
おわりに
クラウドで構成するのと違って、ラズパイベアメタルで構成するのでつまづくポイントがいくつかあったが、 なんとか動かすところまでたどり着いた(丸一日半もかかった)。
今後このクラスタで色々と遊んでいこうと思います。