「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オプションで指定すると全ノードが同一データを共有できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
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
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" } } ]
・パスワードがボリューム定義に含まれるため、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を直接取得する |
localドライバの「ノードごとに別の実体」という仕様を理解した上で、可用性要件に合わせてplacement constraint(シンプル・低コスト)かNFS共有(高可用性・運用コストあり)を選択してください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、本記事で紹介したDocker Swarmのボリューム設計をさらに深く学べる講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:Docker Swarmにcompose.ymlをそのまま持ち込めるか|docker stack deployで効くdeployキーと無視されるbuildの扱い
- この記事の属するカテゴリ:Dockerへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます