インフラ・DevOps
Compose、Ruby、IPv6、そしてKubernetesなしでのホームラボの再構築
Rebuilding My Homelab with Compose, Ruby, IPv6, and No Kubernetes (petekeen.net)
要約
筆者は、以前のホームラボ環境がKubernetesの複雑さとハードウェアの不安定さによって破綻した経験を共有しています。現在は、Docker Composeを基盤とし、RubyスクリプトとIPv6を活用して、よりシンプルで管理しやすい環境を構築しました。この新しいアプローチは、個々のサービスを分離しつつ、IPv6による柔軟なネットワーキングと、Rubyによる自動化を組み合わせています。
全文翻訳
約1年前に私はこう書きました:すべてがめちゃくちゃだ。1台のマシンは壊れて、おそらく戻ってこないだろう。サービスは生き残ったマシンたちにランダムに散らばっている。私のKubernetesプロジェクトは、なんとなく一時停止している。この記事は、その記事の補遺であり、ホームラボで可能な限り落ち着いた環境の概要を説明するものです。少しおさらいすると、前の記事では、まとまりのあるクラスターのように見えるものにたくさんのマシンをまとめていました:hypnotoad、crushinator、そして薄型クライアント上で動作する不安定なコントロールプレーンVMであるlrrr、Proxmoxを実行するN150ボックスであるnibbler、そしてクソみたいなもの。それらの4台のマシンのうち、lrrrだけが生き残っています。
Nibblerの問題
テレビのNibblerは、Futuramaに出てくる愛らしい3つ目のキャラクターで、(ネタバレ注意ですが、20年も経てば私のカットオフは過ぎています)タイムトラベルができる超知能生命体のメンバーであり、実際にFryがクライオポッドに落ちた原因でもあります。私の地下室のnibblerは、私が多くの希望を託していたマシンでした。それは、eBayで破格の値段で手に入れた(伏線)Lenovo M80s Gen3 SFFで、i7-12700とDDR5メモリを搭載していました。メモリを128GBに、ストレージを3TBのNVMeにアップグレードし、残った小銭を使いました。HBAを取り付け、7台の16TBドライブを搭載したディスクシェルフを接続しました。それはコアサーバー、すべての処理が行われる場所になるはずでした。しかし、前述の通り、それはクソでした。何が問題だったのかはまだよくわかりませんが、決して信頼できませんでした。多くの場合、再起動後に起動しませんでした。時々、ハードウェアを認識しなくなることもありました。一度ならず、私や妻がJellyfinで何も再生できなくなったことに気づき、地下室に降りてモニターとキーボードを接続すると、ディスクシェルフ全体がカーネルログの不可解なSASアダプタードライバーエラーでエラーになっていることが判明しました。前の記事を書いた頃、私はもっともらしいストーリーを組み立てました。eBayの出品者はこのマシンが悪いことを知っていましたが、「eBay認定整備済み」ラベルを貼って安く売りつけたのです。それに気づいたときには、マシンを大幅に改造していたため、返金請求はできませんでした。
Kubernetesの問題
nibblerの露見と同時期に、別の気づきもありました:Kubernetesは難しすぎる。私は、トラブルから抜け出すための時間やエネルギーなしでは、メンテナンス方法を知らないシステムを構築してしまったのです。クラスター全体よりも多くのコアとメモリを持つマシンが週に1、2回自己発火すると、実験全体に大きな影響を与えます。私の応急処置は、k8s以前の状態、つまり実質的にdocker composeに戻すことでした。それをしばらく様子を見た後、そのまま進むことにしました。
一時的な解決策について彼らが何を言うか知っているだろう
今は2026年7月です。lrrrは以前とほとんど同じ形で存在しています。まだProxmoxを実行していますが、仮想化されているのは、Omadaを実行する1つのLXCと、エージェント的なライフスタイルに慣れるために使用している小さなVMが1つだけです。また、Proxmoxフォーラムのトロールが想像できる最悪のこともしました:ホストにDockerをインストールして、Home Assistantなどのコアサービスを実行しています。hypnotoadとcrushinatorはオフになっており、必要になるまでそのまま眠っています。morboは、TrueNAS SCALEを実行する、私にとっては新しいHP Elitedesk 800 G3 SFFで、その唯一の仕事はストレージを管理し、邪魔にならないようにすることです。そして、それは素晴らしい仕事をしています。ord-routerはシカゴのデータセンターで動作するVMで、Comcastと直接ピアリングしています。これがパブリックイングレイスノードです。nibblerは、古い部品と新しい部品の集合体になりました:同じメモリ、同じストレージ、新しいゲーマー風ケースとマザーボード、プロセッサー(Ryzen 9 9900X)。最近、ローカルLLM実験のためにRTX 5060ti 16GBビデオカードも追加されました。これが「その他すべて」のマシンです。家としての機能やネットワークとしてのネットワークに不可欠ではないものはすべてここで実行されます。とりわけ、nibblerはForgejo、Minecraft、Jellyfinとその仲間たちが動作する場所であり、その他多くのランダムなガラクタも含まれています。このシステムで展開されているRaspberry Piクラスのデバイスもいくつかありますが、それらはアプライアンス的なことを行います:house-piはZ-Waveスティックを実行し、shed-piはADS-B受信機が接続されている場所です。
追加ステップのあるDocker Compose
風変わり?ええ、風変わりとでもしておきましょう。ホームラボを展開するソフトウェアスタックは次のように構成されています:いくつかのスパイシーなDocker Composeスタック、スパイシーを実装するいくつかのRubyライブラリ、ライブラリを駆動するRakefile、すべてをまとめるYAMLファイル。
スパイシーとは何か、とあなたは尋ねるでしょう:cronジョブ、シークレット管理、セキュアなデフォルト、CaddyによるHTTPイングレイス(オプションの認証/認可とオプションのパブリックIP付き)、Legoによる自動証明書管理、バックアップ、モニタリングダッシュボード、イングレイスによって提供される静的ウェブサイト、スタック間の依存関係宣言、クロスプラットフォームDockerイメージビルド。
各Composeスタックは、Compose拡張機能(つまり、x-で始まるフィールド)を使用して、望むものを宣言します。簡単な例を挙げましょう:
services:
metube:
image: ghcr.io/alexta69/metube
container_name: metube
hostname: metube
restart: unless-stopped
x-security:
enabled: true
volumes:
- media-nfs:/media
environment:
DOWNLOAD_DIR: /media/youtube
HOST: '::'
x-backup:
enabled: false
x-web:
port: 8081
auth: true
dashboard:
name: MeTube
subtitle: Download YouTube Videos
category: Media and Games
x-depends:
- media-volume
これはMeTube、インターネットのさまざまな場所からビデオを取得する小さなアプリを設定します。セキュアなデフォルトを有効にし、バックアップを必要としないことを宣言し、その後イングレイスを設定します。ここでは、x-webはポートを指定し、認証/認可を必要とし、ダッシュボードに表示されることを指定します。この例は、スタック間の依存関係も示しています。メディアボリュームスタック(メディア-nfsボリュームを設定する)を取り込みます。これらすべてを機能させるトリックは、デプロイ時にすべてのスタックが1つの大きなdocker-composeファイルにまとめられることです。つまり、外部ボリュームやネットワークを宣言するような汚いことをする必要がなく、すべてがまとまるのです。
ネットワーキングはタイプツーの楽しみ
通常のDocker Composeの世界では、スタック内のすべてのコンテナはブリッジネットワークを共有します。私の目的にはそれは意味がありません。例えば、昨日ダウンロードしたランダムなバイブコード化されたものがlldapと直接通信できるようなことは望みません。代わりに、個別のスタックを展開した場合に得られるものをエミュレートします:各スタックは独自のブリッジネットワークを取得し、必要に応じて他のスタックのコンテナがそれに参加できます。したがって、ウェブサイトを提供する各コンテナが共有イングレイスブリッジに参加するように、イングレイスを実装するのが自然な方法でしょう。しかし、ここでも、私が目指しているものにはあまり合いません。スタック間の分離を維持したいので、ポップアップしたToDoリストマネージャーが他のすべてを脅かすことがないようにしたいのです。私の解決策は決定論的なIPv6です:
フリート全体は/48 IPv6 ULAを取得します。これはRFC1918 IPv4ネットワーク(例:10.10.10.0/24)の概念に似ていますが、はるかに大きなプールから引き出されます。
各ホストは、ホスト名+ソルトをハッシュして次の16ビットを取得し、/64を取得します。
各スタックは、スタック名+ソルトをハッシュして次の32ビットを取得し、/96を取得します。
各サービスは、コンテナ名+ソルトをハッシュして最後の32ビットを取得し、/128を取得します。
言い換えれば:<48ビットULAプレフィックス>:<16ビットホスト>:<32ビットスタック>:<32ビットサービス>
Caddyはnetwork_mode: hostで実行され、生成されたすべてのアップストリームはコンテナに割り当てられたIPv6を指します。これは、ホストが自然にすべてのブリッジにアクセスできるため機能します。私のセットアップには、もう1つ奇妙/勇敢/愚かな点があります。