[🛠] IT Operations #2: 会社サービス用Linuxサーバー、インストールより先に運用構造を設計した
✨ GPT-5.6 Solの要約
専用Ubuntu Serverのディスク・内部SSH・固定アドレスを構成し、人・AI Cell・業務自動化Runtimeの権限を分離したうえで、ネットワーク全体をCodexが引き継げる運用基盤として整理した記録。
ついに会社サービス専用のLinuxサーバーを立てた。
しかし今回作ったのは、Ubuntuが起動するコンピューター一台ではない。人とAI、業務自動化サービスが一台のサーバー内で同時に動いても、互いの権限や状態を侵さないための運用基盤に近かった。
SSDを手に入れたばかりの頃は、考え方も単純だった。Windows PCにLinuxを入れてSSHで接続すればサーバーになるのではないか。すぐにGUIのことが気になった。サーバーらしく見せるためだけにCLIへこだわり、あとからブラウザログインやComputer Useが必要になって痛い目を見るのではないかと思った。
一つずつ整理してみると、サーバーにGUIが必要な理由と、私がGUIを使う必要がある理由はまったく別の問題だった。OAuthやCodexへのログインが必要なら、SSH画面に表示されたURLをMacBookのブラウザで開けばいい。Computer Useが必要ならMacや業務端末で実行し、Linuxはその成果をデプロイするRuntimeにすればいい。
サーバーは画面を表示する機械ではなく、動き続ける環境だった。
Windowsディスクを守るUEFI・GPT・LVM構成
インストールUSBの段階から混乱した。Ventoy USBがMBRならUbuntuもMBRでインストールされると思っていたし、ブートメニューにUEFI: USB, Partition 2が現れると、これが正しい経路なのかも不安だった。
USBのパーティション方式と、UbuntuをインストールするSSDのパーティション方式は別物だった。UEFI項目からインストーラーを起動し、対象SSDだけを正確に選べばよかった。既存のWindows NVMeには触れず、新しく用意した250GB SSDだけにEFI System Partition、/boot、LVM、root filesystemを作った。
この過程で最も怖かったのはLinuxが難しいことではなく、別のディスクを消してしまうことだった。Windowsのシステムで予約済みパーティションまで見えると削除してもよさそうに感じたが、インストーラー上でディスクのモデルと容量を再度照合し、Ubuntu専用SSDだけを選んだ。既存のWindowsディスクはそのまま残った。
インストールにはUbuntu Server 24.04.4 LTSの標準構成を選んだ。minimizedでもDesktop imageでもない。GUIを省き、SSHとsystemdを中心に管理し、あとからDocker Runtimeを載せやすい土台だけを残した。
固定アドレスより先に必要だったDHCP予約と内部SSH
インストール中、有線LANはDHCPですぐにアドレスを受け取った。最初はここでCreate bondを押せばSSHがつながりやすくなったり、アドレスを固定できたりすると思っていた。しかしbondは複数のNICを束ね、冗長化や帯域の集約を行う機能だ。有線NIC一枚のアドレスを固定することとは関係がなかった。
サーバー内に任意のstatic IPを埋め込む代わりに、メインルーターでNICのMACアドレスと内部IPをDHCP予約で結びつけた。サーバーはDHCP clientのままだが、再起動後も同じアドレスを受け取る。ルーター上で他の予約済み機器との競合も確認できる。
SSHもインターネットには公開しなかった。MacBook専用のEd25519 keyを作り、サーバーのhost keyをコンソールで照合してから内部ネットワークのアドレスに固定した。ルーターには外部22番ポートのforwardingを作らなかった。
インストール直後にssh.serviceがdisabledと表示され、一瞬SSHを正しくインストールできなかったのかと思った。実際にはssh.socketが有効で22番ポートをlistenしており、再起動後もpublic-key loginは繰り返し成功した。socket activationはrequestが来たときに対応するserviceを起動できるため、serviceの一行だけを見て障害と判断してはいけなかった。1
数日前に会社ルーターのインターネット障害を直しながら学んだDHCP lease、reservation、IP–MAC bindingの違いが、今回はそのままサーバー基盤づくりに生きた。アドレスを一つ受け取ったことと、そのアドレスから安全に管理できることは別だった。
一つのアカウントに混ぜなかった人・AI Cell・サービス
SSHがつながると、いつものようにOh My Zsh、Powerlevel10k、個人aliasから入れようとした。そこでまた立ち止まった。
インストール時に作った最初のアカウントは、bootstrapと復旧に使う会社所有の管理者アカウントだ。普段使うshell、AI Runtime、Operations Automationまでこの一つのアカウントに集めれば便利ではある。しかし、誰がどの権限で何を実行したのか区別できず、将来担当者が増えたりアカウントを失効させたりするときに、すべてが絡み合う。
そこで、まずアカウントを次の規則で分けた。
bootstrap管理者
└─ インストール・復旧時だけ使用
usr-<person>
└─ 人がSSH・sudo・開発作業に使うアカウント
aio-cell-<person>
└─ 個人AI Cellが常時実行されるアカウント
svc-<domain>-<purpose>
└─ アプリRuntime、データimporter、制限付きoperation broker
会社名や職種をusernameに入れなかったのも意図的だった。所属や役割が変わっても人は同じ人だ。Linux loginには安定したidentityだけを表し、管理者・開発者・プロジェクト参加といった権限はgroupとRBACで別に変更するほうがよい。
使い慣れたOMZ・P10K・aliasは人のアカウントだけに置くことにした。AI Cellとservice accountはinteractive shellの設定を読まず、固定されたsystemd unit、EnvironmentFile、絶対pathで実行する計画だ。きれいなpromptは私の作業効率を高めるが、サービスの安定性とは何の関係もない。
まだこれらのアカウントを急いですべて作成したわけではない。先に名前、所有するファイル、ログイン可否、sudoとDockerへのアクセス範囲を確定した。空のサーバーで境界を決めておくほうが、すべてが一つのアカウントに混ざったあとで切り離すよりはるかに簡単だった。
AI Cellの境界はUID・HOME・OAuthまで続く
今回、理解が最も大きく変わったのはマルチユーザーだった。
Desktop GUIだけを見ていると、今画面にログインしているユーザーがコンピューター全体を占有しているように感じる。Linuxでは、目に見えるログイン画面と実行中のユーザーは別物だった。複数のユーザーが同時にSSH接続でき、ログインしていないservice accountのprocessもsystemd配下で動き続けられる。
この構造は、macOSのユーザー単位で再定義したAI CellをLinuxへ拡張するときに特に重要だった。AI Cell一つは、単なるprocess名ではなかった。
AI Cell 1つ
= Linux UID・HOME 1セット
= OAuth・CODEX_HOME 1セット
= Gateway・Port 1セット
= Session・Memory・Workspace 1セット
= systemd unit・cgroup budget 1セット
人ごとのCellに別々のUIDとHOMEを持たせると、認証と記憶、作業空間が自然に分かれる。各Cellは異なるportやUnix socketで立ち上がり、ユーザーがSSHにログインしていなくても、起動後はsystemdが動かし続ける。一台のサーバー内で複数のCellが同時に生きていても、あるCellのOAuthやMemoryが別のCellのfallbackに使われない構造だ。
反対に、AI Cellにはsudo、NAS全体のmount、運用DBの管理者credential、Docker socketを与えないことにした。Dockerの公式ドキュメントも、docker groupはユーザーにrootレベルの権限を与えると警告している。2 便利だからと人・AI・service accountをそのgroupに入れれば、先ほど分けた境界は事実上消える。
Dockerの操作は、人の管理者が明示的にsudoを使うか、root所有の制限付きhelperとsystemd unitが決められたoperationだけを実行する形にする予定だ。AIはデプロイを調整できても、任意のshellとDocker全体の権限を直接は持たない。
NASはingress、Linuxはapplication Runtime
LinuxサーバーはAI Orchestrationだけの装置ではない。Operations AutomationのWeb・API・DBも同じ装置上のDocker Runtimeで運用する予定だ。それでも、すべてを一つのアカウント、一つのCompose project、一つのnetworkに入れるわけではない。
外部接続は既存のNASが担うHTTPSとreverse proxyを維持する。Linuxは実際のapplicationを実行する。
外部ユーザー
→ HTTPS 443
→ 会社ルーター
→ NAS TLS・hostname reverse proxy
→ Linux frontend
→ Docker内部API
→ Docker内部DB
NASの管理port、Linux SSH、APIやDB portをインターネットへ直接公開する必要はない。NASはhostnameと証明書を処理するingressであり、storage・backup地点として残り、Linuxはapplication Runtimeを担う。NASへ休暇自動化を移しながら作った外部HTTPS経路を捨てるのではなく、最終backendだけを専用サーバーへ移す方向だ。
コードのデプロイとNASからの人事情報の取り込みも、同じ作業にまとめなかった。
コード
→ release・artifact hash確認
→ 制限付きdeploy helper
→ health check・rollback
人事データ
→ NASの承認済みread-only folder
→ staging・validation・preview
→ 承認
→ DB transaction・audit
AI Orchestrationは変更を分析し、承認を求められる。しかし実際のデプロイとデータ取り込みは、決められたoperationだけを受け付けるbrokerとimporterが分担して行う。MacBookは開発、dry-run、preview確認、承認済み作業の開始点にはできるが、運用DBのcanonical mutation地点にはしない。
現在のLinuxサーバーには、まだDockerもapplication DBもない。そのためDHCP予約とSSHが成功しただけでproduction backendとは呼ばなかった。正確なfrontend port、health check、backup・restore、rollbackを整えてから初めて、NAS reverse proxyのtargetを変更できる。
Codex Skillが記憶するネットワーク全体のtopology
サーバー設定と同じくらい重要だったのが、次の会話への連続性だった。
ルーター、2台のNAS、既存のMac Runtime、新しいLinuxサーバー、外部HTTPS、内部SSHのつながりを、会社ネットワーク専用のCodex Skillと詳細inventoryに反映した。単なるIP一覧ではなく、それぞれの機器が担う役割とtrafficの始点・中間hop・最終Runtimeを区別した。
Skillでは、現在完了した事実と今後作る構造も混ぜなかった。
- Ubuntuのインストール・再起動・内部SSHは完了した。
- マルチユーザーidentityと権限境界は決定したが、アカウントはまだ作成していない。
- LinuxのDocker・AI Cell・Operations Automationはまだデプロイしていない。
- NAS reverse proxyのLinuxへの切り替えは、healthとrollbackを整えてから行う。
これで新しいCodex sessionを開いても、「サーバーIPは何だったか」「NASがapplicationを実行しているのか」「SSH serviceはなぜdisabledだったのか」から説明し直す必要がない。Skillが実際のtopologyとセキュリティ境界、確認済みの状態と未完了の状態をまとめて伝える。
だから今のサーバーは完成したproduction serverではないが、Ubuntuだけを入れた空のSSDでもない。次の作業は、すでに定めた境界の中で人のアカウント、AI Cell、Operations Automation Runtimeを実際に稼働させることだ。
これからは何かをインストールするたびに、「どのアカウントで実行するのか」「どのHOMEとcredentialを所有するのか」「どのportとdata flowだけを許可するのか」を先に問うことになると思う。サーバーを一台作ったというより、サーバーを壊さず拡張し続けるための基準を先に作ったという感覚のほうが強い。
参考資料
-
systemdのsocket unitはsocketを監視し、incoming trafficに応じて対応するserviceを有効化できる。systemd.socket公式マニュアル ↩
-
Docker daemonのUnix socketにアクセスできる
dockergroupはrootレベルの権限を与える。Docker Engine Linuxインストール後の手順 ↩
コメントする