メインコンテンツまでスキップ

Proxmox でおうちクラウドを進化させた

· 約18分
BonyChops
@BonyChops
備考

こちらは長野高専 Advent Calendar 2025 - 13 日目の記事です。

miniPC を買ったので、前々からやってみたかった Proxmox というものにチャレンジしました。

miniPC を買った

ことの発端は友人と Palworld を遊ぶために Raspberry Pi 5 で鯖を立てて遊んでいたのですが、同一 LAN 内から接続している自分すら座標がロールバックするほどお話にならない操作感だったため、鯖をスケールアップしたいなと考えていたことでした。

できるだけ(初期・維持費ともに)金をかけたくないと考え色々調べていたら、どうやら miniPC というのがそれに適しているという話を聞いていたので、Amazon で調べていたら良さそうなのを見つけました

https://amzn.asia/d/eFHTFMI

サクラチェッカーで 1 位だったメーカー(AskHand)の最上位モデルです。このスペックで 3 万はかなり強くないか?と思い購入しました。

価格推移

現在価格(2025/09/20 10:00 現在)は¥ 45,998 になってしまいました 😭
次のタイムセールを狙うのが良いかも。

スペック

コンポーネントスペック
CPURyzen Ryzen5625 6 コア 12 スレッド 2.3 GHz
RAMSO-DIMM DDR4 16GB (8GB x 2)
ストレージm.2 SSD 512GB

OS は Windows 11 がプリインストールされていました。
ライセンスに関する説明はバラバラで、公式では「含まれていない」と記載されていたりレビューでは「ボリュームライセンスが入っている」だったりと少し怪しい感じでしたが、今回は Proxmox で上書きするので何ら問題はないです(実際何が適用されていたかは確認していない)。

Proxmox

そもそも Proxmox とは?

Proxmox は仮想マシンとコンテナを Web ベースの GUI から一元管理できる、OSS の仮想化プラットフォームです。正式には Proxmox VE ( Proxmox Virtual Environment )と呼ばれ、 Debian Linux のカーネルを使用しています。カーネルモジュールに組み込まれた KVM ( Kernel-based Virtual Machine )と LXC ( Linux Containers ) によって VM とコンテナ双方を仮想化できる点が特徴です。

Proxmox とは?基本的な構成や機能と VMWare との違いについて徹底解説 | クラウド導入・システム運用ならアールワークスへ - https://www.rworks.jp/system/system-column/sys-entry/30634/

KVM は Virtual Box のような仮想マシンで、LXC は Docker のような仮装コンテナ技術です。これらを一元管理できるサーバー用途向けプラットフォームと捉えてもらえれば問題ないはず。
KVM を使えば、Linux ではない OS、つまり Windows Server のようなものを走らせることもできるし、LXC を使えば軽量な仮想化レイヤーを用いてアプリケーションを実行できます。

これを使い始めたきっかけはうんちゃまさんの動画だったり、高専の先輩が Proxmox を使っていたのを見て興味を持ったことが挙げられます。

導入

公式の Proxmox VE ダウンロードページから Proxmox VE の Installer ISO をダウンロードします。

ダウンロードした ISO ファイルを USB メモリなりに焼きます。Mac ユーザーの方は bananaEtcher が使い勝手良かったですよ。

運用

ここでは現在と今後の運用について軽く紹介します。過去にこんな構成図を書いたんですが、説明用ではないので見づらすぎます 😅 一応置いておきます。これらは現状全て CT で建てている/建てる予定です。

備考

この章では、そのうちピックアップできそうな部分を軽く説明しています。気になる部分があればコメントなりで聞いていただければ答えられるかもしれません。

Palworld 鯖

当時(鯖を買った頃)は友人と Palworld をするのがトレンドだったので鯖を立てました。いい感じのスペックの鯖用 PC を買った大きな要因にこれが挙げられます。と言うのも、

  • Palworld 鯖の実行には SteamCMD が必要
  • Arm 向けビルドがないため box86, box64 が必要に...
  • その連携がなぜかうまく行かないので出来上がっている Docker イメージを拝借...
  • Raspberry 5 ですら重すぎてロールバック祭り...
  • htop を見る限り、CPU はそこまで使っていない様に見えるけどディスク I/O が張り付いている...外付けストレージ検討かなあ
  • ああめんどくせ!!!!!!!!! もう別マシン買うか

