systemdのせいでNFS mountに失敗する話

Red Hatの森若です。

systemdの前後関係で問題が発生することが原因で、NFS mountに失敗することがあります。

実際の問題で困っている人は以下のナレッジ記事をどうぞ :)

access.redhat.com

今回はこの背景を簡単に紹介します。

systemdによる前後関係の矛盾検出

systemdはどのunitをいつ起動するかを、After= および対応する Before= で管理しています。

この定義はシステム起動やシャットダウン時のような、複数のunitのstart/stopが関連する操作の時に利用され、systemdはこの前後関係が満たされる範囲で並列にstart/stopをおこないます。

以下では「unit Aを開始する」を A/start と記述します。

systemdは、実際に起動の操作をおこなう前の計画段階で矛盾を検出します。ここで言う「矛盾」は、前後関係にループが作成されることです。

たとえば 以下のようになっているとループが発生します。

  • A/start のあとに B/start
  • B/start のあとに C/start
  • C/start のあとに A/start

systemd-fstab-generator による fstabから mount unitの作成

systemdは、/etc/fstab を直接扱えませんが、fstabで定義されたmountをどのような順番で実行するかを決めるのはsystemdです。

systemdの起動直後、rootファイルシステム変更、systemctl daemon-reload 実行時等のタイミングでsystemd-fstab-generator というプログラムが /etc/fstabを読み込み、systemd用の unit fileを /run/systemd/generator/ 以下に生成します。そのため、fstabを編集したあとにはsystemctl daemon-reload を実行して unit fileを再生成して読み込み直す必要があります。

mount unit の名前では、mount pointを一定の規則で置き換えた、以下のような名前が利用されます。

  • /-.mount
  • /var は var.mount
  • /boot/efiboot-efi.mount

シンプルな例

/etc/fstab

UUID=xxxx / xfs defaults 0 0
10.0.0.100:/exp /mnt nfs defaults 0 0

いろいろ省略していますが、関連する前後関係はこのようになります。

前後関係のグラフ

この場合、 -.mount/startNetworkManager.service/startmnt.mount/startの順に実行して特に問題になりません。

問題のある例

/mnt/opt/foo/opt/foo にbind mountすることにします。

/etc/fstab

UUID=xxxx / xfs defaults 0 0
10.0.0.100:/exp /mnt nfs defaults 0 0
/mnt/opt/foo /opt/foo none bind 0 0

前後関係にサイクルができる

この場合、opt-foo.mount/startlocal-fs.target(ローカルファイルシステムのmountが完了したことを示すtarget unit) より先にある必要があるため、矛盾が発生しています。このように実行順が決まらない場合、systemdはサイクル内に登場するランダムなjobを間引いて解決します。

どのunitの起動が間引かれるかは起動毎にかわるので、サイクルをそのままにすると、mnt.mount/startに失敗したり、opt-foo.mount/startに失敗したり、NetworkManager.service/start に失敗するような、部分的な問題が発生します。

問題修正した例

この例の場合、問題の解決は簡単で、 bind mountの行に _netdev オプションを追加するだけです。

/etc/fstab

UUID=xxxx / xfs defaults 0 0
10.0.0.100:/exp /mnt nfs defaults 0 0
/mnt/opt/foo /opt/foo bind,_netdev 0 0

_netdev により矛盾解消

なぜ直るのか?

_netdev の有無であっさり動作がかわりました。

これは systemd-fstab-generator の動作によるもので、/etc/fstab 内でファイルシステムが nfscifs 等である場合や _netdev がある場合にはBefore=remote-fs.target を、そうでない場合には Before=local-fs.target を指定します。 興味がある方はソースを辿ってみると詳細がわかります。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。