ラズパイで自宅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 (
DashboardHeadlamp)を動かす Ingress APIGateway API経由でHTTPベースのサービスを使えるようにする
Go言語でアプリを作成して学習用K8sに展開する記事は後日作成します。
以下の手順では、ディスクイメージを書き込むためにddコマンドを使用しています。他にもシステムの設定を変更するコマンドを多数使用しています。これらのコマンド操作を誤ると、正常に動作していた既存システムを破壊する恐れがあります。(私は何度かコマンドの操作ミスをやってしまいました。)
これらのコマンドを使用することで生じた(使用の喪失、データの喪失、利益の喪失、業務の中断も含め、またそれに限定されない)直接損害、間接損害、偶発的な損害、特別損害、懲罰的損害、または結果損害について、一切責任を負わないものとし、一切の賠償などを行いません。自己責任で行うことを予めご承知願います。
学習用K8sの全体像
Section titled “学習用K8sの全体像”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カードを使っています。
1台目のセットアップ
Section titled “1台目のセットアップ”Raspberry Piは4台ありますが、途中までのセットアップ内容は共通なので、容量の小さいmicroSDカード(ここでは32GB製品を使用)を使って1台目のセットアップを行い、それを吸い出してイメージファイルを作成し、USB SSDや64GB microSDカードに移していきます。
Raspberry Pi OSのセットアップ
Section titled “Raspberry Pi OSのセットアップ”まずは、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カードに書き込んでいきます。
sudo dmesg # microSDカードが挿入されたときのデバイス名を調べる: mmcblk0mount|grep mmcblk0 # マウントしているか確認するsudo umount /dev/mmcblk0p* # マウントしていたらアンマウントする
# OSイメージの配布ファイル(`.img.xz`)をダウンロードしたフォルダーでコマンドを実行するxz -dkT 6 ./2026-06-18-raspios-trixie-arm64-lite.img.xzsudo dd if=./2026-06-18-raspios-trixie-arm64-lite.img of=/dev/mmcblk0 bs=16M1台目を起動して初期設定する
Section titled “1台目を起動して初期設定する”pi4mainにディスプレイとキーボードを接続し、32GB microSDカードを挿入して電源を入れ、キーボードレイアウトを選択して、新規作成するユーザー名とパスワードを入力します。当記事では、例としてpiという名前を使っていますが、古のRaspberry Pi OSのデフォルトユーザー名なので、セキュリティを考慮してpiと異なる名前がお奨めです。
ユーザー名・パスワードでログイン後、raspi-configでpi4mainの設定を行います。
sudo raspi-config- System Options
- Wireless LAN
- 日本国内で使用するので、
JPを選択 - SSIDとパスワードを入力
- 日本国内で使用するので、
- Hostname:
pi4main
- Wireless LAN
- Update: raspi-configを最新版に更新する
- Interface Options
- SSH: 有効にする
- Localisation Options
- Locale:
ja-JP.UTF-8を追加 (日本語表示できない場合を考慮してen_GB.UTF-8は残す) - Timezone: 日本国内で使用するので、
Asia/Tokyoを選択
- Locale:
記事を書いてから気付きましたが、後からノードを増設するのであれば、ホスト名はpi4mainと異なるホスト名が良いです(pi4baseなど)。増設ノードのmicroSDカードにイメージファイルを書き込んで起動したときに、稼働中のpi4mainと重複しなくなります。
いったんraspi-configを終了してsudo apt full-upgradeでシステム全体をアップグレードします。
その後、再びraspi-configを起動してブートローダーを更新します。
sudo apt updatesudo apt full-upgradesudo apt autoremove # 不要なパッケージが現れた場合、実行するsudo raspi-config- Advanced Options
- Bootloader Version
- Latest
- Bootloader Version
raspi-configを終了してrebootします。
Bootloaderのバージョンは、sudo vcgencmd bootloader_versionで確認できます。
Raspberry Pi OS trixieでは既にインストールされていますが、念のため、mDNSサービスのavahi-daemonが動作していることを確認します。
sudo systemctl status avahi-daemonavahi-daemonがインストールされていないときは、sudo apt install avahi-daemonコマンドを実行してインストールします。
avahi-daemonが動作していれば、pi4main.localというホスト名でPCからSSH接続できます。
ssh pi@pi4main.localSSH接続できることを確認したら、電源を落としてディスプレイとキーボードを外します。
以降はPCからSSHでログインして操作します。パスワード入力無しでログインできるように、PCのターミナルでSSHの公開鍵を作成してpi4mainに転送します。
ssh-keygen -t rsa -b 4096 # id_rsa.pubを作成するssh-copy-id -i ~/.ssh/id_rsa.pub pi@pi4main.localネットワーク周りを設定する(共通)
Section titled “ネットワーク周りを設定する(共通)”学習用K8sではIPv6を使用しないので、無効にします。
sudo nano /boot/firmware/cmdline.txt1行目の先頭にipv6.disable=1 を追加します。
ipv6.disable=1 console=serial0,115200 …reboot後、ip aコマンドなどでIPアドレスを確認します。
Raspberry Piをサーバとして使うため、Wi-Fiの電力管理をオフにして、Wi-Fiの省電力による接続不安定を回避します。
sudo apt install wireless-toolssudo nano /etc/rc.local # trixieではこのファイルが無い模様#!/bin/bash/usr/sbin/iwconfig wlan0 power offexit 0sudo chmod 755 /etc/rc.localsudo systemctl start rc-localiwconfig wlan0コマンドを実行して、Power Management:offになっていることを確認します。
永続ボリュームにはNFSを使うので、NFSクライアントをインストールしておきます。
sudo apt updatesudo apt install nfs-commonRaspberry Piハードウェアの設定を変更する
Section titled “Raspberry Piハードウェアの設定を変更する”/boot/firmware/config.txtにハードウェアの設定を必要に応じて追加します。(任意)
sudo nano /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=230603dtoverlay=rpi-poedtparam=poe_fan_temp0=70000,poe_fan_temp0_hyst=1000dtparam=poe_fan_temp1=75000,poe_fan_temp1_hyst=5000dtparam=poe_fan_temp2=80000,poe_fan_temp2_hyst=5000dtparam=poe_fan_temp3=82000,poe_fan_temp3_hyst=2000/etc/hostsを編集する
Section titled “/etc/hostsを編集する”pi4main〜pi4node3同士が有線LAN側(10.0.0.0/22)のネットワークを使って通信するように、これらのホスト名を/etc/hostsに追加します。有線LAN側の固定IPアドレスは後ほど設定します。
sudo nano /etc/hosts10.0.0.1 pi4main10.0.1.1 pi4node110.0.1.2 pi4node210.0.1.3 pi4node3K8s関連のセットアップ
Section titled “K8s関連のセットアップ”次に、K8sを動かすために必要なソフトをインストールします。
- nerdctl
- containerdやruncが同梱されているFullをインストールする
- kubeadm, kubelet, kubectl
K8sのネットワーク制御に関係するソフトをインストールします。
sudo apt updatesudo apt install iptables arptables ebtablescontainerdを動かすために必要なパラメータ(cgroup_で始まる)を追加します。
sudo nano /boot/firmware/cmdline.txtcgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1 ipv6.disable=1 console=serial0,115200 …いったんrebootして、上記のパラメータを反映します。
nerdctl(full)をインストールする
Section titled “nerdctl(full)をインストールする”nerdctlのReleasesページからnerdctl(full)をダウンロードして、/usr/local配下に展開します。
以下はv2.3.5の例です。
wget https://github.com/containerd/nerdctl/releases/download/v2.3.5/nerdctl-full-2.3.5-linux-arm64.tar.gzsudo tar Cxzvf /usr/local nerdctl-full-2.3.5-linux-arm64.tar.gzcontainerdの環境設定を行う
Section titled “containerdの環境設定を行う”K8s docsのContainer Runtimesの説明に則って、cgroup driversはsystemdを使うように設定します。
また、学習用K8sが後述のプライベートレジストリからhttp接続でコンテナイメージをpullできるように設定します。
sudo mkdir -p /etc/containerdcontainerd config default | sudo tee /etc/containerd/config.toml > /dev/nullsudo nano /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プラグインを使えばできそうですが)
sudo mkdir -p /etc/containerd/certs.d/10.0.0.1:5000sudo nano /etc/containerd/certs.d/10.0.0.1:5000/hosts.tomlserver = "http://10.0.0.1:5000"[host."http://10.0.0.1:5000"] capabilities = ["pull", "resolve", "push"] skip_verify = truecontainerdを起動します。
sudo systemctl enable --now containerdsudo nerdctl run hello-world # 動作確認Buildkitをセットアップする
Section titled “Buildkitをセットアップする”nerdctl buildを使えるようにBuildKitをセットアップします。
sudo mkdir -p /etc/buildkitsudo nano /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を起動します。
sudo systemctl enable --now buildkitsudo systemctl status buildkit # 動作確認kubeadm, kubelet, kubectlを使うための準備を行う
Section titled “kubeadm, kubelet, kubectlを使うための準備を行う”IPv4 Forwardの設定とiptablesにブリッジされたトラフィックを見えるようにします。
cat <<EOF | sudo tee /etc/modules-load.d/k8s.confoverlaybr_netfilterEOF
sudo modprobe overlaysudo modprobe br_netfiltersysctlパラメータを設定します。
cat <<EOF | sudo tee /etc/sysctl.d/k8s.confnet.bridge.bridge-nf-call-iptables = 1net.ipv4.ip_forward = 1EOF
sudo sysctl --systemswapがいるとkubeletは適切に動作しないため、swapを停止します。
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=noneEOFrebootして、設定値を確認します。
lsmod | grep br_netfilterlsmod | grep overlaysysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forwardsudo systemctl list-units --type=swapkubeadm, kubelet, kubectlをインストールする
Section titled “kubeadm, kubelet, kubectlをインストールする”kubeadm, kubelet, kubectlをインストールします。
- kubeadm: クラスターを起動するコマンドです。
- kubelet: クラスター内のすべてのマシンで実行されるコンポーネントです。 Podやコンテナの起動などを行います。
- kubectl: クラスターにアクセスするためのコマンドラインツールです。
なお、当記事を書いている間にURLがv1.36がv1.37に変わりました。最新バージョンはこちらから確認できます。
sudo apt updatesudo apt install apt-transport-https ca-certificates curl gpgcurl -fsSL https://pkgs.k8s.io/core:/stable:/v1.37/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpgecho '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 kubectlsudo apt-mark hold kubelet kubeadm kubectlkubectl version # Kubernetesのバージョンを確認ここで1台目をシャットダウンして電源を落とします。
sudo shutdown -h nowSSHが使えるようになったので、キーボードとディスプレイを取り外します。
2台目〜4台目のセットアップ
Section titled “2台目〜4台目のセットアップ”1台目としてセットアップした32GB microSDカードの内容を吸い出して、pi4node1〜pi4node3用の64GB microSDカードに移し、それぞれのホスト名を設定します。また、pi4main用の250GB USB SSDにも移します。
1台目のディスクイメージを吸い出して縮小する
Section titled “1台目のディスクイメージを吸い出して縮小する”pi4mainから32GB microSDカードを取り出して、PCでディスクイメージをファイル(.img)に吸い出します。
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を使ってファイルサイズを縮小します。
# resizeimage.plを使う場合の例: imgファイルはフルパスで指定するsudo perl /path/to/resizeimage.pl /full/path/to/raspios-trixie-k8s.imgsudo chown $(id -u):$(id -g) raspios-trixie-k8s.img2台目〜4台目のmicroSDカードに書き込む
Section titled “2台目〜4台目のmicroSDカードに書き込む”出来上がった1台目のイメージファイルをpi4node1〜pi4node3用の64GB microSDカードに書き込みます。
# 64GB microSDカードを都度入れ替えて、以下のコマンドを実行するsudo dd if=raspios-trixie-k8s.img of=/dev/mmcblk0 bs=16M1台目のUSB SSDにも書き込む
Section titled “1台目のUSB SSDにも書き込む”pi4main用の250GB USB SSDにも1台目のイメージファイルを書き込みます。
sudo dmesg # USB SSDのデバイス名を調べる: /dev/sdcsudo dd if=raspios-trixie-k8s.img of=/dev/sdc bs=16M1台ずつ起動してホスト名を変更する
Section titled “1台ずつ起動してホスト名を変更する”今の状態では、microSDカードのホスト名が全て1台目のまま(pi4main)になっているので、pi4node1から順に1台ずつ64GB microSDカードで起動して、SSH経由でIPアドレスを変更します。
ssh pi@pi4main.localまずraspi-configを実行して、ホスト名を変更します。raspi-configのHostnameの設定は、/etc/hostnameだけでなく/etc/hostsの127.0.1.1に設定されている自ホスト名も変更します。
さらに、microSDカードに書き込んだディスクイメージのファイルシステム領域を拡張しておきます。
sudo raspi-config- System Options
- Hostname:
pi4node1(以下同様にpi4node2,pi4node3)
- Hostname:
- Advanced Options
- Expand Filesystem
- Bootloader Version
- Latest
rebootすると、pi4node1〜pi4node3のホスト名を使ってWi-Fi経由でSSH接続できます。
# pi4node1にSSH接続する場合ssh pi@pi4node1.local
# pi4node2にSSH接続する場合ssh pi@pi4node2.local
# pi4node3にSSH接続する場合ssh pi@pi4node3.localpi4node1〜pi4node3のホスト名を変更して重複が無くなったら、pi4mainをUSB SSDで起動して、USB SSDに書き込んだディスクイメージのファイルシステム領域を拡張します。
sudo raspi-config- Advanced Options
- Expand Filesystem
ネットワークの整理
Section titled “ネットワークの整理”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で設定します。
sudo nmtui- Ethernet
- 有線接続 1 (
eth0):- IPv4 設定:
手作業(Manual)- アドレス:
10.0.0.1/22
- アドレス:
- IPv4 設定:
- 有線接続 1 (
pi4node1〜pi4node3では、有線LAN側のGatewayにはpi4mainのIPアドレス(10.0.0.1)を設定しておきます。
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など)
- アドレス:
- IPv4 設定:
- 有線接続 1 (
rebootして、ip aコマンドでIPアドレスの設定を確認します。
pi4mainからパスワード入力なしでSSH接続できるようにする
Section titled “pi4mainからパスワード入力なしでSSH接続できるようにする”pi4mainからpi4node1〜pi4node3にパスワード入力なしでSSH接続できるようにします。
ssh-keygen -t rsa -b 4096ssh-copy-id -i ~/.ssh/id_rsa.pub pi4node1ssh-copy-id -i ~/.ssh/id_rsa.pub pi4node2ssh-copy-id -i ~/.ssh/id_rsa.pub pi4node3ルーティングを設定する
Section titled “ルーティングを設定する”trixieではnftablesを使ってルーティングを設定することができます。PCからpi4mainにSSHでログインして、/etc/nftables.confを編集します。
sudo nano /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を起動します。
sudo systemctl enable --now nftablessudo systemctl status nftables # エラーが出ていないか確認するreboot後、設定が正しく反映されていることを確認します。
sudo nft list rulesetWi-FiのPromiscuous modeをオンにする
Section titled “Wi-FiのPromiscuous modeをオンにする”後ほどMetalLBでWi-Fi側ネットワークのIPアドレスを割り当てるように設定しますが、これらのIPアドレスをpi4mainのWi-Fi側(wlan0)から使えるようにするため、ネットワークインターフェイスのPromiscuous modeをオンにして全てのパケットを受信できるようにします。
sudo nano /etc/NetworkManager/dispatcher.d/99-wlan0-promisc#!/bin/bash#!/usr/bin/bash -ecase "$2" in up) if [ "$1" = "wlan0" ]; then ip link set dev $1 promisc on fi ;;esacsudo chmod 755 /etc/NetworkManager/dispatcher.d/99-wlan0-promiscrebootして、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を停止します。
ssh pi4node1 # pi4node2, pi4node3も同様に行うsudo nano /boot/firmware/config.txt以下の行を末尾に追加します。
dtoverlay=disable-wifireboot後は、PCからpi4node1〜pi4node3に直接SSH接続できなくなります。
PCからpi4mainにSSH接続した後、pi4mainからpi4node1〜pi4node3にSSH接続します。
# pi4node1にSSH接続する場合ssh pi4node1
# pi4node2にSSH接続する場合ssh pi4node2
# pi4node3にSSH接続する場合ssh pi4node3K8sクラスターの構築
Section titled “K8sクラスターの構築”学習用K8sにおけるpi4main〜pi4node3の役割を以下のようにします。
| ホスト名 | K8sでの役割 |
|---|---|
pi4main | コントロールプレーン |
pi4node1 | ノード (Worker) |
pi4node2 | ノード (Worker) |
pi4node3 | ノード (Worker) |
pi4mainには、さらに以下の役割を持たせます。
- NFSサーバ: 永続ボリュームに用いる
- プライベートレジストリ: 自作アプリのコンテナイメージの展開に用いる
以降の設定ではpi4mainにSSHでログインして操作を行います。
Helmをインストールする
Section titled “Helmをインストールする”K8s用パッケージマネージャのHelmをインストールします。
- インストールスクリプトを使う場合:
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash- aptを使う場合:
export HELM_BUILDKITE_APT_KEY_ID="DDF78C3E6EBB2D2CC223C95C62BA89D07698DBC6"sudo apt install curl gpg apt-transport-httpscurl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey > "${TMPDIR:-/tmp}/helm.gpg"sh <<ENDif [ "$(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; ficat "${TMPDIR:-/tmp}/helm.gpg" | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/nullecho "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.listEND
# ERRORが発生しなければ、aptでhelmをインストールするsudo apt updatesudo apt install helmNFSサーバを起動する
Section titled “NFSサーバを起動する”/shareフォルダーを作成して、NFSサーバで共有します。
sudo mkdir /sharesudo apt install nfs-kernel-serversudo nano /etc/exports/share 10.0.0.0/22(rw,no_root_squash,no_subtree_check)sudo systemctl restart nfs-serverpi4mainからpi4node1〜pi4node3にSSHでログインして、NFSサーバのボリュームをマウントできることを確認します。
ssh pi4node1 # pi4node1にログインする場合# NFSの動作確認sudo mount -t nfs pi4main:/share /mntsudo umount /mntプライベートレジストリを起動する
Section titled “プライベートレジストリを起動する”registryコンテナイメージを起動します。
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を指定します。
sudo kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=10.0.0.1kubeadm initが成功すると、以下のようなメッセージが表示されます。(*は伏せ字)
最後の2行はpi4node1〜pi4node3で実行するコマンドです。メモに控えておきます。
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を制御できるようになります。
mkdir -p $HOME/.kubesudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/configsudo chown $(id -u):$(id -g) $HOME/.kube/configflannelをインストールします。
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.ymlkubectl get pods -n kube-flannel # flannelのPodが作成できていることを確認ノードを追加する
Section titled “ノードを追加する”kubeadm init時に表示された最後の2行のコマンド(kubeadm join)をSSH経由で実行して、pi4node1〜pi4node3をノードとして追加します。
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 ...(以下略)ノードが追加されていることを確認します。
kubectl get nodes成功していれば、以下のような感じでノードのリストが表示されます。
NAME STATUS ROLES AGE VERSIONpi4main Ready control-plane 25m v1.37.0pi4node1 Ready <none> 90s v1.37.0pi4node2 Ready <none> 77s v1.37.0pi4node3 Ready <none> 48s v1.37.0pi4node1〜pi4node3のROLESが<none>になっているので、workerを割り当てます。
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であることを確認MetalLBをインストールする
Section titled “MetalLBをインストールする”公式サイトでURLを確認して、MetalLBをインストールします。FRR-K8s modeが推奨になっていますが、学習用K8sにBGPルーターはありませんので、Native modeをインストールします。
なお、MetalLBが割り当てたIPアドレスにPCからアクセスできるように、後ほどL2Advertisementを作成しますが、コントロールプレーンやにnode.kubernetes.io/exclude-from-external-load-balancersラベルが付いているとアドバタイズしないとのことなので、パラメータに--ignore-exclude-lbを追加します。
wget https://raw.githubusercontent.com/metallb/metallb/v0.16.1/config/manifests/metallb-native.yamlnano metallb-native.yaml# 一部だけ抜粋apiVersion: apps/v1kind: DaemonSetmetadata: name: speaker namespace: metallb-systemspec: template: metadata: spec: containers: - args: - --ignore-exclude-lb # 追加 - --port=9120kubectl apply -f metallb-native.yaml
# 動作確認(READYになるまで少し時間がかかる)kubectl get deploy -n metallb-systemkubectl get ds -n metallb-systemkubectl get pods -n metallb-systemMetalLBが使用するIPアドレスの範囲を指定します。サブネットマスクでも指定できますが、以下のように範囲指定することもできます。PCからアクセスできるように、Wi-Fi側のネットワークで未使用のIPアドレスを指定し、さらにL2Advertisementも作成します。nodeSelectorsでpi4mainのWi-Fiインターフェイス(wlan0)を指定しています。
type: LoadBalancerでデプロイしたサービスのEXTERNAL-IPが<pending>になってしまったことがあるので、明示的にautoAssign: trueをセットするようにしています。
nano metallb-addresses.yamlapiVersion: metallb.io/v1beta1kind: IPAddressPoolmetadata: name: wlan0-pool namespace: metallb-systemspec: addresses: - 192.168.123.240-192.168.123.249 autoAssign: true---apiVersion: metallb.io/v1beta1kind: L2Advertisementmetadata: name: l2-wlan0 namespace: metallb-systemspec: ipAddressPools: - wlan0-pool nodeSelectors: - matchLabels: kubernetes.io/hostname: pi4main interfaces: - wlan0kubectl apply -f metallb-addresses.yamlkubectl 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を作成する
Section titled “PersistentVolumeを作成する”PersistentVolume用フォルダーをNFSサーバの/shareフォルダーの中に作成して、その中にWebページのサンプルファイルを配置します。
sudo mkdir -p /share/pv-wwwecho '<h1>Hello my Kubernetes!</h1>' | sudo tee /share/pv-www/index.html/share/pv-www用のPersistentVolumeを作成します。
nano ./create_pv-www.yamlapiVersion: v1kind: PersistentVolumemetadata: name: pv-www labels: name: pv-wwwspec: capacity: storage: 5Gi volumeMode: Filesystem accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain storageClassName: slow nfs: server: pi4main path: /share/pv-wwwkubectl apply -f create_pv-www.yamlkubectl get pv # PVができていることを確認kubectl describe pv pv-www # PVの詳細を確認PersistentVolumeClaimでストレージを要求する
Section titled “PersistentVolumeClaimでストレージを要求する”nginxサービスが利用するストレージを要求するためにPersistentVolumeClaimを作成します。
nano create_pvc-www.yamlapiVersion: v1kind: PersistentVolumeClaimmetadata: name: my-www-claimspec: accessModes: - ReadWriteMany storageClassName: slow resources: requests: storage: 5Gi selector: matchLabels: name: pv-wwwkubectl apply -f create_pvc-www.yamlkubectl get pvc # PVCができていることを確認kubectl describe pvc my-www-claim # PVCの詳細を確認nginxサービスを起動する
Section titled “nginxサービスを起動する”nginxサービスはPVC my-www-claim経由で/share/pv-wwwをマウントするように構成します。
nano deploy_nginx.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployment labels: app.kubernetes.io/name: nginxspec: 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: v1kind: Servicemetadata: name: nginx-servicespec: selector: app.kubernetes.io/name: nginx ports: - protocol: TCP port: 80 targetPort: 80 type: LoadBalancerkubectl apply -f deploy_nginx.yaml
# nginxのserviceが動いていることを確認し、EXTERNAL-IPを取得するkubectl get svc nginx-service
# nginxのPodが動作していることを確認kubectl get podskubectl describe pod # エラーなどの詳細確認curl http://<EXTERNAL-IPに表示されたIPアドレス>/コマンドを実行したり、Webブラウザでhttp://<EXTERNAL-IPに表示されたIPアドレス>/を開いたりして、<h1>Hello my Kubernetes!</h1>が表示されることを確認します。
管理用のWeb UIのセットアップ
Section titled “管理用のWeb UIのセットアップ”K8s用のダッシュボードUIとしてKubernetes Dashboardがありましたが、現在はメンテナンスが終了しているので、代わりにHeadlampを学習用K8sの管理Web UIとして使います。
Kubernetes Metrics Serverをインストールする
Section titled “Kubernetes Metrics Serverをインストールする”Kubernetes Metrics Serverを入れると、CPUやメモリなどのリソースの使用状況が確認できるようになります。
マニフェストファイルをダウンロードして、必要な箇所を修正します。
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yamlnano components.yamlspec.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やメモリの使用量を確認できるようになります。
kubectl apply -f components.yamlkubectl get pods -n kube-system # READYになるのを待つ
kubectl top nodekubectl top podHeadlampをインストールする
Section titled “Headlampをインストールする”Helmを使ってHeadlampをインストールします。
helm repo add headlamp https://kubernetes-sigs.github.io/headlamp/helm repo updatehelm upgrade --install my-headlamp headlamp/headlamp --namespace kube-systemkubectl get pods -n kube-system # `my-headlamp`が動作していることを確認インストールに成功すると、以下のメッセージが表示されます。このメッセージのとおりに実行すればHeadlampのWeb UIを表示できるのですが、pi4mainはGUIを持たないので、http://127.0.0.1:8080をブラウザで閲覧することはできません。
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_PORT2. Get the token using kubectl create token my-headlamp --namespace kube-systemそこで、ポートフォワード時のホストアドレスとして0.0.0.0を指定して、PCから閲覧できるようにします。
# メッセージからコピペ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.0PCからhttp://pi4main.local:8080をWebブラウザで開くと認証トークンを入力する画面が表示されます。kubectl create token ...で取得した認証トークンを入力すると、HeadlampのUIが表示されます。
Gateway APIのセットアップ
Section titled “Gateway APIのセットアップ”以前使っていた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リソースをインストールします。
# インストールにgitが必要sudo apt updatesudo 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を設定しています。
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のままで動作します。
Gatewayを作成する
Section titled “Gatewayを作成する”kubectl get gatewayclassのNAME列に表示された名前(nginx)を使って、Gatewayを作成します。
nano gateway.yamlapiVersion: gateway.networking.k8s.io/v1kind: Gatewaymetadata: name: first-gatewayspec: gatewayClassName: nginx listeners: - name: http protocol: HTTP port: 80 allowedRoutes: namespaces: from: Allkubectl apply -f gateway.yamlkubectl get gatewayGatewayの作成に成功すると、以下のようにGatewayのIPアドレスが割り当てられます。
NAME CLASS ADDRESS PROGRAMMED AGEfirst-gateway nginx 192.168.123.241 True 6sもし、PROGRAMMEDがFalseになっている場合、Gatewayでエラーが発生している可能性があります。以下のコマンドで動作状況を確認します。
# Messageを確認するkubectl describe gateway first-gateway
# first-gatewayのPod名を取得するkubectl get pods
# Podのログを確認するkubectl logs first-gateway-nginx-*******nginx用HTTPRouteを作成する
Section titled “nginx用HTTPRouteを作成する”deploy_nginx.yamlで作成したnginx-serviceはtype: LoadBalancerになっているので、これをClusterIPに変更します。
nano deploy_nginx.yaml# 一部を抜粋 type: ClusterIPkubectl apply -f deploy_nginx.yamlkubectl get svc nginx-service # ClusterIPに変わっていることを確認nginx用HTTPRouteを作成します。hostnamesはPCからGateway経由でnginx-serviceに接続するホスト名です。
nano httproute_nginx.yamlapiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: nginx-routespec: parentRefs: - name: first-gateway hostnames: - nginx.learning-k8s # Gateway(↑の場合192.168.123.241)のホスト名 rules: - backendRefs: - name: nginx-service port: 80kubectl apply -f httproute_nginx.yamlkubectl get httproutes # `nginx-route`が出来ていることを確認Headlamp用HTTPRouteを作成する
Section titled “Headlamp用HTTPRouteを作成する”同様に、Headlamp用HTTPRouteを作成します。
nano httproute_headlamp.yamlHeadlampのnamespaceはkube-system、Gatewayのnamespaceはdefault(無指定)のため、明示的にnamespace:を指定します。
apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: 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: 80kubectl apply -f httproute_headlamp.yamlkubectl get httproutes -n kube-system # `headlamp-route`が出来ていることを確認
# namespaceなどで設定ミスがあると、以下のコマンドの結果は`Status: False`となっているkubectl describe httproutes headlamp-route -n kube-systemPCの/etc/hostsにホスト名を追加する
Section titled “PCの/etc/hostsにホスト名を追加する”作成したHTTPRouteの一覧は、以下のコマンドで表示できます。
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アドレスとこれらのホスト名を追加します。
sudo nano /etc/hosts192.168.123.241 nginx.learning-k8s192.168.123.241 headlamp.learning-k8sPCからnginxやHeadlampにアクセスしてみる
Section titled “PCからnginxやHeadlampにアクセスしてみる”PCでWebブラウザを開き、以下のURLにアクセスします。
http://nginx.learning-k8shttp://headlamp.learning-k8s
このように画面が表示されれば、PCからGateway経由でサービスにアクセスできています。
ひとまず、ここまで
Section titled “ひとまず、ここまで”4台のRaspberry Piに最新のOSと最新のK8sソフトウェア群をインストールして、ロードバランサーやGateway APIが動くようになり、nginxや管理用Web UIが使えるようになりました。
これでようやく実行環境が整いましたので、次は、本来の目的であるGo言語のアプリ開発(の練習)に取り組みます。