自宅鯖のRHEL8をRHEL10にAIエージェントで移行した

2025年の7月にパーツは調達してあった。それ以来ずっと、そこそこやらないとならんことがあって腰が重いままだったが、意を決して移行した。

今回のお品書き

これが最終にはならないと思うけれど、ひとまず固めた構成をザックリと図にするとこうなる。

旧サーバはコンテナを用いずベアメタルのみで構成しており、ほぼ同じソフトウェア構成のサーバ2台で冗長化していた。これに加えて、レンタルVPSにセカンダリDNS/MTAを持たせる構成。

自宅鯖ではコンテナの管理コストの方が利便性を上回るという判断だったが、WordPressやOwnCloudを動かすPHPの寿命がRHELでは短く、AppStreamを用いるとその特殊性で動かなくなったため、独自にローカルレポを作ってRPMパッケージをメンテしていた。同様に、Azure Blobへのバックアップに用いているazcopyが、ライブラリの依存関係が理由で一時期RHEL8でビルドできなくなったりと、RHEL10+コンテナに移行する機運が高まっていたのは否定できない。

全体のポイント

  • 自宅LANは10Gbps、ラストワンマイルもauひかりホーム10G
  • レンタルHGWの下に10Gスイッチがあり、壁のパッチ盤を介して他の10Gスイッチに接続
  • サーバの10G NICには、192.168.1.3と192.168.1.4が割当たる。さらにv6アドレスもあるがややこしいので上図では省略
  • Internetからのパケットは192.168.1.3にDNATされ、Localnetからのアクセスは192.168.1.4になる
  • DNSは192.168.1.3はKnot、192.168.1.4はUnboundが担当。
  • Knotは権威サーバで、UnboundはKnotが権威を持つドメインのLocalnet内解決をする。旧サーバはBINDでViewを分割していた。
  • DHCP、DNS、SMTP、IMAPはベアメタルで、HTTPSはTraefikがTLS terminationし、全てPodman Composeで構成

Traefikは論理ルータ

  • rio.st / warabi.stは、Traefik - Nginx - WordPress (Apache + php-fpm) - MariaDB / Valkey
  • dify.rio.stは、Traefik - Dify-Web - Dify-API - PostgreSQL / Redis
  • owncloud.rio.stは、Traefik - OwnCloud - MariaDB
  • コンテナ総数は約30

AIエージェントを使った移行の流れ

AIを使えば何でも自動になるわけではないし、きちんとやらせないとむしろ問題は悪化するのはコーディングと全く同じ。

旧サーバのアセスメント

  • ロールとして「サーバの移行プロジェクトを担当するアーキテクト」みたいなものを割り当てておく。さらに「RHELのスペシャリストである」というのも。
  • 定型的に取得できる情報、例えばRPMパッケージのリストや、ネットワーク、ストレージ、ユーザーアカウント設定などはすぐに集められる。一方で稼働している各サービスの設定やデータは、非定型的に取得しないとならないことが多い。自分の場合は、必ずRPMパッケージで管理するようにしているので(だからローカルレポがある)、まだマシな方だと思うが。この非定型的な情報の取得はAIエージェントは得意なので、活用しないともったいない。
  • 取得した情報はフォーマットがバラバラなので、MarkdownやJSONなどでドキュメントにさせておく。一度、抽象化すると言っても良い。その際に、「to-beモデルを検討するのに必要と思われる情報を集める」ように指示を出す。

to-beモデルの検討

  • ロールはアセスとあまり変わらないが、「最新のコンテナやマイクロサービスの設計の知見をもったアーキテクト」という指定をしても良い。
  • アセスで集めた情報と、新サーバで採用候補となる技術を合わせて、to-beモデルを作らせる。当初、KeycloakやOCIS (OwnCloud Infinite Scale) なども候補にあったが、管理運用コストが増える、あるいは十分に枯れてないといった理由で不採用にしたのもこの段階。
  • Difyは自宅鯖とは別のミニPCにRHEL9を入れて動かしており、新自宅鯖に統合したかった。

