Docker Swarmのボリューム設計|localドライバがノードをまたげない理由と共有ストレージの選び方

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Docker > Docker Swarmのボリューム設計|localドライバがノードをまたげない理由と共有ストレージの選び方
「Docker Swarmでステートフルなサービスを動かしたら、タスクが別ノードへ移動したときにデータが消えた」
「localボリュームを設定したはずなのに、ノードごとにデータが食い違う」
そういったトラブルに直面したことはないでしょうか。

Docker Swarmのボリューム設計でハマりやすい最大の落とし穴は、localドライバが「ノードごとに独立した実体」を作るという仕様です。単一ホストで使い慣れたnamed volumeをそのままSwarm構成に持ち込むと、タスクが再配置された瞬間に別ノードの空ボリュームを参照してしまいます。

この記事では、localドライバがなぜノードをまたげないのかを構造から整理し、placement constraintによる回避策の限界、NFSやCIFSを使った共有ストレージの構成手順、そしてバックアップ経路の考え方まで、実サーバーの確認例を交えて解説します。

動作確認環境: Rocky Linux 9.4(Swarm manager 1台 + worker 2台・Docker Engine 26.x)

この記事のポイント

・localドライバはノードごとに別の実体を持ち、ノードをまたいだデータ共有はできない
・タスクが別ノードへ再配置されると直前のデータは参照できなくなる
・placement constraintで縛る回避策は再配置時の可用性とのトレードオフがある
・NFSをvolume driverオプションで指定すると全ノードが同一データを共有できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

localドライバが「ノードごとに別の実体」として作られる仕組み

Docker Swarmでサービスを作成するとき、compose.yml(またはstack.yml)に次のようなボリューム定義を書くことが多いと思います。

# docker-stack.yml(よくある書き方) version: "3.8" services: db: image: mysql:8.0 volumes: - db_data:/var/lib/mysql volumes: db_data: driver: local # ← これがデフォルト

ここで重要なのは、driver: local は「このボリュームをどのノードに作るか」を管理しないという点です。
Swarmがタスクをworker-1に配置すればworker-1の /var/lib/docker/volumes/db_data/ に実体が作られ、worker-2に配置すれば worker-2の同パスに別の実体が作られます。

つまり「db_data」という名前は共有されても、中身(ファイル)は各ノードで独立しています。

# managerノードで確認 $ docker service inspect --format '{{.Spec.TaskTemplate.ContainerSpec.Mounts}}' db_service [{Volume db_data /var/lib/mysql true }] # worker-1でボリューム一覧を確認 $ docker volume ls DRIVER VOLUME NAME local db_data # worker-2でも同名のボリュームが存在するが中身は別 $ docker volume inspect db_data [ { "Name": "db_data", "Driver": "local", "Mountpoint": "/var/lib/docker/volumes/db_data/_data", ... } ]

タスクが別ノードへ再配置されるとデータが見えなくなる仕組み

Swarmでは、ノード障害・ローリングアップデート・スケールダウン後のスケールアップなど、多くの場面でタスクが別ノードへ再配置されます。

1. 再配置によるデータ消失の流れ

具体的なシナリオを追ってみましょう。

・worker-1でdbタスクが稼働し、/var/lib/mysql にデータを書き込む
・worker-1が何らかの原因でダウン(または docker node update --availability drain worker-1 を実行)
・Swarmがworker-2へタスクを再配置する
・worker-2には 空のdb_data しかないため、MySQLは初期化状態で起動してしまう

# worker-1をdrainにしてタスクを強制移動させる $ docker node update --availability drain worker-1 worker-1 # タスクの配置先が変わったことを確認 $ docker service ps db_service ID NAME IMAGE NODE DESIRED STATE CURRENT STATE abc123def456 db_service.1 mysql:8.0 worker-2 Running Running 30 seconds ago xyz789uvw012 \_ db_service.1 mysql:8.0 worker-1 Shutdown Shutdown 35 seconds ago # worker-2のログ(MySQLが初期化を開始している) $ docker service logs db_service db_service.1.abc123def456@worker-2 | 2026-09-12T10:15:23.012Z 0 [System] [MY-013169] Initializing database...

2. なぜ単一ホストでは問題にならないか

