Docker Swarm + NFSでdocker volumeを共有化する

こんにちは。
株式会社クラスアクト インフラストラクチャ事業部の大塚です。

今回はDocker SwarmとNFSを使ってdocker volumeを共有化した環境を構築してみます。
docker volumeを共有化することでSwarmのservice本領発揮です！

# 環境イメージ
今回構築する環境は以下となります。
3台でSwarm環境を構成。Swarmのマスターは第4オクテッドが70のホスト(ohtsuka-swarm01)です。
うち1台をNFSサーバとして兼用して構築します。NFSサーバに/home/docker-volumeディレクトリを作成して、そこをエクスポートしてNFSクライアントからマウント出来るようにします。
docker volumeはデフォルトで/var/lib/docker/volume/配下に作成されるのでNFSクライアント側ではこれを/home/docker-volumeとマウントしてvolumeをSwarm環境で共有化します。
※余談のところでも書いていますが、wordpress serviceに関してもreplicateは1の方が良いと思います。3つ立てるとたぶんセッション管理等などもやらないといけなくなり、、、ページの遷移があべこべになってしまうかと思います。
![docker-ページ25.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/51230069-e560-be3f-2679-7966903b751b.png)

# 参考手順
過去に私が投稿した記事になります。
Docker Swarm環境構築は以下を参考にしています。

https://qiita.com/ohtsuka-shota/items/314ed4a8a2bb6c6d92df

NFSサーバの構築に関しては以下を参考にしてます。

https://qiita.com/ohtsuka-shota/items/78afa122e07ecaa3ad24

# 構築
## Swarm環境構築
Swarm環境は参考手順のまま実行すればOKです。

## NFSサーバ構築
以下のコマンドを作ってNFSサーバインストール、環境設定を行います。
```
apt -y install nfs-kernel-server
cp -p /etc/exports /etc/exports.org
vi /etc/exports
cat /etc/exports
# /etc/exports: the access control list for filesystems which may be exported
#               to NFS clients.  See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes       hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4        gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes  gss/krb5i(rw,sync,no_subtree_check)
#
/home/docker-volume 192.168.2.0/24(rw,no_root_squash)
mkdir /home/docker-volume
cd /home/
chmod 777 docker-volume
exportfs -a
systemctl restart nfs-server
systemctl enable nfs-server
```

## NFSクライアント構築
NFSサーバを使用したい全てのサーバで以下を実行します。
```
apt install -y nfs-common
mount -t nfs 192.168.2.72:/home/docker-volume /var/lib/docker/volumes
```
コマンド実行後、以下のコマンドを実行してマウント先が表示されていればOKです。
```
root@ohtsuka-swarm01:~# df -m
Filesystem                        1M-blocks  Used Available Use% Mounted on
tmpfs                                   794     2       793   1% /run
/dev/mapper/ubuntu--vg-ubuntu--lv     15011  5305      8923  38% /
tmpfs                                  3969     0      3969   0% /dev/shm
tmpfs                                     5     0         5   0% /run/lock
/dev/sda2                              1946   129      1699   8% /boot
tmpfs                                   794     1       794   1% /run/user/1000
192.168.2.72:/home/docker-volume      15011  4957      9270  35% /var/lib/docker                                                                             /volumes
```
実際にdocker volumeを作成してvolumeを共有化することが出来ているかを確認します。まずSwarmのマスタで以下を実行します。
```
root@ohtsuka-swarm01:~# docker volume create test
test
root@ohtsuka-swarm01:~# docker volume ls
DRIVER    VOLUME NAME
local     test
```
次にSwarmのworkerにsshしてvolumeが格納されているディレクトリでlsをしてみます。testと出力されることを確認します。
※docker volume lsでも表示できるのですが、testという名前ではなく別の名前で保存されているようなので確認するためには微妙な気がしました。
```
root@ohtsuka-swarm03-nfs:~# ls /var/lib/docker/volumes/
test
```
またNFSサーバのマウント先でも確認をしてみます。
あることから問題ないことが判断できると思います。
```
root@ohtsuka-swarm03-nfs:~# ls /home/docker-volume/
test
```

