はじめに
Proxmox VE (PVE) を2台で運用すると、1台が止まった時点で過半数を割ってクォーラムを失います。これを防ぐ定番の方法が、外部投票ノード(QDevice / corosync-qnetd)の導入です。ただ、そのためだけに物理マシンを用意するのはもったいないので、余っているRaspberry Piなどで動かしたいところです。
私の場合、ラズパイは他の用途にも使っています。ホストの環境を汚さず、権限も最小限にするため、Rootless Podman(一般ユーザー権限) のコンテナで運用することにしましたが、ここで壁にぶつかりました。
Proxmox標準のセットアップコマンド pvecm qdevice setup <IP> は、相手先ホストのrootユーザーへSSH接続(既定はポート22)して、証明書の生成と配置を行う作りになっています。qnetdはコンテナの中にあり、標準の手順ではsshでコンテナ内に入れないので、そのままではセットアップに失敗します。かといって、両者の証明書データベース(NSSDB)を手作業で何往復もコピーして構築するのは手間がかかり、入力ミスや鍵の取り扱いミスも起こしやすくなります。
そこで、次の方法を取りました。
- セットアップの間だけ、コンテナ内でsshdを起動する(実行ごとにランダムな一時パスワードを生成)
- PVE側のssh設定でポートを2222に振り替え、標準の
pvecm qdevice setupをそのまま通す - 10分たつとコンテナ内のタイマーがsshdを終了させ、rootパスワードもロックする
コードはGitHubに公開しています。
👉 https://github.com/t-inada-shokai/podman-proxmox-qdevice
設計のポイント
-
Rootlessコンテナ: コンテナ内のrootは、ホストでは一般ユーザーに対応します。コンテナ内で何かあっても、ホストのroot権限までは及びません。ただし、後述の
network_mode: hostではコンテナがホストのネットワークに直接つながるため、LAN上の他のサービスへ到達できる点には注意が必要です。 -
sshdのオンデマンド運用: 通常時にコンテナが待ち受けているのは
qnetd(ポート5403)だけです。セットアップ時にだけ、./start-auth-wait.shがコンテナ内でsshd(ポート2222)を起動します。- 実行のたびにランダムな一時パスワードを生成し、画面に表示します。固定パスワードは使いません。
- 期限(既定10分)はコンテナ内の
timeoutが管理します。ホスト側のスクリプトが終了しても、SSHセッションが切れても、sshdは期限で終了します。 - sshd終了後と、コンテナ起動時には、rootパスワードをロックします。
- 窓が開いている間のリスク(LANから2222番に到達できること)は残るので、
LoginGraceTime 30とMaxAuthTries 3で試行を絞っています。 -
pvecm qdevice setupの途中で、PVEノードの公開鍵がコンテナ内のauthorized_keysに追加されます。パスワードのロックは公開鍵認証を止めないので、sshdが起動している間は、セットアップを実行したPVEノードのrootの鍵で入れます(コンテナを作り直すと消えます)。
-
ホストネットワークモード: クラスタノードの送信元IPを正しく認識させるため、
network_mode: hostで動かします。2222番と5403番はどちらも非特権ポート(1024以上)なので、Rootlessでもバインドできます。
構築手順の概要
詳しいファイル構成やコードはGitHubリポジトリを参照してください。大まかな流れは次のとおりです。
1. QDevice側(Raspberry Piなど)の準備と起動
リポジトリを一般ユーザー権限でクローンし、ホストの前提条件を確認してから起動します。
git clone https://github.com/t-inada-shokai/podman-proxmox-qdevice.git
cd podman-proxmox-qdevice
./bootstrap-host.sh # subuid / linger / podman-restart / 5403番の開放などを確認
podman compose up -d --build
この時点でコンテナが待ち受けているのは 5403 だけで、SSHポート 2222 は閉じています。
2. 認証待ち受けを開始する
セットアップの直前に、ラズパイ側で次を実行します。
./start-auth-wait.sh
実行すると、コンテナ内でsshd(ポート2222)が起動し、一時パスワードと有効期限が表示されます。途中で閉じたいときは ./start-auth-wait.sh stop です。
3. Proxmox側でセットアップする
事前に、各PVEノードで corosync-qdevice パッケージがインストールされている必要があります (apt install corosync-qdevice)。
PVEノード(作業する1台)の /root/.ssh/config に、次を追記します。pvecm が2222番に接続するようにするためです。
Host 192.168.xxx.xxx
Port 2222
User root
一度手動でSSH接続し、ホストキーを承諾します。パスワードは手順2で表示された一時パスワードです。
ssh root@192.168.xxx.xxx
exit
続いて、標準のセットアップコマンドを実行します。
pvecm qdevice setup 192.168.xxx.xxx -f
途中で ssh-copy-id のパスワード入力を求められたら、同じ一時パスワードを入力します。-f は、PVE各ノード側に残った古いQDevice用の証明書DBを作り直すためのオプションです。新規構築で古い証明書が無ければ不要なこともあります。
実行中に Certificate database ... already exists. Delete it ... と表示されますが、qnetd側のNSSDBはコンテナ起動時に初期化済みのためで、想定どおりの表示です。
既にQDeviceが設定されているクラスタの場合
最後に Qdevice already configured, must be removed before setting up new one! と出て止まります。これは、クラスタ設定(corosync.conf)に既存のQDevice設定があるためです。証明書の再発行は、このエラーが出る前の段階で完了しています。
接続先が今回のホストと同じなら、設定はそのまま使えるので、新しい証明書を読み込ませるために、各ノードで systemctl restart corosync-qdevice を1台ずつ実行すれば足ります。
接続先が違う場合や、やり直したい場合は、両ノードがオンラインの状態で、先に pvecm qdevice remove を実行してから、もう一度 pvecm qdevice setup を実行してください。
4. 後片付け
- 10分たつと、コンテナ内のタイマーがsshdを終了させ(ポート2222が閉じる)、rootパスワードがロックされます。
- PVE側の
/root/.ssh/configに追記したHostの記述は、セットアップ後に削除してください。残しておくと、以降のsshが意図せず2222番に向かいます。
NSSDBのバックアップと復元
qnetdの証明書DB(NSSDB)には秘密鍵が含まれます。ベアメタルが壊れたときに、 restore-nssdb.sh でボリュームを復元して起動するだけで戻せるよう、ageで暗号化してGitで管理します。暗号化した情報は公開リポジトリには置かず、別の非公開リポジトリに保存します。
# 初回のみ: age の鍵を作り、秘密鍵はリポジトリ外(紙やオフライン媒体)にも保管する
mkdir -p ~/.config/age
age-keygen -o ~/.config/age/qnetd.key
age-keygen -y ~/.config/age/qnetd.key > recipients.txt
# 初回のみ: 空の非公開リポジトリを作り ~/qnetd-backup に紐付ける
# cd ~/qnetd-backup
git init -b main ~/qnetd-backup
git -C ~/qnetd-backup remote add origin <非公開リポジトリのURL>
# (中身のある非公開リポジトリを使う場合は、上2行の代わりに次の行を実行してください)
git clone <非公開リポジトリのURL> ~/qnetd-backup
# バックアップ(初回のみ push -u origin main、、2回目以降は git push でよい)
./backup-nssdb.sh --commit && git -C ~/qnetd-backup push -u origin main
# 復元(新しいホストでは、先に非公開リポジトリを ~/qnetd-backup に clone し、
# ageの秘密鍵を ~/.config/age/qnetd.key に置いてから実行)
./restore-nssdb.sh --up
状態の確認
PVE側で、QDeviceが1票を持ってクォーラムを支えていることを確認できます。
root@pve001:~# pvecm status
...
Votequorum information
----------------------
Expected votes: 3
Highest expected: 3
Total votes: 3
Quorum: 2
Flags: Quorate Qdevice
...
root@pve001:~# corosync-qdevice-tool -s
...
QNetd host: 192.168.xxx.xxx:5403
State: Connected
ラズパイ側では、両ノードが接続しているかを確認できます。
podman exec qnetd corosync-qnetd-tool -l
corosync-qdevice を再起動しても Connected に戻ること、コンテナを作り直しても(ボリュームを消さなければ)再接続できることも確認しました。
検証環境
| 項目 | バージョン |
|---|---|
| Proxmox | pve-manager 9.2.21 |
| Raspberry Pi(OS / アーキテクチャ) | Debian 13 trixie / aarch64 |
| Podman | 5.4.2 |
| コンテナのベース | Debian trixie (debian:trixie-slim) |
おわりに
ProxmoxのQDeviceは非常に軽量ですが、標準のセットアップがSSH前提のため、コンテナ化やRootless運用は敬遠されがちでした。「セットアップの間だけコンテナ内でsshdを起動し、ランダムなパスワードと期限で窓を絞る」ことで、公式の自動セットアップをそのまま使いながら、Rootless環境でQDeviceを運用できました。
窓が開いている間のリスクがゼロになるわけではないので、LAN内の信頼できる環境で、必要なときだけ実行する運用をおすすめします。