これが動機です。

NAS 側に定時実行スクリプト1を組んでおり、 Palworld CT から rsync でセーブデータをバックアップしています。

名称スペック
CPU制限なし
RAM10 GiB

Minecraft 鯖

お馴染みですね。こちらも NAS 側に定時実行スクリプト1を組んでおり、 Palworld CT から rsync でセーブデータをバックアップしています。

最近ではGeyserというツールを使うと Java 鯖に Bedrock(統合版)のプレイヤーが参加できることを知りました。この鯖でもやってみましたが確かに参加できるみたいです。すごい時代が来ましたね。

名称スペック
CPU2 コア
RAM8 GiB

DNS

dnsmasq で LAN 向けの DNS 鯖を立てています。この記事を書き始めた当初(2025/09 あたり)、拙宅では Buffalo WSR-1800AX4Pというルータを使用していました。IPoE に対応する良いルータだったのですが、DNS サーバーのパフォーマンスが悪く、LAN で Web ページのロードが開始されるのに 3s-10s 待たされることが多々ありました。

注記

この事象について、原因が本当にBuffalo WSR-1800AX4Pだったかどうかはしっかり調査できていません

そこで、DNS 鯖用 CT を建て、LAN クライアントはそこへ問い合わせするようルータの DHCP 鯖設定を変更します。

ただし、Buffalo WSR-1800AX4Pは IPv6 における DNS の矛先を設定できない2ため、RA で DNS 鯖の矛先を LAN 内に周知しています。

/etc/dnsmasq.conf
# IPv6 RA を有効化
enable-ra

# 対象インターフェース
interface=br0

# RA のみ行い、DHCPv6 は使わない
ra-only

# RDNSS(DNSサーバ)を通知
ra-param=br0,0,0
注意

現在は GL.iNet Flint2を使用しており、その OpenWrt 上にカスタマイズ性の高い DNS 鯖があるのでこの CT は使わなくなると思います。 その上で、この RA がどうのみたいな話は適切でない構成である可能性があることに留意してください(現在はこの CT をオフにしている)。

せっかく独自の DNS 鯖を立てたので、この LAN でのみ使える独自 TLD .bonyを設定してみました。

/etc/hosts
# 下記は例
192.168.10.1 router.local router.bony
192.168.10.20 nas.local
192.168.10.50 mc.bony

これで各デバイスの IP アドレスを覚えていなくてもアクセスできるようになります。

独自 TLD はあんまり良くないらしい

今回 LAN 内で展開できる独自 TLD .bony を展開しましたが、本来はあんまりやるべきではないようです。というのも、.bony は ICANN に登録されていない TLD であるため、

  • 将来衝突するリスクがある
  • 環境によってによってはリンクとして正しく動作しない

ことなどが考えられます。適切な運用を考える場合は .home.arpaを使うか、購入済みドメインのサブドメインとして運用しましょう。

名称スペック
CPU1 コア
RAM256 MiB

Nginx

Web ページホスト用に建てています。今の所メインは index.bony 用。

名称スペック
CPU1 コア
RAM256 MiB

Wireguard

軽量で高速な UDP ベースの VPN 鯖。GL.iNet Flint2に標準で搭載されているので退役予定です。

名称スペック
CPU1 コア
RAM512 MiB

step-ca (予定)

LAN 内でも安全な通信をしたいですよね?# DNSで建てた独自 TLD 向けに TLS 証明書を発行しましょう。

Proxmox を建てたばかりの頃、WebAuthn で 2FA を設定したかったが無効な TLS 証明書が使われていたことが起因して3設定できなかったため、自前 mac で mkcert による証明書を発行しこれに当てていました。ただ、これだと

  • サイト(サブドメイン)ごと
  • 有効期限が切れるたび

に実行しないといけないので、

  • .bony向けの Root 証明書 を作成
  • 各 LAN 端末にその Root 証明書を信頼させる
  • step-ca 鯖でサブドメインごとにサーバー証明書を発行する
  • 以降の更新は ACME を用い、建てた step-ca 鯖とやりとりすることで自動更新する

と言った管理にしたいなーと考えています。

Valutwarden (予定)

使用しているパスワードマネージャを自ホストしたいなと考えています。Valutwardenは BitWarden 鯖の Rust 実装です。

IaC 化する

せっかく頑張って設定したあれこれも、その CT を誤って消してしまったら 1 からやり直しです。それは悲しいですよね?
そこで、最近ではこれらの IaC 化に取り組んでいます。

IaC とは、

Infrastructure as Code の略で、CPU、メモリといったサーバのインフラ構築を、コードを用いて自動的に行うことを意味します。

Terraform 連載 第 1 回:いまさら聞けない、IaC ってなに?~ Terraform、IaSQL の紹介~ | NTT テクノクロスブログ - https://www.ntt-tx.co.jp/column/iac/230815/

今回 CT を IaC 化するにあたり、

  1. Proxmox 上の構成設定
  2. CT 内で実行すべきコマンド群

これらを包括できるツールを使う必要があります。上記用途に対して下記を採用しました。

  1. インターンで使った Terraform4
  2. 次回の ISUCON でも使えそうな Ansible5

候補の一つに cloud-init もありましたが、VE 向けのツールで CT には向いていないので今回は採用しませんでした。

Terraform

まずは、「Proxmox 上の構成設定」をコード化する Terraform の設定です。

Terraform を使うにあたり、tf と使うプロバイダーの架け橋となる Provider を使用する必要があります。Proxmox の場合

の何から選ぶことになりますが、 bpg/proxmoxを使うことを強く推奨します。 公式の Provider は滞っており、少なくとも当方が使用している Proxmox VE v9.0.3 では使用できませんでした。

あとは、使い回しそうな部分をまとめたモジュールを作成し、それを使って CT 定義を増やしていきます。
現状は、

  • Cloudflared CT の定義
  • CT の IPv4 一覧を Ansible 用 inventory 出力

のみが設定されています。これにより、Ansible 側で CT の接続先 IPv4 を特定できるわけですね。

terraform/main.tf
module "cloudflared" {
source = "./modules/ct"

target_node = var.target_node
vm_id = var.vm_id + 8

hostname = "cloudflared"
cores = 1
memory_mb = 512
disk_size = 4096

ip = "192.168.2.15/16"
}

locals {
hosts = [
{ name = "cloudflared", ip = module.cloudflared.container_ipv4, role = "cloudflared" },
]
}

resource "local_file" "ansible_inventory" {
filename = "${path.root}/../ansible/inventory.yml"
content = templatefile("${path.root}/inventory.tmpl", { hosts = local.hosts })
}

output "ansible_inventory_path" {
value = local_file.ansible_inventory.filename
}

詳しい実装は bonycloud-infra-sample/terraform を確認してください。

使用する Template について

余談ですが、使用している Template は ubuntu-24.04-standard です。それの前に使用していた、任意で落としてきたテンプレートには openssh-server が同封されておらず、のちの Ansible 実行ができないため頭を抱えていました。
Proxmox が持つ公式の Template には普通に同封されているのでそちらを使いましょう。Datacenter -> (ホスト名) -> (storage) local -> CT Templates -> Templates から一覧を見れます。

Ansible

次に、「CT 内で実行すべきコマンド群」をコード化する Ansible の設定です。

そもそも論、コマンド群なら sh を書いて ssh で叩けば良いのでは?と感じる人も多いと思います。Ansible の恩恵は、その哲学が手続的ではなく宣言的であることにあります。

手続的宣言的
  • パッケージ A をインストールする
  • ファイル B に foobar を追加する
  • サービス C を起動する
  • パッケージ A をインストールされている
  • ファイル B に foobar が存在する
  • サービス C が実行されている

コマンドを実行する ことを目標にするのではなく、 この状態になっていること を目標にします。これにより、現状のサーバー(今回は CT)がその状態かどうかが適宜確認6され、重複実行や意図しない状態になることを避けることができます。

現状 Cloudflared だけ Ansible に起こせています。下記にその例を示します。

ansible/roles/cloudflared/tasks/cloudflared.yml
---
- name: Ensure keyrings dir exists
ansible.builtin.file:
path: /usr/share/keyrings
state: directory
mode: "0755"

