コンテンツにスキップ

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

Go言語のアプリ開発練習用の実行環境としてKubernetes(以降はK8s)を使いたくなりましたので、Raspberry Piを4台使って、2026年夏の時点で最新の学習用K8sの環境構築を行ったときのメモです。

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

当記事の目標は以下のとおりです。(順不同)

  • 最新のRaspberry Pi OS trixieをインストールして、必要最低限の設定を行う
  • K8sのコントロールプレーンとノードを立ち上げる
  • K8sから利用できる永続ボリュームを作成する
  • 自作アプリのコンテナイメージを展開するプライベートレジストリを用意する
  • ロードバランサー (MetalLB)を動かす
  • 管理用のWeb UI (Dashboard Headlamp)を動かす
  • Ingress API Gateway API経由でHTTPベースのサービスを使えるようにする

Go言語でアプリを作成して学習用K8sに展開する記事は後日作成します。

以下の手順では、ディスクイメージを書き込むためにddコマンドを使用しています。他にもシステムの設定を変更するコマンドを多数使用しています。これらのコマンド操作を誤ると、正常に動作していた既存システムを破壊する恐れがあります。(私は何度かコマンドの操作ミスをやってしまいました。)

これらのコマンドを使用することで生じた(使用の喪失、データの喪失、利益の喪失、業務の中断も含め、またそれに限定されない)直接損害、間接損害、偶発的な損害、特別損害、懲罰的損害、または結果損害について、一切責任を負わないものとし、一切の賠償などを行いません。自己責任で行うことを予めご承知願います。

Raspberry Pi 4を4台使ってハードウェア環境を構築しています。積層式のケースはAmazonで購入したのですが、今探すと同等品はほとんど見つからないようです。

写真は下図の学習用K8sの枠で囲んだ部分になります。各Raspberry PiはPoE HATを搭載しており、電源は有線LANポートから得ているので、セットアップ完了後の学習用K8sから出ている配線はPoEハブのACアダプタだけになり、非常にすっきりしています。

flowchart TB
  hub[PoEハブ 1Gbps<br>10.0.0.0/22]
  pi1[Raspberry Pi<br>'pi4main']
  pi2[Raspberry Pi<br>'pi4node1']
  pi3[Raspberry Pi<br>'pi4node2']
  pi4[Raspberry Pi<br>'pi4node3']
  wlan[Wi-Fiルーター]
  internet([インターネット])
  pc[PC<br>Ubuntu 24.04 LTS]

  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

  wlan --- internet
  • PoEハブ: スイッチングハブ(1GbE・PoE対応)
    • 10.0.0.0/22のプライベートネットワークを構築してラズパイ同士を接続する。
    • PoE HAT経由で給電可能。
    • 5ポート中4ポートはクラスターで使用。残り1ポートはPCを有線LANで接続する場合の予備。
  • pi4main: Raspberry Pi 4 Model B 4GB + PoE HAT + 250GB USB SSD
    • K8sのコントロールプレーンとして使用する。
    • 永続ボリュームやプライベートレジストリを提供するため、microSDカードより高速・大容量なUSB SSDを使う。
    • Wi-Fi接続して、インターネット接続やPCからの遠隔操作を可能にする。最終的には、pi4node1〜pi4node3からのインターネット接続をルーティングする。Avahiにより、PCからはホスト名.localという名前で接続できる。
  • pi4node1〜pi4node3: Raspberry Pi 4 Model B 4GB + PoE HAT + 64GB microSD
    • K8sのノードとして使用する。
    • クラスターの通信はPoEハブ側のネットワークで行うので、Wi-Fi側の通信は無効にしておく。
    • ただし、パブリックリポジトリからDebianパッケージやコンテナイメージをダウンロードできるように、pi4mainを経由してインターネット接続できるようにする。

大量のデータやリクエストを処理するときは、PoEハブ側にPCを接続することも想定しています。

