はじめに
この記事は 「ラクス Advent Calendar 2025」 23日目の記事です。
趣味で「おうちKubernetes」の構築をRaspberry Piでしたので、その時の構築手順をまとめました。
多くの方が同じような記事を上げているので何番煎じなのか分かりませんが、
自分用の作業メモとして残しておきたいと思います。
元になったスクラップ(構築時の生ログ寄りメモ)
https://zenn.dev/imamoto_hikaru/scraps/384ee0ae3408cd
全体方針
- 宅内ネットワーク上で、Raspberry Pi 複数台の Kubernetes クラスタを構築する
- kubeadm を使って Kubernetes を構築する
- コンテナランタイムは containerd
-
kube-vip を使って Control Plane の接続先を VIP 化(
--control-plane-endpoint) - CNI は Calico
| 項目 | 方針 |
|---|---|
| OS | Ubuntu Server 24.04.1 LTS |
| Kubernetes 構築 | kubeadm |
| コンテナランタイム | containerd + runc |
| Control Plane Endpoint | kube-vip(static pod / ARP)で VIP を持たせる |
| Pod CIDR | 10.1.0.0/16 |
| CNI | Calico |
| リモートアクセス | Cloudflare Tunnel(Zero Trust)で SSH(ポート開放しない) |
0. Raspberry PiでUbuntuの初期セットアップを実施
0-1. 事前準備
- microSD に OS を焼く
- microSD と SSD を Raspberry Pi に接続
0-2. Raspberry Pi Imager で Ubuntu Server を用意
Raspberry Pi Imager で Ubuntu Server 24.04.1 LTS を選択して焼きました。
- Timezone:
Asia/Tokyo - Keyboard:
us - User/PW 設定
- SSH 有効化
0-3. (SSD起動向け)config.txt の編集
Raspberry Pi OS 側で system-boot の config.txt に以下を追記しました(環境により pciex1 / nvme が変わる想定)。
dtparam=pciex1(またはdtparam=nvme)
dtparam=pciex1_gen=3
1. Ubuntu 側の基本設定
1-1. ホスト名変更
hostnamectl set-hostname rasp001
(ノードごとに rasp001, rasp002…のように振ると後が楽です)
1-2. net-tools 入れる(任意)
sudo apt update
sudo apt install -y net-tools
1-3. (必要なら)日本語化
sudo apt -y install language-pack-ja-base language-pack-ja
localectl set-locale LANG=ja_JP.UTF-8 LANGUAGE="ja_JP:ja"
source /etc/default/locale
echo $LANG
2. 宅内ネットワークの整理(静的IP)
まずは 現在のIP/サブネット/インタフェース を確認。
ip a
route -n
2-1. netplan で静的IPを設定(Ethernet利用)
/etc/netplan/51-manual.yaml を パーミッション600 で作成し、以下のように設定しました。
設定項目 意味 dhcp4: noDHCP を使わない addresses固定IP/プレフィックス routesデフォルトゲートウェイ指定 nameserversDNS(例では Google DNS)
network:
version: 2
ethernets:
eth0:
dhcp4: no
addresses: [192.168.130.1/20]
routes:
- to: default
via: 192.168.128.2
nameservers:
addresses: [8.8.8.8,8.8.8.4]
反映前にエラーがないか確認:
sudo netplan try
3. Cloudflare Tunnel で自宅Raspberry PiへSSH(ポート開放しない)
自宅ネットワークに SSH を直接開ける必要がないよう、
Cloudflare Zero Trust(Tunnel + Access) で入る形にしました。
大まかな流れは以下です。
- Cloudflare アカウント作成
- Cloudflare でドメイン取得
- Zero Trust → Networks → Tunnels でトンネル作成
- Raspberry Pi に cloudflared を入れてトンネルを張る
- Zero Trust → Access → Applications でアプリ定義
- 接続元PCにも cloudflared を入れて、ドメイン宛に SSH
- ブラウザが立ち上がるので認証して接続
参考にした記事:
4. コンテナランタイム(containerd + runc)を入れる
Kubernetes 公式手順に沿って、まずはカーネル設定周りから。
4-1. IPv4フォワーディング + br_netfilter
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# この構成に必要なカーネルパラメーター、再起動しても値は永続します
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
確認:
# 各モジュールが読み込まれていることを確認
lsmod | grep br_netfilter
lsmod | grep overlay
# 各カーネルパラメータが1に設定されていることを確認
sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward
4-2. containerd / runc のインストール(公式バイナリ)
Docker 配布の containerd もありますが、今回は 公式のバイナリで入れました。
# containerd
wget https://github.com/containerd/containerd/releases/download/v2.0.1/containerd-2.0.1-linux-arm64.tar.gz
sudo tar Cxzvf /usr/local containerd-2.0.1-linux-arm64.tar.gz
# systemd service
wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service
sudo mv containerd.service /lib/systemd/system/containerd.service
sudo systemctl daemon-reload
sudo systemctl enable --now containerd
# runc
wget https://github.com/opencontainers/runc/releases/download/v1.2.3/runc.arm64
sudo install -m 755 runc.arm64 /usr/local/sbin/runc
4-3. containerd の設定ファイル生成
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
4-4. runc の cgroup ドライバを systemd に
Ubuntu Server(systemd)かつ cgroup v2 なので、SystemdCgroup = true を設定。
/etc/containerd/config.toml の以下へ追記:
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runc.options]
SystemdCgroup = true
反映:
sudo systemctl restart containerd
5. kubeadm / kubelet / kubectl をインストール
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/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.32/deb/ /' | 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
6. kubelet の cgroup ドライバも systemd に揃える(重要)
kubelet と container runtime の cgroup driver がズレるとハマります。
/etc/default/kubelet を編集:
KUBELET_EXTRA_ARGS=--cgroup-driver=systemd
反映:
sudo systemctl daemon-reload
sudo systemctl restart kubelet
7. kubeadm でクラスタ構築(Control Plane)
7-1. 必要 image を pull できるか確認
sudo kubeadm config images pull
結果の例
[config/images] Pulled registry.k8s.io/kube-apiserver:v1.32.0
[config/images] Pulled registry.k8s.io/kube-controller-manager:v1.32.0
[config/images] Pulled registry.k8s.io/kube-scheduler:v1.32.0
[config/images] Pulled registry.k8s.io/kube-proxy:v1.32.0
[config/images] Pulled registry.k8s.io/coredns/coredns:v1.11.3
[config/images] Pulled registry.k8s.io/pause:3.10
[config/images] Pulled registry.k8s.io/etcd:3.5.16-0
7-2. kube-vip で Control Plane VIP を作る(static pod / ARP)
Control Plane の接続先を固定したくて、kube-vip を使って VIP を持たせました。
# VIP(宅内で未使用のIPを選ぶ)
export VIP=192.168.130.21
# インタフェース名(ip a で確認)
export INTERFACE=eth0
# 最新版の取得
KVVERSION=$(curl -sL https://api.github.com/repos/kube-vip/kube-vip/releases | jq -r ".[0].name")
# containerd の場合
alias kube-vip="ctr image pull ghcr.io/kube-vip/kube-vip:$KVVERSION; ctr run --rm --net-host ghcr.io/kube-vip/kube-vip:$KVVERSION vip /kube-vip"
# manifest 作成(static pod)
kube-vip manifest pod \
--interface $INTERFACE \
--address $VIP \
--controlplane \
--services \
--arp \
--leaderElection | sudo tee /etc/kubernetes/manifests/kube-vip.yaml
※ 私はここで一度、manifest を一時的に修正しました(super-admin.conf を指定するやつ)。このあたりは環境依存で詰まりやすいので、必要なら以下の Issue comment を参照してください。
7-3. kubeadm init
Pod CIDR は 10.1.0.0/16 にしました。
sudo kubeadm init \
--control-plane-endpoint 192.168.130.21:6443 \
--pod-network-cidr=10.1.0.0/16 \
--upload-certs
(もし kube-vip.yaml を一時的に super-admin.conf で動かしていたら、ここで admin.conf に戻す)
8. kubectl を一般ユーザーで叩けるようにする
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
9. Worker Node をクラスタへ参加させる
Control Plane の kubeadm init 実行時に出力された kubeadm join ... を Worker 側で実行します。
sudo kubeadm join 192.168.130.21:6443 --token <token> \
--discovery-token-ca-cert-hash <hash値>
10. CNI(Calico)を入れる
Calico 公式手順に沿って Operator → CRD → 本体を入れます。
10-1. Operator のデプロイ
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/tigera-operator.yaml
10-2. custom-resources.yaml を適用
curl https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/custom-resources.yaml -O
kubectl create -f custom-resources.yaml
10-3. calico.yaml を適用(Pod CIDR を合わせる)
kubeadm init の --pod-network-cidr と、Calico 側の CALICO_IPV4POOL_CIDR を一致させます。
curl https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/calico.yaml -O
# calico.yaml 内の CALICO_IPV4POOL_CIDR を 10.1.0.0/16 に合わせる
kubectl apply -f calico.yaml
11. 動作確認(最低限)
kubectl get nodes -o wide
kubectl get pods -A
ここまでで、少なくとも以下が揃っている状態になります。
- Control Plane が Ready
- Worker が Ready
-
calico-system系の Pod が Running
12. ハマりどころ(自分用メモを共有)
| 症状 | ありがちな原因 / 対処 |
|---|---|
| kubelet が立ち上がらない/不安定 | containerd と kubelet の cgroup driver 不一致 を疑う。どちらも systemd に揃える |
kubeadm init 周りで VIP がうまく効かない |
kube-vip の manifest(static pod)生成/配置、参照する kubeconfig(必要なら super-admin.conf)を確認 |
| ノードのIPが変わって辛い | netplan で静的IPにする(宅内 DHCP 範囲と衝突しないところへ) |
| 外から入るために 22 番を開けたくない | Cloudflare Tunnel + Access で SSH(ポート開放しない) |
おわりに
おうちKubernetesを構築すると、普段は意識しづらい cgroup / runtime / CNI / endpoint あたりが一気に具体化して良かったです。
クラスタ構築が一段落した後、久しく放置してしまっていたのですが、来年はこの辺をやってみたいです。
- Control Planeの冗長化
- cloudflaredをKubernetesで管理
- ArgoCDでのGitOpsやObservabilityの対応
以上です、メリークリスマス!!!