Raspberry Pi 4×4台でKubernetesクラスタを構成するためにしたこと(Kubernetes v1.20 with kubeadm)

概要

Raspberry Pi 4台でkubeadmを使用したシングルコントロールプレーンクラスターの作成を参考にKubernetesクラスタを構成する際に躓いたところを中心に記録した記事です。(2021年1月3日時点)

構成

  • Raspberry Pi 4 ModelB 8GB:4台(Master 1台、Worker3台)
    • SDカード:32GB
  • kubeadm v1.20(GitVersion:v1.20.1)
  • コンテナランタイム:containerd(1.4.3)
  • ネットワークアドオン:Flannel

Raspberry Piの設定

以下、Kubernetesクラスタ構成時特有の設定ではないが、計4つのRaspberry Piに行います。

  1. OS書き込み
  2. ホスト名変更
    • ディスプレイ、キーボードを接続するなどしてログインし、raspi-configからホスト名を変更(System Optionsより)
  3. SSH設定
    • raspi-configから設定。(Interface Optionsより)
  4. kubeletのためにswapをオフにする
$ sudo dphys-swapfile swapoff
$ sudo systemctl stop dphys-swapfile
$ sudo systemctl disable dphys-swapfile
  1. cgroupを有効にするため、/boot/cmdline.txt を次のようにする(cgroup_enable以降を加筆。改行せず、1行に記載する)
console=serial0,115200 console=tty1 root=PARTUUID=eec83cbb-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory

kubeadmインストール

kubernetes.io より、 Rasberry Pi OSはDebian(Buster)系のため、iptables設定として

net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1

となるようにし、加えてnftablesバックエンドを使わないように切り替えました。

また、 /sys/class/dmi/id/product_uuid が存在しなかったため、ユニーク性の検証は省略しています。

containerdインストール

Kubernetes v1.20から、採用するコンテナランタイムの選択肢としてのDockerが非推奨となったため、今回はcontainerdを選択しました。 kubernetes.io

kubernetes.io より、Ubuntu 16.04の手順に倣ってインストールします。

ただし、Raspberry Piではadd-apt-repositoryの実行時に

aptsources.distro.NoDistroTemplateException: Error: could not find a distribution template for Raspbian/buster

のようなエラーが出ること、CPUアーキテクチャが異なること、ubuntuではなくdebianを指定するため、以下のように一部手順を変更しました。

# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - # GPG鍵追加

# add-apt-repository \
#    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
#   $(lsb_release -cs) \
#   stable"

 echo  "deb [arch=armhf] https://download.docker.com/linux/debian\
$(lsb_release -cs) \
 stable" > /etc/apt/sources.list.d/docker.list #リポジトリ設定追加

cgroupドライバーの設定

cgroupドライバーをcgroupfsではなくsystemdに変更します。 必須の項目ではないですが、

systemdと一緒に cgroupfs を使用するということは、2つの異なるcgroupマネージャーがあることを意味します。 コントロールグループはプロセスに割り当てられるリソースを制御するために使用されます。 単一のcgroupマネージャーは、割り当てられているリソースのビューを単純化し、 デフォルトでは使用可能なリソースと使用中のリソースについてより一貫性のあるビューになります。

ということを踏まえて、systemdへ統一します。

ここで、containerdがsystemdのcgroupドライバを使う方法について、

systemdのcgroupドライバーを使うには、/etc/containerd/config.toml内でplugins.cri.systemd_cgroup = trueを設定してください。

について具体的にどの項目を指すかというところに躓きました。

containerd config default > /etc/containerd/config.toml で得られた設定ファイルについて、systemd_cgroupという項目があるため、当初はtrueに変更すればよいように思われたのですが、実際はdeprecatedになっていました。

 [plugins."io.containerd.grpc.v1.cri"]
    disable_tcp_service = true
    stream_server_address = "127.0.0.1"
    stream_server_port = "0"
    stream_idle_timeout = "4h0m0s"
    enable_selinux = false
    selinux_category_range = 1024
    sandbox_image = "k8s.gcr.io/pause:3.2"
    stats_collect_period = 10
    systemd_cgroup = false # ここかと思いきやここではない

github.com

runtiime_type= io.containerd.runc.v2 の場合は次のように設定します。

      [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
          runtime_type = "io.containerd.runc.v2"
          runtime_engine = ""
          runtime_root = ""
          privileged_without_host_devices = false
          base_runtime_spec = ""
          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
            SystemdCgroup = true # 追加

加えて、kubeletからもsystemdを使用するよう、 /etc/default/kubelet を作成し、

KUBELET_EXTRA_ARGS="--cgroup-driver=systemd"

を記載しました。この作業は計4台全ノードで必要なものです。

クラスタ作成

マスタノードでクラスタを作成し、残りのノードで作成したクラスタに参加します。 まず、マスタノードで kubeadm init としてクラスタを作成します。

Flannelを使うために、kubeadm init 時に --pod-network-cidr=10.244.0.0/16 を渡す必要がありますが、設定項目を保存するため、あらかじめ kubeadm config init-defaults > ~/kubeadm_config.yaml としてから kubeadm init --config kubeadm_config.yaml とすることとしました。デフォルト設定との差分は以下のとおりです。

<   advertiseAddress: 1.2.3.4
---
>   advertiseAddress: <IP address> # masterノードのIPアドレス。worker側でkubeadm joinする際にアクセスするためのものも兼ねる

<   criSocket: /var/run/dockershim.sock
---
>   criSocket: "unix:///run/containerd/containerd.sock"

また、pod-network-cidrは以下の箇所で指定しました。

networking:
  dnsDomain: cluster.local
  serviceSubnet: 10.96.0.0/12
  podSubnet: 10.244.0.0/16 # 追加

最後に、 kubeadm init --config kubeadm_config.yamlクラスタ作成を試みると、以下のエラーで失敗しました。

[ERROR Mem]: the system RAM (1 MB) is less than the minimum 1700 MB

これはkubeadmのメモリ容量計算式のバグで、Raspberry Pi 4 8GBのような32bitシステムかつ4GB以上のメモリを持つシステムでのみ起きる(メモリサイズを誤判定する)挙動のようです。

github.com

そこで、(上記GitHub Issueでも示されているように)

kubeadm init --config kubeadm_config.yaml --ignore-preflight-errors=Mem

とすることで作成できました。
※ Issueに対応するPull Requestがマージ済みのため、後のkubeadmでは --ignore-preflight-errors=Mem は不要になる

最後に、クラスタ作成時に表示された kubeadm join コマンド(トークンを含む)を他の3ノードでも実行して完了です。

結果

kubectl get pod -n kube-system としてapiserverなどの各種コンポーネントが確認でき、実際に kubectl run としてPodが動作したため、ひとまず構築できたようです。
Raspberry Piなのでやはり kubectl get pod のようなリクエストでも重く、時間がかかるようですが、Kubernetesの実験のために使っていきたいところです。