🐳

RaspberryPiでKubernetesクラスタを構築

2023/07/30に公開

1.はじめに

今回はラズパイでKubernetesクラスタを構築してみたいと思います。

OSはUBUNTU SERVER 22.04.2 LTS(64-BIT)を使用し、全てのラズパイにインストール済みのところから始めます。OSのインストール方法は他の記事を参照してください。

また、クラスタの構成は以下のようになります。

2.k8sクラスタを構築するための予備知識

まず、クラスタ構築の前に必要となる知識を整理していきたいと思います。

  • kubectl
    KubernetesのCLIです。
  • kube-apiserver
    Kubernetes APIを提供するコンポーネントです。kubectlを使ってkube-apiserverに対してリクエストを送り、Kubernetesのリソースを管理します。
  • kubelet
    コンテナランタイムと連携し、コンテナを管理します。
  • コンテナランタイム
    高レベルコンテナランタイムと低レベルコンテナランタイムがあります。
    • 高レベルコンテナランタイム
      CRI(Container Runtime Interface)と呼ばれる規格でkubeletとのやり取りが行われます。また、高レベルコンテナランタイムは、コンテナイメージを管理する役割を持ちます。さらに、低レベルコンテナランタイムに対してコンテナ作成の命令を送ります。代表的な高レベルコンテナランタイムにcontainerdやcri-oがあります。
    • 低レベルコンテナランタイム
      OCI(Open Container Initiative)と呼ばれる規格で高レベルコンテナランタイムとのやり取りが行われます。また、低レベルコンテナランタイムは、実際にコンテナの生成、実行、停止、削除の役割を担います。代表的な低レベルコンテナランタイムにrunCやgVisorがあります。

https://github.com/containerd/containerd

https://github.com/cri-o/cri-o

https://github.com/opencontainers/runc

https://github.com/google/gvisor

今回のKubernetesクラスタの構築では、高レベルコンテナランタイムにcontainerd、低レベルコンテナランタイムにrunCを使用します。

3.事前準備

3.1.秘密鍵と公開鍵を作成

$ ssh-keygen -t rsa -b 4096
~/.ssh
├ id_rsa     # 秘密鍵
└ id_rsa.pub # 公開鍵

3.2.公開鍵を送る

ラズパイの方に公開鍵を送ります。

$ ssh-copy-id -i ~/.ssh/id_rsa.pub user-name@XXX.XXX.XXX.XXX # XXX.XXX.XXX.XXXはラズパイのIPアドレス

ラズパイのIPアドレスが見当たらない場合は、こちらの方法で送ります。

$ ssh-copy-id -i ~/.ssh/id_rsa.pub user-name@host-name.local

3.3.接続

秘密鍵を指定してラズパイに接続します。

$ ssh -i id_rsa user-name@XXX.XXX.XXX.XXX

同じくラズパイのIPアドレスが見当たらない場合は、こちらの方法で接続します。

$ ssh -i id_rsa user-name@host-name.local

3.4.IPアドレスを固定

/etc/netplan/99_config.yamlを作成しIPアドレスを固定します。

https://ubuntu.com/server/docs/network-configuration

$ vi /etc/netplan/99_config.yaml
network:
    version: 2
    ethernets:
      eth0:
        dhcp4: false
        addresses:
          - 192.168.13.101/24
        nameservers:
          addresses:
            - 8.8.8.8

編集したらnetplanコマンドで反映させます。

$ sudo netplan apply

他のNodeにも接続してIPアドレスを以下のように設定しておきます。

ホスト名 IPアドレス 役割
k8s-master 192.168.13.101 Master Node
k8s-node1 192.168.13.102 Worker Node
k8s-node2 192.168.13.103 Worker Node

3.5.エイリアス作成

簡単にssh接続できるようにエイリアスを作成しておきます。

$ vi ~/.ssh/config
Host k8s-master              # エイリアス名
  HostName 192.168.13.101    # 接続先IPアドレス
  User user-name             # 接続先ユーザ名
  IdentityFile ~/.ssh/id_ras # 秘密鍵
Host k8s-node1
  HostName 192.168.13.102
  User user-name   
  IdentityFile ~/.ssh/id_ras
Host k8s-node2
  HostName 192.168.13.103
  User user-name   
  IdentityFile ~/.ssh/id_ras
$ ssh k8s-master

4.k8sクラスタの構築

4.1.linux-modules-extra-raspiのインストール

