数週間前、自宅ラボの多数のVMをKubernetesへ移行する作業の中で、当時はかなり特殊だと思えるユースケースに出会いました。NFSボリュームをLinuxマシンやDockerコンテナに自動マウントする、というものです。
狙いは、InfluxDB、MySQL、Grafanaのバックエンドストレージを、稼働中デバイスのローカルファイルシステムではなく自宅のNASに保存できるよう移行することでした。NFSはシンプルで対応デバイスも幅広いため、私のネットワークでは複数デバイス間のファイル共有に重宝しています。
自宅ラボでセットアップを終えてみると、NFSは世界中で広く使われており、これは決して特殊なユースケースではないと気づきました。クラウド環境からオンプレミスのNFS共有をマウントしてデータを取得する、プロセスにNFS共有への読み書きを許可する、ユーザーのホームディレクトリをNFSからマウントするなど、活用シーンは枚挙にいとまがありません。
自動マウントを実現する上での課題と鍵
まずは一番慣れている方法、つまりこの数十年たびたび使ってきたfstabから試しました。しかしすぐに、データベースデーモンが認識する前にNFSマウントが間に合わず、深刻な不具合を引き起こしていることが判明しました。
そこで出会ったのが、Linuxのautofsというパッケージです。動かすまでには試行錯誤の連続でした。オンラインのガイドの多くは情報が古かったり、肝心なポイントが抜けていたりしたためです。本ガイドでは、半日かけて調べ直す手間を省けるよう手順だけを簡潔にまとめます。業界に身を置く者にとって、時間はいくらあっても足りませんから。
autofsは要するに、必要に応じてバックグラウンドで共有を自動的にマウント・アンマウントするデーモンです。fstabと違ってリクエストに応じて動作するため、デーモンの起動順序を気にせずブート時にも処理できます。
前提条件
話を簡潔にするため、本記事ではNASやLinuxマシン上にNFSサーバーがすでに構築されているか、Google CloudのFilestoreのようなサービスをNAS代わりに利用していることを前提とします。
追加したい各共有について、完全なNFS共有パスを必ず把握しておきましょう。想定と違うことがあるので必ず確認してください。たとえばSynology NASでは、/volume1/share_pathのようにボリューム名がプレフィックスとして付きます。
また、コマンドはDebian系ディストリビューションを前提に記載するため、Debian、Ubuntu、Kaliなどで動作します。RHELやCentOSをお使いの方が読み替えに苦労しないよう、各セクションでRed Hat系ディストリビューション向けのコマンドも併記します。
本ガイドではNFSのセキュリティ対策については扱いません。これは非常に範囲の広いテーマで、踏み込めば本ガイドの長さがゆうに倍になりかねないため割愛します。決して重要でないという意味ではありません。むしろ非常に重要ですので、本記事の手順でセットアップを終えたあとは、別途学習し、組織に合った形で組み込むことを強くおすすめします。さらに知りたい方は、Red Hatによる基礎をまとめた優れた記事を出発点としてこちらからご覧ください。
手順
お使いのディストリビューションに応じて、以下のコマンドでautofsパッケージをインストールします。
Ubuntu:
sudo apt -y install nfs-common autofsRed Hat:sudo yum -y install nfs-common autofsお好みのエディタで
/etc/auto.masterを開きます。ファイルの末尾までスクロールし、追加したいマウントごとに以下のような行を追記します。
auto.shareのshareの部分は、共有用にお好みの名前へ変更してください。/- /etc/auto.share -nosuid,noowners(読みやすさのために空白を増やしても構いません。Mediumでは番号付きリスト内のコードブロックで連続した空白が反映されない仕様です。)次に、前の手順で参照した各ファイルを作成します。これらはすべて
/etc配下に配置し、auto.[share_name]という形式の名前にします。前手順で追加したauto.share行ごとに、お好みのテキストエディタで該当ファイルを開いて作成してください。ファイルには以下の行を記述し、共有名(私個人としては /mnt 配下にまとめるのが好みですが、お好みで変更してください)、サーバー名、共有パスを置き換えます。/mnt/[share_name] -fstyle=nfs,user,nolock,nosuid,rw [server_name]:[share_path](ここでも読みやすさのために空白を増やしてかまいません。読み書き可能ではなく読み取り専用にしたい場合は、上記の rw を ro に変更してください。)ここまで完了したら、以下のコマンドでautofsサービスを再起動します。
Ubuntu:
sudo service autofs restartRed Hat:
sudo systemctl restart autofs続いて、サービスが正しく起動し、構文・ネットワーク・その他のエラーが起動時に発生していないことを確認します。以下のコマンドでステータスを確認してください。
Ubuntu:
sudo service autofs statusRed Hat:
sudo systemctl status autofs上記コマンドの出力にステータスが「running」と表示されていれば問題ありません。そうでない場合はログの末尾数行が表示され、デバッグの手がかりになります。記事の最後に基本的なデバッグ手順もまとめてあります。
この時点で、上のファイルに記述したディレクトリへ
cdコマンドで移動すれば共有にアクセスできます。これらのディレクトリを事前に作成しておく必要はありません。autofsデーモンが自動で作成してくれます。もうひとつの確認方法として、
mountコマンドを実行するとインスタンス上のすべてのマウントが一覧表示されます。autofsが作成したものだけを簡単に絞り込むには、mount | grep autofsを実行してください。これ以降、マウントはシステム上で自動的に読み込まれ、必要に応じて再アタッチされます。
この段階で、NFSのセキュリティについて学び、マウントを保護したうえで、用途に最適なセキュリティ手法を実装することを強くおすすめします。
よくある問題のデバッグ
このセットアップを学ぶ過程でいくつかの問題に遭遇したので、その内容と対処方法を紹介します。
最初に直面したのは、デーモンがNFSに接続できないという各種エラーを大量に出力する現象です。原因は、Synologyが共有名の前にボリューム番号を付与している仕様を当時知らなかったことでした。切り分けには、ホームディレクトリ内のフォルダにNFS共有を手動でマウントする手法を使いました。mkdir tmp_mnt && sudo mount -v -r -o user,nolock,nosuid [server_name]:[share_path] tmp_mnt を実行すると、共有がローカルディレクトリにマウントされます。成功すればその旨が、失敗すれば原因を示すエラーが表示されます。アンマウントするには sudo umount tmp_mnt を実行してください。
次に遭遇したのは、サーバーにホスト名でアクセスできないというエラーでした。原因は内部DNSの問題で、ホスト名が解決できていませんでした。IPアドレスでは共有にアクセスできることを確認し、nslookup hostname を実行してIPアドレスからホスト名への解決ができていないと突き止めました。なお、この確認には bind-utils(Red Hatの場合は sudo yum -y install bind-utils)または dnsutils(Debianの場合は sudo apt install -y dnsutils)パッケージのインストールが必要な場合があります。
最後の問題はKubernetes固有のものでした。クラスター内のどのPodからも、NFS共有を含むクラスター外部のサービスにまったくアクセスできなかったのです。原因は、クラスター外部にある内部DNSサーバーの存在をクラスターが認識しておらず、ホスト名を解決できなかったためでした。背景の理論は本ガイドの範囲を大きく超えるため、解決策と詳しく学べるリンクだけを示します。
- 以下のコマンドでCoreDNSのconfigmapを編集します。
kubectl edit configmap coredns -n kube-system - テキストエディタでCoreDNSのyamlファイルが開きます。その中に Corefile というセクションがあり、
.:53で始まるyamlブロックがあります。 - 内部ドメインを確認します。これは内部DNSサーバーの設定に従って決まります。内部DNSサーバーがない場合は
localを使ってください。 - DNSサーバーのIPアドレスを確認します。専用のサーバーがなければ、おそらくルーターのIPアドレスになります。
- そのブロックの下に新しい行として、ご自身の情報に置き換えた以下のコードブロックを追加し(タブではなくスペースを使うこと!)、保存してエディタを終了します。
[domain_name]:53 {
errors
cache 30
forward . [dns_server]
}
この設定が機能する理由について詳しくはこちらをご覧ください。