移行プランの作成

  • まず各サービスごとに要件と、実現すべき責務、及びその検証方法を定義させた。SMTPサーバを例に取ると、「メールが送受信できる」ではなく、オープンリレーにならないことや、DNSと連動しての現代的なメールの送受信方法、Google宛に送信できることなど、より具体的な内容を盛り込む。
  • 複数のフェーズに分割して、各サービスを実装するプランを、Ansible Playbookにさせる。ここでのサービスの単位は「デーモン」だけでなく、カーネルパラメータのチューニングや、データバックアップの方法、ユーザーアカウントとメールデータの移行、といったものも含む。
  • ただしここで大きな失敗をしたので突起しておくと、Postfix、Rspamd、Dovecot、Knot等、サービスとしては別だけれど、連携して成立するサービス群は、まとめてプラン、実装、検証する構造を持たせておくべきだった。例えば、DovecotはMaildirを想定しているのに、PostfixはMailboxのまま稼働し始める、といったことが実際に起きた。旧サーバでは当然に双方ともMaildirなので、油断したとも言える。
  • Ansible Playbookで、syntax check / dry-runまで通したら、プランとしては成立。
  • トータルで23個のサービスを実装する形になった。実装順もAIエージェントに検討させた。

移行作業

  • Ansible Playbookをrunする前に、vaultに実値を入れる。パスワードやトークンは人間が管理しないものも多く、opensslコマンドなどで生成させる。
  • runすると、様々な問題に突き当たる。その際、AIエージェントは公式ドキュメントを見に行かない。helpは実行できるので、「自力で」解決しようと試み、深みにハマることがある。例えば、APIの引数はたぶんこれ、という推測を平気でやる。プラン作成時に参照すべきドキュメントを定義し、.rules / README / AGENT.mdなどで参照することを指示した方が良い。
  • コンテキストの混線は大敵で、旧サーバと新サーバを間違えたりもする。今回、1枚のNICに2つのIPアドレスを割り当てた上、WiFiでStarlinkのルータ内のNTPサーバを参照させるようにしたため、Chronyの設定時は混線しまくった。
  • 十分に調査させたつもりでも、AIエージェントと人間の間で認識の齟齬は発生する。例えば、WordPressの公式Dockerイメージは複数の、つまりphp-fpmのみとapache + php-fpmを含むパターンがある。AIエージェントはtraefik - php-fpmで動くと認識しており、こちらはtraefik - nginx - php-fpmを提案してると認識していた。当然、動かない(すごい頑張ればたぶん動く)。
  • 世の中一般では、Docker Composeで動かしている構成は、Podman Composeではそのまま動かない。特にrootlessは本当に動かないので諦めた。SELinuxはEnforcingのままで、独自に作成したポリシーは1つだけだった。ほぼ問題は無い。
  • Quadletは採用せず、systemd unitにするにとどめた。利便性は高いけれど、このぐらいの規模ならsystemdで問題は無いと思う。

検証作業

  • 23個のサービスを実装する形になったと上述したうち、3つは修正する「サービス」の実装だった。つまり、検証した結果不足していたり、プランの大幅な変更が必要になったものも多い。
  • 実装の粒度がバラバラなので、検証もバラバラになった。前述したように「サービス群」として定義したものとして、Observability「可観測性」がある。要はPrometheus / Grafana、それにいくつかのagentなのだけど、難易度が低いので「サービス」とした。Grafanaの検証は定義が難しい。情報が取得できているだけではなく、人間が見やすい、というのは数値化しにくい。
  • 実装なのか検証なのか判断に迷ったことに、セキュリティハードニングがある。OpenSCAPによる内部監査と、OpenVASによる外部監査の両方を実施した。

まとめ

AIエージェントと書いたが利用したモデルはGPT-5.4。十分に移行作業の期間を短縮できることはよく分かった。コードを書かせている時に、Dockerを使うこともあって、イケるだろうと予測はしていたため、それほど驚きはない。

とはいえ、移行作業をAIエージェントにやらせたのは初めてで、むしろこちらが不慣れな面も多かった。この記事がどなたかの役に立てば幸いである。