単一ホストでは「ノード = 1台」のため、タスクが停止して再起動しても同じマシンの同じボリュームを参照します。Swarmの問題はタスクが「別のマシン」に移ることで初めて顕在化します。
既存のnamed volumeの解説記事はほぼ全てシングルホスト前提で書かれているため、Swarm環境での動作を誤解しやすい点に注意が必要です。

placement constraintで縛る回避策とその限界

手っ取り早い回避策として、特定のノードにタスクを固定するplacement constraintがあります。

# docker-stack.yml services: db: image: mysql:8.0 volumes: - db_data:/var/lib/mysql deploy: placement: constraints: - node.labels.role == db # dbラベルが付いたノード専用 # ノードにラベルを付ける $ docker node update --label-add role=db worker-1

この方法はシンプルで導入コストが低く、ステートフルサービスを特定ノードに縛りたい場合に有効です。
ただし、以下の限界があります。

そのノードが落ちるとサービスも落ちる(他ノードへの自動フェイルオーバーが機能しない)
・Swarmの分散メリット(高可用性)を捨てることになる
・ノードのメンテナンス(OS更新・ハードウェア交換)のたびにデータ移行作業が発生する
・レプリカ数を2以上にすると、別々のノードに別々の空ボリュームが作られる問題が再燃する

つまり、placement constraintは「回避策」であって「解決策」ではありません。
本番でステートフルなサービスの可用性を確保するなら、共有ストレージが必要になります。

NFSボリュームドライバでノードをまたぐ共有ストレージを構築する

最も広く使われている共有ストレージ方式がNFS(Network File System)です。
DockerはNFSをvolumeのdriverオプションとして指定でき、全ノードが同じNFSエクスポートパスをマウントできます。

1. NFSサーバー側の準備

まずNFSサーバー(ここでは別の専用サーバーまたはNASを想定)でエクスポートを設定します。

# NFSサーバー側(nfs-server.example.com)で設定 $ mkdir -p /exports/db_data $ chmod 755 /exports/db_data # /etc/exportsにエクスポート定義を追加 $ cat /etc/exports /exports/db_data 192.168.10.0/24(rw,sync,no_subtree_check,no_root_squash) # 設定を反映 $ exportfs -ra $ showmount -e localhost Export list for localhost: /exports/db_data 192.168.10.0/24

Swarmの各ノードからNFSサーバーへの疎通確認も行っておきます。mountコマンドの使い方を事前に確認しておくと、NFSマウントのトラブル対応がスムーズです。

2. Swarmノード側でNFSボリュームを作成する

Dockerのvolume createコマンドに --opt でNFSオプションを渡します。
全Swarmノード(managerとworker全台)で同じコマンドを実行する必要があります。

# 全ノードで実行(manager・worker共通) $ docker volume create \ --driver local \ --opt type=nfs \ --opt o=addr=192.168.10.100,rw,sync,hard,nointr \ --opt device=:/exports/db_data \ db_data_nfs # 作成されたボリュームの詳細を確認 $ docker volume inspect db_data_nfs [ { "Name": "db_data_nfs", "Driver": "local", "Options": { "device": ":/exports/db_data", "o": "addr=192.168.10.100,rw,sync,hard,nointr", "type": "nfs" }, "Mountpoint": "/var/lib/docker/volumes/db_data_nfs/_data", "Scope": "local" } ] # 実際にマウントされているか確認(コンテナ起動後) $ mount | grep nfs 192.168.10.100:/exports/db_data on /var/lib/docker/volumes/db_data_nfs/_data type nfs4 (rw,sync,hard)

3. docker-stack.ymlでNFSボリュームを宣言する

stack.ymlでは external: true を使い、手動で作成したボリュームを参照させます。

# docker-stack.yml version: "3.8" services: db: image: mysql:8.0 volumes: - db_data_nfs:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_pw secrets: - db_root_pw volumes: db_data_nfs: external: true # ← docker volume createで作成済みのボリュームを使う secrets: db_root_pw: external: true # デプロイ $ docker stack deploy -c docker-stack.yml myapp

external: true にすることで、Dockerは新規にボリュームを作成せず、既存の db_data_nfs をそのまま使います。タスクがworker-1からworker-2に移っても、両ノードとも同じNFSパスをマウントしているためデータは継続して参照できます。