microSDカードの容量は、当記事のセットアップが完了した段階では16GBで十分足りそうですが、色んなコンテナイメージを展開することを想定して、64GB microSDカードを使っています。

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の最新版(Debian 13 trixie)をダウンロードして、32GB microSDカードに書き込んでいきます。

PC
sudo dmesg # microSDカードが挿入されたときのデバイス名を調べる: mmcblk0
mount|grep mmcblk0 # マウントしているか確認する
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

pi4mainにディスプレイとキーボードを接続し、32GB microSDカードを挿入して電源を入れ、キーボードレイアウトを選択して、新規作成するユーザー名とパスワードを入力します。当記事では、例としてpiという名前を使っていますが、古のRaspberry Pi OSのデフォルトユーザー名なので、セキュリティを考慮してpiと異なる名前がお奨めです。

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

pi4main
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を選択

記事を書いてから気付きましたが、後からノードを増設するのであれば、ホスト名はpi4mainと異なるホスト名が良いです(pi4baseなど)。増設ノードのmicroSDカードにイメージファイルを書き込んで起動したときに、稼働中のpi4mainと重複しなくなります。

いったんraspi-configを終了してsudo apt full-upgradeでシステム全体をアップグレードします。 その後、再びraspi-configを起動してブートローダーを更新します。

pi4main
sudo apt update
sudo apt full-upgrade
sudo apt autoremove # 不要なパッケージが現れた場合、実行する
sudo raspi-config
  • Advanced Options
    • Bootloader Version
      • Latest

raspi-configを終了してrebootします。 Bootloaderのバージョンは、sudo vcgencmd bootloader_versionで確認できます。

Raspberry Pi OS trixieでは既にインストールされていますが、念のため、mDNSサービスのavahi-daemonが動作していることを確認します。

pi4main
sudo systemctl status avahi-daemon

avahi-daemonがインストールされていないときは、sudo apt install avahi-daemonコマンドを実行してインストールします。

avahi-daemonが動作していれば、pi4main.localというホスト名でPCからSSH接続できます。

pi4main
ssh pi@pi4main.local

SSH接続できることを確認したら、電源を落としてディスプレイとキーボードを外します。

以降はPCからSSHでログインして操作します。パスワード入力無しでログインできるように、PCのターミナルでSSHの公開鍵を作成してpi4mainに転送します。

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

ネットワーク周りを設定する(共通)

Section titled “ネットワーク周りを設定する(共通)”

学習用K8sではIPv6を使用しないので、無効にします。

pi4main
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の省電力による接続不安定を回避します。

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

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

永続ボリュームにはNFSを使うので、NFSクライアントをインストールしておきます。

pi4main
sudo apt update
sudo apt install nfs-common

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

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

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

pi4main
sudo nano /boot/firmware/config.txt
/boot/firmware/config.txt(一部抜粋)
# Bluetoothを無効にする場合
dtoverlay=disable-bt
# ディスプレイなしで運用する場合
hdmi_force_hotplug=1 # hdmi_groupやhdmi_modeはコメントアウトする
# PoE HATのファンの回転数制御を変更する場合
# 参考: https://forums.raspberrypi.com/viewtopic.php?t=230603
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

pi4main〜pi4node3同士が有線LAN側(10.0.0.0/22)のネットワークを使って通信するように、これらのホスト名を/etc/hostsに追加します。有線LAN側の固定IPアドレスは後ほど設定します。

pi4main
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

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

K8sのネットワーク制御に関係するソフトをインストールします。

pi4main
sudo apt update
sudo apt install iptables arptables ebtables

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

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

いったんrebootして、上記のパラメータを反映します。

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

pi4main
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

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

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

pi4main
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接続できるようにします。/etc/hostsはPodから見えないので、IPアドレスでレジストリにアクセスするようにしています。(CoreDNSのhostsプラグインを使えばできそうですが)

pi4main
sudo mkdir -p /etc/containerd/certs.d/10.0.0.1:5000
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を起動します。

pi4main
sudo systemctl enable --now containerd
sudo nerdctl run hello-world # 動作確認

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

