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で設定を行う。

metallb-config.yaml
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のみ)

下記内容のファイルを作成

dashboard-adminuser.yaml
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のみ)

下記内容のファイルを作成

dashboard-svc.yaml
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

下記のハイライト箇所を修正・追加。

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を入力すれば、ダッシュボードにアクセスできるはず。

おわりに

クラウドで構成するのと違って、ラズパイベアメタルで構成するのでつまづくポイントがいくつかあったが、 なんとか動かすところまでたどり着いた(丸一日半もかかった)。

今後このクラスタで色々と遊んでいこうと思います。