複数のKubernetes Node上に跨るPod間のネットワーク通信を機能させるために、CNI(Container Network Interface)Pluginと呼ばれるものがあります。linux-modules-extra-raspiをインストールすることで、CNIが正常に働きPod間のネットワーク通信が上手く機能するそうです。因みに今回はCNIにFlannelを使います。(後述)

$ apt install -y linux-modules-extra-raspi
$ reboot now

4.2.カーネル

Kubernetesコンテナランタイムを参照しながら進めていきます。

4.2.1.カーネルモジュールのロード

カーネルモジュールは小さなコードの集まりであり、必要に応じてカーネルにロード・アンロードすることができます。カーネルモジュールはシステムを再起動する必要なくカーネルの機能を拡張します。

カーネルモジュール

overlay、br_netfilterモジュールが起動時に読み込まれるようにします。

# confファイルに読み込みたいモジュールを明示
$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

# カーネルモジュールをロード
$ sudo modprobe overlay
$ sudo modprobe br_netfilter

4.2.2.カーネルパラメータの設定

カーネルパラメータ

カーネルにパラメータを渡して、カーネルの動作を制御します。

# カーネルパラメーターを永続的に設定
$ 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

4.2.3.確認

br_netfilter、overlayモジュールがロードされていることを確認します。

$ lsmod | grep br_netfilter

br_netfilter           32768  0
bridge                319488  1 br_netfilter
$ lsmod | grep overlay

overlay               155648  0

カーネルパラメーターの設定が1であることを確認します。

$ sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward

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

4.3.コンテナランタイムのインストール

4.3.1.containerdのインストール(高レベルコンテナランタイム)

containerd step1を参照しながら進めていきます。

# containerdのバイナリをダウンロード
$ curl -LO https://github.com/containerd/containerd/releases/download/v1.7.2/containerd-1.7.2-linux-arm64.tar.gz
# 展開
$ tar Cxzvf /usr/local containerd-1.7.2-linux-arm64.tar.gz
# バイナリが置かれていることを確認
$ ls -la /usr/local/bin/containerd*

/usr/local/bin/containerd
/usr/local/bin/containerd-shim
/usr/local/bin/containerd-shim-runc-v1
/usr/local/bin/containerd-shim-runc-v2
/usr/local/bin/containerd-stress
$ mkdir -p /usr/local/lib/systemd/system
# unitファイルをダウンロード
$ curl -L https://raw.githubusercontent.com/containerd/containerd/main/containerd.service > /usr/local/lib/systemd/system/containerd.service
# unitファイルが置かれていることを確認
$ ls -la /usr/local/lib/systemd/system/containerd.service

/usr/local/lib/systemd/system/containerd.service
# unitファイルを再読み込み
$ systemctl daemon-reload
# unitファイルを反映
$ systemctl enable --now containerd
# 反映できているか確認
$ systemctl is-active containerd

active

4.3.2.runcのインストール(低レベルコンテナランタイム)

containerd step2を参照しながら進めていきます。

# runcのバイナリをダウンロード
$ curl -LO https://github.com/opencontainers/runc/releases/download/v1.1.7/runc.arm64
# インストール
$ install -m 755 runc.arm64 /usr/local/sbin/runc
# バイナリが置かれていることを確認
$ ls -la /usr/local/sbin/runc

/usr/local/sbin/runc

4.4.CNI pluginsのインストール

containerd step3を参照しながら進めていきます。

$ mkdir -p /opt/cni/bin
# CNI pluginsのバイナリをダウンロード
$ curl -LO https://github.com/containernetworking/plugins/releases/download/v1.3.0/cni-plugins-linux-arm-v1.3.0.tgz
# インストール
$ tar Cxzvf /opt/cni/bin cni-plugins-linux-arm-v1.3.0.tgz
# バイナリが置かれていることを確認
$ ls /opt/cni/bin

bandwidth  bridge  dhcp  dummy  firewall  flannel  host-device  host-local  ipvlan  loopback  macvlan  portmap  ptp  sbr  static  tap  tuning  vlan  vrf

4.5.systemd cgroupドライバーの設定

Kubernetesコンテナランタイムを参照しながら進めていきます。

$ mkdir /etc/containerd
# containerdにデフォルトの設定を適用
$ containerd config default > /etc/containerd/config.toml
# containerdにsystemd cgroupドライバーを適用
# /etc/containerd/config.tomlのSystemdCgroupをtrueに変更
vi /etc/containerd/config.toml

  ...
    SystemdCgroup = true
  ...