pi4main
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を起動します。

pi4main
sudo systemctl enable --now buildkit
sudo systemctl status buildkit # 動作確認

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

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

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

pi4main
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter

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

pi4main
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system

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

pi4main
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して、設定値を確認します。

pi4main
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.36がv1.37に変わりました。最新バージョンはこちらから確認できます。

pi4main
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のバージョンを確認

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

pi4main
sudo shutdown -h now

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

1台目としてセットアップした32GB microSDカードの内容を吸い出して、pi4node1〜pi4node3用の64GB microSDカードに移し、それぞれのホスト名を設定します。また、pi4main用の250GB USB SSDにも移します。

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

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

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

PC
sudo dmesg # /dev/mmcblk0 であることを確認する
mount|grep mmcblk0 # マウントしているか確認する
sudo umount /dev/mmcblk0p* # マウントしていたらアンマウントする
sudo dd if=/dev/mmcblk0 of=raspios-trixie-k8s.img bs=16M # 処理時間はmicroSDカードの容量に比例

吸い出したイメージファイルのサイズは大きい(microSDカードの容量)ので、他のmicroSDカードやUSB SSDに書き込む時間を短縮するために、PiShrinkやresizeimage.plを使ってファイルサイズを縮小します。

PC
# resizeimage.plを使う場合の例: imgファイルはフルパスで指定する
sudo perl /path/to/resizeimage.pl /full/path/to/raspios-trixie-k8s.img
sudo chown $(id -u):$(id -g) raspios-trixie-k8s.img

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

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

出来上がった1台目のイメージファイルをpi4node1〜pi4node3用の64GB microSDカードに書き込みます。

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

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

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

1台ずつ起動してホスト名を変更する

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

今の状態では、microSDカードのホスト名が全て1台目のまま(pi4main)になっているので、pi4node1から順に1台ずつ64GB microSDカードで起動して、SSH経由でIPアドレスを変更します。

PC
ssh pi@pi4main.local

まずraspi-configを実行して、ホスト名を変更します。raspi-configのHostnameの設定は、/etc/hostnameだけでなく/etc/hostsの127.0.1.1に設定されている自ホスト名も変更します。

さらに、microSDカードに書き込んだディスクイメージのファイルシステム領域を拡張しておきます。

pi4node1・pi4node2・pi4node3
sudo raspi-config
  • System Options
    • Hostname: pi4node1 (以下同様に pi4node2, pi4node3)
  • Advanced Options
    • Expand Filesystem
    • Bootloader Version
      • Latest

rebootすると、pi4node1〜pi4node3のホスト名を使ってWi-Fi経由でSSH接続できます。

PC
# pi4node1にSSH接続する場合
ssh pi@pi4node1.local
# pi4node2にSSH接続する場合
ssh pi@pi4node2.local
# pi4node3にSSH接続する場合
ssh pi@pi4node3.local

pi4node1〜pi4node3のホスト名を変更して重複が無くなったら、pi4mainをUSB SSDで起動して、USB SSDに書き込んだディスクイメージのファイルシステム領域を拡張します。

pi4main
sudo raspi-config
  • Advanced Options
    • Expand Filesystem

K8sのノードとして動かすpi4node1〜pi4node3は、Wi-Fiを停止してpi4mainからSSH接続で使うようにします。ただし、pi4node1〜pi4node3がDebianパッケージやコンテナイメージなどをパブリックリポジトリからダウンロードできるようにする必要があるため、pi4mainでルーティングできるようにします。

PoEハブ側の固定IPアドレスを設定する

Section titled “PoEハブ側の固定IPアドレスを設定する”

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

pi4main
sudo nmtui
  • Ethernet
    • 有線接続 1 (eth0):
      • IPv4 設定: 手作業 (Manual)
        • アドレス: 10.0.0.1/22

pi4node1〜pi4node3では、有線LAN側のGatewayにはpi4mainのIPアドレス(10.0.0.1)を設定しておきます。