CIFSボリュームドライバでWindowsファイルサーバーと共有する

NFS以外の選択肢として、CIFS(SMB)も利用できます。Windows Server上の共有フォルダや、NASのSMB共有と組み合わせるケースに適しています。

# CIFSボリュームの作成(全ノードで実行) # cifs-utilsパッケージが必要: dnf install -y cifs-utils $ docker volume create \ --driver local \ --opt type=cifs \ --opt device=//192.168.10.200/db_share \ --opt o=username=svcaccount,password=xxxxx,domain=CORP,vers=3.0 \ db_data_cifs # マウント確認 $ docker volume inspect db_data_cifs [ { "Name": "db_data_cifs", "Driver": "local", "Options": { "device": "//192.168.10.200/db_share", "o": "username=svcaccount,password=xxxxx,domain=CORP,vers=3.0", "type": "cifs" } } ]

CIFSを使う場合の注意点は以下の通りです。

パスワードがボリューム定義に含まれるため、Swarm secretsや環境変数との組み合わせ管理が必要
・MySQL・PostgreSQLのようなDBはCIFSボリューム上での動作を公式サポートしていないケースがある(ロック機構の違い)
・静的ファイル(アセット・アップロードファイル)の共有には問題なく使える
・NFSと比べてLinux環境ではオーバーヘッドが大きい傾向がある

バックアップ経路の考え方

共有ストレージを導入した場合、バックアップはNFS/CIFSサーバー側で行うのが基本です。

1. NFSサーバー側でのスナップショット・バックアップ

# NFSサーバー上でrsyncを使って別ディスクにバックアップ $ rsync -av --delete /exports/db_data/ /backup/db_data/ # cronで毎日深夜に実行する例(NFSサーバー上のcrontab) 0 2 * * * rsync -av --delete /exports/db_data/ /backup/db_data/ >> /var/log/db_backup.log 2>&1

2. DBのダンプをSwarmサービス内から取得する場合

NFS共有でもDBエンジンが動いているコンテナからダンプを取得するのが最も確実です。

# 稼働中のdbコンテナIDを取得 $ docker ps --filter "name=myapp_db" --format "{{.ID}}" a1b2c3d4e5f6 # mysqldumpをローカルに取得 $ docker exec a1b2c3d4e5f6 \ mysqldump -u root -p --all-databases \ > /backup/mysql_$(date +%Y%m%d).sql # または Swarmサービス経由(managerから) $ docker service exec myapp_db \ mysqldump -u root -p --all-databases \ > /backup/mysql_$(date +%Y%m%d).sql

注意点として、docker service exec は Docker Engine 25以降でサポートされています。それ以前のバージョンでは docker exec にコンテナIDを直接指定してください。

本記事のまとめ

課題・目的 対策・手段
localドライバがノードをまたげない理由を知りたい localドライバは各ノードに独立した実体を作るためデータは共有されない
タスク再配置後もデータを保持したい(簡易) placement: constraintsで特定ノードに固定する(可用性とのトレードオフあり)
全ノードでデータを共有したい(本格対応) docker volume create --opt type=nfsでNFSボリュームを全ノードに作成する
stack.ymlで手動作成ボリュームを使う volumes:ブロックにexternal: trueを指定する
WindowsファイルサーバーとLinux Swarmを連携させたい docker volume create --opt type=cifs --opt device=//NASアドレス/共有名で全ノードにCIFSボリュームを作成する(DBには非推奨・静的ファイル向き)
NFSボリュームのバックアップを取りたい NFSサーバー側でrsync、またはコンテナ内からdumpを直接取得する
Docker Swarmのボリューム設計でつまずく原因の多くは、単一ホスト前提の知識をそのままマルチノードに持ち込むことにあります。
localドライバの「ノードごとに別の実体」という仕様を理解した上で、可用性要件に合わせてplacement constraint(シンプル・低コスト)かNFS共有(高可用性・運用コストあり)を選択してください。

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、本記事で紹介したDocker Swarmのボリューム設計をさらに深く学べる講座を用意しています。
Dockerマスター講座の詳細はこちら >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。