## 正常なSwarm + NFS環境となっているかを確認する
まず今回デプロイするservice用のオーバーレイネットワークを作成します。
以下のコマンドを実行します。
Swarm環境下では同じネットワークにおり、serviceデプロイ時に--nameで名前を指定すると、その名前で名前解決が出来るようです。
```
root@ohtsuka-swarm01:~# docker network create --driver overlay wordpress-net
root@ohtsuka-swarm01:~# docker network ls
NETWORK ID     NAME              DRIVER    SCOPE
6984d9f9992c   bridge            bridge    local
bbadddba21f4   docker_gwbridge   bridge    local
90186f06fe04   host              host      local
o4q6jh524kdd   ingress           overlay   swarm
b15de6d941c8   none              null      local
jsf4rm30hp61   wordpress-net     overlay   swarm
```
次にwordpressで使用する実データを保存するvolumeを作成します。
```
root@ohtsuka-swarm01:~# docker volume create mysql-volume
mysql-volume
root@ohtsuka-swarm01:~# docker volume ls
DRIVER    VOLUME NAME
local     mysql-volume
local     test
```
wordpressに紐づけられるMySQL serviceをデプロイしていきます。
以下のコマンドを実行します。
```
docker service create --name mysql-container \
--env MYSQL_ROOT_PASSWORD=password \
--env MYSQL_DATABASE=wordpress \
--env MYSQL_USER=test \
--env MYSQL_PASSWORD=password \
--network wordpress-net \
--replicas 1 \
--mount type=volume,source=mysql-volume,target=/var/lib/mysql \
mysql:latest
```
次にwordpress serviceをデプロイします。
```
docker service create --name wordpress-container \
-e DB_HOST=mysql-container \
-e DB_NAME=wordpress \
-e DB_USER=test \
-e DB_PASSWORD=password \
--network wordpress-net \
--replicas 3 \
--publish 8080:80 \
wordpress:latest
```
serviceがデプロイされていることを確認します。
```
root@ohtsuka-swarm01:~# docker service ls
ID             NAME                  MODE         REPLICAS   IMAGE              PORTS
gdzyjypak9l9   mysql-container       replicated   1/1        mysql:latest
lgsaflc1u2ag   wordpress-container   replicated   3/3        wordpress:latest   *:8080->80/tcp
```
どこのホストで稼働しているかも確認してみます。
wordpressのserviceに関してはSwarmの全てのノード上にデプロイされています。一方でMySQL serviceに関しては1号機（マスター）上にデプロイされていることがわかります。
```
root@ohtsuka-swarm01:~# docker service ps wordpress-container
ID             NAME                    IMAGE              NODE                  DESIRED STATE   CURRENT STATE           ERROR     PORTS
pjl1kfcbjefq   wordpress-container.1   wordpress:latest   ohtsuka-swarm03-nfs   Running         Running 3 minutes ago
bkivh2c2ykh1   wordpress-container.2   wordpress:latest   ohtsuka-swarm02       Running         Running 3 minutes ago
v6ljjbdxgq37   wordpress-container.3   wordpress:latest   ohtsuka-swarm01       Running         Running 3 minutes ago
root@ohtsuka-swarm01:~# docker service ps mysql-container
ID             NAME                IMAGE          NODE              DESIRED STATE   CURRENT STATE           ERROR     PORTS
krvel5swqie6   mysql-container.1   mysql:latest   ohtsuka-swarm01   Running         Running 3 minutes ago
```
任意のWebブラウザを開いて任意のノードのIPアドレス:8080にアクセスします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/89fc39d6-e8a1-4c93-e618-94c26bc14013.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/27b3a3f5-4d3f-c41d-7f71-56c99857689d.png)
Database HostはMySQL serviceをデプロイした時の--nameオプションと同じです。名前解決が出来るはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/eede9e52-90af-c652-8d9e-8770ff67883d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/97e2c746-c590-1d83-20e2-8972fa11e5d7.png)
![screencapture-192-168-2-70-8080-wp-admin-install-php-2024-03-17-13_57_51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/1210afc1-9c33-8b57-66eb-83cd5a6e9321.png)
ログインしてみたり、画面遷移が正常に進むことを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/a7a10bf1-2d10-da89-f568-3d4192a3af1c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/180a511b-991e-2752-acf9-60dfe67bf2f9.png)

MySQLコンテナを削除してみます。
その前にMySQLが乗っかっているノードに意図的に負荷をかけるためにnginxをデプロイします。
※これをしないとMySQLコンテナを消しても同じノードにデプロイしなおされてしまってvolumeが共有化されていることが確認できません。
```
root@ohtsuka-swarm01:~# docker run --name nginx-container -d nginx:latest
root@ohtsuka-swarm01:~# docker ps
CONTAINER ID   IMAGE              COMMAND                  CREATED          STATUS          PORTS                 NAMES
16fe7dd29de1   nginx:latest       "/docker-entrypoint.…"   5 seconds ago    Up 3 seconds    80/tcp                nginx-container
d600da8ecf77   mysql:latest       "docker-entrypoint.s…"   3 minutes ago    Up 3 minutes    3306/tcp, 33060/tcp   mysql-container.1.oxghwygrgc3mm7rd9lzl516rs
29c9d907398f   wordpress:latest   "docker-entrypoint.s…"   27 minutes ago   Up 27 minutes   80/tcp                wordpress-container.3.v6ljjbdxgq37ttv5et7vzzjtp
```
改めてMySQLコンテナを削除します。
削除後、service psコマンドを実行してMySQL serviceがどのノード上にデプロイしなおされたかを確認します。今度はohtsuka-swarm02ノード上にデプロイされていますね。移動していることがわかります。
```
root@ohtsuka-swarm01:~# docker stop d600da8ecf77 && docker rm d600da8ecf77
d600da8ecf77
d600da8ecf77
root@ohtsuka-swarm01:~# docker service ps mysql-container
ID             NAME                    IMAGE          NODE              DESIRED STATE   CURRENT STATE              ERROR     PORTS
ke85nk3z83w2   mysql-container.1       mysql:latest   ohtsuka-swarm02   Running         Preparing 20 seconds ago
```
改めてwebブラウジングしてみます。
同じ画面が表示されることを確認します。共有化出来ていることがわかりますね。
※共有化出来ていないとwordpressの初期設定画面が表示されるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/de7ed917-5c17-3ce0-78d4-c4e2ef20022e.png)

# 余談
ここまでグダグダとwordpressコンテナを3つで構築していますが、こちらも基本的に1の方が良い気がします。。。