pi4node1・pi4node2・pi4node3
sudo nmtui
  • Ethernet
    • 有線接続 1 (eth0):
      • IPv4 設定: 手作業 (Manual)
        • アドレス: 10.0.1.1/22 (pi4node2は10.0.1.2/22, pi4node3は10.0.1.3/22)
        • ゲートウェイ: 10.0.0.1
        • DNSサーバー: DNSサーバーのIPアドレス (8.8.8.8など)

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

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

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

pi4mainからpi4node1〜pi4node3にパスワード入力なしでSSH接続できるようにします。

pi4main
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

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

pi4main
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;
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を起動します。

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

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

pi4main
sudo nft list ruleset

Wi-FiのPromiscuous modeをオンにする

Section titled “Wi-FiのPromiscuous modeをオンにする”

後ほどMetalLBでWi-Fi側ネットワークのIPアドレスを割り当てるように設定しますが、これらのIPアドレスをpi4mainのWi-Fi側(wlan0)から使えるようにするため、ネットワークインターフェイスのPromiscuous modeをオンにして全てのパケットを受信できるようにします。

pi4main
sudo nano /etc/NetworkManager/dispatcher.d/99-wlan0-promisc
/etc/NetworkManager/dispatcher.d/99-wlan0-promisc
#!/bin/bash
#!/usr/bin/bash -e
case "$2" in
up)
if [ "$1" = "wlan0" ]; then
ip link set dev $1 promisc on
fi
;;
esac
pi4main
sudo chmod 755 /etc/NetworkManager/dispatcher.d/99-wlan0-promisc

rebootして、sudo ip -d link show wlan0コマンドの実行結果がpromiscuity 1になっていることを確認します。

pi4node1〜pi4node3のWi-Fiを停止する

Section titled “pi4node1〜pi4node3のWi-Fiを停止する”

pi4node1〜pi4node3の/boot/firmware/config.txtを変更してWi-Fiを停止します。

pi4main
ssh pi4node1 # pi4node2, pi4node3も同様に行う
pi4node1・pi4node2・pi4node3
sudo nano /boot/firmware/config.txt

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

/boot/firmware/config.txt
dtoverlay=disable-wifi

reboot後は、PCからpi4node1〜pi4node3に直接SSH接続できなくなります。 PCからpi4mainにSSH接続した後、pi4mainからpi4node1〜pi4node3にSSH接続します。

pi4main
# pi4node1にSSH接続する場合
ssh pi4node1
# pi4node2にSSH接続する場合
ssh pi4node2
# pi4node3にSSH接続する場合
ssh pi4node3

学習用K8sにおけるpi4main〜pi4node3の役割を以下のようにします。

ホスト名K8sでの役割
pi4mainコントロールプレーン
pi4node1ノード (Worker)
pi4node2ノード (Worker)
pi4node3ノード (Worker)

pi4mainには、さらに以下の役割を持たせます。

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

以降の設定ではpi4mainにSSHでログインして操作を行います。

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

  • インストールスクリプトを使う場合:
pi4main
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash
  • aptを使う場合:
pi4main
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サーバで共有します。

pi4main
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)
pi4main
sudo systemctl restart nfs-server

pi4mainからpi4node1〜pi4node3にSSHでログインして、NFSサーバのボリュームをマウントできることを確認します。

pi4main
ssh pi4node1 # pi4node1にログインする場合
pi4node1・pi4node2・pi4node3
# NFSの動作確認
sudo mount -t nfs pi4main:/share /mnt
sudo umount /mnt

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

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

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

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

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

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

kubeadm initコマンドを実行して、pi4mainをK8sのコントロールプレーンとしてセットアップします。Podネットワークアドオン(CNIプラグイン)にはflannelを使用するため、--pod-network-cidr=10.244.0.0/16を指定します。

pi4main
sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=10.0.0.1

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

最後の2行はpi4node1〜pi4node3で実行するコマンドです。メモに控えておきます。

