VMからLXCへお引越し。MicroK8sを諦め、Incus上のK3sで超軽量K8s環境を構築してみた

Progress 5 / 5
目次

軽いAI推論用のノードを作ろうと思い、新しいマシンをあれこれ検討しています。今のところはStrix Halo搭載機にする予定で、特にGMKTekのEvo-X2が最有力候補です。とはいえ、お財布の事情もあるのでおとなしくセール待ちをしている状態です。

さて、新しいノードをお迎えする前にどうしてもやっておきたい「検証」がありました。

現在、我が家のKubernetesはVM上で動かしているのですが、やはりリソースのオーバーヘッドが気になります。そこで、少しでもリソースを節約するための選択肢の一つとして、コンテナ(LXC)運用へと切り替えられるのか、実際に試してみることにしました。あくまで検証であり、実際に本番環境として導入するかは未確定です。

最初は使い慣れたMicroK8sでサクッと構築しようと考えていたのですが、これが甘かった。MicroK8sはSnap依存のため、コンテナ環境だと権限周りの設定や構成が非常に複雑になってしまうという罠があります。

どうやらLXCの中でSnap版のMicroK8sを動かすのはかなり面倒なようなので、今回は潔くMicroK8sを諦めることにしました。代わりに、Snap不要・単一バイナリで動く「K3s」に移行することにします。K8sのマニフェスト自体はそのまま使い回せるはずなので、そこまで苦労はしないだろうという見立てです。

Incusプロファイルの設定:コンテナの中にコンテナを

コンテナをホストマシンの「一級市民」として扱うため、Incus上で専用のプロファイルを作成していきます。今回はcloud-initを使って固定IPも割り当てていますが、皆さんの環境に合わせてここは適宜書き換えてください。

ちなみに、ホスト側(Ubuntu)でのブリッジネットワーク(br0)の具体的な構築方法については以下の記事でまとめていますので、あわせて参考にしてみてください。

LXDの後継「Incus」を試す:インスタンスをホストネットワークに接続する

>-

blog.otama-playground.com

ここでのポイントは、LXCコンテナの中でさらにコンテナ(Pod)を立てる構成になるため、ネストを許可し、強い権限を渡す必要があるという点です。今回は完全に公開予定のない「おうちKubernetes」なので、細かいセキュリティはあまり気にせず進めます。

# -------------------------------------------------------------------
# K3s (Kubernetes) 実行用プロファイル
# -------------------------------------------------------------------
name: k3s
description: K3s実行用特権・ブリッジプロファイル
devices:
# Kubeletがログ出力やシステム状態の確認に使用する /dev/kmsg をパススルー
kmsg:
path: /dev/kmsg
source: /dev/kmsg
type: unix-char
# ネットワーク設定(ホストのL2ブリッジ br0 に直接接続)
eth0:
name: eth0
nictype: bridged
parent: br0
type: nic
# コンテナの動作・セキュリティ設定
config:
# コンテナ内でDockerやさらなるコンテナ(Pod)を動かすために必須
security.nesting: "true"
# カーネルへの深い操作(iptablesやマウント)を許可するために特権モードを有効化
security.privileged: "true"
# コンテナ起動時にホスト側でロードしておくべきカーネルモジュールを指定
linux.kernel_modules: overlay,br_netfilter,ip_tables,ip6_tables
# cloud-initによるUbuntuの固定IP設定(初回起動時に適用される)
cloud-init.network-config: |+
network:
version: 2
ethernets:
eth0:
dhcp4: false
dhcp6: true
addresses:
- 192.168.100.201/24
routes:
- to: default
via: 192.168.100.1
nameservers:
addresses:
- 192.168.100.1
# LXCの低レイヤー設定(標準の制限を外してK8sが動けるようにする)
raw.lxc: |
# AppArmorの制限を解除し、コンテナ内からのシステム操作をブロックしない
lxc.apparmor.profile=unconfined
# すべてのデバイスへのアクセスを許可(cgroup v1用)
lxc.cgroup.devices.allow=a
# コンテナのケーパビリティ制限(権限削除)を無効化し、フル権限を維持
lxc.cap.drop=
# /proc と /sys を読み書き可能でマウント(Kubeletの起動エラー回避に必須)
lxc.mount.auto=proc:rw sys:rw

このプロファイルを適用したコンテナを作成し、中に入ります。

Terminal window
incus launch images:ubuntu/24.04 k3s-test --profile k3s
incus exec k3s-test bash

次にコンテナ内でK3sをインストール。

Terminal window
apt update && apt install -y curl
curl -sfL https://get.k3s.io | sh -

ここまでは非常に順調でした。

LXC特有の起動オプション設定

インストールは無事に成功したように見えました。意気揚々と kubectl get nodes を叩いたのですが、返ってきたのはアクセスを拒絶するエラーメッセージでした。

調べてみたところ、Kubeletの起動設定を一部修正する必要があるようです。

/etc/systemd/system/k3s.service を編集し、ExecStart の引数に以下を追記します。ここでの引数は、公式ドキュメント通りに進めただけでは解決しない、環境依存の回避策になります。

ExecStart=/usr/local/bin/k3s \
server \
--kubelet-arg="protect-kernel-defaults=false" \
--kubelet-arg="make-iptables-util-chains=false"

LXCのような制限された環境では、システムへの書き込み権限がホスト側から制限されているため、K3s側の要求を少し緩めてもらう必要があります。これもコンテナ運用ならではのハマりポイントですね。

設定を保存し、再起動をかけます。

Terminal window
systemctl daemon-reload
systemctl restart k3s

構築を終えての所感

もう一度コマンドを叩いて動作確認をします。

Terminal window
kubectl get nodes

無事に Ready の文字が表示されました。なんとか動いてよかったです。

これで、いつでもLXC環境にK3sを再現できる基盤が完成しました。確かにVMより圧倒的に軽く、リソース面では十分すぎるほど優秀です。

しかし、実際に一通り構築してみて改めて考えさせられました。

若干のメモリ有利はあるものの、やはりセキュリティ面のメリットや、ホストOSとの環境が完全に隔離できるVMの安心感は捨てがたいなと。自宅サーバー・検証環境という性質上、気兼ねなく環境の「創造と破壊」を繰り返せるVMのほうが都合が良い場面も多いんですよね。

ということで、LXC上のK3s基盤は選択肢の一つとして検証するにとどめ、当面は元のVM運用をそのまま継続しようと思い直しています。とはいえ、IncusとK3sの組み合わせ自体は非常に面白いので、リソースが極端に厳しい環境などでは大いに活躍してくれそうです。