この記事を作った動機

 最近 Blender に Flamenco を導入して分散コンピューティング?を試す において、SMB ではなく SSHFSを使う必要があることがわかったので、SSHFS を試そうとしたところ、躓いたため記録を取る。

 私の環境では、元々 SMB を使ってファイル共有を導入し、それを VPN 経由で利用していたが、それは普段の利用であってもファイルが大量にあるディレクトリを高遅延なネットワーク環境で開いたりすると1分ほど待たされたりするなど、明確に動作が遅いところがあった。そしてその挙動が、Blender の Flamenco の利用において顕著になり、限界が見受けられたため、SFTPを使った方法に移行するということがあった。

 Blender の Flamenco において問題が起きる前までは、SMB 以外にすでに SFTP を使っていたが、それは Gnome Shell 内臓のファイラーで使っていたが、そちらでは、sftp://の URL から始まるパスで Gnome が勝手に独自でマウントしたフォルダーにアクセスするようになっている。SMB のときのように、マウントポイント用のフォルダを作成して、そこにネットワークドライブをマウントするという形態は取っていなかったため、問題になった。

 そこで、SMB のように SFTP をマウントできる方法について、Google Gemini Fast 3に投げてみたところ、一つの提案として SSHFS を使う案が出てきたため試した。試したところ、コマンドラインにおいて、sshfsコマンドを使って、特定のフォルダに特定の SSH 接続先のフォルダをマウントするというような動作はできた。しかし、それを Systemd において自動化する際において、問題が起きた。

 問題としては、私が Systemd に専用に備わっている方法や、/etc/fstab に明記する方法ではなく、起動後に自分で書いた専用の管理 Python スクリプトを Systemd で動かし、VPN と ネットワークドライブの管理を自分で自動的に行う方法を取っていたことによってその処理内容がsshfsコマンドの特性上適切でないことで起こった。SMB の時点では、/etc/fstab に書くことでネットワークドライブをマウントする方法もやっていたことがあった。しかし、それだと起動時やシャットダウン時において、ネットワークドライブにアクセスできなかった場合に、OS ごと巻き込んで、起動が途中で止まったりシャットダウンが途中で止まったりするなど、課題になったため、自分で手動で Systemd サービスやスクリプトを書いて起動して Gnome Shell にログインしてから具体的な処理が自動で走るように書いていた。その仕様が今回 SSHFS を導入するにあたって問題となった。

 SSHFS - ArchWiki などに書いてあるように、専用の Systemd の automount を使ったりする方法もあるようであるが、私としては、ネットワークドライブは VPN の接続状況に応じて一緒に管理したいということがあった。SMB を使っていたときと同じように、SSHFS も自分でマウントするスクリプトを書いて、それを Systemd 経由でスクリプトとして実行し、専用の Python 管理スクリプトからは、systemd の自分で用意したネットワークドライブに関するサービスを叩くだけにしたいということがあった。

 そこで今回は、Systemd の automount などではなく、私がやりたい方法(Systemd からスクリプトを叩いて、SSHFS を動かす)はどうやったら実現できるか色々試したため、どうやったら動いたかそれについて軽くまとめたい。

内容

Systemd スクリプト(/etc/systemd/system/mount.service)

[Unit]
Description="Mount Network Drive (SSHFS)"
# After=[任意に必要であれば書く]

[Service]
Type=forking
User=[UserNameWhoCanGetSSHconnected]
Group=[UserNameWhoCanGetSSHconnected]
ExecStart=/Path/To.Script/File/mount.sh

Restart=on-failure

[Install]
WantedBy=multi-user.target

マウント用スクリプト(/Path/To.Script/File/mount.sh)

#!/bin/bash
sshfs [UserName]@[SftpServerIP]:/Path/On/The/SFTP/Server/shared/Folder /Path/To/MountPoint -o ServerAliveInterval=15,reconnect,ServerAliveCountMax=3,IdentityFile=/Path/To/SSH/PrivateKey,allow_other -f &
sshfs [UserName]@[SftpServerIP]:/Path/On/The/SFTP/Server/shared/Folder1 /Path/To/MountPoint1 -o ServerAliveInterval=15,reconnect,ServerAliveCountMax=3,IdentityFile=/Path/To/SSH/PrivateKey,allow_other -f &
# 他の接続も更に指定できる...

使い方

# マウントする
systemctl start mount
# 再マウントする
systemctl restart mount
# マウントを止める
systemctl stop mount

問題の説明

 今回の問題は、Systemd においてサービスタイプをsimpleに指定し、マウント用のスクリプト側でもプロセスを維持するように書いていないことによって、sshfsがすぐ終了してプロセスがなくなるために問題が起こった。問題が起こったときの挙動としては以下のような状態のループに陥った。

  1. Python 管理スクリプトが VPN に関する疎通確認をして、適宜必要な処理を走らせる
  2. Python 管理スクリプトがネットワークドライブについて、mountコマンドを使い、所定のドライブがマウントされているか確認する。
  3. Pyhton 管理スクリプトがネットワークドライブが意図されたとおりにマウントされていないことを検出し、再接続を試みる。
  4. Python 管理スクリプトが Root 権限で実行されているため、ネットワークドライブ再接続においてSystemctl restart mountもルート権限で実行される。
  5. Systemd が、mount.shを実行しネットワークドライブへの接続を試みる。
  6. 一瞬ネットワークドライブの接続に成功するが、スクリプトの最後まで素通りしたらそれでプロセスが終了し、接続成功後数秒後にネットワークドライブの接続も消え、mountコマンドからも実態をすぐ確認できなくなる。
  7. Python 管理スクリプトが、一定時間経過後ポーリングとして次の VPN と ネットワークドライブの疎通状況を確認するときに、ネットワークドライブが接続されていないことが検出される。
  8. “1.“に戻って永遠に成功しない処理を実行し続けて処理が接続に失敗し続ける。

 この問題の解決のために、以下の 3 つの変更を行ったところ、動作が安定するようになり、ネットワークドライブが利用可能になった。

  • Systemd サービスにおいて、ネットワークドライブをマウントするサービスタイプを、simpleからforkingに変更し、プロセスが勝手になくならないようにした。
  • マウント処理を実際に実行する bash スクリプトにおいて、&を各sshfsコマンドの最後につけることで、フォアグラウンドの実行にすることで、sshfsコマンド実行時にスクリプトの実行がそこで妨げられて後発が実行されないことを避ける。
  • sshfsの引数で-fを指定する。