# containerdを再起動
$ systemctl restart containerd
# 動いているか確認
$ systemctl is-active containerd

active

4.5.kubectl、kubelet、kubeadmのインストール

kubeadm、kubelet、kubectlのインストールを参照しながら進めていきます。

$ sudo apt update
$ sudo apt install -y apt-transport-https ca-certificates curl
$ curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-archive-keyring.gpg
$ echo "deb [signed-by=/etc/apt/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
$ sudo apt update
$ sudo apt install -y kubelet kubeadm kubectl
$ sudo apt-mark hold kubelet kubeadm kubectl

kubeletによって使用されるcgroupドライバーを設定します。

$ vi /etc/default/kubelet
# 以下のように設定
KUBELET_EXTRA_ARGS=--cgroup-driver=systemd

4.6.k8sクラスタ構築

$ kubeadm init --node-name master --apiserver-advertise-address=192.168.13.101 --pod-network-cidr=10.244.0.0/16
# 以下のように出力される
Your Kubernetes control-plane has initialized successfully!

...

kubeadm join ...

kubectlをrootユーザ以外でも使えるようにしておきます。

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

Worker Nodeに必要なものをインストールした後にkubeadm join ...をWorker Nodeで実行します。

$ kubeadm join 192.168.13.101:6443 --node-name XXXXX --token XXXXX --discovery-token-ca-cert-hash XXXXX

今回のクラスタ構築では、flannelを使いPod間のネットワークが機能するようにします。

$ kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

https://github.com/flannel-io/flannel

4.7.kubeconfigの設定

手元にあるPCからkubectlでMaster Nodeと通信できるようにします。kubectlは$HOME/.kube/configに書かれている情報を用いて接続が行われます。

# 手元のPCで実行
$ mkdir -p $HOME/.kube
$ scp k8s-master:/etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

5.完成

$ kubectl get nodes

NAME     STATUS   ROLES           AGE   VERSION
master   Ready    control-plane   15d   v1.27.3
node1    Ready    <none>          14d   v1.27.3
node2    Ready    <none>          14d   v1.27.3
$ kubectl get nodes -o wide

NAME     STATUS   ROLES           AGE   VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
master   Ready    control-plane   15d   v1.27.3   192.168.13.101   <none>        Ubuntu 22.04.2 LTS   5.15.0-1033-raspi   containerd://1.7.2
node1    Ready    <none>          14d   v1.27.3   192.168.13.102   <none>        Ubuntu 22.04.2 LTS   5.15.0-1033-raspi   containerd://1.7.2
node2    Ready    <none>          14d   v1.27.3   192.168.13.103   <none>        Ubuntu 22.04.2 LTS   5.15.0-1033-raspi   containerd://1.7.2

6.Nodeをクラスタから除外

Nodeを除外したいときは、Nodeを削除した後にkubeadm resetを実行してください。

# Master Nodeで指定するNodeを削除
$ kubectl delete node <node name>
# 削除したNodeで実行
$ kubeadm reset

7.おわりに

クラウドを使えばクラスタは一瞬で構築できると思いますが、自前のクラスタを構築することでKubernetesやコンテナの理解が深まりました。今後もKubernetesは最強のコンテナオーケストレーションであり続けると思うので、積極的に触れていきたいです。

また、Kubernetesやコンテナの活躍の場は非常に幅広く、F16戦闘機や自動車にも搭載されるほどです。これからの技術的発展が非常に楽しみです。

参考

https://github.com/containerd/containerd
https://github.com/cri-o/cri-o
https://github.com/opencontainers/runc
https://github.com/google/gvisor
https://ubuntu.com/server/docs/network-configuration
https://qiita.com/yoshimi0227/items/3cac86915d47a2b788cd
https://manpages.ubuntu.com/manpages/jammy/man5/modules-load.d.5.html
https://manpages.ubuntu.com/manpages/jammy/man5/sysctl.d.5.html
https://wiki.archlinux.jp/index.php/カーネルモジュール
https://zenn.dev/ttnt_1013/articles/f36e251a0cd24e
https://qiita.com/kentarok/items/6e818c2e6cf66c55f19a
https://skanehira.github.io/this-week-in-gorilla/articles/raspberry-pi-cluster.html
https://kubernetes.io/docs/reference/setup-tools/kubeadm/
https://github.com/flannel-io/flannel

Discussion