AIに運用を委譲するホームサーバー設計
Language: 日本語
今年の正月休みに、ホームサーバーを作り直しました。物理マシン1台だけの、ノード数として最小のKubernetesクラスタです。上で動いているのは外部公開用のサービスではなく、Home AssistantやImmich、Paperless-ngxなど自宅内で使うサービスです。また、Kubernetesを学ぶための検証環境ではありません(多少はそういう意図もありますが)。「AI時代のホームサーバー運用はどんな形がいいか」を考えて作った、実用のためのサーバーです。
目指したのは管理の手間を最小にすること。方針は、管理するものを減らし、それでも管理しなければいけないものは、可能な限りAIに任せられる形にしておくことです。この記事では両方に触れますが、話の中心は後者——運用をAIに安全に渡すための境界をどう作ったか、です。
構成はhomelab-k8sリポジトリとして公開しています。実装の詳細はそちらに任せて、この記事では設計思想を紹介します。
運用コストと構築コストは別物
運用コストを減らすことと、構築コストを減らすことは、別の話です。このクラスタでは、運用時のコストを減らすために、意図的にコストを構築時へ寄せています。素朴にサーバーを立てるのに比べて、構築でやることはずっと多い設計です。
理由は単純で、構築は一度きりですが、運用はサーバーが動いている限り続くからです。日々の運用で労力を使いたくない。そのために、先に労力を使っておく。いわば運用コストの前払いです。
そして、この前払いのコストは、AIコーディングツールを使うことで大きく下げられました。やることは多くても、その大半はAIがこなしてくれます。実際、このサーバーの基盤構築にかかったのは数日です。ただし、作業を任せるには、AIがクラスタの構成を読み、安全に変更を提案できる必要があります。この読み取り面と書き込み面をGitに集約しました。
状態をすべてGitに置く
サーバー1台であれば、Kubernetesのメリットは少ない。普通にUbuntuサーバーを立てるほうが楽ではないか——そう考える人は多いと思います。それでもこの一見オーバースペックな構成にしているのは、初期構築は重くても、継続運用や再構築まで含めた総コストを下げられると判断したからです。
このクラスタの運用は、GitOpsの徹底が土台になっています。マニフェストをすべてGitリポジトリに置き、Flux CDが数分おきにリポジトリとクラスタを同期する。SecretもSOPS(age)で暗号化してリポジトリに含めているので、クラスタのあるべき状態(desired state)は、原則すべてGitにあります。GitOpsを徹底するための仕組みとして、Kubernetesは周辺のエコシステムまで含めて成熟しています。1台構成でもKubernetesを選んでいるのは、この成熟度が理由です。
OSのレイヤも例外にしたくなかったので、Talos Linuxを選びました。SSHもシェルもなく、APIでしか操作できない、イミュータブルなKubernetes専用OSです。構成はすべて宣言的な設定ファイルで、「ログインしてちょっと直す」がそもそもできません。マシンに宣言的な設定ファイルを一度適用すれば、以後の構成変更もすべてこのファイルの更新として行います。永続的なOS構成の操作面がこのファイルとAPIに絞られていて、シェル越しの場当たり的な変更がそもそもできないので、宣言した状態と実機の状態が乖離しにくいのです。マシンが今日壊れても、構成はリポジトリと復号鍵から再生成でき、データにはバックアップからの復元経路があります(復元の検証は後述します)。
これは手作業を排除するための構成です。すべての変更がGitの履歴に残っていれば、設定を覚えておく必要はありませんし、失敗しても、多くの変更はrevertで戻せます。DBやストレージが絡む、revertでは戻らない類いの変更のためには、あらかじめロールバック手順を用意しておきます(これは後述します)。
もうひとつ、可能な限りコード化しておくのは、Renovateの追跡対象にするためでもあります。Gitに置いてあるものは、更新の検知から提案までを自動化することができます。
自動化する領域と、あえて自動化しない領域
日常の変更は、ほぼRenovateが回しています。RenovateはHelmチャートやコンテナイメージだけでなく、Talos本体とKubernetes本体のバージョンまで追跡していて、通常の更新は週に一度まとめてPRになります。
Renovateのauto-merge機能は、あえてすべて切っています。毎回判断を挟みたいからです。ただ、その判断を毎週自分の手でやっているわけではありません。爆発半径の見立てから、マージ、マージ後の正常性確認、問題があったときのrevertまでの一連の流れをAgent Skillにしており(aoshimash/skillsで管理しています)、普段はこの手順どおりにAIが処理します。
それでも、TalosとKubernetes本体のアップグレードだけは特別扱いです。1台構成のクラスタでは、ここでの失敗はそのまま全停止だからです。この2つはマージしても自動では適用されず、PR本文に残りの作業が記載されるので、それに沿って人間が手動で適用する、最後のゲートを残しています。
平時、AIが加える永続的な変更はGit上のあるべき状態に閉じています。変更はPRとして提案され、lintやセキュリティスキャンが走り、マージされて初めてFluxが実機へ反映する。SSHで直接操作させれば、間違いはそのまま実機に届きます。この差があるから、判断をAIに渡せるのです。緊急時に直接操作したものは、事後にGitへ収束させます。
簡単に復旧できる失敗は自動に任せ、復旧が重い失敗には人間のチェックを挟む。そして残った仕事も、手順として明文化できたものからAIに渡していく。目的は意思決定以外の人間の仕事をゼロに近づけることです。
あえて捨てているもの
管理の手間を最小にする、のもう半分は、そもそも管理するものを減らすことです。これ自体は昔からの定石ですが、どれだけAIに任せられるとしても、仕事は発生しないほうが安い。このクラスタには、NGINXやTraefikのような汎用のIngressコントローラーも、cert-managerも、自前のPrometheusやGrafanaも、専用のバックアップ基盤もありません。インターネットに公開する入口を持たず、アクセスはtailnet内からのみで、IngressとHTTPS証明書はTailscale Kubernetes Operatorだけで処理しています。
監視はGrafana Alloyとkube-state-metricsで集めてGrafana Cloudへ送り、アラートルールはGitで管理しています。バックアップはLonghornとCloudNativePGの標準機能で毎日Cloudflare R2へ送るだけ——この規模で両者を使うのは、HAのためではなく、バックアップと復元を標準化された手順に乗せるためです。外に公開しないと決めた時点で攻撃対象領域の管理という仕事が消えるように、持たないと決めたものの分だけ、運用の仕事はまるごと消えます。
捨てた要件、捨てない要件
「減らす」は、管理対象だけの話ではありません。要件のレベルでも削っています。ここまで良いことばかり書いてきたので、この構成が何を捨てているのかも正直に書いておきます。
可用性・機密性・完全性のうち、このクラスタが要件から外しているのは、ノード障害に対する高可用性です。物理マシン1台で、冗長化はしていません。高可用性を外した代わりに、受け入れるリスクを具体的に決めています。ハードウェアが壊れれば直すまでサービスは止まり、バックアップは日次なので、最悪のケースでは丸一日ぶんのデータ消失もあり得ます。載せているのは停止を許容できるサービスだけなので、冗長構成のためのハードウェアと運用コストを払うより、この停止と損失を受け入れるほうが合理的だと判断しました。
ただし、捨てたのは「止まらないこと」であって、「戻せること」ではありません。クラスタの構成はGitと復号鍵から再生成でき、データはR2のバックアップから戻せます。LonghornのボリュームとCloudNativePGのデータベースについては、実データを使った復元リハーサルまで実施済みです。加えて、「バックアップが失敗した」「一度成功したバックアップが26時間以上更新されていない」はアラートにしています。バックアップは普段誰も見ていないので、何かの拍子に黙って止まり、復元が必要になった日に初めて気づく——それが一番怖いシナリオだからです。止まらないための投資ではなく、実際に戻せることの検証にコストを寄せています。
一方で、機密性と完全性は捨てられません。実用サービスを動かして使っているわけなので、「漏れる」ことと「丸ごと消える」ことは許容できないからです。インターネットに入口を持たない構成と暗号化してからGitに置くSecretは前者への、失敗を監視される日次バックアップは後者への対策です。ただし、ここで守れているのはネットワーク経由の脅威とGit上のSecretまでで、物理的な盗難やノード上に保存されたデータの暗号化は、まだ手をつけていない別のリスクとして残っています。
前払いしたものの正体
では、構築時に前払いしたコストの正体は何だったのかというと、その大部分は意思決定でした。どんなクラスタにしたいか、何を持ち、何を持たないか、どこまで自動化して、どこにゲートを残すか。AIと対話しながらこれらを決めていくのが私の仕事で、コードを書くのも、ドキュメントを書き起こすのも、ほとんどはAIがやっています。
決めたことのなかで特に効いているのが、ドキュメントに関する2つのルールです。ひとつは、状態を持つものやrevertで戻らない変更を導入するとき、動いたところで終わりにせず、「どうリストアするか」「どうロールバックするか」まで書けてから完了とすること。もうひとつは、障害対応をしたら、記録と教訓を必ずリポジトリに残すことです。教訓は障害報告書に埋もれさせず、関係する設定ファイルのコメントとしても書き込みます。
この積み重ねで、運用実績がそのままリポジトリに資産として溜まっていきます。十分な可観測性と、溜まったドキュメントがあるので、何か問題が起きても、多くの場合、障害調査から修正まではAIが自律的にやってくれます。実際、このクラスタの運用で、私自身が障害調査をしたことはほとんどありません。
いちばん大きな障害は、今年の5月に起きた、アップグレード起因で主要なサービスが使えなくなる約2時間半の障害でした。原因の詳細はインシデントレポートに譲りますが、リスクの高い変更を同じ変更窓に複数載せるという私の判断ミスが、切り分けを難しくしました。このときの調査を主導したのはAIです。絡み合った症状を3つの独立した問題に切り分け、GitOpsの経路そのものが機能しなくなっていたため例外的な緊急時対応を提案し、復旧後は手動の変更をGitへ収束させ、教訓をリポジトリに書き残す。私がやったのは、提案を確認して承認することだけでした。先に書いた更新PR処理のAgent Skill——爆発半径を見積もり、リスクの高い変更を同じ窓に載せず、1件ずつ検証してから次へ進む——は、この失敗の教訓をそのまま手順に固定したものです。失敗の教訓を手順に固定しておけば、私が同じ判断ミスをしようとしても、今度は仕組みが止めてくれます。
正直に言えば、これをそのまま直ちに業務の本番環境に持ち込めるとは思っていません。ゲートは完璧ではなく、この構成でも障害は何度か起きています。それでもここまで任せられているのは、壊れても大きな問題がないホームサーバーだから、というだけではありません。AIに安全に任せられるよう、ゲート自体をそのために設計しているからです。業務の本番環境をいきなりAIに全任せするのは危険だと思いますが、任せられる範囲を少しずつ広げていく設計は可能だと思っています。このホームサーバーは、その設計を試す実験の場にもなっています。
まとめ
こうしてできあがったのは、ほぼ自動で回るホームサーバーです。日常の運用では、週に一度の更新PRはAIが手順どおりに処理し、私に残るのは、TalosやKubernetesのアップグレードのような例外と、たまの通知に判断を返すことくらい。コストを構築時に前払いし、管理するものを減らし、あるべき状態をGitに置く。作業はAIに渡し、変更の経路と承認の境界は人間が設計する。「AI時代のホームサーバー運用はどんな形がいいか」への、いまのところの私の答えがこれです。