注意したほうが良さそうな点

ユーザを Systemd サービスにおいて明示する

 sshfsコマンドは SMB のときのように Root で実行する必要はなく、ssh 接続が可能なユーザとして実行するようにした。Root などとして実行するとセキュリティ問題が出るだけでなく、意図しないホームディレクトリの SSH 設定を覗きに行ったり、公開鍵を参照したりしてしまうことで、SSH 接続の認証に失敗する可能性がある。

[Service]
...
User=[UserNameWhoCanGetSSHconnected]
Group=[UserNameWhoCanGetSSHconnected]
...

SSHで使うキーのパスを明示する

 Systemd サービスにおいて、実行するユーザやグループを明示的に指定しても、Systemd サービスの実行自体を Root などから行っていると、意図しないホームディレクトリの SSH 設定を覗きに行ったり、公開鍵を参照したりしてしまうことで、SSH 接続の認証に失敗する可能性がある。

sshfs ... -o ...,IdentityFile=/Path/To/SSH/PrivateKey,...

SSHのタイムアウトや再試行回数を指定しておく

 SSHFS において、通常の SSH 接続と同じようにオプションを指定することができるがここでタイムアウトや再試行回数を制限しておかないと、どこかで動作が詰まったときにそこでずっと固まり続けて、強制再起動などでしか問題を解決できない状態になる恐れがある。

sshfs ... -o ServerAliveInterval=15,reconnect,ServerAliveCountMax=3,...

マウントポイントの権限を確認

 通常マウントポイントとして作成するフォルダは、/mnt配下に作成することが多いと思われるが、その時に作成したフォルダの権限や所有者について、SSHFS を実行するユーザの権限で利用可能にしておかないと、正しくsshfsコマンドを実行できない。

pwd
# /mnt
mkdir test
# mkdir: cannot create directory ‘mountPoint2’: Permission denied
sudo mkdir mountPoint2
ls -l
# drwxr-xr-x 1 userName userName 4096 Mar  3 06:45 mountPoint
# drwxr-xr-x 1 userName userName 4096 Jan 18 04:05 mountPoint1
# drwxr-xr-x 2 root     root     4096 Mar 24 20:13 mountPoint2

allow_otherオプションが有効にできない

 SSHFS において、SFTPのマウントを行ったユーザ以外もネットワークドライブを使えるようにするためにallow_otherオプションを有効する設定が必要である。設定をしていないときには以下のような挙動になり、allow_otherオプションをつけたときにsshfsコマンドが失敗することが確認できた。

systemctl status mount
# ○ mount.service - "Mount Network Drive (SSHFS)"
#      Loaded: loaded (/etc/systemd/system/mount.service; disabled; preset: disabled)
#      Active: inactive (dead)
# 
# Mar 26 17:02:42 HostName systemd[1]: Started "Mount Network Drive (SSHFS)".
# ...
# Mar 26 17:02:42 HostName mount.sh[18911]: fusermount3: option allow_other only allowed if 'user_allow_other' is set in /etc/fuse.conf
# Mar 26 17:02:42 HostName mount.sh[18912]: fusermount3: option allow_other only allowed if 'user_allow_other' is set in /etc/fuse.conf
# Mar 26 17:02:42 HostName sudo[18902]: pam_unix(sudo:session): session closed for user UserName
# Mar 26 17:02:42 HostName sudo[18903]: pam_unix(sudo:session): session closed for user UserName
# Mar 26 17:02:42 HostName systemd[1]: mount.service: Deactivated successfully.

 この問題に対応するためには、/etc/fuse.confを以下のように編集した。

cat /etc/fuse.conf 
# ...
user_allow_other
# ...

SMB に比べて良かった点

 SFTP を使う SSHFS には、以下の利点が見受けられた。

  • VPN 環境を越しにネットワークドライブにアクセスするなど、高遅延環境に対して耐性があり、SMB とことなりファイル数が多いディレクトリなどを開いてもすぐに内容を表示したりできる。
  • SMB の時点では、以下のようにパスワードやユーザ名などがmountコマンド実行時に、直接プレインテキストとして含まれておりセキュリティの懸念がある。SFTP を使っている SSHFS は公開鍵認証を使っており直接パスワードや認証情報をコマンド文中で取り扱わないため、認証に使う公開鍵のファイルへの権限を個別に設定したりでき、より安全に認証情報など機密情報が管理できる。
sudo mount -t cifs -o user=[userName],password="[PASSWORD]",gid=[GroupID],uid=[userId] //[serverIP]/data /mnt/mountPointFolder

参考にしたサイトとか