pi4main
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:*********************************

メッセージにある以下のコマンドを実行すると、pi4mainのログインユーザー(pi)がkubectlコマンドを使ってK8sを制御できるようになります。

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

flannelをインストールします。

pi4main
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
kubectl get pods -n kube-flannel # flannelのPodが作成できていることを確認

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

pi4main
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 ...(以下略)

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

pi4main
kubectl get nodes

成功していれば、以下のような感じでノードのリストが表示されます。

pi4main
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

pi4node1〜pi4node3のROLESが<none>になっているので、workerを割り当てます。

pi4main
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をインストールします。FRR-K8s modeが推奨になっていますが、学習用K8sにBGPルーターはありませんので、Native modeをインストールします。

なお、MetalLBが割り当てたIPアドレスにPCからアクセスできるように、後ほどL2Advertisementを作成しますが、コントロールプレーンやにnode.kubernetes.io/exclude-from-external-load-balancersラベルが付いているとアドバタイズしないとのことなので、パラメータに--ignore-exclude-lbを追加します。

pi4main
wget https://raw.githubusercontent.com/metallb/metallb/v0.16.1/config/manifests/metallb-native.yaml
nano metallb-native.yaml
metallb-native.yaml
# 一部だけ抜粋
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: speaker
namespace: metallb-system
spec:
template:
metadata:
spec:
containers:
- args:
- --ignore-exclude-lb # 追加
- --port=9120
pi4main
kubectl apply -f metallb-native.yaml
# 動作確認(READYになるまで少し時間がかかる)
kubectl get deploy -n metallb-system
kubectl get ds -n metallb-system
kubectl get pods -n metallb-system

MetalLBが使用するIPアドレスの範囲を指定します。サブネットマスクでも指定できますが、以下のように範囲指定することもできます。PCからアクセスできるように、Wi-Fi側のネットワークで未使用のIPアドレスを指定し、さらにL2Advertisementも作成します。nodeSelectorsでpi4mainのWi-Fiインターフェイス(wlan0)を指定しています。

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

pi4main
nano metallb-addresses.yaml
metallb-addresses.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: wlan0-pool
namespace: metallb-system
spec:
addresses:
- 192.168.123.240-192.168.123.249
autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-wlan0
namespace: metallb-system
spec:
ipAddressPools:
- wlan0-pool
nodeSelectors:
- matchLabels:
kubernetes.io/hostname: pi4main
interfaces:
- wlan0
pi4main
kubectl apply -f metallb-addresses.yaml
kubectl describe -n metallb-system IPAddressPool wlan0-pool # 設定値の確認
kubectl describe -n metallb-system L2Advertisement l2-wlan0 # 設定値の確認

永続ボリュームを使ったnginxサービスの立ち上げ

Section titled “永続ボリュームを使ったnginxサービスの立ち上げ”

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

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

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

pi4main
sudo mkdir -p /share/pv-www
echo '<h1>Hello my Kubernetes!</h1>' | sudo tee /share/pv-www/index.html

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

pi4main
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: pi4main
path: /share/pv-www
pi4main
kubectl apply -f create_pv-www.yaml
kubectl get pv # PVができていることを確認
kubectl describe pv pv-www # PVの詳細を確認

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

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

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

pi4main
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
pi4main
kubectl apply -f create_pvc-www.yaml
kubectl get pvc # PVCができていることを確認
kubectl describe pvc my-www-claim # PVCの詳細を確認

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

pi4main
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: www-volume
mountPath: "/usr/share/nginx/html"
volumes:
- name: www-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
pi4main
kubectl apply -f deploy_nginx.yaml
# nginxのserviceが動いていることを確認し、EXTERNAL-IPを取得する
kubectl get svc nginx-service
# nginxのPodが動作していることを確認
kubectl get pods
kubectl describe pod # エラーなどの詳細確認

curl http://<EXTERNAL-IPに表示されたIPアドレス>/コマンドを実行したり、Webブラウザでhttp://<EXTERNAL-IPに表示されたIPアドレス>/を開いたりして、<h1>Hello my Kubernetes!</h1>が表示されることを確認します。

K8s用のダッシュボードUIとしてKubernetes Dashboardがありましたが、現在はメンテナンスが終了しているので、代わりにHeadlampを学習用K8sの管理Web UIとして使います。

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

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

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

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

pi4main
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ではこれで運用します。

components.yaml
# 一部だけ抜粋
spec:
template:
spec:
containers:
- args:
- --kubelet-insecure-tls # 追加
- --cert-dir=/tmp

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

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

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

pi4main
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を表示できるのですが、pi4mainはGUIを持たないので、http://127.0.0.1:8080をブラウザで閲覧することはできません。

pi4main
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

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

pi4main
# メッセージからコピペ
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
# --addressでホストのアドレスを指定する
kubectl -n kube-system port-forward $POD_NAME 8080:$CONTAINER_PORT --address 0.0.0.0

PCからhttp://pi4main.local:8080をWebブラウザで開くと認証トークンを入力する画面が表示されます。kubectl create token ...で取得した認証トークンを入力すると、HeadlampのUIが表示されます。

以前使っていたIngress NGINXはリタイアしたとのことですので、Ingress APIの代わりにGateway APIを使うように設定します。

ここまでの手順で、HTTP接続で利用できるサービスとしてnginxとHeadlampを立ち上げました。これらをGateway API経由で使えるようにします。

flowchart LR
  pc[PC<br>Ubuntu 24.04 LTS]
  gw[Gateway]
  route_nginx[HTTPRoute<br>nginx_route]
  route_headlamp[HTTPRoute<br>headlamp_route]
  nginx[サービス<br>nginx]
  headlamp[サービス<br>Headlamp]

  pc --> gw

  subgraph 学習用K8s
  gw --> route_nginx --> nginx
  gw --> route_headlamp --> headlamp
  end

Gateway Controllerの実装は色々ありますが、ここではNGINX Gateway Fabricを使います。

NGINX Gateway Fabricをインストールする

Section titled “NGINX Gateway Fabricをインストールする”

公式のインストール手順に則って、まずStandard channelからGateway APIリソースをインストールします。

pi4main
# インストールにgitが必要
sudo apt update
sudo apt install git
kubectl kustomize "https://github.com/nginx/nginx-gateway-fabric/config/crd/gateway-api/standard?ref=v2.7.0" | kubectl apply -f -

続いて、Helmを使ってNGINX Gateway Fabricをインストールします。

学習用K8sではIPv6を無効にしているため、公式のコマンドラインに--set nginx.config.ipFamily=ipv4を追加しています。

また、GatewayのPodがpi4main以外のノードに作られても、PCからのトラフィックをpi4mainから他ノードに転送できるように、nginx.service.externalTrafficPolicy=Clusterを設定しています。

pi4main
helm install ngf oci://ghcr.io/nginx/charts/nginx-gateway-fabric \
--create-namespace -n nginx-gateway \
--set nginx.config.ipFamily=ipv4 \
--set nginx.service.externalTrafficPolicy=Cluster
# 利用可能になるまで待つ
kubectl wait --timeout=5m -n nginx-gateway \
deployment/ngf-nginx-gateway-fabric --for=condition=Available
# GatewayClassが出来ていることを確認する
kubectl get gatewayclass

なお、pi4node1〜pi4node3のWi-Fiが有効でpi4mainを通さずにPCから各ノードに直接接続できるネットワーク構成の場合、MetalLBのL2AdvertisementからnodeSelectorsを削除すると、PCはGatewayのPodが動作しているノードにWi-Fi経由で直接接続できるようになり、nginx.service.externalTrafficPolicyはLocalのままで動作します。

kubectl get gatewayclassのNAME列に表示された名前(nginx)を使って、Gatewayを作成します。

pi4main
nano gateway.yaml
gateway.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: first-gateway
spec:
gatewayClassName: nginx
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
pi4main
kubectl apply -f gateway.yaml
kubectl get gateway

