2019年7月7日日曜日

docker-compose で nfs ボリュームを使ってみた

概要

前回 docker stack を使って Swarm 環境に docker-compose で定義したコンテナやネットワーク、ボリュームをデプロイしました
その際にボリュームがコンテナホスト間で共有できない問題を取り上げました
解決策の一つとして nfs があるようなので今回は nfs を使ったボリューム共有の方法を紹介したいと思います

環境

  • macOS 10.14.5
  • Vagrant 2.1.1
  • Ubuntu 16.04 LTS
  • docker 18.09.7
  • nfs-kernel-server 1.2.8

nfs サーバ構築

Vagrant で構築した Ubuntu 16.04 上に構築します
過去の記事で紹介しているのでそちらを参考にして構築してください

なお今回は動作確認用に nfs で exports したパスに root ユーザでファイルを 1 つ作成しています

  • date > /opt/nfs/hoge.txt

とりあえず docker で nfs ボリュームを触ってみる

docker-compose.yml を書く前にまずは docker コマンドだけで nfs ボリュームを試してみます
というかそもそも docker コマンドでの挙動を理解していないと docker-compose にしても理解不能になってしまうと思います

nfs ボリューム作成

volume create で作成できます
nfs の場合 --opt でいろいろと指定する必要があります

  • docker volume create --driver local --opt type=nfs --opt o=addr=172.28.128.3,rw,nfsvers=4 --opt device=:/opt/nfs nfs-volume

今回主に使用している --opt は以下の通りです

  • --opt type=nfs
  • --opt o=addr=172.28.128.3,rw,nfsvers=4
  • --opt device=:/opt/nfs

1 つ目はタイプを指定します
docker 18.09.7 の場合、特にボリューム用のプラグインなどを別途インストールしなくても上記の形式で記載することで nfs が使用できます
2 つ目は nfs をマウントする際のオプションを指定します
これは nfs 自体のオプションとほぼ同じになります
詳しくはこの辺りを御覧ください
なお今回は「nfs サーバの IP アドレス」「Read/Write の許可」「nfs サーバのバージョン」を指定しています
3 つ目は nfs で export したパスを指定します

これで nfs ボリュームが作成されます
inspect した結果は以下の通りです

  • docker volume inspect nfs-volume
[
    {
        "CreatedAt": "2019-07-04T01:49:07Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/nfs-volume/_data",
        "Name": "nfs-volume",
        "Options": {
            "device": ":/opt/nfs",
            "o": "addr=172.28.128.3,rw,nfsvers=4",
            "type": "nfs"
        },
        "Scope": "local"
    }
]

コンテナでマウントしてみる

では作成した nfs ボリュームをマウントしてみましょう
docker コマンドでボリュームをマウントするには -v (or --mount) オプションを使います
今回は挙動を確認しやすい redis イメージを使っています

  • docker run -d -v nfs-volume:/opt/nfs --name redis redis

これでマウントできます
コンテナが起動したら df コマンドでマウント状況を確認してみましょう

  • docker exec redis df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay         9.7G  2.0G  7.7G  21% /
tmpfs            64M     0   64M   0% /dev
tmpfs           496M     0  496M   0% /sys/fs/cgroup
/dev/sda1       9.7G  2.0G  7.7G  21% /data
:/opt/nfs       9.7G  2.2G  7.6G  22% /opt/nfs
shm              64M     0   64M   0% /dev/shm
tmpfs           496M     0  496M   0% /proc/acpi
tmpfs           496M     0  496M   0% /proc/scsi
tmpfs           496M     0  496M   0% /sys/firmware

こんな感じで /opt/nfs にマウントされていれば OK です
読込できるか確認してみます

  • docker exec redis cat /opt/nfs/hoge.txt

=> Thu Jul 4 01:36:43 UTC 2019

こんな感じで事前に作成したファイルの内容が表示されれば OK です

書き込みしてみる

次に書き込みしてみましょう
先程のコンテナ上で以下のコマンドを実行してみます

  • docker exec redis /bin/sh -c "date > /opt/nfs/hoge2.txt"

問題なく書き込めると思います
また nfs サーバ側にもデータあることを確認できると思います

権限に注意

過去の記事の手順で nfs サーバを構築した場合、基本的には root ユーザでのみ書き込みができるようになります

例えば vagrant 環境なので vagrant ユーザからの読み書きができるようにする場合は nfs サーバ側でマウントポイント (/opt/nfs) の所有者を以下のように変更する必要があります

  • chown -R vagrant:vagrant /opt

また exports する場合にも no_root_squash を削除しなければいけません

  • vim /etc/exports
/opt/nfs 172.28.128.0/24(rw,no_subtree_check,async)

この設定をすると vagrant ユーザで読み書きできる nfs のパスが作成できるのですがもしこのパスをコンテナで使おうとすると読みはできますが書き込みはできません
なぜなら redis コンテナ上では root ユーザで読み書きが行われる上記の nfs サーバの設定だと権限がないためです

docker は基本的に root ユーザでコンテナは動作するため今回の nfs サーバの設定で基本的には大丈夫だと思いますがもしユーザを変える場合には注意しましょう
もし別のユーザで動作させる場合には nfs サーバで別のパスをそのユーザ専用に作成してあげると良いと思います

既存のボリュームにマウントはできないのか

redis コンテナの場合 /data にダンプデータが出力されます
基本的にはこのパスをホスト側でマウントしたりしてデータの永続化を行います
なら /data を nfs ボリュームでマウントできないか試してみました

  • docker run -d -v nfs-volume:/data --name redis redis