- name: Download Cloudflare APT GPG key
ansible.builtin.get_url:
url: https://pkg.cloudflare.com/cloudflare-main.gpg
dest: /usr/share/keyrings/cloudflare-main.gpg
mode: "0644"

- name: Add Cloudflared APT repository
ansible.builtin.apt_repository:
repo: "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared {{ ansible_facts.lsb.codename | default(ansible_facts.distribution_release) }} main"
filename: cloudflared
state: present
register: repo_added

- name: Update apt cache if repo changed
ansible.builtin.apt:
update_cache: yes
when: repo_added is changed

- name: Install cloudflared
ansible.builtin.apt:
name: cloudflared
state: present
update_cache: yes # 念のため(初回やキャッシュ切れ対策)

- name: Ensure /etc/cloudflared exists
ansible.builtin.file:
path: /etc/cloudflared
state: directory
owner: root
group: root
mode: "0755"

- name: Deploy cloudflared config
ansible.builtin.template:
src: config.yml.j2
dest: /etc/cloudflared/config.yml
owner: root
group: root
mode: "0644"
notify: Restart cloudflared

- name: Deploy tunnel credentials
ansible.builtin.copy:
src: "{{ cloudflared_tunnel_id }}.json" # roles/bonycloud-public/files/ に置く
dest: "/etc/cloudflared/{{ cloudflared_tunnel_id }}.json"
owner: root
group: root
mode: "0600"
notify: Restart cloudflared

- name: Deploy service file
ansible.builtin.copy:
src: "cloudflared.service" # roles/bonycloud-public/files/ に置く
dest: "/etc/systemd/system/cloudflared.service"
owner: root
group: root
mode: "0600"
notify: Restart cloudflared

- name: Enable and start cloudflared service
ansible.builtin.systemd:
name: cloudflared
enabled: yes

詳しい実装は bonycloud-infra-sample/ansible を確認してください。

余談

近年 AI が発展してきて、Qiita や Stack Overflow を漁ることがめっきり減りました。多分こういう方は自分だけではないと思います。
そう言った現状の中、ブログを発信するモチベーションとは?と自分自身を問うことがあります(偉そうなことを言いつつそもそもの投稿頻度が低いだろ!というツッコミはできそうですが)。
そう言った中で、少なくともこのブログでできることといえば 自分が面白いとおもったことをアウトプットすることで、近しい興味を持った方の行動のトリガーになること かなと思いました7

情報が溢れ、ほとんどのことが解決している(と感じる)現代、なかなか手を動かすきっかけってないんですよね。自分もそうでした。
そんな中、純粋な興味で手を動かす姿を見た時に自分もやってみたいなーとときめく経験があると思います。そういうのは AI ではあまり得られないんですよね。
この記事ではそれを意識したこともあり、あえて実際のマシンに関することもしっかり目に載せました。
これを読んで自分もやってみたいなーと感じる人がいれば幸いです。

ただ、「人のため」をモチベーションにした行動は、精神衛生上良いと言える状態を保ちながら続けるのは難しいです。
このブログの主目的はあくまでも 自分がやったことを振り返るための備忘録 というスタンスをとりながら、副次的に誰かのためになればより良いよね 👍 みたいな感じでやっていきたいですね。

Footnotes

  1. https://kb.synology.com/ja-jp/DSM/help/DSM/AdminCenter/system_taskscheduler?version=7 2

  2. 外界との接続方式である特定のものを選んでいる場合

  3. 実際この問題を直しても設定できなかったので別問題(起因していなかった)ことが後でわかりましたが

  4. インターン生がプロジェクトの GitHub Actions を AWS CodePipeline に移行した話 | by Bony_Chops | MIXI DEVELOPERS

  5. 【初参加】Web サービスを限界までチューニング、ISUCON に参加してみた - ISUCON14 参加記

  6. 具体的にどう確認し、何を実行するか自体は手続的に定義されています。基本的にはすでに存在する定義を呼び出す形で定義します。

  7. これに関する議論において「そもそも AI の情報源はネットの有象無象からできるものなんだからブログが廃れたら世の中が発展しないだろ!」「最近の AI は自己教師あり学習 etc があるからブログの意義はない!」みたいな主張もあると思いますが、そこらへんに触れるつもりはあんまりないです(自分が発信できる内容がそもそもそれを語れるほどのクオリティがあるとは思えないため)。