コンテンツにスキップ

ラズパイで自宅Kubernetes構築 (2026年夏版)

go言語で作成したアプリの実行環境としてKubernetes(以下ではK8s)を使いたくなりましたので、2026年の夏にRaspberry Piを使って最新の環境構築を行ったときのメモです。

なお、この記事は2026年8月25日時点で最新版だったv1.36.2を使って書き始めたのですが、翌日にK8sの最新版v1.37リリースされてしまいました。そのため、v1.37で最初からやり直しています。

数年前にK8s v1.28 (Raspberry Pi OS Bookworm)で構築したときと比べて色々変わっていましたので、この記事もすぐに時代遅れになってしまいそうですが、参考になれば幸いです。

Raspberry Pi 4を4台使ってハードウェア環境を構築しています。

写真は下図のK8s用システムの枠で囲んだ部分になります。

flowchart TB
  pow[ACアダプタ]
  hub[PoEハブ 1Gbps<br>10.0.0.0/22]
  pi1[ラズパイ#1<br>'pi4main']
  pi2[ラズパイ#2<br>'pi4node1']
  pi3[ラズパイ#3<br>'pi4node2']
  pi4[ラズパイ#4<br>'pi4node3']
  wlan[Wi-Fiルーター]
  internet([インターネット])
  pc[PC<br>Ubuntu 24.04 LTS]

  pow --- hub

  subgraph K8s用システム
  hub -- 10.0.0.1 --- pi1
  hub -- 10.0.1.1 --- pi2
  hub -- 10.0.1.2 --- pi3
  hub -- 10.0.1.3 --- pi4
  end

  pc --- wlan
  pi1 -- pi4main.local --- wlan
  pi2 -- pi4node1.local --- wlan
  pi3 -- pi4node2.local --- wlan
  pi4 -- pi4node3.local --- wlan

  wlan --- internet

各Raspberry PiはPoE HATを搭載しており、電源は有線LANポートから得ているので、セットアップ完了後のK8s用システムから出ている配線はPoEハブのACアダプタだけになり、非常にすっきりしています。

  • PoEハブ: スイッチングハブ(1GbE・PoE対応)
    • 10.0.0.0/22のプライベートネットワークを構築してラズパイ同士を接続する。
    • PoE HAT経由で給電可能。
  • ラズパイ#1: Raspberry Pi 4 Model B 4GB + PoE HAT + 250GB USB SSD
  • ラズパイ#2ラズパイ#4: Raspberry Pi 4 Model B 4GB + PoE HAT + 64GB microSD
    • K8sのノードとして使用する。
    • セットアップ初期段階ではWi-Fiは有効になっているが、不要なWi-Fi通信を減らすため、最終的にはWi-Fiを無効にする。
    • セットアップ完了後は、ラズパイ#1を経由してインターネット接続する。

Raspberry Piは4台ありますが、途中までのセットアップ内容は共通なので、容量の小さいmicroSDカード(ここでは32GB製品を使用)を使って1台目のセットアップを行い、それを吸い出してイメージファイルを作成し、USB SSDや64GB microSDカードに移していきます。

まずは、Raspberry Pi OSに関する基本的な部分をセットアップします。

microSDカードにOSイメージを書き込む

Section titled “microSDカードにOSイメージを書き込む”

PC(Ubuntu 24.04 LTS)を使って、Raspberry Pi OS (64-bit)のページからRaspberry Pi OS Liteの最新版(13 trixie)をダウンロードして、32GB microSDカードに書き込んでいきます。

PC
sudo dmesg # microSDカードが挿入されたときのデバイス名を調べる: mmcblk0
mount|grep mmcblk0 # Ubuntuデスクトップがマウントしているか確認
sudo umount /dev/mmcblk0p* # マウントしていたらアンマウントする
# OSイメージの配布ファイル(`.img.xz`)をダウンロードしたフォルダーでコマンドを実行する
xz -dkT 6 ./2026-06-18-raspios-trixie-arm64-lite.img.xz
sudo dd if=./2026-06-18-raspios-trixie-arm64-lite.img of=/dev/mmcblk0 bs=16M

ラズパイ#1にディスプレイとキーボードを接続し、32GB microSDカードを挿入して電源を入れ、キーボードレイアウトを選択して、新規作成するユーザー名とパスワードを入力します。当記事では、例としてpiという名前を使っています。(古のRaspberry Pi OSのデフォルトユーザー名)

ユーザー名・パスワードでログイン後、raspi-configラズパイ#1の設定を行います。

ラズパイ#1
sudo raspi-config
  • System Options
    • Wireless LAN
      • 日本国内で使用するので、JPを選択
      • SSIDとパスワードを入力
    • Hostname: pi4main
  • Update: raspi-configを最新版に更新する
  • Interface Options
    • SSH: 有効にする
  • Localisation Options
    • Locale: ja-JP.UTF-8を追加 (日本語表示できない場合を考慮してen_GB.UTF-8は残す)
    • Timezone: 日本国内で使用するので、Asia/Tokyoを選択
  • Advanced Options
    • Bootloader Version
      • Latest

Bootloaderのバージョンは、sudo vcgencmd bootloader_versionで確認できます。

raspi-configを終了してreboot後、システム全体をアップグレードしておきます。

ラズパイ#1
sudo apt update
sudo apt full-upgrade
sudo apt autoremove # アップグレード後に不要になったパッケージが生じた場合に実行

サーバーとして使うために固定IPアドレスを設定します。

以前のRaspberry Pi OSはdhcpcdでネットワークを設定していましたが、Bookworm以降はNetworkManagerを使うようになっています。GUIの無い環境のため、TUI版ツールnmtuiで設定します。

ラズパイ#1
# 日本語表示できない場合`en_GB.UTF-8`に変更する。再ログインが必要。
sudo update-locale LANG=en_GB.UTF-8
sudo nmtui
# nmtuiが終わったら、ロケールを戻す。
sudo update-locale LANG=ja_JP.UTF-8
  • 有線LAN側:
    • IPv4 CONFIGURATION: Manual
      • Addresses: 10.0.0.1/22
    • IPv6 CONFIGURATION: Disabled
  • Wi-Fi側:
    • IPv4 CONFIGURATION: Manual
      • Addresses: x.y.z.90/24
      • Gateway: ルーターのIPアドレス
      • DNS servers: DNSサーバのIPアドレス (1.1.1.1など)
    • IPv6 CONFIGURATION: Disabled

K8s用システムで使用しないIPv6は無効にします。

ラズパイ#1
sudo nano /boot/firmware/cmdline.txt

1行目の先頭にipv6.disable=1 を追加します。

/boot/firmware/cmdline.txt
ipv6.disable=1 console=serial0,115200 …

reboot後、ip aコマンドなどでIPアドレスを確認します。

Raspberry Piをサーバとして使うため、Wi-Fiの電力管理をオフにして、Wi-Fiの省電力による接続不安定を回避します。

ラズパイ#1
sudo apt install wireless-tools
sudo nano /etc/rc.local # trixieではこのファイルが無い模様
/etc/rc.local
#!/bin/bash
/usr/sbin/iwconfig wlan0 power off
exit 0
ラズパイ#1
sudo chmod 755 /etc/rc.local
sudo systemctl start rc-local

iwconfig wlan0コマンドを実行して、Power Management:offになっていることを確認します。

Raspberry Piハードウェアの設定を変更する

Section titled “Raspberry Piハードウェアの設定を変更する”

/boot/firmware/config.txtにハードウェアの設定を必要に応じて追加します。(任意)

ラズパイ#1
sudo nano /boot/firmware/config.txt
/boot/firmware/config.txt(一部抜粋)
# https://github.com/raspberrypi/firmware/blob/master/boot/overlays/README
# Bluetoothを無効にする場合
dtoverlay=disable-bt
# ディスプレイなしで運用する場合
hdmi_force_hotplug=1 # hdmi_groupやhdmi_modeはコメントアウト
# PoE HATのファンの回転数制御を変更する場合
dtoverlay=rpi-poe
dtparam=poe_fan_temp0=70000,poe_fan_temp0_hyst=1000
dtparam=poe_fan_temp1=75000,poe_fan_temp1_hyst=5000
dtparam=poe_fan_temp2=80000,poe_fan_temp2_hyst=5000
dtparam=poe_fan_temp3=82000,poe_fan_temp3_hyst=2000

SSHをパスワード入力なしで接続できるようにする

Section titled “SSHをパスワード入力なしで接続できるようにする”

PCのターミナルでSSHの公開鍵を作成してラズパイ#1に転送します。

PC
ssh-keygen -t rsa -b 4096 # id_rsa.pubを作成する
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@x.y.z.90

これでPCからssh pi@x.y.z.90で接続するときにパスワード入力が不要になります。

次に、K8sを動かすために必要なソフトをインストールします。

まずはK8sのネットワーク設定に必要な(あるいは必要だった?)ソフトをインストールします。

ラズパイ#1
sudo apt update
sudo apt install iptables arptables ebtables
sudo nano /boot/firmware/cmdline.txt

containerdを動かすために必要なパラメータ(cgroup_で始まる)を追加します。

/boot/firmware/cmdline.txt
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 ipv6.disable=1 console=serial0,115200 …

nerdctlのReleasesページからnerdctl(full)をダウンロードして、/usr/local配下に展開します。 以下はv2.3.5の例です。

ラズパイ#1
mkdir -p ~/temp
cd ~/temp
wget https://github.com/containerd/nerdctl/releases/download/v2.3.5/nerdctl-full-2.3.5-linux-arm64.tar.gz
sudo tar Cxzvf /usr/local nerdctl-full-2.3.5-linux-arm64.tar.gz # Cでdir指定できる

K8s docsのContainer Runtimesの説明に則って、cgroup driverssystemdを使うように設定します。

また、K8s用システム後述のプライベートレジストリからhttp接続でコンテナイメージをpullできるように設定します。

ラズパイ#1
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
sudo nano /etc/containerd/config.toml

/etc/containerd/config.tomlの デフォルト設定値を以下のように変更します。

/etc/containerd/config.toml
...
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc]
...
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options]
SystemdCgroup = true
...
[plugins.'io.containerd.cri.v1.images'.registry]
config_path = '/etc/containerd/certs.d'

レジストリを設定します。こちらの説明に則って、プライベートレジストリにhttp接続できるようにします。

ラズパイ#1
sudo mkdir -p /etc/containerd/certs.d/docker.io
sudo mkdir -p /etc/containerd/certs.d/10.0.0.1:5000

Dockerのレジストリの設定:

ラズパイ#1
sudo nano /etc/containerd/certs.d/docker.io/hosts.toml
/etc/containerd/certs.d/docker.io/hosts.toml
server = "https://registry-1.docker.io"
[host."https://registry-1.docker.io"]

プライベートレジストリの設定:

ラズパイ#1
sudo nano /etc/containerd/certs.d/10.0.0.1:5000/hosts.toml
/etc/containerd/certs.d/10.0.0.1:5000/hosts.toml
server = "http://10.0.0.1:5000"
[host."http://10.0.0.1:5000"]
capabilities = ["pull", "resolve", "push"]
skip_verify = true

containerdを起動します。

ラズパイ#1
sudo systemctl enable --now containerd
sudo nerdctl run hello-world # 動作確認

nerdctl buildを使えるようにBuildKitをセットアップします。

ラズパイ#1
sudo mkdir -p /etc/buildkit
sudo nano /etc/buildkit/buildkitd.toml

/etc/buildkit/buildkitd.tomlを作成します。

/etc/buildkit/buildkitd.toml
[worker.oci]
enabled = false
[worker.containerd]
enabled = true
# namespace should be "k8s.io" for Kubernetes (including Rancher Desktop)
namespace = "default"

buildkitを起動します。

ラズパイ#1
sudo systemctl enable --now buildkit

kubeadm, kubelet, kubectlを使うための準備を行う

Section titled “kubeadm, kubelet, kubectlを使うための準備を行う”

IPv4 Forwardの設定とiptablesにブリッジされたトラフィックを見えるようにします。

ラズパイ#1
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter

sysctlパラメータを設定します。

ラズパイ#1
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

swapがいるとkubeletは適切に動作しないため、swapを停止します。

ラズパイ#1
sudo systemctl list-units --type=swap # SWAPがいるかどうか確認
sudo mkdir -p /etc/rpi/swap.conf.d/
sudo tee /etc/rpi/swap.conf.d/90-disable-swap.conf > /dev/null <<EOF
[Main]
Mechanism=none
EOF

rebootして、設定値を確認します。

ラズパイ#1
lsmod | grep br_netfilter
lsmod | grep overlay
sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
sudo systemctl list-units --type=swap

kubeadm, kubelet, kubectlをインストールする

Section titled “kubeadm, kubelet, kubectlをインストールする”

kubeadm, kubelet, kubectlをインストールします。

  • kubeadm: クラスターを起動するコマンドです。
  • kubelet: クラスター内のすべてのマシンで実行されるコンポーネントです。 Podやコンテナの起動などを行います。
  • kubectl: クラスターにアクセスするためのコマンドラインツールです。

なお、当記事を書いている間にURLがv1.36v1.37に変わっています。最新のURLはこちらから確認できます。

ラズパイ#1
sudo apt update
sudo apt install apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.37/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.37/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt update # K8s用のリポジトリを追加
sudo apt install kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
kubectl version # Kubernetesのバージョンを確認

NFSクライアントをインストールする

Section titled “NFSクライアントをインストールする”

Persistent Volume用にNFSを使うので、NFSクライアントをインストールしておきます。

ラズパイ#1
sudo apt update
sudo apt install nfs-common

ラズパイ#1ラズパイ#4のホスト名を/etc/hostsに追加します。

ラズパイ#1
sudo nano /etc/hosts
/etc/hosts
10.0.0.1 pi4main
10.0.1.1 pi4node1
10.0.1.2 pi4node2
10.0.1.3 pi4node3

ここで1台目をシャットダウンして電源を落とします。

ラズパイ#1
sudo shutdown -h now

SSHが使えるようになっているので、不要になるキーボードとディスプレイも取り外します。

1台目としてセットアップした32GB microSDカードの内容を吸い出して、ラズパイ#2ラズパイ#4用の64GB microSDカードに移し、それぞれのIPアドレスを設定します。また、ラズパイ#1用の250GB USB SSDにも移します。

1台目のディスクイメージを吸い出して縮小する

Section titled “1台目のディスクイメージを吸い出して縮小する”

ラズパイ#1から32GB microSDカードを取り出して、PCでディスクイメージをファイル(.img)に吸い出します。

PC
sudo dmesg # /dev/mmcblk0 であることを確認する
cd ~/temp
sudo dd if=/dev/mmcblk0 of=raspios-trixie-k8s.img bs=16M # 処理時間はmicroSDカードの容量に比例

吸い出したイメージファイルのサイズは大きい(microSDカードの容量)ので、PiShrinkresizeimage.plを使ってファイルサイズを縮小します。

PC
# resizeimage.plを使う場合の例
sudo perl /path/to/resizeimage.pl $HOME/temp/raspios-trixie-k8s.img
sudo chown $(id -u):$(id -g) raspios-trixie-k8s.img

2台目〜4台目のmicroSDカードに書き込む

Section titled “2台目〜4台目のmicroSDカードに書き込む”

出来上がった1台目のイメージファイルをラズパイ#2ラズパイ#4用の64GB microSDカードに書き込みます。

PC
# 64GB microSDカードを都度入れ替えて以下のコマンドを実行する
sudo dd if=raspios-trixie-k8s.img of=/dev/mmcblk0 bs=16M

ラズパイ#1用の250GB USB SSDにも1台目のイメージファイルを書き込みます。

PC
sudo dmesg # USB SSDのデバイス名を調べる: /dev/sdc
sudo dd if=raspios-trixie-k8s.img of=/dev/sdc bs=16M

1台ずつ起動してホスト名とIPアドレスを変更する

Section titled “1台ずつ起動してホスト名とIPアドレスを変更する”

今の状態では、microSDカードのホスト名とIPアドレスが全て1台目のまま(pi4main10.0.0.1/22 x.y.z.90/24)になっているので、ラズパイ#2から順に1台ずつ64GB microSDカードで起動して、SSH経由でIPアドレスを変更します。

PC
ssh pi@x.y.z.90

まずraspi-configを実行して、ホスト名を変更します。さらに、microSDカードに書き込んだディスクイメージのファイルシステム領域を拡張しておきます。

ラズパイ#2 (#3, #4も同様)
sudo raspi-config
  • System Options
    • Hostname: pi4node1 (#3はpi4node2, #4はpi4node3)
  • Advanced Options
    • Expand Filesystem
    • Bootloader Version
      • Latest

そして、nmtuiでIPアドレスの設定を変更します。有線LAN側のGatewayにはラズパイ#1のIPアドレス(10.0.0.1)を設定しておきます。

ラズパイ#2 (#3, #4も同様)
sudo nmtui
  • 有線LAN側:
    • IPv4 CONFIGURATION: Manual
      • Addresses: 10.0.1.1/22 (#3は10.0.1.2/22, #4は10.0.1.3/22)
      • Gateway: 10.0.0.1
      • DNS servers: DNSサーバのIPアドレス (1.1.1.1など)
  • Wi-Fi側:
    • IPv4 CONFIGURATION: Manual
      • Addresses: x.y.z.91/24 (#3はx.y.z.92/24, #4はx.y.z.93/24)

rebootして新しいIPアドレスでSSH接続し、ip aコマンドでIPアドレスの設定を確認します。

PC
ssh pi@x.y.z.91 # #3は`ssh pi@x.y.z.92`, #4は`ssh pi@x.y.z.93`

ラズパイ#2ラズパイ#4のIPアドレスを変更して重複が無くなったら、ラズパイ#1をUSB SSDで起動して、USB SSDに書き込んだディスクイメージのファイルシステム領域を拡張します。

ラズパイ#1
sudo raspi-config
  • Advanced Options
    • Expand Filesystem

ネットワークの整理(オプション)

Section titled “ネットワークの整理(オプション)”

K8sのノードとして動かすラズパイ#2ラズパイ#4は、PCからSSH接続で使う機会がほぼ無いので、Wi-Fiを停止してラズパイ#1からSSH接続で使うようにします。ただし、ラズパイ#2ラズパイ#4がDebianパッケージやコンテナイメージなどをパブリックリポジトリからダウンロードできるようにする必要があるため、ラズパイ#1でルーティングできるようにします。

flowchart TB
  pow[ACアダプタ]
  hub[PoEハブ 1Gbps<br>10.0.0.0/22]
  pi1[ラズパイ#1<br>'pi4main']
  pi2[ラズパイ#2<br>'pi4node1']
  pi3[ラズパイ#3<br>'pi4node2']
  pi4[ラズパイ#4<br>'pi4node3']
  wlan[Wi-Fiルーター<br>x.y.z.0/24]
  internet([インターネット])
  pc[PC<br>Ubuntu 24.04 LTS]

  pow --- hub

  subgraph K8s用システム
  hub -- 10.0.0.1 --- pi1
  hub -- 10.0.1.1 --- pi2
  hub -- 10.0.1.2 --- pi3
  hub -- 10.0.1.3 --- pi4
  end

  pc -- DHCP --- wlan
  pi1 -- x.y.z.90 --- wlan

  wlan --- internet

trixieではnftablesを使ってルーティングを設定することができます。PCからラズパイ#1にSSHでログインして、/etc/nftables.confを編集します。

PC
ssh pi@x.y.z.90
ラズパイ#1
sudo nano /etc/nftables.conf
/etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter;
}
chain forward {
type filter hook forward priority filter; policy accept;
iifname "wlan0" oifname "eth0" ct state established,related accept
iifname "eth0" oifname "wlan0" accept
}
chain output {
type filter hook output priority filter;
}
}
table inet nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname "wlan0" masquerade
}
}

nftablesを起動します。

ラズパイ#1
sudo systemctl enable --now nftables
sudo systemctl status nftables # エラーが出ていないか確認する

reboot後、設定が正しく反映されていることを確認します。

ラズパイ#1
sudo nft list ruleset

ラズパイ#1からパスワード入力なしでSSH接続できるようにする

Section titled “ラズパイ#1からパスワード入力なしでSSH接続できるようにする”

ラズパイ#1からラズパイ#2ラズパイ#4にパスワード入力なしでSSH接続できるようにします。

ラズパイ#1
ssh-keygen -t rsa -b 4096
ssh-copy-id -i ~/.ssh/id_rsa.pub pi4node1
ssh-copy-id -i ~/.ssh/id_rsa.pub pi4node2
ssh-copy-id -i ~/.ssh/id_rsa.pub pi4node3

ラズパイ#2〜#4のWi-Fiを停止する

Section titled “ラズパイ#2〜#4のWi-Fiを停止する”

ラズパイ#2ラズパイ#4/boot/firmware/config.txtを変更してWi-Fiを停止します。

ラズパイ#1
ssh pi4node1 # pi4node2, pi4node3も同様に行う
ラズパイ#2 (#3, #4も同様)
sudo nano /boot/firmware/config.txt

以下の行を末尾に追加します。

dtoverlay=disable-wifi

reboot後は、PCからラズパイ#2ラズパイ#4に直接SSH接続できなくなります。

K8s用システムにおけるラズパイ#1ラズパイ#4の役割を以下のようにします。

機体ホスト名K8sでの役割
ラズパイ#1pi4mainコントロールプレーン
ラズパイ#2pi4node1ノード (Worker)
ラズパイ#3pi4node2ノード (Worker)
ラズパイ#4pi4node3ノード (Worker)

ラズパイ#1には、さらに以下の役割を持たせます。

  • NFSサーバ: Persistent Volumeに用いる
  • プライベートレジストリ: 自作アプリのコンテナイメージの展開に用いる

以降の設定ではラズパイ#1にSSHでログインして操作を行います。

K8s用パッケージマネージャのHelmをインストールします。

  • インストールスクリプトを使う場合:
ラズパイ#1
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash
  • aptを使う場合:
ラズパイ#1
export HELM_BUILDKITE_APT_KEY_ID="DDF78C3E6EBB2D2CC223C95C62BA89D07698DBC6"
sudo apt install curl gpg apt-transport-https
curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey > "${TMPDIR:-/tmp}/helm.gpg"
sh <<END
if [ "$(gpg --show-keys --with-colons "${TMPDIR:-/tmp}/helm.gpg" | awk -F: '$1 == "fpr" {print $10}' | head -n 1)" != "${HELM_BUILDKITE_APT_KEY_ID}" ]; then echo "ERROR: Unexpected Helm APT key ID: potential key compromise"; exit 1; fi
cat "${TMPDIR:-/tmp}/helm.gpg" | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
END
# ERRORが発生しなければ、aptでhelmをインストールする
sudo apt update
sudo apt install helm

/shareフォルダーを作成して、NFSサーバで共有します。

ラズパイ#1
sudo mkdir /share
sudo apt install nfs-kernel-server
sudo nano /etc/exports
/etc/exports
/share 10.0.0.0/22(rw,no_root_squash,no_subtree_check)
ラズパイ#1
sudo systemctl restart nfs-server

ラズパイ#1からラズパイ#2ラズパイ#4にSSHでログインして、NFSサーバのボリュームをマウントできることを確認します。

ラズパイ#2〜#4
# NFSの動作確認
sudo mount -t nfs 10.0.0.1:/share /mnt
sudo umount /mnt

プライベートレジストリを起動する

Section titled “プライベートレジストリを起動する”

registryコンテナイメージを起動します。

ラズパイ#1
sudo nerdctl container run -d -p 5000:5000 --restart always --name registry registry
# 動作確認: レジストリ内を確認する
curl http://pi4main:5000/v2/_catalog

コントロールプレーンを構築する

Section titled “コントロールプレーンを構築する”

kubeadm initコマンドを実行して、ラズパイ#1をK8sのコントロールプレーンとしてセットアップします。flannelをデフォルト設定で使用するため、--pod-network-cidr=10.244.0.0/16を指定します。

ラズパイ#1
# kubeletがエラーになっていないことを確認する
sudo systemctl status kubelet # エラーの場合、swap無効化などの設定が抜けている可能性あり
sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=10.0.0.1

kubeadm initが成功すると、以下のようなメッセージが表示されます。(*は伏せ字)

最後の2行はラズパイ#2ラズパイ#4で実行するコマンドです。メモに控えておきます。

ラズパイ#1
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 10.0.0.1:6443 --token ******.**************** \
--discovery-token-ca-cert-hash sha256:*********************************

以下のコマンドを実行すると、ラズパイ#1のログインユーザー(pi)がkubectlコマンドを使ってK8sを制御できるようになります。

ラズパイ#1
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Podネットワークアドオンのflannelをインストールします。

ラズパイ#1
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
kubectl get pods --all-namespaces # flannelのPodが作成できていることを確認

kubeadm init時に表示された最後の2行のコマンド(kubeadm join)をSSH経由で実行して、ラズパイ#2ラズパイ#4をノードとして追加します。

ラズパイ#1
ssh pi4node1 sudo kubeadm join 10.0.0.1:6443 --token ...(以下略)
ssh pi4node2 sudo kubeadm join 10.0.0.1:6443 --token ...(以下略)
ssh pi4node3 sudo kubeadm join 10.0.0.1:6443 --token ...(以下略)

ノードが追加されていることを確認します。

ラズパイ#1
kubectl get nodes
# 成功していれば、以下のような感じでノードのリストが表示される
# NAME STATUS ROLES AGE VERSION
# pi4main Ready control-plane 25m v1.37.0
# pi4node1 Ready <none> 90s v1.37.0
# pi4node2 Ready <none> 77s v1.37.0
# pi4node3 Ready <none> 48s v1.37.0

ラズパイ#2ラズパイ#4のROLESが<none>になっているので、workerを割り当てます。

ラズパイ#1
kubectl label node pi4node1 node-role.kubernetes.io/worker=
kubectl label node pi4node2 node-role.kubernetes.io/worker=
kubectl label node pi4node3 node-role.kubernetes.io/worker=
kubectl get nodes # ワーカーのROLESがworkerであることを確認

公式サイトでURLを確認して、MetalLBをインストールします。

ラズパイ#1
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.16.1/config/manifests/metallb-frr-k8s.yaml
# 動作確認(READYになるまで少し時間がかかる)
kubectl get deploy -n metallb-system
kubectl get ds -n metallb-system
kubectl get pod -n metallb-system

MetalLBが使用するIPアドレスの範囲を指定します。サブネットマスクで指定できますが、以下のように範囲指定することもできます。

type: LoadBalancerでデプロイしたサービスのEXTERNAL-IP<pending>になってしまったことがあるので、明示的にautoAssign: trueをセットするようにしています。

ラズパイ#1
nano ./ip-address-pool.yaml
ip-address-pool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 10.0.2.1-10.0.2.100
autoAssign: true
ラズパイ#1
kubectl apply -f ./ip-address-pool.yaml
kubectl describe -n metallb-system IPAddressPool first-pool # 設定値の確認

Persistent Volumeを使ったnginxサービスを展開する

Section titled “Persistent Volumeを使ったnginxサービスを展開する”

K8sでアプリケーションを展開できる準備が整いましたので、試しにnginxでWebサーバを動かしてみます。

nginxで公開するWebページはPersistent Volumeに格納して、独自のコンテンツを配信できるようにします。

PersistentVolume用フォルダーをNFSサーバの/shareフォルダーの中に作成して、その中にWebページのサンプルファイルを配置します。

ラズパイ#1
sudo mkdir -p /share/pv-www
echo '<h1>Hello my Kubernetes!</h1>' | sudo tee /share/pv-www/index.html

/share/pv-www用のPersistentVolumeを作成します。

ラズパイ#1
nano ./create_pv-www.yaml
create_pv-www.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-www
labels:
name: pv-www
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: slow
nfs:
server: 10.0.0.1
path: /share/pv-www
ラズパイ#1
kubectl apply -f create_pv-www.yaml
kubectl get pv
kubectl describe pv pv-www # PVができていることを確認

PersistentVolumeClaimでストレージを要求する

Section titled “PersistentVolumeClaimでストレージを要求する”

nginxサービスが利用するストレージを要求するためにPersistentVolumeClaimを作成します。

ラズパイ#1
nano create_pvc-www.yaml
create_pvc-www.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-www-claim
spec:
accessModes:
- ReadWriteMany
storageClassName: slow
resources:
requests:
storage: 5Gi
selector:
matchLabels:
name: pv-www
ラズパイ#1
kubectl apply -f create_pvc-www.yaml
kubectl get pvc
kubectl describe pvc my-www-claim # PVCができていることを確認

nginxサービスはPVC my-www-claim経由で/share/pv-wwwをマウントするように構成します。

ラズパイ#1
nano deploy_nginx.yaml
deploy_nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app.kubernetes.io/name: nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: nginx
template:
metadata:
labels:
app.kubernetes.io/name: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: html-volume
mountPath: "/usr/share/nginx/html"
volumes:
- name: html-volume
persistentVolumeClaim:
claimName: my-www-claim
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app.kubernetes.io/name: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
ラズパイ#1
kubectl apply -f deploy_nginx.yaml
# nginxのserviceが動いていることを確認し、EXTERNAL-IPを取得する
kubectl get service
# Webページを取得できることを確認
curl http://10.0.2.1/ # EXTERNAL-IPにアクセスする

管理用のWeb UIをセットアップする

Section titled “管理用のWeb UIをセットアップする”

以前はKubernetes Dashboardがありましたが、現在はメンテナンスが行われていません。代わりにHeadlampを使います。

Kubernetes Metrics Serverをインストールする

Section titled “Kubernetes Metrics Serverをインストールする”

Kubernetes Metrics Serverを入れると、CPUやメモリなどのリソースの使用状況が確認できるようになります。

マニフェストファイルをダウンロードして、必要な箇所を修正します。

ラズパイ#1
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
nano components.yaml

spec.template.spec.containers.argsの配下に--kubelet-insecure-tlsを追加して、httpで使えるようにします。 公式の文書にFor testing purposes onlyと書かれているように、K8s用システムはテスト用です。

spec:
template:
spec:
containers:
- args:
- --kubelet-insecure-tls # 追加
- --cert-dir=/tmp

マニフェストファイルを適用し、READYになるのを待ちます。kubectl top nodeコマンドやkubectl top podコマンドでCPUやメモリの使用量を確認できるようになります。

ラズパイ#1
kubectl apply -f components.yaml
kubectl get pods -n kube-system # READYになるのを待つ
kubectl top node
kubectl top pod

Helmを使ってHeadlampをインストールします。

ラズパイ#1
helm repo add headlamp https://kubernetes-sigs.github.io/headlamp/
helm repo update
helm upgrade --install my-headlamp headlamp/headlamp --namespace kube-system
kubectl get pods -n kube-system # `my-headlamp`が動作していることを確認

インストールに成功すると、以下のメッセージが表示されます。このメッセージのとおりに実行すればHeadlampのWeb UIを表示できるのですが、ラズパイ#1はGUIを持たないので、http://127.0.0.1:8080を閲覧することはできません。

ラズパイ#1
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace kube-system -l "app.kubernetes.io/name=headlamp,app.kubernetes.io/instance=my-headlamp" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace kube-system $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace kube-system port-forward $POD_NAME 8080:$CONTAINER_PORT
2. Get the token using
kubectl create token my-headlamp --namespace kube-system

そこで、PCから閲覧できるように、ポートフォワード時にホストのアドレスとしてx.y.z.90または0.0.0.0を指定します。

ラズパイ#1
# メッセージからコピペ
export POD_NAME=$(kubectl get pods --namespace kube-system -l "app.kubernetes.io/name=headlamp,app.kubernetes.io/instance=my-headlamp" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace kube-system $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
# 認証トークンを取得する
kubectl create token my-headlamp -n kube-system > token.txt
# --addressでホストのアドレスを指定する
kubectl -n kube-system port-forward $POD_NAME 8080:$CONTAINER_PORT --address x.y.z.90

PCからhttp://x.y.z.90:8080をWebブラウザで開くと認証トークンを入力する画面が表示されるので、token.txtの内容をコピペして認証ボタンを押下します。