こんな感じでコンテナ側のマウントパスを /data に変更してあげます
これで df を実行すると以下のようになります

  • docker exec redis df -h
Filesystem             Size  Used Avail Use% Mounted on
overlay                9.7G  2.0G  7.7G  21% /
tmpfs                   64M     0   64M   0% /dev
tmpfs                  496M     0  496M   0% /sys/fs/cgroup
172.28.128.3:/opt/nfs  9.7G  2.2G  7.6G  22% /data
/dev/sda1              9.7G  2.0G  7.7G  21% /etc/hosts
shm                     64M     0   64M   0% /dev/shm
tmpfs                  496M     0  496M   0% /proc/acpi
tmpfs                  496M     0  496M   0% /proc/scsi
tmpfs                  496M     0  496M   0% /sys/firmware

これでダンプファイルが出力されるかテストしてみましょう

  • docker exec redis redis-cli set a a
  • docker stop redis

nfs サーバ側を直接確認すると dump.rdb がちゃんと作成されていました

  • ls -l /opt/nfs/dump.rdb
-rw-r--r-- 1 999 docker 102 Jul  4 02:13 /opt/nfs/dump.rdb

が気になったのは 999:docker というユーザの権限で保存されていたことです
更に上位の階層を見ると nfs フォルダが 999 ユーザの所有権になっていました

  • ls -l /opt/
drwxr-xr-x 2  999 root 4096 Jul  4 02:13 nfs

詳しく調査していないので、おそらくですが dockerd が都合の良いように nfs 側のマウントパスの所有者を書き換えたんだと思います
一応コンテナの root ユーザからは問題なく読み書きできるので挙動的にバグることはないのですが少し気持ち悪い感じはします

なおこの後再度 redis コンテナを起動してデータを確認しましたがちゃんと前のデータが復元されていました

  • docker start redis
  • docker exec redis redis-cli get a

=> a

本題: docker-compose.yml で使ってみる

ではいよいよ docker-compose.yml で nfs ボリュームを使ってみます

  • vim docker-compose.yml
version: '3.4'

services:
  redis:
    image: redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    deploy:
      placement:
        constraints: [node.role == worker]
volumes:
  redis_data:
    driver_opts:
      type: nfs
      o: "addr=172.28.128.3,rw,nfsvers=4"
      device: ":/opt/nfs"

docker コマンド時に最後にやった redis + nfs ボリュームを実現するための docker-compse.yml になります
これで stack deploy してみましょう

  • docker stack deploy -c docker-compose.yml test
  • docker stack ls
NAME                SERVICES            ORCHESTRATOR
test                1                   Swarm
  • docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
yszxwqr2fjsn        test_redis          replicated          1/1                 redis:latest        *:6379->6379/tcp

こんな感じになれば OK です
redis-cli でデータを保存しておきます

  • redis-cli set a aa

なお今回は worker 側にコンテナを立ち上げているので worker 側のホストで exec してあげれば df などでマウント状況も確認できます (前述と同じマウント結果なので省略)

あとは stack を削除してデータが nfs 上に残っているかとそれを復元できるか再度確認してみましょう

  • docker stack rm test
  • docker stack deploy -c docker-compose.yml test
  • redis-cli get a

=> aa

こんな感じで復元できれば成功です
なお nfs サーバの /opt/nfs に dump.rdb があるのも確認できました

動作するコンテナを manager 側にした場合どうなるのか

ここで疑問に思ったのは nfs ボリュームと言えど結局ドライバは local ドライバなので作成されているボリュームは worker 側にしかありません
なので結局ボリュームをコンテナホスト間で共有できていないことになってしまいます
では nfs ボリュームを manager 側にも作成すれば結局見ている nfs サーバは同じなのでボリュームが違えどデータは同じなのではどうことで試してみました
docker-compose.yml の placement.constraints を manager に変更します

  • vim docker-compose.yml
version: '3.4'

services:
  redis:
    image: redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    deploy:
      placement:
        constraints: [node.role == manager]
volumes:
  redis_data:
    driver_opts:
      type: nfs
      o: "addr=172.28.128.3,rw,nfsvers=4"
      device: ":/opt/nfs"

これで再度 redis コンテナを立ち上げてみてデータが復元できるか確認したところちゃんと復元できていました

  • docker stack rm test
  • docker stack deploy -c docker-compose.yml test
  • redis-cli get a

=> aa

なおこの後で worker 側と manager 側で docker volume ls をしてみると両方に nfs ボリュームが作成されているのが確認できると思います

  • docker volume ls
DRIVER              VOLUME NAME
local               test_redis_data

未検証: 気になったこと

  • nfs サーバ側の /etc/exports などで設定を変更した場合に docker の nfs ボリュームは作成し直さないとダメなのか

最後に

docker Swarm + docker-compose でボリュームがコンテナホスト間で共有できない問題を nfs サーバを構築し各コンテナホストに nfs ボリュームを作成することで解決してみました
おそらくこれが Swarm を使ってボリューム共有する最善手なのではないかと思います

nfs サーバは今回は VM 上に直接インストールしましたがコンテナでもいいしクラウド上に作成しても良いかなと思います
ただレイテンシーが少なからず発生するのでできればコンテナの近くに置いてあげたほうがコンテナ自体のパフォーマンスも良くなると思います

参考サイト

0 件のコメント:

コメントを投稿