Gatewayの作成に成功すると、以下のようにGatewayのIPアドレスが割り当てられます。

pi4main
NAME CLASS ADDRESS PROGRAMMED AGE
first-gateway nginx 192.168.123.241 True 6s

もし、PROGRAMMEDがFalseになっている場合、Gatewayでエラーが発生している可能性があります。以下のコマンドで動作状況を確認します。

pi4main
# Messageを確認する
kubectl describe gateway first-gateway
# first-gatewayのPod名を取得する
kubectl get pods
# Podのログを確認する
kubectl logs first-gateway-nginx-*******

deploy_nginx.yamlで作成したnginx-serviceはtype: LoadBalancerになっているので、これをClusterIPに変更します。

pi4main
nano deploy_nginx.yaml
deploy_nginx.yaml
# 一部を抜粋
type: ClusterIP
pi4main
kubectl apply -f deploy_nginx.yaml
kubectl get svc nginx-service # ClusterIPに変わっていることを確認

nginx用HTTPRouteを作成します。hostnamesはPCからGateway経由でnginx-serviceに接続するホスト名です。

pi4main
nano httproute_nginx.yaml
httproute_nginx.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: nginx-route
spec:
parentRefs:
- name: first-gateway
hostnames:
- nginx.learning-k8s # Gateway(↑の場合192.168.123.241)のホスト名
rules:
- backendRefs:
- name: nginx-service
port: 80
pi4main
kubectl apply -f httproute_nginx.yaml
kubectl get httproutes # `nginx-route`が出来ていることを確認

同様に、Headlamp用HTTPRouteを作成します。

pi4main
nano httproute_headlamp.yaml

Headlampのnamespaceはkube-system、Gatewayのnamespaceはdefault(無指定)のため、明示的にnamespace:を指定します。

httproute_nginx.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: headlamp-route
namespace: kube-system # サービスと同じ
spec:
parentRefs:
- name: first-gateway
namespace: default # gatewayと同じ
hostnames:
- headlamp.learning-k8s # Gateway(↑の場合192.168.123.241)のホスト名
rules:
- backendRefs:
- name: my-headlamp
port: 80
pi4main
kubectl apply -f httproute_headlamp.yaml
kubectl get httproutes -n kube-system # `headlamp-route`が出来ていることを確認
# namespaceなどで設定ミスがあると、以下のコマンドの結果は`Status: False`となっている
kubectl describe httproutes headlamp-route -n kube-system

PCの/etc/hostsにホスト名を追加する

Section titled “PCの/etc/hostsにホスト名を追加する”

作成したHTTPRouteの一覧は、以下のコマンドで表示できます。

pi4main
kubectl get httproutes --all-namespaces
# 上記コマンドの結果例:
# NAMESPACE NAME HOSTNAMES AGE
# default nginx-route ["nginx.learning-k8s"] 17m
# kube-system headlamp-route ["headlamp.learning-k8s"] 9m24s

これらのサービスにアクセスするPCの/etc/hostsに、first-gatewayのIPアドレスとこれらのホスト名を追加します。

pi4main
sudo nano /etc/hosts
/etc/hosts
192.168.123.241 nginx.learning-k8s
192.168.123.241 headlamp.learning-k8s

PCからnginxやHeadlampにアクセスしてみる

Section titled “PCからnginxやHeadlampにアクセスしてみる”

PCでWebブラウザを開き、以下のURLにアクセスします。

  • http://nginx.learning-k8s
  • http://headlamp.learning-k8s

このように画面が表示されれば、PCからGateway経由でサービスにアクセスできています。

4台のRaspberry Piに最新のOSと最新のK8sソフトウェア群をインストールして、ロードバランサーやGateway APIが動くようになり、nginxや管理用Web UIが使えるようになりました。

これでようやく実行環境が整いましたので、次は、本来の目的であるGo言語のアプリ開発(の練習)に取り組みます。