UGREENのRAID内にコンテナ領域を移行する
- ccf代表
- 4 日前
- 読了時間: 11分
はじめに
前回の記事では、UGREEN DXP6800 Pro に eGPU(RTX 2000 Ada)を載せて、Docker から --gpus all で叩ける状態まで持っていった話を書きました。今回はその続編、というか「続きを動かそうとしたら、また別のところで派手に詰まった」記録です。
結論から書くと、最終的にはこんな構成で安定運用に入れました。
UGREEN DXP6800 Pro
UGOS Pro
Kernel: 6.12.74+deb12-amd64
NVIDIA Driver: 535.261.03
GPU: NVIDIA RTX 2000 Ada Generation (VRAM 16GB)
Docker Compose: Ollama + Open-WebUI
モデル領域: 14.5TB HDD (RAID6) の上に手動 LV + ext4
SSD 領域: コンテナ実行ベースとして温存
ただ、ここに辿り着くまでに「Open-WebUI からモデルが見えない」という一見シンプルな症状を入口に、Docker のネットワーク、SSD の容量枯渇、UGOS のボリューム作成、果てはカーネルと ext4 ドライバの非互換まで降りていく羽目になりました。同じ構成で同じ場所に落ちる人がいるかもしれないので、順番に書いておきます。
症状:Open-WebUI からモデルが消えた
最初の症状はこれだけでした。
「Open-WebUI のモデル選択が空になっている。前は Ollama に pull したモデルが見えていたのに」
Ollama 単体では問題なく動いていて、コンテナ内から ollama list を叩けばモデル一覧もちゃんと返ってきます。
docker exec -it ollama ollama list
NAME ID SIZE MODIFIED
gemma4:31b 6316f0629137 19 GB 42 hours ago
qwen3:14b bdbd181c33f2 9.3 GB 11 days ago
gpt-oss:20b 17052f91a42e 13 GB 11 days ago
qwen3-coder:30b 06c1097efce0 18 GB 11 days ago
llama3.2:3b a80c4f17acd5 2.0 GB 11 days ago
つまり「モデルは存在している、Ollama も動いている、それなのに Open-WebUI からだけ見えない」という状態。ありがちなパターンですが、原因が表面の見た目より深いところに刺さっていたのが今回の特徴です。
原因①:Docker ネットワークの分離
まず疑ったのは Open-WebUI から Ollama API への到達性です。Open-WebUI の環境変数を見ると、こうなっていました。
OLLAMA_BASE_URL=http://ollama:11434
つまりコンテナ名 ollama で名前解決して通信する設定。それ自体は正しいのですが、コンテナのネットワーク所属を見るとこうでした。
# Ollama 側
docker inspect ollama --format '{{json .NetworkSettings.Networks}}' | jq 'keys'
[ "bridge" ]
# Open-WebUI 側
... "Networks": { "llm-stack_default": { ... } }
Ollama は bridge、Open-WebUI は llm-stack_default。別ネットワーク同士なのでコンテナ名で名前解決できない、というオチでした。最初に Ollama を docker run -d --name ollama … で素直に立てていたので、明示的にネットワークを指定していなかったのが効いていました。
対処はシンプルで、Ollama を Open-WebUI と同じネットワークに参加させるだけ。
docker network connect llm-stack_default ollama
docker exec -it open-webui curl -s http://ollama:11434/api/tags
これでコンテナ内から API が叩けるようになり、Open-WebUI でもモデル一覧が復活しました。
ただ「docker network connect はコンテナ再作成で消える」のが嫌だったので、せっかくなので Ollama も compose 配下に取り込んで、Open-WebUI とまとめて宣言しておくことに。これで再起動・再作成にも強い構成になりました。
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
networks:
- llm-stack_default
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
...
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
networks:
- llm-stack_default
depends_on:
ollama:
condition: service_healthy
...
networks:
llm-stack_default:
name: llm-stack_default
external: true
外側で既に存在しているネットワークなので、external: true を付けるのを忘れずに。これがないと「ラベル不一致だぞ」と compose に怒られます。
原因②:SSD の容量が枯渇していた
compose を up し直したら、今度は別のエラーが Open-WebUI のログに出ました。
UserWarning: Not enough free disk space to download the file.
The target location /app/backend/data/cache/embedding/models/...
only has 0.00 MB free disk space.
「ディスクが空いてない」。df を打って唖然としました。
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p7 107G 103G 0 100% /overlay
Docker 関連ファイル(イメージ・ボリューム)が /overlay の上に乗っているのですが、そこが完全に埋まっていた。Ollama のモデルだけで 62GB 食っているうえに、過去の検証で残った大量のイメージや、消し忘れたボリュームが溜まっていました。
掃除すべきものを整理するとこんな感じでした。
古いバージョンの ollama/ollama イメージ(複数)
使っていない nvidia/cuda イメージ
compose で作り直す前の llm-stack_open-webui ボリューム
順番に削除。
docker image prune -a # → 7.15GB 回収
docker volume rm llm-stack_open-webui # → 1.12GB 回収
これで /overlay に 19GB の空きが戻り、Open-WebUI の埋め込みモデル(約500MB)のダウンロードも通って起動完了。とりあえずモデル選択は復活したのですが、ここで根本的な問題に気付きます。
「そもそも 107GB の NVMe SSD で運用してる時点で、いずれまた詰む」
Ollama のモデルだけで 62GB、Docker のレイヤーキャッシュも積み重なる。これ、HDD 側の 14TB を使わない手はない、というか使わないと持続不能だな、と方針を切り替えました。
原因③:14TB の HDD が SSH 側から見えない
DXP6800 Pro は 3.6TB × 6 本の RAID6 で、合計 14.5TB の領域があります。ところが df -h を打っても、それらしいマウントが出てきません。
df -h | grep -v -E "tmpfs|loop|overlay"
udev 3.8G ...
/dev/nvme0n1p6 3.9G ...
/dev/nvme0n1p1 256M ...
/dev/nvme0n1p2 2.0G ...
efivarfs 192K ...
/dev/nvme0n1p3 8.6M ...
SSD 系のパーティションしか見えない。HDD はどこに消えたのか?
lsblk を見ると、ディスク自体は認識されていてアレイも組まれていました。
sda〜sdf 3.6T × 6本
└ md1 14.5T RAID6
つまり RAID6 アレイは構築済み、なのにどこにもマウントされていない。さらに blkid で見ると md1 は LVM の PV になっていて、VG も作られているのに LV が空という、いかにも「途中まで作って止まっている」状態でした。
pvs
PV VG Fmt Attr PSize PFree
/dev/md1 ug_76CE3A_1778657717_pool1 lvm2 a-- 14.49t 14.49t
lvs
(出力なし)
VG 名のプレフィックス ug_ から見ても、UGOS が裏で作ったやつです。なるほど、UGOS の Web UI から「ボリュームを作成」を実行すれば LV ができてマウントされる、という UGOS 設計だなと納得しました。
原因④:UGOS のボリューム作成が、毎回ロールバックされる
ここから本番でした。
UGOS Web UI の「ストレージマネージャー」を開いて、ストレージプールに対してボリュームを作成。ファイルシステムは ext4 を選択。ウィザードを進めると、画面上は「ボリューム 1 の作成が完了しました」と通知が出ます。一瞬「やった、終わった」と思うのですが、その直後にこうなる。
「ボリュームがマウントされていない」
しばらく(数分)待つと、作成されたはずのボリュームが UGOS の画面から消えます。LV も同じく消えています。何度やっても同じ。さすがにこれは挙動として変なので、UGOS が何をやっているのかをカーネルログ側から眺めることにしました。
そして dmesg に決定的な行が出ていました。
EXT4-fs (dm-0): Couldn't mount because of unsupported optional features (20000000)
これで全部繋がりました。
UGOS のボリューム作成プロセスは内部で「LV 作成 → mke2fs で ext4 フォーマット → マウント」を順に実行します。LV の作成も、ext4 フォーマット自体も成功している。ところがマウントの段階で、カーネルの ext4 ドライバが「そのオプション機能(0x20000000)は知らない」と拒否している。UGOS は失敗を検知してロールバック処理を走らせるので、LV ごと巻き戻されて画面から消える、というカラクリです。
なぜこんなことが起きるかというと、前回の記事で eGPU を動かすために UGOS の独自カーネル(6.12.30+)を捨てて、Debian backports の 6.12.74+deb12-amd64 に差し替えていたからです。UGOS のユーザーランド(mke2fs)は元のカーネルに合わせた機能フラグを ext4 に付けにいくのに対し、Debian backports カーネル側の ext4 ドライバはその組み合わせを認識できない。eGPU を動かすためにカーネルを入れ替えたことが、ストレージ管理の側に副作用として出てきていたわけです。
「カーネルを元に戻せば一発で直る」のは間違いないのですが、それをやると eGPU が使えなくなる。本末転倒です。カーネル交換と UGOS の互換性、どちらかを諦めないといけないという構造的な問題に行き当たりました。
解決:UGOS をバイパスして、SSH 側で LV を手動構築する
少し考えて、こう割り切りました。
UGOS の Web UI からのストレージ管理は今回のホスト構成では諦める
VG までは UGOS が作ってくれているので、それは使わせてもらう
LV 作成・mkfs.ext4・マウントは SSH 側で手動でやる
Docker のボリュームとして使えればよく、UGOS の共有フォルダ機能までは要らない
ポイントは mkfs.ext4 のオプションをあえて控えめに指定すること。UGOS が裏で付けていたであろう機能フラグを最初から無効にしておけば、Debian backports カーネルでも素直にマウントできます。
# 干渉防止のため Docker を一旦停止
systemctl stop docker.socket docker.service containerd
# UGOS が作った VG を再利用して LV を作る(今回は 10TB だけ確保)
lvcreate -L 10T -n data ug_76CE3A_1778657717_pool1
# 問題のオプション機能を除外して ext4 フォーマット
mkfs.ext4 -O '^casefold,^verity,^orphan_file' \
/dev/ug_76CE3A_1778657717_pool1/data
# マウントポイントを用意してマウント
mkdir -p /volume1
mount /dev/ug_76CE3A_1778657717_pool1/data /volume1
# 永続化(再起動後も自動でマウント)
UUID=$(blkid -s UUID -o value /dev/ug_76CE3A_1778657717_pool1/data)
echo "UUID=$UUID /volume1 ext4 defaults,noatime,nofail 0 2" >> /etc/fstab
mount -a # 構文チェックを兼ねて
# Docker を戻す
systemctl start containerd docker.service docker.socket
結果。
df -h /volume1
/dev/mapper/ug_76CE3A_1778657717_pool1-data 10T 28K 9.5T 1% /volume1
14TB プールの上に 10TB の ext4 領域を確保して、ようやくまともに使える状態になりました。これでやっと、本来やりたかった「Ollama のモデルを HDD に置く」に進めます。
注意点として、この方法で作った領域はUGOS Web UI からは「正規のボリューム」として認識されません。共有フォルダ機能、スナップショット、UGOS のバックアップ対象などから外れます。NAS としての標準機能を捨てる代わりに、Docker のストレージとしては自由に使える、というトレードオフです。eGPU を載せて Docker 推論サーバとして使うなら、これで割り切るのが現実的だと思います。
Ollama データを SSD から HDD に引っ越す
ここからは普通の作業です。compose を一度落とし、既存の named volume の中身を新しい HDD 側にコピーして、bind mount に切り替えます。
cd /opt/llm-stack
docker compose down
# 移行先ディレクトリ
mkdir -p /volume1/docker/ollama /volume1/docker/open-webui
# 中身を丸ごとコピー(ACL は backports カーネルで未対応なので -A は外す)
rsync -aHX --info=progress2 \
/overlay/docker/volumes/ollama/_data/ /volume1/docker/ollama/
rsync -aHX --info=progress2 \
/overlay/docker/volumes/open-webui/_data/ /volume1/docker/open-webui/
HDD なのでもっと遅いかなと思いましたが、RAID6 の並列読み書きが効いているのか、転送はだいぶ気持ちよかったです。
43,645,727,863 100% 194.61MB/s 0:03:33 (xfr#27, to-chk=0/38)
41GB を 3分半。普通の単発 HDD だと出ない数字なので、RAID6 構成のありがたみを実感しました。
compose の volumes を named volume から bind mount に書き換えて、起動。
services:
ollama:
...
volumes:
- /volume1/docker/ollama:/root/.ollama # ← bind mount
open-webui:
...
volumes:
- /volume1/docker/open-webui:/app/backend/data # ← bind mount
docker compose up -d
docker exec ollama ollama list
NAME ID SIZE MODIFIED
qwen3:14b bdbd181c33f2 9.3 GB 12 days ago
gpt-oss:20b 17052f91a42e 13 GB 12 days ago
qwen3-coder:30b 06c1097efce0 18 GB 12 days ago
llama3.2:3b a80c4f17acd5 2.0 GB 12 days ago
……4 モデル。ひとつ足りない。gemma4:31b(19GB)が消えていました。サイズ感的に、過去のどこかのタイミング(おそらく一度コンテナを作り直したあたり)で実体が落ちていたようです。HDD 側に直接 pull し直して終了。
docker exec -it ollama ollama pull gemma4:31b
これで 5 モデル揃いました。
最終的なディスク構成
整理するとこうなりました。
領域 | 用途 | サイズ |
NVMe SSD (/overlay) | Docker イメージ・コンテナ実行領域 | 107GB(うち空き ~37GB) |
HDD RAID6 (/volume1) | Ollama モデル・Open-WebUI データ | 10TB(うち空き ~9.5TB) |
SSD は「Docker を動かすための場所」、HDD は「データを置く場所」と役割を分離。SSD が満杯になってデプロイが詰まる悪夢からも解放されました。
振り返り:このルートを通る人へのメモ
最後に、同じことをやろうとしている人向けに「このルートに入ったら気を付けるところ」を5つ書いておきます。
1. eGPU 用にカーネルを差し替えたら、UGOS のストレージ管理は半分諦める
UGOS の Web UI は独自カーネル前提で動いている部分があります。Debian backports のカーネルに乗り換えると、ボリューム作成の mkfs/mount パスで噛み合わなくなります。今回のような EXT4-fs: unsupported optional features を踏んだら、まずカーネルと UGOS の互換性を疑うのが早いです。
2. dmesg をリアルタイムで眺める癖をつける
UGOS 側の Web UI は「ボリューム作成が完了しました」と表示しますが、その裏で dmesg はちゃんと拒否の悲鳴を上げています。UI のメッセージだけ見て切り分けようとすると一日溶けます。dmesg -wT を別ターミナルで流しっぱなしにしておくと、原因が嫌でも目に飛び込んできます。
3. UGOS の VG はそのまま使える
UGOS が作る LVM の VG(ug_xxxx_pool1)は、SSH からも普通の LVM として扱えます。lvcreate で LV を切って、自分で mkfs して fstab に書く、というオーソドックスな運用に持ち込めます。UGOS と完全に決別するわけではなく、レイヤを下げて共存する、というイメージ。
4. mkfs.ext4 のオプションは控えめに
マウントできないファイルシステムを毎回作っても意味がないので、最初から -O '^casefold,^verity,^orphan_file' を付けて、新しめのオプション機能を切っておくのが安全です。データ用途であればこれで困ることはありません。
5. rsync は backports カーネル環境では -A を外す
細かい話ですが、rsync -aHAX の -A(ACL)は backports 環境だとサポートされず、コマンドごと弾かれて1バイトもコピーされません。-aHX で十分です。ここで気付かずに「コピーしたつもりで空ボリュームに対してコンテナを起動 → モデル消失」という遠回りを一度しました。
おわりに
今回のトラブルは入口こそ「Open-WebUI でモデルが出ない」という地味なものでしたが、辿っていくとカーネル差し替えの影響が思わぬ場所(ext4 のオプション機能)に出ていて、最終的には UGOS のストレージ管理を一段下のレイヤで置き換えるところまで行きました。
NAS としての一体感は少し損なわれますが、その代わり「NVMe SSD + 14TB HDD + RTX 2000 Ada + Ollama + Open-WebUI」が一台のミドルレンジ NAS の中で完結する、という構成は手元に残りました。常時稼働している NAS の中で複数のローカル LLM が走っていて、ブラウザからすぐ叩ける。当初やりたかったことそのものです。
同じ DXP6800 Pro 改造ルートに入って、同じ場所で詰まっている方の参考になれば嬉しいです。次は載せたモデルたちで何ができるか、運用編をまた書きたいと思います。
最後まで読んでいただき、ありがとうございました。



コメント