えんでぃの技術ブログ

えんでぃの技術ブログ

ネットワークエンジニアの視点で、IT系のお役立ち情報を提供する技術ブログです。

Pythonパッケージの管理方法

python-logo

お伝えしたいこと

Pythonパッケージ管理で色々悩んだ結果、私が実践している方法を紹介します。

基本はvenvを使うことで環境をクリーンに保ちますが、VS Codeなどの外部ツールとライブラリを連携する際にvenvが問題となるケースもあります。
本記事では、そういったお悩みを解消するやり方を紹介します。

本記事で紹介する管理方法には、venvなどデフォルトで含まれている仕組みのみを利用しています。
本記事の管理手法を試すのに、追加のソフトウェア導入は不要です。

なお、本記事ではvenvの基本機能を理解していることを前提としています。
venvを全く知らない方にとっては解説が不十分かもしれませんので、必要に応じて別の情報ソースを検索いただければと思います。

サマリ

私が現在実践しているPythonパッケージ管理方法は、以下の通りです。

  • Pythonパッケージは、原則venv配下にインストールする
  • ただし、venvの外部から参照されるパッケージは以下のように管理する
    • 一般ユーザー権限のpip install~/.local/配下にインストールする
    • 必要なパッケージと理由を書いてrequirements.txtに保存しておく
    • Pythonパッケージ構成をリセットする便利コマンドを作っておく

原則、venv配下にパッケージをインストールする

Pythonパッケージをインストールする場合、特に理由がなければ原則としてvenv配下にインストールすることをおすすめします。

venvを使うことのメリットは、以下の2点だと思います。

  1. 環境を汚しにくい
  2. 最小限のパッケージ構成で開発できる

環境を汚しにくい

venvの実体は、いくつかの設定ファイルを含むディレクトリです。

venvを作成すると、いくつかのファイルやフォルダを含むディレクトリが生成します。
そしてvenvをactivateしてからPythonパッケージをインストールすると、venvディレクトリ配下にパッケージを利用するためのライブラリや実行ファイルがインストールされます1

最後に、venvが不要になったらvenvディレクトリごと削除することで、venv配下のPythonパッケージも含めて環境をリセットできます。

以下にコマンド例を示します。

お試しでvenvを作成し、任意のPythonパッケージを導入して開発を進めます。

$ mkdir ~/venv
$ python3 -m venv ~/venv/tmp
$ source ~/venv/tmp/bin/activate
(tmp)$ pip install ansible

venvが不要になったら、venvをdeactivateし、ディレクトリごと削除します。

(tmp)$ deactivate
$ rm -rf ~/venv/tmp

(参考) pip uninstallによる環境リセットは面倒

venvを使わなくてもpip install --userコマンドを実行した場合は~/.local/配下に、pip install --systemコマンドを実行した場合は/usr/local/配下にPythonパッケージファイルがインストールされます2
そして、pip uninstallコマンドでパッケージを1つずつアンインストールすることで環境を元に戻すことができます。

しかし、pip uninstalldnfなどのディストリビューション付属のパッケージマネージャとは異なり、依存パッケージをアンインストールしてくれません。

具体例を示します。

例えば、pip install ansible-lintを実行すると、依存関係にあるパッケージを複数インストールします。

# 最初は何もインストールされてない状態
pip list --user

pip install yamllint
# Successfully installed pathspec-0.8.1 yamllint-1.26.1

pip list --user
# Package  Version
# -------- -------
# pathspec 0.8.1
# yamllint 1.26.1

ここで、yamllintをアンインストールします。
これで元の状態に戻ってくれれば良いのですが、依存パッケージのpathspecは消えずに残ってしまいました。

pip uninstall yamllint
# Proceed (y/n)? y
#   Successfully uninstalled yamllint-1.26.1

pip list --user
# Package  Version
# -------- -------
# pathspec 0.8.1

元の状態に戻すには、以下のコマンドのように依存パッケージも含めて全て指定してアンインストールする必要があります。

pip uninstall yamllint pathspec

venvのディレクトリ一括削除と比べて、pip uninstallによる環境リセットには手間がかかることがご理解いただけたかなと思います。

最小限のパッケージ構成で開発できる

venvに入ると、venv外のパッケージが見えなくなります。
以下に具体例を示します。

venvの外でpip listを実行すると、システム全体やユーザー単位でインストールされたPythonパッケージが全て列挙されます。

pip list
# Package            Version
# ------------------ ----------
# argcomplete        1.12.3
# beautifulsoup4     4.9.3
# blivet             3.4.2
# (以下略)

ここでvenvを新規作成してactiavateし、再度pip listを実行します。
するとvenvの外のパッケージが見えなくなり、最小構成の状態になります。

$ python3 -m venv ~/venv/tmp
$ source ~/venv/tmp/bin/activate
(tmp)$ pip list
# Package    Version
# ---------- -------
# pip        21.2.3
# setuptools 57.4.0

デフォルト構成のLinuxでも、ディストリビューションのパッケージに付属してPythonパッケージが多数導入されています。
Python開発をする際に開発環境に不要なパッケージまでインストールされていると、開発したツールが動作するのに最低限必要なPythonパッケージがわかりにくくなってしまいます。
そうなってはREADME.mdにツールの動作要件を書くことができませんし、自分で使うときにも他の環境でツールを正しく実行できるか自信を持てなくなってしまいます。

venvを使うことで、Python開発環境をクリーンに保ちやすくなります。
開発プロジェクトの数だけvenvを作成し、それぞれ独立してパッケージを管理すれば不要なパッケージの存在に頭を悩ませることはなくなります。
そして、開発が一段落したらvenvをディレクトリごと削除すれば環境をリセットできます。

外部から参照されるPythonパッケージの管理方法

外部からの参照とは?

例えば、Ansible Playbook (ソースコード) をVS Code (エディタ) で編集する際にAnsible拡張機能を使うと様々な機能を利用できます3

機能の1つとしてエラーチェックがあるのですが、この機能を動作するには以下のPythonパッケージがインストールされている必要があります。

  • ansible-core (またはansible)
  • ansible-lint
  • yamllint

ここでVS Codeがvenv外で動作していて、上記3パッケージがvenv配下にインストールされている場合、「venv外からのPythonパッケージ参照」に該当します。

他にもVS CodePython拡張機能のLinter機能を利用したい場合など、似たようなケースは実際にいくつも存在します。

venv外からの参照はさせるべきではない

これらのPythonパッケージをvenv配下でインストールしたとしても、venv外で動作しているVS Codeから利用することはできません。
なぜなら、Python実行ファイルやPythonライブラリの検索先のディレクトリに上記3パッケージが含まれていないためです。

以下のようにいくつかの方法で無理やり動作させることはできるかもしれませんが、手間がかかる上に何かしら問題が起こることが多いです。

  • venv配下の実行ファイルをフルパス指定で実行する
  • venv配下でVS Codeを起動する
  • venvをPATHに追加する (venvの意味がほぼなくなりますが...)

問題が起こるのは、主に以下の原因からです。

  • 拡張機能の仕様として、実行ファイルのパスを指定できないことがある
  • 拡張機能ソースコード上で、実行ファイルのフルパスがハードコードされていることがある

したがってVS Codeのような外部からPythonパッケージを参照したい場合、そのPythonパッケージはvenv配下にインストールするべきではありません。

おすすめの対処法

pip install --userでインストールする

VS Codeなどの外部ツールからPythonパッケージを利用したい場合、venvを使わずにpip install --userでインストールするのが素直な方法です。
この方法であればPythonパッケージのインストール先がホームディレクトリ配下 (~/.local/) なので影響範囲が特定ユーザーに限定され、システム全体を汚しません。
また、venv配下ではないのでVS Codeのような外部ツールとの連携で困ることもありません。

venv外とは言っても、sudo pip installは実行しないでくださいね。
これを実行すると、色々と混乱の元になります。
参考: sudo pip を実行してはいけない理由

以下にコマンドの具体例を示します。

pip install --user ansible-lint yamllint ansible

この方法は、AnsibleのようなPythonパッケージベースのツールをシステムワイドではなく、ユーザー単位でインストールして気軽に使いたい時にも便利です。
よく使うコマンドであれば毎回venvに入るのも面倒なので、VS Code連携などの理由がなくてもこの方法でインストールしちゃって良いかもしれません。

しかし、venv外へのインストールは、前述の#(参考) pip uninstallによる環境リセットは面倒の通り、依存パッケージも含めて削除するのが面倒という弱点があります。
この弱点を軽減する方法を次のセクションで紹介します。

ユーザー権限でインストールしたパッケージを一括削除する

venvのディレクトリ削除相当として、pip list --userで表示されるPythonパッケージを全て削除するコマンドを紹介します。
以下のコマンドを実行すれば、依存パッケージを1つ1つ指定してアンインストールする必要はなくなります。

pip freeze --user | xargs pip uninstall -y

もちろんのことですが、このコマンドはユーザー配下とはいえ影響が大きいので実行する際は注意してくださいね。
またvenvディレクトリの削除と比較して、パッケージ数が多いと実行時間が長くかかります。

Pythonパッケージ構成のバックアップ

インストールしたパッケージは、requirements.txtに書いておくと、後々手軽に復旧できます。
必要な理由もセットでメモしておくと良いと思います。

requirements.txtの置き場所は任意ですが、venv外であれば例えば~/.local/lib/requirements.txtに置くのはいかがでしょうか。
venvの場合は、venvのディレクトリ配下に置くとわかりやすいと思います。

以下のようにインストールしたパッケージ名と共に理由もコメントで付記しておくと、後々経緯を追いやすいです。

# For Ansible VS Code Extension
ansible-lint
yamllint
ansible

上記パッケージをインストールしたいときは、pip install -r requirements.txtを実行します。

今回の例では、以下のコマンドになります。

pip install -r ~/.local/lib/requirements.txt

(参考) Pythonパッケージ構成をリセットするalias

ここから先は好みの世界になってきますが、私の環境では以下のaliasを~/.bashrcに登録しています。
pip_resetをただ実行するだけで、いつでもPythonパッケージ構成を決まった状態に戻せます。
※requirements.txtにPythonパッケージを指定していないと、毎回バージョンが変わってしまいますが...

あまり頻繁に使うことはありませんが、私の場合はこのaliasがあることで気軽にpip install --userを実行できるようになりました。

コマンドの構造は単純で、Pythonパッケージの一括削除コマンドと、requirements.txtからの復旧コマンドを;で連結しただけです。

alias pip_reset='pip freeze --user | xargs pip uninstall -y; pip install -r ~/.local/lib/requirements.txt'

よろしければお使いください。

まとめ

Pythonパッケージを気軽に導入できるように、自分なりに考えたことを共有しました。

「venvは使いたいけど、VS Codeの連携時にうまく行かずに困っている」といったケースにハマるソリューションとして、お役に立てれば幸いです。


  1. venvは、Pythonの仕組みによってvenv配下の実行ファイルやライブラリを優先的に参照するよう制御する仕組みです。venvの詳しい動作について気になる方は、venvが動作する仕組みを調べてみたをぜひご覧ください。

  2. sudo pip installpip install --systemは基本的に使うべきではありません。その理由は、sudo pip を実行してはいけない理由に書きました。

  3. VS CodeのAnsible拡張機能について、詳細はAnsibleユーザーのためのVS Code拡張機能の紹介をご覧ください

systemd-resolvedの特徴と使い方紹介

systemd-light

前の記事

Fedora33以降より、systemd-resolovedがデフォルトで利用されるようになりました。
/etc/nsswitch.confがどのように変わったのか、記事の中で見比べます。

/etc/nsswitch.confがどのように変わったかについては、冒頭のサマリに書いてあります。
残りは参考情報です。

endy-tech.hatenablog.jp

お伝えしたいこと

Fedora33以降で/etc/resolv.confに代わってデフォルトのDNSクライアントとなったsystemd-resolvedについて概要を紹介します。

従来の/etc/resolv.confと比較して、systemd-resolvedは以下の特徴を持ちます。

  • モダンなDNS機能に対応する
  • D-Bus APIに対応し、NetworkManagerなどと連携して動作できる
  • 名前解決結果をキャッシュできる
  • hosts databaseのfiles, myhostname, dnsと同等の機能を持つ
  • ドメイン名に応じたDNSサーバの使い分けができる (※)

(※) 一般にはSplit DNSとしばしば呼ばれる機能です。systemd-resolved用語ではper-link DNSDNS Routingとも呼ばれます。本記事ではper-link DNSという用語を主に使います

systemd-resolvedの挙動を変更する方法は以下の通り複数あります。
今回は1のNetworkManagerを利用した方法を紹介します。
NetworkManagerを使ってネットワーク設定変更をするのがRHEL7以降の標準であり、馴染み深いと考えたためです。

  1. NetworkManagerを変更する。変更内容は、D-Bus経由でsystemd-resolvedに自動連携される (メモリ上のやり取り)
  2. systemd-resolved設定ファイルへの記述 (/etc/systemd/resolved.conf)
  3. systemd-networkd設定ファイルへの記述 (/etc/systemd/networkd.conf)

DNSサーバの指定方法は2つあります。
構成にもよりますが、前者の設定よりも後者の設定が優先されることがしばしばあります。
VPN利用環境など宛先ドメインによってDNSサーバを使い分けたい場合は、後者の宛先ドメインに応じたDNSサーバの設定を使う必要があります。

  1. Globalな設定
  2. 宛先ドメインに応じたDNSサーバの設定 (per-link DNS)

このように、systemd-resolvedの設定の記述場所は3パターンに分かれ、コンフィグも2パターンに分かれます。
これらを混在させても動作すると思うのですが、見通しを良くするためにもやり方を統一するのが良いと思います。

本記事ではNetworkManager経由で設定変更し、DNSサーバは後者の方法で指定する方法に統一します。
特にDNSサーバの決定は、DNS Default Routeを利用します。
詳細はper-link DNS#基本動作の要点で説明します。

systemd-resolvedとは

systemd-resolvedは、Linux においてDNSクライアントとして動作します。
多くのLinuxディストリビューションでinitプロセスを管理しているsystemdの1コンポーネントでもあります。

systemd-resolvedの主要な構成要素は、下表の通りです。

要素 説明
systemd-resolved.service systemd に管理されているサービス。
systemctlによって起動・停止などを制御する
/etc/systemd/resolved.conf systemd-resolvedの設定ファイル
/run/systemd/resolve/stub-resolv.conf systemd-resolvedが動作する環境では、/etc/resolv.confがこのファイルへのシンボリックリンクになる。
/etc/resolv.confを固定的に参照する(レガシー)プログラムとの互換性のために、systemd-resolved.service/run/systemd/resolve/stub-resolv.confを自動的に更新する
resolvectl systemd-resolvedのコマンドラインツール。
systemd-resolvedの動作を確認したり、リアルタイムに変更したりできる

systemd-resolvedのAPI

systemd-resolvedと外部アプリケーションを連携させる手段 (API: Application Programming Interface) は3種類あります。1,2
いずれのAPIもローカル専用で、ネットワーク越しに外部ホストからアクセスすることはできません。

サポートされる機能の多さとしては(1) > (2) >> (3)という関係になっており、systemdの開発者としては(3)よりも(1), (2) のAPIを使うことをより推奨しています。
(3) は、古いプログラムへの互換性を保つために残されています。

次のセクションでこれらのAPIがどのような使われ方をするのか、より具体的に紹介します。

# API 説明
1 D-Bus (Desktop Bus) プロセス間通信機能を提供するAPI
2 NSS (Name System Switch) /etc/nsswitch.confを介してアクセスする方式。
GNU C ライブラリによって実装されている
3 Local DNS Stub Listener TCP/UDP127.0.0.53:53 でリッスンしている
/etc/resolv.confを直接参照するプログラムが利用する

(1) D-Bus (Desktop Bus)

プロセス間で各種設定情報や命令を直接やり取りするための仕組みです (IPC: InterProcess Communication)
D-Busは、同一OS内のローカルのやり取りのみをサポートします。
ネットワーク越しに外部ホストとメッセージをやり取りするような使い方はサポートされていません。

ユーザーがD-Busに対応したアプリケーションを操作すると、D-Bus API経由で関連するプロセスに情報を伝達して動作に反映します。
D-Busの情報のやり取りはプロセス間で行われるので、ユーザーがD-Busの動きを意識することは基本ありません。
D-Busは、いわば「アプリケーションの作り込み」に相当する領域です。

D-Busの概要は、下記サイトにとてもわかりやすく解説されていました。

www.silex.jp

D-Busを利用している身近な例としては、私の知る限り2つあります。
どちらもNetworkManager関連です。

1つ目は、nmcliです。
nmcliで設定する際、裏ではNetworkManager.serviceD-Bus API を叩いて設定変更を反映しているようです。
nmtuiなど、その他のコマンドも恐らく同様です

以下の出力からも何となく察せると思います。

nmcli conn up eno1
# Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/5)

2つ目は、NetworkManagerからsystemd-resolvedへの設定情報連携です。
具体例としては、NetworkManagerでDNSサーバのIPアドレスを変更したら、D-Bus経由でsystemd-resolved側でもDNSサーバを変更するといったものが挙げられます。

NetworkManagerからsystemd-resolvedにD-Bus連携する設定は、/etc/NetworkManager/NetworkManager.confsystemd-resolvedというパラメータです3
デフォルト値はtrueで、systemd-resolvedにD-Bus API経由で設定情報を連携するようになっています。

つまり、NetworkManagerのDNS設定を変更すると、systemd-resolvedにも動作が反映されます。

NetworkManagerとsystemd-resolvedの連携については、(参考) 具体例にて実機操作のサンプルを示します。

(2) NSS (Name Service Switch)

Linuxの多くのプログラムは、FQDNやホスト名をIPアドレスに名前解決する際に/etc/nsswitch.confhosts:行を参照します。

RHEL7とRHEL8は/etc/resolv.confに従ってDNSサーバを決定します。
Fedora33以降は、systemd-resolvedに従ってDNSサーバを決定します。

/etc/nsswitch.confの中身や具体的な挙動について、詳細は前回記事の/etc/nsswitch.confのhosts行を理解するを参照してください。

(3) Local DNS Stub Listener

Local DNS Stub Listener とは、TCP/UDPでリッスンしている 127.0.0.53:53のことです。

Fedora33以降では/etc/resolv.conf/run/systemd/resolve/stub-resolv.confへのシンボリックリンクとなっており、/run/systemd/resolve/stub-resolv.confをsystemd-resolvedが自動更新するという構造になっています。
結果として、/etc/resolv.confを参照すると、システムのDNSサーバが127.0.0.53、つまりsystemd-resolvedに設定されていることになります。

digコマンドやhostコマンドのように、/etc/nsswitch.confではなく/etc/resolv.confを直接参照するプログラムにも互換性を持たせるため、こういったアクセスの仕方も用意されています。

ちなみに、/runはtmpfsという仮想メモリファイルシステムをマウントしています。
/runはメモリ上にあるデータということですね。
このあたりが気になる方は、man tmpfsをご覧ください。

systemd-resolved の基本機能

前回記事の/etc/nsswitch.confのhosts行を理解するで紹介しましたが、名前解決の手段 (NSS用語でservice、またはservice specificationと呼ばれます) は1つではありません。
systemd-resolvedが導入される前は、少なくともfiles (/etc/hosts)dns (/etc/resolv.conf)がありました。

resolve (systemd-resolved) は、既存のserviceと同等の機能を持ちます。

resolveは、下表の全てのserviceと同じ機能を提供します。

service 機能
files /etc/hosts
mdns4_minimal mDNS機能 (※)
dns /etc/resolv.conf
myhostname 自身のホスト名やデフォルトゲートウェイなどの名前解決

(※) systemd-resolvedはmDNS機能をデフォルトで無効化しているが、後から有効化できる

既存の機能と比べて、systemd-resolvedはより多機能かつ高効率です。
具体的にはDNSSecやper-link DNS機能に対応し、過去の名前解決結果をキャッシュに保持します。

以降のセクションでは、systemd-resolvedのDNSサーバによる名前解決機能 (/etc/resolv.conf相当) に着目し、更に詳しく説明します。

systemd-resolvedと/etc/resolv.confの違い

Fedora Wiki - Changes/systemd-resolvedの内容を参考にしつつ、systemd-resolvedと/etc/resolv.confの主な違いを紹介します。

標準化

Ubuntuなど、最近のディストリビューションは、systemdのコンポーネントを活用する方向にシフトしてきています。
systemd-resolvedを使うことで、Red Hat系のディストリビューションも他のディストリビューションとの挙動差異が埋まります。

動的な挙動変更

/etc/resolv.confに記述可能なオプションは限定的で、対応していない機能がいくつかありました。
また、NetworkManagerとの連携方法も限定的なものでした (※)
(※) D-Busにはもちろん対応していませんでした。/etc/resolv.confシンボリックリンクとし、シンボリックリンク先をNetworkManagerが自動更新することで連携していました。これは、systemd-resolvedが非推奨としている#Local DNS Stub Listenerに相当する連携方法です。

systemd-resolvedは、/etc/systemd/resolved.confだけでなく、D-BuS APIの連携によって他プロセスときめ細かに連携できます。
ステータスは設定ファイルのみに縛られず、柔軟かつ動的に挙動を変えます。
一方で、設定ファイルだけでは現在のステータスが読み取れないという特性もあります。
その点は、resolvectlというコマンドが解決してくれます。
resolvectlによってステータスを確認したり、DNSクライアントとしての一部の挙動を変えることも可能です。

resolvectlの利用例については#(参考) 具体例で、主なコマンドについては[#resolvectl]で紹介します。

search (ドメイン自動補完)

まずは、search機能についておさらいします。
例えばsearch に xxx.com を指定していたとします。
ping Aを実行すると、Aが名前解決できなくてもxxx.comを自動補完してA.xxx.compingを実行してくれます。

従来のDNSでは、/etc/resolv.confndotsというオプションによって「ピリオドをいくつまで含んでいるときに自動補完の対象とするか」を制御できました4

systemd-resolvedの場合はndotsを敢えて実装しておらず、単一ラベル (ピリオドを含まない名前) の場合のみドメインを補完します5
systemd-resolvedがndotsを実装していない理由は、「既にトップレベルドメインは1500以上存在し、常々増加している。新しいトップレベルドメインが生まれたことで、ndotsに依存した名前解決が不意に失敗する可能性を危惧したため、ndotsを無効化した」と書かれています6

DNSサーバの複数指定

従来のDNSでは、最大3台までDNSサーバを指定できます。
基本的には1台目のみにクエリを投げます。
1台目のDNSサーバへのクエリがタイムアウトしたら、次のDNSサーバに問い合わせます。

systemd-resolvedでは、Routing という仕組みによってDNSサーバが選択されます。
ネットワークのIP Routing とは若干関係ありますが、完全に別物です。
詳細は、次セクションの#per-link DNS (Routing)で紹介します。

基本動作

systemd-resolvedは、ネットワークインターフェースごとにDNSサーバを指定できます。
その上で、Routing という仕組みによって、クエリごとにDNSサーバを使い分けます。

具体的には以下の動きになります7
(※) グレーにした部分はあまり重要ではないので、読み飛ばして結構です
(※) 下表の項番5については一部ドキュメントに書かれていない情報もありますが、私の手元で検証済みです。長くなってしまうので、エビデンスは掲載していません

# 詳細
1 `/etc/hosts`や自身のホスト名、`localhost`、`localhost.localdomain`、`XXX.localhost`などは、ネットワークにクエリを出すことなくローカルのみで名前解決する
2 単一ラベル (ピリオドを含まない名前) の場合は、通常のUnicast DNSは使われず、LLMNR (マルチキャストベースの名前解決を行う技術。mDNSとはまた別物) で名前解決する。
ただし、AレコードやAAAAレコードの場合は search domains によってドメイン名が補完され、Unicast DNS でクエリされる
3 *.localという名前はmDNSとして扱われるため、Unicast DNSとしては動作しない
4 複数ラベルA, AAAA, PTRレコードの場合 (ピリオドを含む場合) は、以下のロジックで最適なDNSサーバが選出される (※1) (※2) (※3)
  • アクセス先のFQDNが search domains やroute-only domainsと重なる場合は、そのインターフェースと紐づくDNSサーバが優先される
  • 複数の search domains や route-only domains に該当する場合は、ラベル数 (※4) の多い search domains と紐づくDNSサーバが優先される (best matching)
  • best matchingなDNSサーバが複数あった場合、それらのDNSサーバ全てに対してクエリされる。クエリ結果が複数返ってきた場合は先に受信した方が優先される
  • ~.を含めてsearch domains に全く一致しなかった場合、5 に進む
5 4 のper-link DNSのsearch domains に一致しなかった場合、以下の挙動となる。
  • グローバルなDNSサーバ (/etc/systemd/resolved.confDNS) が設定されていれば、そこに問い合わせる
  • グローバルなDNSサーバが設定されていなかった場合、IPルーティングのデフォルトゲートウェイと紐づくper-link DNSサーバ (resolvectlコマンド出力で+DefaultRouteフラグがついているDNS) に問い合わせる
  • グローバルなDNSサーバ、IPデフォルトゲートウェイのいずれも設定がない場合、全てのper-link DNSとして設定されたDNSサーバに問い合わせ、最初に返ってきた応答を採用する

(※1) man resolv.confのDomainsに関する説明も参照

(※2) search domains に ~. を指定することで、どのFQDNにもマッチする指定が可能です (DNS Default Route)。best matching の観点では、最も優先順位が低いです。~をつけるとRouting Domain の扱いとなり、「DNS Routingの評価対象になるが単一ラベルに対するドメイン補完には利用されない」挙動になります。.はルートドメインを意味し、全てのドメインにマッチします

(※3) グローバルなDNS設定 (/etc/systemd/resolved.confDNSDomains) もper-link DNSと同様に評価されます。グローバルなDNSサーバがbest matching となれば、グローバルなDNSサーバのみにクエリされます。グローバルなDNSサーバとper-link DNSサーバの両方がbest matching となった場合は、どちらにもクエリされます

(※4) 例えば、www.google.comというFQDNにはwwwgooglecomという3つのラベルが含まれます

基本動作の要点

DNS Routing の挙動が非常にややこしいですが、上表の4と5をまとめると、A, AAAA, PTR レコードのクエリ先のDNSサーバは、以下の順序で評価されるようです。
あらゆる状況に対処できるように、systemd-resolvedには細かいルールが設定されていますが、基本的には1か2で確定できるように設計するのがわかりやすくてオススメです。

  1. ~.を含め、search domains に一致したDNS (best matching)。per-link DNS もグローバルなDNSも特に差はなく、同列に評価される
  2. グローバルなDNSが設定されていれば、そこにクエリする
  3. IP Routing の世界でデフォルトルートと紐づくNICにper-link DNSサーバが設定されていれば、そこにクエリする
  4. 全てのper-link DNSサーバにクエリする

設定方法

per-link DNSの設定方法は、私の知る限り以下の3つがあります。

設定方法 詳細
NetworkManager
nmcliなど
変更はD-Bus APIにより、NetworkManagerとsystemd-resolvedに即時反映される。
OS再起動後も持続する
systemd-networkの
設定ファイル
/etc/systemd/network/*.network/etc/systemd/network.d/*.confファイルなどに追記する。
/usr/lib/配下にもデフォルト設定がある。
ファイル読み込み順序や優先順位、設定オプションについては、[man systemd.network](https://man7.org/linux/man-pages/man5/systemd.network.5.html)を参照。
本記事では、systemd-networkdについては対象外とする
resolvectl このコマンドで変更された内容は、systemd-resolvedsystemd-networkdに伝搬する。
Fedora35実機で試したところ、NetworkManagerには伝搬しなかった。
OS再起動後、resolvectlで変更した設定の「一部」が消えた。
revertコマンドが便利だが、試験用途以外ではあまり使わない印象。
詳細は、[#(参考) resolvectlによる設定変更](#参考-resolvectlによる設定変更)を参照

なお、/etc/systemd/resolved.confではGlobalな設定としてDNSサーバを指定できますが、 per-link DNSの指定はできません。
このことは、man resolved.confの以下のような表現から読み取れます。

per-link DNS servers acquired from systemd-networkd.service(8) 
or set at runtime by external applications.

本記事では、使い慣れたnmcliを使用します。
今後ネットワーク設定の標準がNetworkManagerからsystemd.networkdに変更されることがあれば、systemd.networkdの設定ファイルの挙動を別途検証するかもしれません。
しかし、今のところその予定はありません。

(参考) 具体例

実際にDNS設定がどうなるか、色々な例で試してみました。

systemd-resolvedで/etc/hostsを使用する

/etc/hostsに試験用のエントリを追加します。

8.8.8.8     google-hosts

この状態で、以下のコマンドを実行します。
getentは、NSS (/etc/nsswitch.conf)を参照して名前解決を行うコマンドです。
-sによってresolve serivce、つまりsystemd-resolvedを指定しています。

以下の通り、systemd-resolvedが/etc/hostsの内容にしたがって名前解決をしていることがわかりました。

getent -s resolve hosts google-hosts
# 8.8.8.8         google-hosts

別の方法として、resolvectl queryによってsystemd-resolvedによる名前解決を行うことでも確認できます。

resolvectl query google-hosts
# google-hosts: 8.8.8.8

# -- Information acquired via protocol DNS in 2.1ms.
# -- Data is authenticated: yes; Data was acquired via local or encrypted transport: yes
# -- Data from: synthetic

検証が終わったので、/etc/hostsを元の状態に戻しておきます。

search domains にヒットする方を優先する

こちらの構成で検証します。

dns1

DNS1, DNS2 という2台のDNSサーバを用意し、PCからそれぞれにクエリを投げて名前解決する構成を組みました。
PCには、DNS1と紐づけてendy1.testを、DNS2と紐づけてendy2.testを search domains (ipv4.dns-search) に設定しています。
systemd-resolvedにおいて、問い合わせ先のDNSサーバは search domains に一致するものが優先される仕様です。
rr.endy1.testはDNS1に、rr.endy2.testはDNS2に問い合わせる構成となります。

PCのNetworkManager設定は、以下のようになっています。

# DNS1向け
nmcli connection add \
con-name dnstest1 \
ifname enp7s0 \
type ethernet \
ipv4.method manual \
ipv4.addresses 192.168.100.100/24 \
ipv4.dns 192.168.100.2 \
ipv4.dns-search endy1.test  # ★search domains

nmcli connection up dnstest1

# DNS2向け
nmcli connection add \
con-name dnstest2 \
ifname enp1s0 \
type ethernet \
ipv4.method manual \
ipv4.addresses 192.168.122.100/24 \
ipv4.dns 192.168.122.3 \
ipv4.dns-search endy2.test  # ★search domains

nmcli connection up dnstest2

DNS1には、 endy1.test.zoneendy2.test.zoneには、それぞれ以下の行を記載しました。
rr.endy1.testrr.endy2.testに、それぞれ192.168.100.2 (DNS1のIPアドレス) を紐づけました。
ゾーンファイルを2つ作ったのは、ドメイン名を区別するためです。

rr    A   192.168.100.2

DNS2には、 endy1.test.zoneendy2.test.zoneには、それぞれ以下の行を記載しました。
rr.endy1.testrr.endy2.testに、それぞれ192.168.100.2 (DNS2のIPアドレス) を紐づけました。
DNS1とは異なるアドレスを返すため、どちらにPCからDNS1とDNS2のどちらに問い合わせたかわかるようになっています。

rr    A   192.168.122.3

ここで、PCから2箇所にDNSクエリを投げてみます。
endy1.testはDNS1に問い合わせており、endy2.testはDNS2に問い合わせていることがわかります。
この挙動から、 search domains と紐づくDNSサーバに優先して問い合わせることがわかります。

dig rr.endy1.test | sed -ne '/ANSWER SECTION/,/^$/p'
# ;; ANSWER SECTION:
# rr.endy1.test.       6779    IN  A   192.168.100.2

dig rr.endy2.test | sed -ne '/ANSWER SECTION/,/^$/p'
# ;; ANSWER SECTION:
# rr.endy2.test.       6758    IN  A   192.168.122.3

resolvectl domainでsystemd-resolvedが選択するリンクと search domains の関係を確認すると、以下のようになります。

resolvectl domain
# Global:
# Link 2 (enp1s0): endy2.test
# Link 3 (enp7s0): endy1.test

best matching が優先される (longest match)

黄色の部分のみ構成を変更しました。

dns2

rr.endy1.testrr.endy2.test共に、.testドメインに属します。
ただ、上図の構成においては、仕様上以下の動作になります。

  • rr.endy1.testはDNS1とDNS2の両方の search domains に該当するが、best matching の考え方に基づいて2階層分のドメイン名に一致するDNS1が優先される
  • rr.endy2.testは、DNS2の search domains にしか一致しないため、DNS2が優先される

黄色部分の変更差分のNetworkManager設定は、以下のようになります。

sudo nmcli connection modify dnstest2 ipv4.dns-search test
sudo nmcli connection up dnstest2

ここで、PCから2箇所にdigを実行してみます。
endy1.testはDNS1に問い合わせており、endy2.testはDNS2に問い合わせていることがわかります。
この挙動から事前の予想通り、より多くの label (ピリオドで区切られた文字列) に一致するDNSサーバが優先されることがわかりました。

dig rr.endy1.test | sed -ne '/ANSWER SECTION/,/^$/p'
# ;; ANSWER SECTION:
# rr.endy1.test.       4800    IN  A   192.168.100.2

dig rr.endy2.test | sed -ne '/ANSWER SECTION/,/^$/p'
# ;; ANSWER SECTION:
# rr.endy2.test.       86400   IN  A   192.168.122.3

resolvectl domainでsystemd-resolvedが選択するリンクと search domains の関係を確認すると、以下のようになります。

resolvectl domain
# Global:
# Link 2 (enp1s0): test
# Link 3 (enp7s0): endy1.test

best matching なDNSサーバが複数存在する場合、全てのDNSサーバにクエリする

次は以下の黄色部分を変更しました。

dns3

search domains の条件を両方のリンクで揃えました。
search domains のbest matching でも同等な場合は、それらのDNSサーバ全てに問い合わせる仕様です。
そして、最初にDNS応答を受信した経路が採用されます。

NetworkManagerの設定差分は、以下のようになります。

sudo nmcli connection modify dnstest1 ipv4.dns-search endy1.test,endy2.test
sudo nmcli connection up dnstest1

sudo nmcli connection modify dnstest2 ipv4.dns-search endy1.test,endy2.test
sudo nmcli connection up dnstest2

dig で確認します。
DNSキャッシュをクリアしながら実行していると、途中で結果が変わっていることがわかります。
DNS1とDNS2のうち、先に受信した応答が表示されています。
どちらもほぼ同時に応答を受信するため、結果が不定になっています。

dig rr.endy1.test | sed -ne '/ANSWER SECTION/,/^$/p'
# ;; ANSWER SECTION:
# rr.endy1.test.       6554    IN  A   192.168.100.2

dig rr.endy2.test | sed -ne '/ANSWER SECTION/,/^$/p'
# ;; ANSWER SECTION:
# rr.endy2.test.       86400   IN  A   192.168.100.2

resolvectl flush-caches

dig rr.endy1.test | sed -ne '/ANSWER SECTION/,/^$/p'
# ;; ANSWER SECTION:
# rr.endy1.test.       86400   IN  A   192.168.100.2

dig rr.endy2.test | sed -ne '/ANSWER SECTION/,/^$/p'
# ;; ANSWER SECTION:
# rr.endy2.test.       86400   IN  A   192.168.122.3

最後に、resolvectlで設定を確認します。
両方の search domains にbest matching となっていることがわかります。

resolvectl domain
# Global:
# Link 2 (enp1s0): endy1.test endy2.test
# Link 3 (enp7s0): endy1.test endy2.test

DNSデフォルトルート

DNSデフォルトルートの挙動を確認します。
DNS1と紐づくリンクに、デフォルトゲートウェイを設定します。
また、 search domains を空欄にすることで一致しないようにします。

dns4

search domains に具体的なキーワードが全く該当しない場合、デフォルトルートが評価されます。
以下の2通りのいずれかの条件に該当するリンクは、デフォルトルートとして扱われます。

(※) .は、ルートドメインなので全てのドメインに一致します。また、ドメイン名の先頭に~を付けると、「ホスト名にドメイン名を補う」効果はなく、「best matchingなDNSサーバの選択」のみに寄与するという意味になります (routing domain)。

即ち、今回の場合は全てのクエリがDNS1を向くようになります。

NetworkManager上は、以下の変更差分となります。

sudo nmcli connection modify dnstest1 ipv4.gateway 192.168.100.1
sudo nmcli connection modify dnstest1 ipv4.dns-search ""
sudo nmcli connection up dnstest1

sudo nmcli connection modify dnstest2 ipv4.dns-search ""
sudo nmcli connection up dnstest2

digを実行してみると、実際に全てのクエリがDNS1を向くようになります。

dig rr.endy1.test | sed -ne '/ANSWER SECTION/,/^$/p'
# ;; ANSWER SECTION:
# rr.endy1.test.       86400   IN  A   192.168.100.2

dig rr.endy2.test | sed -ne '/ANSWER SECTION/,/^$/p'
# ;; ANSWER SECTION:
# rr.endy2.test.       86400   IN  A   192.168.100.2

resolvectl domainからは、両リンクで search domains が空っぽであることがわかります。
resolvectl dnsの出力では、実質的にDNS1にしか問い合わせが向いていないことを反映して、DNS1のみが表示されます。
resolvectlからは、IPルーティングのdefault routeが反映されて、DefaultRouteのフラグが片方のリンクで有効 (+) になっていることがわかります。

resolvectl domain
# Global:
# Link 2 (enp1s0):
# Link 3 (enp7s0):

resolvectl dns
# Global:
# Link 2 (enp1s0):
# Link 3 (enp7s0): 192.168.100.2

resolvectl
# Link 2 (enp1s0)
#      Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported

# Link 3 (enp7s0)
#          Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
# Current DNS Server: 192.168.100.2                                               

長くなるのでコマンド実行結果は省きますが、いくつか追加で補足します。

DNS2の search domains に~.を追加すると、IP Routing のデフォルトゲートウェイよりも優先されてDNS2のみに問い合わせるようになります。
NetworkManagerの設定コマンドとしては、以下のようになります。

sudo nmcli connection modify dnstest2 ipv4.dns-search "~."
sudo nmcli connection up dnstest2

更にDNS1の search domains に~.を追加すると、DNS1とDNS2の両方にクエリを出すようになりました。
search domains に~.が設定されたリンク同士であれば、ipv4.gatewayの設定有無が優先順位に影響することは無いようです。

resolvectl

systemd-resolvedは、/etc/systemd/resolved.confやデフォルトルート、他デーモンからのD-Bus連携によって挙動が変わるため、非常に複雑です。
resolvectlコマンドを使えば、systemd-resolvedの設定情報を確認できるため便利です。
また、このコマンドはDNSキャッシュクリアにも使えます。

コマンドの詳細は、下表のとおりです。

コマンド 詳細
resolvectl status グローバルな設定の表示。
resolvectlでも同じ
resolvectl domain per-link DNSと関係する search domains の表示
resolvectl dns 各リンクと紐づくDNSサーバの表示
resolvectl query <domain> DNSクエリ
resolvectl flush-caches DNSキャッシュのクリア

出力のサンプルを以下に貼ります。

resolvectl
# Global
#        Protocols: LLMNR=resolve -mDNS -DNSOverTLS DNSSEC=no/unsupported
# resolv.conf mode: stub

# Link 2 (eno1)
#   Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6
#     Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
# Current DNS Server: 192.168.1.1
#   DNS Servers: 192.168.1.1

# Link 3 (wlp0s20f3)
# Current Scopes: none
#   Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported

resolvectl domain
# Global:
# Link 2 (eno1): endy1.test
# Link 3 (wlp0s20f3):

resolvectl dns
# Global:
# Link 2 (eno1): 192.168.1.1
# Link 3 (wlp0s20f3):

resolvectl query google.com
# google.com: 2404:6800:4004:81d::200e           -- link: eno1
#             172.217.175.46                     -- link: eno1

# -- Information acquired via protocol DNS in 466.3ms.
# -- Data is authenticated: no

resolvectl flush-caches

(参考) resolvectlによる設定変更

resolvectlによって、DNSサーバやsearchを動的に変更することもできます。
ただ、後述のとおり、恒久的な設定変更にはあまり向かない印象です。

構文は以下のとおりです。

# DNSサーバの変更
resolvectl dns LINK [SERVER...]

# searchの変更
resolvectl domain LINK [DOMAIN...]

# 設定ファイルを参照し、動的な設定を元に戻す
resolvectl revert LINK

Fedora35における実行例を示します。
以下の変更を行っています。

  • enp7s0に192.168.100.2192.168.100.3というDNSサーバを設定する
  • enp7s0にendy1.testendy2.testというドメインに対するper-link DNSを設定する

以下のコマンド出力を見るとわかりますが、resolvectlで行った設定変更はNetworkManagerには伝搬していないようです。
man resolvectlによると、「resolvectl dnsresolvectl domainによる変更はsystemd-resolvedとsystemd-networkdに伝わる」とあるので、NetworkManagerに通知されないのは不具合ではなく仕様かもしれません。

resolvectl dns
# Global: 192.168.122.2
# Link 2 (enp1s0):
# Link 3 (enp7s0):

resolvectl domain
# Global:
# Link 2 (enp1s0):
# Link 3 (enp7s0):

nmcli conn show enp7s0 | grep ipv4.dns
# ipv4.dns:                               --
# ipv4.dns-search:                        --
# ipv4.dns-options:                       --
# ipv4.dns-priority:                      0

sudo resolvectl dns enp7s0 192.168.100.2 192.168.100.3
sudo resolvectl domain enp7s0 endy1.test endy2.test

resolvectl dns
# Global: 192.168.122.2
# Link 2 (enp1s0):
# Link 3 (enp7s0): 192.168.100.2 192.168.100.3

resolvectl domain
# Global:
# Link 2 (enp1s0):
# Link 3 (enp7s0): endy1.test endy2.test

nmcli conn show enp7s0 | grep ipv4.dns
# ipv4.dns:                               --
# ipv4.dns-search:                        --
# ipv4.dns-options:                       --
# ipv4.dns-priority:                      0

上記変更を元に戻します。

resolvectl dns
# Global: 192.168.122.2
# Link 2 (enp1s0):
# Link 3 (enp7s0):

resolvectl domain
# Global:
# Link 2 (enp1s0):
# Link 3 (enp7s0):

上記の挙動は概ね良いのですが、NetworkManagerに設定変更が伝搬していないのが気になりました。
そこで設定変更後にOS再起動して、どうなるか確認してみました。

OS再起動後はresolvectl dnsの設定は残っていましたが、resolvectl domainの設定は消えてしまいました。
挙動としてはnmcliと比較してわかりづらいので、resolvectlによる設定変更を使うことは今のところ無さそうです。
revertコマンドが便利なので、一時的な検証用途であれば使ってみても良いかもしれません。

Network Manager経由 (nmcli) で設定した場合にはOS再起動で消えなかったので、基本的にはnmcliを使うのが良いかなという印象です。

(参考) D-Bus関連コマンド

D-Bus周りでそれっぽいコマンドを実行してみました。
D-Busに対応しているプロセスであれば、CLIGUIだけでなくD-Bus APIでもステータスを確認できます。
--xmlオプションをつければ、XMLで出力することも可能です。

オブジェクトパス (/org/freedesktop/NetworkManager/DnsManager) はインターネットで「NetworkManager D-Bus」で調べても良いですが、bash-completion の働きでタブ補完することでも簡単に確認できました。

D-Bus オブジェクトにはプロパティとメソッドが紐付いて定義されており、コマンドラインで参照できます。
中々使う機会はないのですが、こういったものも存在することを知っておくとたまに便利かもしれません。

# オブジェクトを指定して、メソッドやプロパティの名前や型情報、持っている値を参照できる
gdbus introspect --system --dest org.freedesktop.NetworkManager --object-path /org/freedesktop/NetworkManager/DnsManager

# node /org/freedesktop/NetworkManager/DnsManager {
# (中略)
#   interface org.freedesktop.NetworkManager.DnsManager {
#     methods:
#     signals:
#     properties:
#       readonly s Mode = 'systemd-resolved';
#       readonly s RcManager = 'symlink';
#       readonly aa{sv} Configuration = [{'nameservers': <['192.168.1.1']>, 'interface': <'eno1'>, 'priority': <100>, 'vpn': <false>}];
#   };
# };

まとめ

具体例を入れたら非常に長くなってしまいましたが、systemd-resolvedの挙動についてまとめました。

D-Busの働きにより、NetworkManagerの設定変更によりsystemd-resolvedの設定を変更できるので、使い勝手は従来と大きく変わりません。

一方でVPNを利用している場合など、宛先ドメインによってDNSサーバを使い分けたいときにはsystemd-resolvedの per-link DNS 機能が役に立ちそうです。

/etc/nsswitch.confのhosts行を理解する

multiple_switch

お伝えしたいこと

Fedora33以降で、/etc/nsswitch.confのhosts行のデフォルト値が変わりました。
その変更に伴ってLinuxの名前解決の挙動がどのように変わったかを記事前半の#サマリで紹介します。

また記事後半の#Name Service Switch以降では、Name Service Switchの概要と/etc/nsswitch.confファイルの読み方を紹介します。

サマリ

本セクションに各ディストリビューション/etc/nsswitch.confのhosts行に書かれている内容の意味を記載します。
挙動のみを知りたい場合は、本セクションのみ読めば十分です。

/etc/nsswitch.confの読み方については、本記事の後半で取り扱います。

Fedora33以降

Fedora33以降の/etc/nsswitch.confの内容は、以下のとおりです。

hosts:      files myhostname resolve [!UNAVAIL=return] dns

端的に言うと、ほとんどの場合は/etc/hostsかsystemd-resolvedによる名前解決になります。

/etc/resolv.confは、基本的に使われません。
systemd-resolved.serviceが停止している場合のみ、/etc/resolv.confによるDNS名前解決が行われます。

以下に詳細を示します。
まずは1で名前解決を試み、1で名前解決できなければ2、次に3...という流れで進みます。

  1. /etc/hostsの定義を元に名前解決する (files)
  2. 自身のホスト名やFQDNを名前解決する (myhostname)
  3. systemd-resolvedによるDNS名前解決を行う (resolve)
    • systemd-resolvedが問題なく起動していた場合、この先に進まず名前解決の結果を返す ([!UNAVAIL=return])
  4. /etc/resolv.confによるDNS名前解決を行う (dns)

Fedora33以前、RHEL7、RHEL8

Fedora32以前、RHEL7、RHEL8の/etc/nsswitch.confの内容は、以下のとおりです。
RHEL9 Bataについても同じ内容でした。

hosts:      files dns myhostname

ほとんどの場合は/etc/hosts/etc/resolv.confによる名前解決になります。

Name Service Switch

Name Service Switch (NSS) とは、GNU C Library (glibc) に含まれる名前解決手段のデータベースです1
例えばFQDNやホスト名をIPアドレスに解決する手段は、以下のとおり複数存在します。

  • /etc/hostsを参照して名前解決する
  • /etc/resolv.confに記載されたDNSサーバに問い合わせて名前解決する
  • systemd-resolved DNSクライアントの機能で名前解決する
  • (他)

NSSは、上記のような名前解決手段に優先順位付けをします。
そして、優先順位の高い順に名前解決を試みます。
多くの場合は/etc/hostsの優先順位が高く、その次に/etc/resolv.conf、またはsystemd-resolvedが使用されるようNSSに制御されます。

上記はFQDNやホスト名からIPアドレスへ名前解決する場合の例ですが、NSSの用途はもっと多岐に渡ります。
以下にNSSが提供する名前解決の対象 (※) を列挙します2
(※) 以下に列挙した名前解決の対象は、NSSの用語でdatabaseといいます

  • aliases
  • ethers
  • group
  • hosts
  • initgroups
  • netgroup
  • networks
  • passwd
  • protocols
  • publickey
  • rpc
  • services
  • shadow

Linuxでは、多くのアプリケーションが/etc/nsswitch.confを参照し、NSSによって名前解決の手段を決定します。
しかし、全てのプログラムがNSSを使うわけではありません。
「NSSが何に使われ、何に使われていないか」については、#(参考) NSSの利用例でいくつかの例を示します。

次のセクションからは、NSSの設定ファイルである/etc/nsswitch.confの読み方について詳しく説明します。

/etc/nsswitch.confの読み方

基本構造

本セクションの情報は、man nsswitch.confの内容を情報源としています。

/etc/nsswitch.confは、NSSの設定ファイルです。
1列目にdatabase、2列目以降にservice specifications (※) を列挙します。
databaseとservice specificationsの間は:で区切ります。
(※) service specificationsは、単にservicesと呼ばれることもあります

serviceは、1つ以上指定します。
左に書いてあるものほど優先順位が高く、最初に検索されます。
優先順位が高いserviceにおいて名前解決できなかった場合、次に優先順位の高いserviceで名前解決を試みます。

以下に、Fedora35の/etc/nsswitch.confのhosts database行の例を示します。
hosts databaseは、FQDNやホスト名からIPアドレスを求める、またはその逆の名前解決を行う際に参照されるdatabaseです。

hosts:      files myhostname resolve [!UNAVAIL=return] dns

上記において、hostsがdatabaseです。
そして、service specificationsは以下の通りです。
上に書いてあるものほど優先順位が高いです。

  1. files
  2. myhostname
  3. resolve [!UNAVAIL=return]
  4. dns

[]で囲まれた文字列は、actionと呼ばれるパラメータです。
service specificationの後に指定することで、デフォルトの動作を一部変えることができます。
詳細は#actionセクションにて説明します。

service specifications

本セクションでは、代表的なservice specificationsの意味を紹介します。

files

filesは、/etc/配下の設定ファイルによる名前解決を行うserviceです。
具体的にどのファイルに記述するかは、databaseによって異なります。

下表に例を示します。

database名 設定ファイル (※) 内容
hosts /etc/hosts IPアドレスとホスト名/FQDNの紐付け
passwd /etc/passwd ユーザー名と詳細情報 (※) の紐付け
(※) UID, GID, ホームディレクトリパス, ログインシェルなど
group /etc/group グループ名とGID/所属ユーザー名の紐付け
shadow /etc/shadow ユーザー名とパスワード (ハッシュ値) の紐付け
networks /etc/networks ネットワーク名とネットワークアドレスの紐付け
services /etc/services サービス名とTCP/UDPポート番号の紐付け

(※) 各databaseのfiles serviceに紐づく具体的なファイルは、manを追いかけることで推定できます。例えばhosts databaseの場合、man nsswitch.confman gethostsbynameと辿ると/etc/hostsという情報に行き着きます

各種ファイルの詳細情報は、ファイル名のmanページで確認できます。
例えば/etc/hostsの情報はman hostsで、/etc/passwdの情報はman 5 passwdで確認できます。

myhostname

myhostnameは、自分自身のホスト名をIPアドレスに名前解決します。
私達がmyhostnameの機能を直接使うことはほとんどありません。
どちらかというと、アプリケーションの内部処理で利用される機能だと思います。

myhostnameの簡単な例を示します。
以下のコマンドにより、myhostname serviceを指定してNSSを検索しています。
実行しているLinuxのホスト名はpcです。
(※) getentはNSSを参照して名前解決を行うコマンドです。詳細は#getentで説明します

getent -s myhostname ahostsv4 pc | grep STREAM
# 192.168.1.110   STREAM pc
# 192.168.122.1   STREAM 
# 192.168.100.1   STREAM 

ホスト名の他にも、以下のような名前解決が可能です。

名前 IPアドレス
※自身のホスト名
  • 自身に設定されたIPアドレス
  • IPが未設定だった場合は、以下を返す
    • 127.0.0.2 (local loopback address)
    • ::1 (localhost)
  • localhost
  • localhost.localdomain
  • *.localhost
  • *.localhost.localdomain
  • 127.0.0.1
  • ::1
_gateway デフォルトゲートウェイIPアドレス

dns

dnsは、/etc/resolv.confの記載内容に基づいてDNSによって名前解決を行うserviceです。

/etc/resolv.confの書き方については詳しく触れません。
詳細はman resolv.confに書いてあります。
nameserverとsearchを指定するのが一般的だと思います。

resolve

resolveは、systemd-resolvedというDNSクライアントによって名前解決を行うserviceです3

systemd-resolvedは、RHEL7やRHEL8においては現状使われていません。
Fedoraでは、Fedora33以降でdnsを置き換える形で使われ始めました。

systemd-resolvedは、以下の特徴を持ちます。

  • モダンなDNS機能に対応する
  • D-Bus APIに対応し、NetworkManagerなどと連携して動作できる
  • 名前解決結果をキャッシュできる
  • hosts databaseのfiles, myhostname, dnsと同等の機能を持つ
  • ドメイン名に応じたDNSサーバの使い分けができる (※)

(※) Split DNSと呼ばれる機能。systemd-resolved用語ではPer-link DNSDNS Routingとも呼ばれる

systemd-resolvedについては、以下の記事で詳細に説明します。

endy-tech.hatenablog.jp

(参考) mdns4_minimal

環境によっては、mdns4_minimal [NOTFOUND=return]がhosts databaseに含まれることもあるので、軽く触れておきます。
mdns4_minimalがhosts databaseに含まれていない場合は、本セクションごとスキップして差し支えありません。
私の環境では、Cinnamon Desktop Environmentに依存関係として含まれていました。

mdns4_minimalは、デフォルトでは/etc/nsswitch.confには書いてありませんし、書いたとしても使えません。
このserviceが表すmDNS機能は、以下のRPMパッケージをインストールすることで使えるようになります。

sudo dnf install nss-mdns

nss-mdnsをインストールすることで、/etc/nsswitch.confmdns4_minimal [NOTFOUND=return]が追記されます。
また、mDNSの動作に必要なライブラリやプログラムも追加されます。

mdns4_minimalは、ホスト名.localの名前解決を行います。
上記に該当しない名前については、そもそも名前解決のクエリ自体がスキップされます。
そして、NSSの優先順位にしたがい、次のserviceで名前解決を試みるという動きになります。

言い換えると、ホスト名.local以外の名前を使う限り、mdns4_minimalは一切関与しません。
多くの場合、mdns4_minimalを利用したり意識することはないと思います。

nss-mdnsには、他にも以下のservice specificationが含まれます4

mDNSのservice 機能
mdns IPv4/IPv6の全てのmDNS機能 (.local以外のドメインも名前解決可能)
mdns4 IPv4の全てのmDNS機能
mdns6 IPv6の全てのmDNS機能
mdns_minimal IPv4/IPv6について、.local限定のmDNS機能
mdns4_minimal IPv4について、.local限定のmDNS機能
mdns6_minimal IPv6について、.local限定のmDNS機能

2点ほど余談を挟みます。

1つ目ですが、DNSサーバの検証をする際は.local.localhost.localhost.localdomainを使わないように気をつけましょう。
.localはmDNSと競合しますし、後者の2つはmyhostname serviceと競合します。
結果として上記ドメインDNSサーバに問い合わせられず、意図せぬ結果を招くことがあります。

2つ目は、mDNS (multicast DNS) についてです。5,6
mDNSは、名前解決のクエリにマルチキャストを使います。
IPv4224.0.0.251IPv6ff02::fbを宛先にセットしてクエリします。
宛先ポート番号はUDP5353です。

通常のDNSとは異なり、DNSサーバのIPアドレスを指定せず使えるのがmDNSの利点です。
その代わり、基本的には同一ネットワーク内限定のサービスとなります。

(参考) sss

sssは、sssデーモンによる名前解決を行います。
ここでの名前解決はFQDNやホスト名ではなく、ユーザー名やグループの名前解決です。

RHEL8では、passwd databaseやgroup databaseにおいてデフォルトで使用されています。
sssを使う主なメリットは、名前解決結果のキャッシュ機構を持つことで処理が効率化されていることです。

詳細が気になる方は、RHEL8のマニュアルを参照してください。

Fedoraについては、Fedora35以降よりsssdを使わなくなりました (参考: Fedora35の変更点)。

action

actionとは、/etc/nsswitch.confにおいて[]で囲まれた部分のことです。
以下のフォーマットで記述されます。

[STATUS=ACTION]

STATUSもACTIONも複数存在しますが、最低限resolve [!UNAVAIL=return]の意味さえ理解できれば十分です。

STATUSに入る値は、下表のいずれかです。
クエリに成功した場合はreturn、何らかのエラーが発生した場合はcontinueするのがデフォルトの動作です。

STATUS名 意味
SUCCESS
  • 名前解決に成功したことを表す
  • デフォルトのACTIONはreturn
NOTFOUND
  • クエリには成功したが、名前と対応する情報が見つからなかったことを表す
  • デフォルトのActionはcontinue
UNAVAIL
  • serviceが恒久的に利用不能であることを表す
  • 例えば...
    • 名前解決を行うデーモンが起動していない
    • 名前解決の定義ファイルの読み取り権限がない
  • デフォルトのActionはcontinue
TRYAGAIN
  • serviceが一時的に利用不能であることを表す
  • 例えば...
    • デーモンのポート番号が枯渇し、一時的にリクエストを受けられない場合
    • ファイルがロックされている場合
  • デフォルトのActionはcontinue

ACTIONに入る値は、下表のいずれかです。

ACTION名 意味
return
  • その場で結果を返す
  • 次のserviceには進まない
continue 次のNSS serviceによる名前解決を行う
merge
  • service1 [SUCCESS=merge] service2のように使われる
  • 上記の場合、service1とservice2の名前解決結果の両方が返される
  • 同じ名前解決結果が存在したとしても重複削除はされない

actionは、service specificationsの後に記述することで意味を持ちます。
例えば、以下の例ではmdns4_minimalで名前解決を試みたとき、クエリ結果が "not found" だった場合はその場で結果を返します。

mdns4_minimal [NOTFOUND=return]

STATUSの前に! (否定) を記述することで、STATUS部分の意味が逆になります。
例えば以下の場合、resolveが恒久的に利用不能な場合を除けば必ず結果を返すようになります。
具体的には、systemd-resolvedサービスが停止している場合にはcontinueになりますが、そうでなければreturnになります。

resolve [!UNAVAIL=return]

getent

/etc/nsswitch.confに記述したNSSの挙動を確認するには、getentコマンドが便利です。
getentは、databaseを指定して名前解決を実行し、その結果を返すコマンドです。

syntaxは以下の通りです。

getent [-s <service>] <database> <key>

それぞれ以下の意味を持ちます。

キーワード 意味
-s <service>
  • 特定のservice specificationでのみ名前解決を行う
  • 指定しなかった場合、/etc/nsswitch.confに従う
<database>
  • database名
  • 例えばfilesmyhostnameresolvednsなど
  • 他にもahostsahostsv4ahostsv6などを指定可能
|| <key> |
  • 名前解決の対象となる文字列を指定する
  • 例えば、files databaseの場合はFQDNIPアドレスを指定する

いくつか例を示します。

hosts databaseの場合は、以下のようなコマンドが便利です。
あまり詳しくは理解していませんが、hostsを指定するとIPv6の結果が返ることが多いです。
IPv4アドレスの結果が欲しい場合は、ahostsv4が便利です。

getent hosts google.com
# 2404:6800:4004:812::200e google.com

getent ahostsv4 google.com
# 142.250.196.142 STREAM google.com
# 142.250.196.142 DGRAM  
# 142.250.196.142 RAW

STREAM、DGRAM、RAWはUnix Socketを表しています。
Unix Socketについてはあまり詳しくありませんが、TCP通信はSTREAM、UDP通信はDGRAM、ICMP通信はRAWを使うようです。7,8,9,10,11
3行とも同じ結果が出てくるので、あまり気にしなくても良いのではと思います。

もう一つ例を示します。
以下の出力では、passwd databaseの内容を表示しています。
LDAPを利用している環境では、LDAP経由で得られるユーザー情報もこのコマンドから確認できるようです。

getent passwd
# root:x:0:0:root:/root:/bin/bash
# (以下略)

(参考) その他のDNSクエリコマンド

getent以外にもDNSの名前解決をテストするコマンドはいくつかあります。
これらのコマンドは特定のserviceに特化したもので、NSSを参照しません。

  • dig
  • host
  • resolvectl query

bind-utils RPMパッケージに含まれるdighostは、/etc/resolv.confのみを参照します。12,13

systemd-resolvedを操作するresolvectl queryコマンドは、systemd-resolvedを介した名前解決を試みます14

(参考) NSSの利用例

本セクションでは、NSSが利用される具体的な場面をいくつか紹介します。
一方、NSSを使わないケースについても一部触れます。

hosts databaseの利用例

FQDNやホスト名を指定して通信を行う場合、名前解決を行ってIPアドレスに変換した上で通信します。
Linuxにおいては、このFQDN/ホスト名の名前解決手段を決めるためにNSSのhosts databaseを使用します。

具体的な挙動は、#サマリにて説明したとおりです。

passwd,shadow,group,gshadow databaseの利用例

以下のdatabaseは、ユーザー/グループの認証に使用されます。

  • passwd
  • shadow
  • group
  • gshadow

それぞれ/etc/配下の同名のファイルに従ってユーザー名やグループ名と詳細情報を紐付けます。
詳細情報とは、具体的に以下のような内容です。

LDAPなどの認証プロトコルを利用する場合は、上記databaseを書き換えることになります。

services databaseの利用例

RHEL7、RHEL8の/etc/nsswitch.confの一部を以下に抜粋します。

services:   files sss

files serviceに対応するファイルは、/etc/servicesです。
このファイルには、TCP/UDPポート番号とサービス名の紐付け情報が書かれています。
一部を抜粋します。

# (一部抜粋)
ssh             22/sctp                 # SSH

ssなどのプログラムは、ポート番号の表示にservices databaseを使用しています。
ssの実行結果を一部抜粋します。
以下の出力ではポート番号の表示にsshと表示されていますが、これは22と同じ意味です。
このsshと名前で表示する処理に、NSSのservices databaseを介して/etc/nsswitch.confが使われています(※)
(※) /etc/nsswitch.confservices: sssに書き換えたり、/etc/servicesssh行を#コメントアウトするとssの表示結果に影響を与えました。

ss -lt
# State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process       
# LISTEN  0       128     0.0.0.0:ssh         0.0.0.0:*
# LISTEN  0       128     [::]:ssh            [::]:*

networks databaseは、ipコマンドには使われていない

servicesと似たようなdatabaseとして、networksもあります。
/etc/networksは、以下のようにネットワークアドレスと和名を紐付けています。

default 0.0.0.0
loopback 127.0.0.0
link-local 169.254.0.0

ip routeコマンドで表示されるネットワークアドレスにもdefaultキーワードが出てきます。
しかし、ip routeコマンドについては/etc/nsswitch.conf/etc/networksも参照していません(※)
(※) どちらの設定ファイルをいじっても、ip routeの出力結果に影響を与えませんでした

その他

ここまで見てきたように、NSSを使っているか使っていないかを見分けることは容易ではありません。
hostsやpasswdのような重要なdatabaseについては挙動をしっかり理解しておくべきですが、他のdatabaseについてはあまり気にしないのが良いバランスなのではないかと思います。

他にも色々なdatabaseがありますが、どうしても情報が必要なケースがあれば/etc/nsswitch.confを変更しながら地道に挙動確認しようと思います。
しかし、そのようなケースはほとんどないと思います。

(参考) /etc/nsswitch.confのさらなる最適化

systemd-resolvedは、NSS hosts databaseの以下の機能を内包します。

  • files (/etc/hosts)
  • dns (DNSサーバによる名前解決。/etc/resolv.confとの互換性もある)
  • myhostname (自分のホスト名やデフォルトゲートウェイなどの名前解決)

また上記の仕組みとは異なり、systemd-resolvedは名前解決の結果を一定時間キャッシュするので、処理がより効率化されています。
唯一の弱点は、systemd-resolvedデーモンがクラッシュした場合に名前解決機能を継続できなくなることぐらいです。

こういった事情から、systemd-resolvedの作者は以下のように/etc/nsswitch.confを更新することを推奨しています15

↓更新前 (Fedora33以降)

hosts:      files myhostname resolve [!UNAVAIL=return] dns

↓更新後

hosts:      resolve [!UNAVAIL=return] files myhostname dns

「基本的には、処理が効率的なsystemd-resolvedに全て任せる。万が一systemd-resolvedがクラッシュしたときのために、filesやmyhostnameは後ろに残しておく。」

こういった設計思想のようです。
Fedoraのデフォルト値はあくまで「更新前」の状態ですが、名前解決が処理能力のボトルネックになるような状況が万が一発生した場合は「更新後」に変更することを検討しても良いのかもしれませんね。

まとめ

前半では、/etc/nsswitch.confのhosts database行の意味を解釈しました。
後半では、その解釈の根拠として/etc/nsswitch.confのファイルの読み方について、詳細に説明しました。

参考情報として、hosts以外のdatabaseやgetentコマンドについても少し触れました。

次の記事

Fedora33以降で使われ始めたsystemd-resolvedについて紹介します。
RHEL7、RHEL8ではまだ使われていませんが、そのうちスタンダードになるかもしれません。

endy-tech.hatenablog.jp


  1. The GNU C Library - System Databases and Name Service Switch

  2. man nsswitch.conf

  3. man - nss-resolve

  4. Arch Linux Wiki - Avahi

  5. ジョンのblog - vol37. mDNSとは

  6. Wikipedia - マルチキャストDNS

  7. Stack Overflow - What is SOCK_DGRAM and SOCK_STREAM?

  8. man socket

  9. man tcptcpの説明にSOCK_STREAMというキーワードが登場します

  10. man udpudpの説明にSOCK_DGRAMというキーワードが登場します

  11. man raw → SOCK_RAWの説明でICMPが言及されています。man icmpも参照

  12. man host

  13. man dig

  14. resolvectl queryがNSSではなくsystemd-resolvedを参照するというドキュメント上のエビデンスはありませんが、手元の検証で確認しました。NSSでmdns4_minimalを有効化した上でgetent hosts remote.localリモートホストのmDNSによる名前解決を試みたところ、成功しました。一方で、resolvectl query remote.localでは失敗しました。systemd-resolvedのmDNS機能はデフォルトで無効化されていることから、systemd-resolvedでmDNSの名前解決を実行しようとすると失敗するのは想定通りです。もちろん、getent -s resolve hosts remote.localでも名前解決できません。以上の結果から、resolvectl queryはNSSを利用せず、systemd-resolvedの機能のみで名前解決を行うことが確認できました。

  15. man nss-resolve

Fedora33の変更点

Fedoraの変更点シリーズ

過去リリース分の記事は、以下のリンクを参照してください。

Fedoraの変更点シリーズ

お伝えしたいこと

Fedora 33の変更点を今更ながら紹介します。
私が気になるポイントのみなので、量は少なめです。

公式情報の見方

Fedora 33の変更点は、以下のリンクに載っています。
概要はリリースノートに、詳細情報はChange Setsのページに書いてあります。

Change Setの各サブタイトルのリンクから詳細情報に飛べるようになっています (下図赤枠部)。
詳細を知りたい時に便利なので、こちらも活用ください。

fedora_change_sets

Fedora33の既知の問題は、以下にまとめられています。
ざっと見ましたが、それほど気になるものはありませんでした。

他バージョンのFedoraについて知りたい場合は、以下のリンクを参照してください。

主な変更点

個人的に気になった箇所だけピックアップします。

デフォルトのエディタがvimからnanoに変更

Fedora 33以降は、デフォルトのエディタがvimからnanoに変更されました。
echo $EDITOR を実行すると、nano が指定されています。

この設定は、/etc/profile.d/nano-default-editor.shに書いてあります。
気になる方は、~/.bashrcに以下の行を足すことでデフォルトのエディタをvimに戻せます。

export EDITOR=/usr/bin/vim

Changesに書いてあったのですが、変更の背景は「viの使い方の学習に時間を割きたくない方にとって、Fedoraを使う時に障害を感じてほしくないため」とのことです。

Fedora Workstation のデフォルトのファイルシステムがBtrFSに

Fedora Workstation をこれからインストールする方のみ該当します。
インストール画面におけるデフォルトのFile System が BtrFSになります (元は恐らくXFSでしょうか)。

既にFedoraをインストール済みの方には関係ありません。
また、Fedora Server などには該当しません。

systemd-resolvedがデフォルトで有効化されるように

タイトルの通り、systemd-resolved.serviceがデフォルトで有効化されるようになりました。
また、/etc/nsswitch.confも書き換えられ、名前解決時の検索の優先順位が変更されました。

# Fedora32 以前では、dns (nss-dns) が主なDNSクライアントだった
hosts:      files mdns4_minimal [NOTFOUND=return] dns myhostname

# Fedora33以降では、resolve(nss-resolve) に置き換えられた
hosts:      files mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] myhostname dns

/etc/nsswitch.confは左から優先的に評価されます。
Fedora33以降では、files (/etc/hosts) > mdns4_minimal (ほぼ使わない) > resolve (systemd-resolved) > ... という優先順位になり、systemd-resolvedが実質的にメインのDNSクライアントになりました。

デフォルトの構成であれば、従来通りNetworkManagerでDNSクライアントの挙動は変更できます。
systemd-resolvedを本格的に使う場合は、このあたりを確認すると良いと思います。

man コマンド 内容
man systemd-resolved systemd-resolvedの機能と動作の説明
man resolvectl systemd-resolvedのステータス確認コマンド
man resolved.conf systemd-resolved.service の設定ファイル

systemd-resolvedを導入したメリットは、上述のChangesのリンクに書いてありました。

  • 標準化: systemd系サービスを取り入れ、ディストリビューション間の挙動の差異を埋める。Ubuntuは16.10以降既にsystemd-resolvedを使用している
  • resolvectlコマンドが便利
  • DNS問い合わせ結果のキャッシュが効く。WEBアクセスで秒単位の性能改善に繋がりうる (参考)
  • Split-DNS: 宛先ドメインごとにDNSサーバを使い分けることができる。VPN環境においては特に重要

systemd-resolvedについて記事を書きましたので、詳細についてはこちらをご参照ください。

endy-tech.hatenablog.jp

デフォルトでzramにswapする。ディスク上のswapは原則使われなくなる

Fedora33以降、デフォルトで/dev/zram0バイスが作成されます。
これは/dev/sda (SATAディスク) などと同じブロックデバイスであり、メモリ (RAM) 上に作成されます。

また、zramに配置されるデータは圧縮されます。
例えば、「zramに10MiBのデータを配置すると、データが圧縮されるため実際には5MiBのRAM容量を消費する」といった具合です。

更にこのデバイスが、zram-generatorの働きによりswapデバイスとして登録されます。
swapには実はpriorityというパラメータがあり、複数のswapデバイスが存在するときはpriorityの高いものから使われます。
既存のディスク上のswap領域はpriority=-2で、zram上のswap領域はpriority=100です。
これにより、zram上の領域を使い切らない限り、既存のswap領域は使われなくなります。
もちろん、/etc/fstabの変更によりpriorityをチューニングしたり、別の方法でzramデバイスの作成を抑止できますが、zramには以下のメリットがあります。

  • swap利用時もI/Oが高速になる
  • 積極的にzram上にswapすることで、メモリ領域を節約できる。メモリ容量が少ないマシンに効果的
  • データ圧縮効率が高い (元のファイルサイズの1/2や1/3など)
  • 圧縮処理によるCPUオーバーヘッドは、zramのサイズを極端に大きくしなければそれほど問題にならない

zramに割り当てる容量は、マシンのメモリサイズによって動的に決定されます。

詳しい挙動については、別の記事にまとめたのでそちらをご覧ください。

endy-tech.hatenablog.jp

まとめ

Fedora 33 の変更点のうち、気になった部分のみまとめました。

  • デフォルトのエディタがvimからnanoに変更
  • Fedora Workstation インストール時、デフォルトで選択されるファイルシステムEXT4からBtrFSに変更
  • デフォルトのDNSクライアントがnss-dnsからsystemd-resolvedに変更
  • swapにzramを利用するよう変更

RHEL9にもこの変更が適用されるかもしれないと思うと、わくわくしますね。

次の記事

Fedora33以降でデフォルトになった機能について、一部深堀りして紹介します。

/etc/nsswitch.confがFedora33以降でどう変わったか。
/etc/nsswitch.confをそもそもどう読むのか紹介します。

endy-tech.hatenablog.jp

systemd-resolved の特徴と基本的な使い方について紹介します。

endy-tech.hatenablog.jp

zramを使ったswapの挙動について紹介します。

endy-tech.hatenablog.jp

FedoraのOSバージョンアップ手順

fedora_logo

お伝えしたいこと

FedoraのOSをバージョンアップしました。
公式手順通りに実行しただけですが、実際にそのまま試してうまくいったということで記録に残します。
バージョンアップ手順は、公式サイトを都度ご確認ください。
手順が更新されている可能性もありますし、複数メジャーバージョンをまたいでバージョンアップするときはアップグレードパスが異なる場合もあります (※)。

(※) Fedora公式ページに書いてあります。Fedora20以前の場合は、Fedora21近辺を経由してから最新版にバージョンアップします。また、Fedora20以前からのバージョンアップ手順は異なります。

バージョンアップ手順の前に、なぜバージョンアップすべきなのか知りたい方は、先に#(参考) バージョンアップすべき理由をご覧ください。

バージョンアップ手順

公式情報

  1. Fedora Quick Docs - Upgrading to a new release of Fedora
  2. Fedora Quick Docs - DNF System Upgrade

1つ目の記事に、Fedoraのバージョンアップ手順が複数パターン書いてあります。
今回は2つ目の記事で紹介されている、DNF System Upgrade によるOSアップグレードを実施しました。
2021年現在、DNF System UpgradeによるOSアップグレードが公式で最も推奨されています。

事前バックアップ

公式手順にはさらっと書いてありますが、事前にバックアップを取得します。
今回はrsyncによるバックアップを取得しました。
Timeshift による定期バックアップももちろんセットしていますが、下記記事に記載の通り今回のケースにおいては3つの理由からrsyncの方が向いています。

  • ハードリンクを保持できる
  • システム不具合のリスクが高い作業である
  • バックアップ取得したいタイミングが明確である (バックアップ定期実行ではなく、手動実行でもよい)

rsyncによるバックアップ方法とメリットは、こちらの記事で紹介しています。Timeshiftのメリットにも触れています

endy-tech.hatenablog.jp

↓Timeshiftとは何か、Timeshiftの使い方についてはこちらの記事で紹介しています (基本GUIが必要です)。

endy-tech.hatenablog.jp

バージョンアップの実施

下記公式情報に従ってバージョンアップします。
Fedora Quick Docs - DNF System Upgrade

パッケージ更新、再起動

まずは、既存のrpmパッケージを全て最新にします。
--refreshを付けることで、ローカルのメタデータのキャッシュを「期限切れ」扱いにします。
要するにこのタイミングでリポジトリから改めてパッケージ一覧をダウンロードすることで、現時点の最新版のパッケージを確実にオンラインからダウンロードし、インストールするということをやっています。

sudo dnf upgrade --refresh

そしてコンピュータを再起動します (重要プロセスやカーネルの更新が入っている可能性もあるためだと思います)。

sudo reboot

dnf-plugin-system-upgrade のインストール

DNF によって Fedora をバージョンアップするために、DNF System Upgrade Plugin をインストールします。
既にインストール済みでしたらこの手順は不要ですが、とりあえず実行しちゃっても害はありません。

sudo dnf install dnf-plugin-system-upgrade

Fedora 新バージョン用のパッケージをダウンロード

ここは要注意です。
コピペする前にコマンドをよく確認してください。

dnf system-upgrade downloadは、バージョンアップに必要なパッケージをダウンロードするコマンドです (man dnf-system-upgrade)。
--refreshオプションは、メタデータのキャッシュを利用せず、必ずダウンロードするオプションです (man dnf)。

さて、ここが重要です。
--releaseverで、アップグレード先のFedoraバージョンを指定します。
基本的には最新の安定版を指定します (今回は37)。
最新の安定版は、Fedoraダウンロードサイトで確認できます (例えば初期パッケージが最小限のFedora Serverはコチラ)。
その他アグレッシブなバージョン (rawhide版や、2022年11月時点では安定版ではない38など) のインストール方法はまた異なりますので、公式サイトにてご確認ください。
https://docs.fedoraproject.org/en-US/quick-docs/dnf-system-upgrade/

ダウンロードには以下のコマンドを実行します。
大量のダウンロードが発生しますが、この時点ではまだ影響はありません。

sudo dnf system-upgrade download --refresh --releasever=37

もし従来バージョンでは提供されていたパッケージが、バージョンアップ後のリポジトリに存在しない場合にはエラーになることがあります。
その際は、--allowerasingオプションを追加することで存在しないパッケージについてはインストールを諦めることができます。
このオプションをつけて実行した場合、実際に削除されるパッケージ名を確認できるはずです (未検証ですが、downloadコマンドなので悪影響はないはずです)。
このオプションを追加して、「実際に消えても問題ないパッケージか」、「代替品を探しておくべきか」、「新バージョンのリポジトリに追加される見込みがあれば待つべきか」など検討した上で、更新作業を進めてよいかご判断ください。
非公式のリポジトリを追加しているとこのような事態になりうるとのことですが、幸いなことに今回のケースでは何も起こりませんでした。

Fedoraバージョンアップ

以下のコマンドを実行した直後、OS再起動してバージョンアップ処理が実行されます。

sudo dnf system-upgrade reboot

バージョンアップされたことの確認

以下のようなコマンドで確認可能です。

cat /etc/os-release 
# NAME="Fedora Linux"
# VERSION="36 (Server Edition)"
# (以下略)

dnf list
# (fc35など、新しいバージョンに対応したパッケージが表示される)

RHEL系の場合は以下コマンドでも確認できます。

cat /etc/redhat-release
# Fedora release 36 (Thirty Six)

以上でバージョンアップ作業は完了です。
お疲れ様でした。

(任意) retired packagesの削除

(参考) retired packagesとは

参考: Fedora Docs - #Clean-up retired package

新しいバージョンのFedoraがリリースされるたびに、様々な理由でFedora公式リポジトリで一部のパッケージが配布対象から外されます。
配布対象からはずされたRPMパッケージは、今後Fedora公式のRPMリポジトリでアップデートが配布されなくなります。

retired packagesとは、以下の理由でFedora公式リポジトリでの配布が終了したパッケージのことです。
retired packagesとみなされる主な原因は、以下が挙げられます。

  • パッケージが古く、今後は使われなくなる
  • アップストリームの更新が停止している
  • メンテナが更新終了を宣言した

retired packagesは、明示的に削除しない限りずっとインストールされたまま残り続けます。
そのままでもFedoraの動作に直接悪影響を与えることはあまりないと思います。
しかし、更新が止まったパッケージが存在し続けることで、セキュリティの脆弱性を踏む可能性が高まります。

retired packagesの中で自分が使わないものがあれば、この機に一括削除することがFedora公式でも推奨されています。

retired packagesの削除手順

Fedora36より1remove-retired-packagesというコマンドが利用できるようになりました。
このコマンドを使えば、「Fedoraのあるバージョン以降のretired packagesを一覧化して削除する」ことが可能となります。

削除する際に気をつけていただきたいのですが、retired packagesが自分にとって本当に不要なパッケージなのか、都度判断が必要です。
retired packagesの洗い出しはツールに任せられますが、削除するか否かの判断はご自身で行うようにしてください。

初回利用時は、以下のコマンドでremove-retired-packagesをインストールします。

sudo dnf install remove-retired-packages

remove-retired-packagesを実行すると、以下の条件の両方に該当するパッケージが一覧表示されます。
その後、それらのパッケージ1つ1つに対してdnf removeコマンドが実行され、該当パッケージを削除するか否かを選択できます。

  • 「今のバージョン-1」以降にリリースされたretired packages
    • (例えば、今Fedora36なら、Fedora35のretired packagesが対象になります)
  • 現在インストールされているパッケージ
remove-retired-packages

以下のようにFedoraバージョンを指定すると、そのバージョン以降のretired packagesが対象になります。
Fedoraを繰り返しバージョンアップしてきたマシンで今回初めてバージョンアップする場合は、バージョン番号を指定することをおすすめします。
あまりにも古いバージョンを指定するとretired packagesの洗い出しに時間がかかりますので、適切なバージョン番号を指定すると良いでしょう。

# remove-retired-packages 32

バージョン番号をどう指定すれば良いかわからないときは、以下のコマンドで今インストールされているパッケージのFedoraリリースを確認するのが良いと思います。

以下のコマンド出力例からは、インストール済みのパッケージがFedora32向け以降であることがわかります。
したがって、remove-retired-packages 32を実行すれば良さそうと判断できます。
(※) リリースバージョンが古くてもretired packagesではないものもありますので、この結果はあくまで目安と考えてください。

rpm -qa | grep -Po 'fc\d+' | uniq | sort
# fc32
# fc34
# fc35
# fc36

最後に、remove-retired-packagesを実行したときのログを添付します。

# remove-retired-packages 32
# Looking for retired packages betweeen Fedora 32 and Fedora 36

# These packages have been retired:
# bashtop: Linux resource monitor
# compat-openssl10: Compatibility version of the OpenSSL library
# ...

# ===================================================
#  Package  Architecture  Version  Repository  Size
# ===================================================
# Removing:
#  bashtop   noarch  0.9.25-1.fc33  @fedora   252 k
# Removing unused dependencies:
#  pcp-conf  x86_64  5.3.6-2.fc36   @fedora   63 k
#  pcp-libs  x86_64  5.3.6-2.fc36   @fedora   1.4 M
#  sysstat   x86_64  12.5.6-1.fc36  @updates  1.6 M

# Transaction Summary
# ===================================================
# Remove  4 Packages

# Freed space: 3.3 M
# Is this ok [y/N]:

# (以下略)

(参考) バージョンアップすべき理由

1つ目の理由は、古いOSバージョンでは最新のパッケージが使えないことがあるためです。
OSがサポート期間中であっても、最新のOSバージョンに紐づく公式リポジトリでしかパッケージが更新されないことがあります。

今回についても、GNS3クライアントをFedora に導入しようとしたら、最新版のGNS3クライアントが新しいバージョンのFedora公式リポジトリでしか提供されていなかったのがバージョンアップのきっかけになりました。
※GNS3サーバとGNS3クライアントは、同じバージョンでないと動きません。今回はGNS3サーバが最新だったので、クライアントも最新にしたかったのです

2つ目の理由は、OSがサポート切れ (End of Life) になると色々と困るためです。
これは言わずもがなですね。
サポートが切れると、パッケージが更新されなくなったり、各種製品の動作要件から外れたり、新しいツールが動作しなくなったりします。
ツールのバージョンを古いまま保ったとしても外部サービスと連携する時に互換性がなくなり、不具合が起こるリスクもあります。

3つ目の理由は、あまりにも古いバージョンのままにしておくと、アップグレードパスが複雑になるためです。
Fedora公式のアップグレード手順にも記載がありますが、End of Life のOSからのアップグレードはサポートされないか、良くても複雑な手順を踏むことになります。
中間のバージョンを経由しつつ複数のバージョンアップ作業が必要になることもザラです。
常に最新に保つ必要まではないですが、あまり最新版とかけ離れないよう意識するのが大事かなと思います。
特にFedoraのようにリリースサイクルの短いディストリビューションではなおさらです。

(参考) Fedoraのサポート期間について

サポート対象のFedoraは、基本的に最新版とその1つ前のバージョンです。
2つ先のバージョンがGeneral Availableとしてリリースされてから、4週間が経過するとEnd of Lifeになります。2

例えば、2021年現在でFedora35が最新です。
この場合、サポートされるのはFedora35, Fedora34のみです。
Fedora35がリリースされてから4週間以内であればFedora33のサポートが継続するので、この間であればFedora33→Fedora35というアップグレードもサポート期間内に可能です。
Fedora33以下はEnd of Life (EoL) となります。

サポート期間の目安ですが、13ヶ月程度と言われています3
Fedoraの新しいバージョンのリリースはおよそ6ヶ月ごとに行われます。

現時点でどのバージョンのFedoraがサポートされているかの情報は、以下のリンクが参考になります。

リンク コメント
Fedora - End of Life ※EoLになったFedoraの一覧
Get Fedora "Download Now"のリンクから、Fedoraの最新リリースを確認できる
Fedora - Releases/HistoricalSchedules 過去のリリース日とリリースノートへのリンクがある。
たまに更新が遅れる

EoLになってもOSバージョンアップは可能ですが、それでもなるべく早く最新版に追従した方が良いと思います。
ないとは思いますが、10年ぐらい塩漬けにするとアップグレードパスが複雑になるリスクもあります。

(参考) アップグレードパスの実績

本記事の手順にて、私が経験したFedoraのアップグレードパスを記録しておきます。

アップグレード元 アップグレード先
32 33, 35
33 35
35 36
36 37

(参考) ディスク空き容量について

アップグレードに際し、大量のパッケージをダウンロードします。
ルートファイルシステムサイズが16GiBのVMをアップグレードするのに、約10GiBの空き容量が必要でした。

空き容量が足りない場合、sudo dnf system-upgrade download コマンドがcurlのエラーなどによって失敗しました。

まとめ

Fedoraのバージョンアップ手順を書きました。
公式通りの手順でうまく行きました。
基本は公式ページに従って操作するのが良いと思いますが、ざっくり予習するのに本記事が少しでもお役に立てれば幸いです。

rsync+TimeshiftによるLinuxシステム・データバックアップ方法の紹介

sync

Icons made by Freepik from www.flaticon.com

前の記事

Linux のシステムバックアップツールとして、Timeshift を紹介しました。

Timeshift の特徴と使い方
endy-tech.hatenablog.jp

Timeshift の詳細
endy-tech.hatenablog.jp

お伝えしたいこと

Timeshift は優秀なシステムバックアップ手段ですが、2つの弱点があります。

  • Timeshift がシステムバックアップに特化しており、データバックアップには別製品を利用するのが推奨されていること
  • Timeshiftが発行するrsyncコマンドは -H が指定されていないため、コピー元のハードリンクの関係性を保持しないこと

それらの弱点を補強する手段として、rsyncコマンドの使い方を紹介します。

なお、Timeshift に全く興味のない方でもrsyncのオプションは参考にしていただけると思いますので是非ご覧ください!

今回実現したいバックアップ構成

今回は、2種類のバックアップを使い分けます。
それぞれ長所と短所があるのですが、両方実行することで補えるようになっています。
ここでは概要のみ示します。
詳細については、(参考) Timeshift と rsync の違いの詳細で紹介します。

比較軸 方式1 方式2
バックアップツール Timeshift (GUI) rsync (コマンド)
実行間隔 定期自動実行 不定期手動実行
世代管理 する しない
バックアップ先 同一ディスクの別パーティション (/home) 外付けHDD
コピー元のハードリンク 保持する 保持しない

方式1 のTimeshiftは、システムバックアップを定期的かつ高頻度に実行します。
不意のシステム不具合が発生した時に、ロールバックするために使います。
ロールバック先が比較的最近のデータになるので、ロールバック時の影響範囲が少なくて済むのが利点です。

方式2 のrsyncは、システムバックアップとデータバックアップの両方を不定期かつ低頻度に実行します。
外付けHDDにバックアップを取得するので、ディスク故障時の復旧にも対応できます。
またハードリンクを保持してのバックアップが可能なので、システム復旧時においてもTimeshiftより理想的な復旧手段となります。
ただし、バックアップ頻度が少ないため、データ損失の範囲が大きくなりがちな点はTimeshift とトレードオフになります。

なお、以下の条件を両方満たす方は方式2のrsyncのみに振り切って、rsyncコマンドをanacronで定期実行する方式も良いと思います。

  • 常時マウント可能な外部ディスクを持っている (例えばNASNFSストレージを持っているか、SSHでリモートサーバにrsyncできる環境など)
  • 複数世代のバックアップが必ずしも必要ではない (複数世代の自力実装は作り込みに労力がかかります)

(方式1) Timeshift による定期システムバックアップ

Timeshift の使い方については、前回の記事を参照してください。

endy-tech.hatenablog.jp

(方式2) rsync による不定期全体バックアップ

バックアップ取得コマンド

以下のコマンドでバックアップを実行します。

sudo rsync -a -ADHRSX -vi --delete --force --stats --delete-excluded \
--exclude-from=/home/stopendy/Documents/backup/exclude.list \
/ \
/run/media/stopendy/toshiba_ext/pc/

以下、補足です。

  • 今回はシステムバックアップとデータバックアップを同時に取得しています
  • データのみバックアップを取りたい場合はコピー元ディレクトリを / から /home に変更します
  • コピー先の/run/media/stopendy/toshiba_ext/pc/は、外付けHDDを表しています

オプションの意味

Timeshift が発行しているコマンドをベースに、以下のアレンジを加えました。

  • ファイル属性保持のオプション (-AHX) の追加
  • 画面出力の簡素化 (-ii-i)
  • ログ出力オプション (--log-file) の削除
  • 世代管理用のオプション (--link-dest) の削除

各オプションの意味は、以下の通りです。

オプション 意味
-a, --archive -rlptgoD と同等。
-A, -H, -X は含まない
-r, --recursive ディレクトリ配下もコピーする
-l, --links シンボリックリンクをそのままコピーする
一方で-L, --copy-linksを指定すると、リンクを追跡してファイルの実体をコピーする
-p, --perms パーミッションを保持する
-t, --times 更新時刻を保持する。
ファイルのタイムスタンプが同じだった場合にコピーをスキップさせたい場合は、必須のオプション
-g, --group 所有グループを保持する
-o, --owner オーナーユーザーを保持する
-A, --acls ACLを保持する
-D --devices --specials と同義
--devices バイスファイル (/dev/*) も送れるようにする
--specials 特殊ファイル (named sockets, fifo など) も送れるようにする
-H, --hard-links ハードリンクを保持する (コピー元でinodeが等しいファイルの組み合わせを見つけたら、コピー先でも同じinodeになるようにする)
-R, --relative 送信元として指定したディレクトリ階層を保持してコピーする。例えばrsync a/b cを実行すると、c/bにファイルが生成するが、rsync -R a/b cを実行すると、c/a/bにファイルが生成する。今回はコピー元のパスが/なので意味はない
-S, --sparse スパースファイルを認識し、スパースファイルとしてコピーする
-X, --xattrs 拡張ファイル属性を保持する
v, --verbose より詳細に画面/ログ出力する
-i, --itemize-changes 変更点のあったファイルを一覧表示する。
-iiのように繰り返すと、変更されていないファイルも一覧表示する
--delete バックアップ先のディレクトリ内のファイルで、対応するバックアップ元のディレクトリに存在しないものがあった場合、そのファイルを削除する
この挙動はバックアップ元のパスにディレクトリを指定した場合のみ有効となる
--force バックアップ先の空でないディレクトリ名とバックアップ元のファイル名が同じだった場合、ディレクトリを削除してファイルをコピーする
--stats コピー完了後に表示される統計値をより詳細にする
--delete-excluded --excluded--exclude-fromなどによってコピー対象から除外されたファイルも、コピー先に存在した場合は削除する
--exclude-from=<FILE> コピー対象から除外するファイル・ディレクトリの一覧を<FILE>から読み取る。
コマンドラインに直接除外条件を書く場合は、--exclude=<PATTERN>を使う

除外リスト

除外リストの中身

--exclude-from オプションに指定したexclude.listは、以下のような中身になっています。
リストの書きっぷりは環境によって若干変わります。
/home 配下のユーザー名を変えたり、条件を足し引きしたり、一部をワイルドカード (*) で置き換えることで、他の環境にもマッチする形にアレンジできると思います。

#####################
# For System Backup (From Timeshift)
#####################
/dev/*
/proc/*
/sys/*
/media/*
/mnt/*
/tmp/*
/run/*
/var/run/*
/var/lock/*
/var/lib/docker/*
/var/lib/schroot/*
/lost+found
/timeshift/*
/timeshift-btrfs/*
/data/*
/DATA/*
/cdrom/*
/sdcard/*
/system/*
/etc/timeshift.json
/var/log/timeshift/*
/var/log/timeshift-btrfs/*
/swapfile
/snap/*
/root/.thumbnails
/root/.cache
/root/.dbus
/root/.gvfs
/root/.local/share/[Tt]rash
/home/*/.thumbnails
/home/*/.cache
/home/*/.dbus
/home/*/.gvfs
/home/*/.local/share/[Tt]rash
/root/.mozilla/firefox/*.default/Cache
/root/.mozilla/firefox/*.default/OfflineCache
/root/.opera/cache
/root/.kde/share/apps/kio_http/cache
/root/.kde/share/cache/http
/home/*/.mozilla/firefox/*.default/Cache
/home/*/.mozilla/firefox/*.default/OfflineCache
/home/*/.opera/cache
/home/*/.kde/share/apps/kio_http/cache
/home/*/.kde/share/cache/http
/var/cache/apt/archives/*
/var/cache/pacman/pkg/*
/var/cache/yum/*
/var/cache/dnf/*
/var/cache/eopkg/*
/var/cache/xbps/*
/var/cache/zypp/*
/var/cache/edb/*
#/home/stopendy/**
#/root/**
#/home/*/**

#####################
# /home/
#####################
# /home/stopendy/, /home/*/.*
+ /home/stopendy/Applications/
+ /home/stopendy/Desktop/
+ /home/stopendy/Documents/
+ /home/stopendy/Music/
+ /home/stopendy/Pictures/
+ /home/stopendy/Videos/

+ /home/*/.config
+ /home/*/.dir_colors
+ /home/*/.local/
+ /home/*/.local/bin/***
+ /home/*/.local/lib/***
- /home/*/.local/*
+ /home/*/.ssh/

- /home/stopendy/*
- /home/*/.*

- /home/*/.config/google-chrome/
- /home/stopendy/.config/Slack/
+ /home/*/.config/Code/User
- /home/*/.config/Code/*
+ /home/*/.config/Code/User/*.json
- /home/*/.config/Code/User/*

# /home/**/*.log
+ /home/*/Documents/**/*.log
- /home/**/*.log

#/home/**/Cache/
- /home/**/Cache/

# /home/shared/
- /home/shared/libvirt/images/*.qcow2

# /home/timeshift/
- /home/timeshift/

#####################
# /root/
#####################
# /root/
+ /root/Applications/
+ /root/Documents/
+ /root/Music/
+ /root/Pictures/
+ /root/Videos/

+ /root/.config
+ /root/.dir_colors
+ /root/.local/
+ /root/.local/bin/***
+ /root/.local/lib/***
- /root/.local/*
+ /root/.ssh/

- /root/*
- /root/.*

- /root/.config/google-chrome/
- /root/.config/Slack/
+ /root/.config/Code/User
- /root/.config/Code/*
+ /root/.config/Code/User/*.json
- /root/.config/Code/User/*

# /root/**/*.log
+ /root/Documents/**/*.log
- /root/**/*.log

除外リストの設計思想

前半のシステムバックアップに関係する部分は、Timeshift で使っているexclude.list をほぼ流用しています。

後半のデータバックアップに関係する部分は、ホームディレクトリ直下の隠しフォルダ配下のバックアップ対象を厳選しています。
特に、ブラウザやVSCodeのキャッシュファイルが大量にあって邪魔だったので明示的に除外しています (Code, google-chrome, Slack)。

除外リストの文法

除外リストの文法を簡単にまとめました。
公式情報は、man rsync の「FILTER RULES」セクションに書いてあります。

  • コピー対象から除外するディレクトリを列挙する
  • 先頭に+を記載すると、「このファイルを含める」という意味になる (プラス記号の後に半角スペースが必要)
  • 先頭に-を記載すると、「このファイルを除外する」という意味になる。この記号は省略しても同じ意味 (マイナス記号の後に半角スペースが必要)
  • 先頭に#を記載すると、コメント行扱いとなって無視される
  • *は1文字以上 (スラッシュを含まない) にマッチする
    • /a/*は、/a/bにマッチするが、/a/a/b/cにはマッチしない
  • **は1文字以上 (スラッシュを含んで良い) にマッチする
    • /a/**は、/a/b/a/b/cにマッチするが、/aにはマッチしない
  • ***は0文字以上 (スラッシュを含んで良い) にマッチする
    • /a/***は、/a/a/b/a/b/cの全てにマッチする
  • 除外条件と含める条件の評価順は、--exclude--includeと同じ考え方となる
    • 例えば、a/b/cにあるファイルcは、aa/ba/b/cの全てが除外扱いではない場合のみコピーされる
    • 除外リストの複数条件に一致する場合、リストの上に書いてある方が優先される
    • 詳細はてっく煮ブログが参考になる

除外リストの動作確認

exclude.listを更新する際は、テスト実行モード (-n, --dry-run) とバックアップ対象ファイルの全表示 (-ii) も一緒に指定しつつrsyncを繰り返し実行すると便利です。
出力されたバックアップ対象ファイルをチェックすることで、exclude.listの定義にミスがないかを事前に確認することができます。
今回の例では、以下がテストコマンドになります。

sudo rsync -n -a -ADHRSX -vii --delete --force --stats --delete-excluded \
--exclude-from=/home/stopendy/Documents/backup/exclude.list \
/ \
/run/media/stopendy/toshiba_ext/pc/

rsync バックアップからの復旧

注意事項

復旧手順は、以下の条件で検証しました。

  • KVM上の仮想マシンFedora 32 + Cinnamon Desktop など、模擬的にパソコン環境を再現
  • rsync でシステム・データバックアップ
  • Fedora 33 にバージョンアップ
  • データ復旧確認
    • ファイルを1つ削除して、データ復旧コマンドを実行
    • 再ログインし、最低限の動作確認を実施
  • システム復旧確認
    • システム復旧コマンドを実行し、OS再起動
    • カーネルやパッケージのバージョンが元に戻ったことを確認
    • 最低限の動作確認を実施

ご覧の通り、擬似環境で最低限の確認しかしていません。
情報としては参考になると思いますが、実際に使う前に必ず手元のVMなどでも検証するようにしてくださいね。

復旧コマンドの基本的な考え方

以降のセクションでシステム復旧、データ復旧、システム+データ復旧の3つの例を紹介します。

3つの例に共通して、復旧の際はバックアップ取得コマンドを部分的に変更して実行します。
基本的には引数を入れ替えて、--delete-excluded-R を外します。
単純にコピー元とコピー先の引数を入れ替えただけで実行すると、大事故に繋がる可能性がありますのでご注意ください。
必ず検証してくださいね。

(1) システムの復旧

システム復旧コマンドは、バックアップコマンドをベースに以下のように作りました。

  • --delete-excluded-Rを外す
  • コピー元とコピー先の引数を入れ替える
  • --exclude-fromより手前に--exclude=/homeも入れることでデータフォルダを除外する

検証したところ、OSをISOファイルから起動するレスキューモードからシステム復旧する必要は必ずしもありませんでした。
実際にシステムディスクがマウントされた状態で復旧してみたら、/var配下の1ファイルがエラーになっただけでしたので、このやり方でも問題ないようです (環境にもよるかもしれません)。
とはいえ、「やはり念のためレスキューモードから実行したい」と思った場合は、(参考) レスキューモードからの起動を参考にしてみてください。

システムをリストアした後は、必ずOS再起動してください。
大抵のファイルはrsyncで巻き戻されますが、メモリや/procなどの情報はそのままです。
システム復旧は、OS再起動まで実行して初めて完了するとご認識ください。

# システム復旧
sudo rsync -a -ADHSX -vi --delete --force --stats \
--exclude=/home  \
--exclude-from=/home/stopendy/Documents/backup/exclude.list \
/run/media/stopendy/toshiba_ext/pc/ \
/

(2) データの復旧

データ復旧コマンドは、バックアップコマンドをベースに以下のように作りました。

  • --delete-excluded-Rを外す
  • コピー元とコピー先の引数を入れ替えつつ、対象がデータディレクトリとなるように変更
  • 念のため--deleteを除外した (必要なら再度追加してもよい)
  • コピー先が/homeである場合、コピー元はxxx/home/のように末尾に/を入れること!
    • rsyncはコピー元に/を入れると、ディレクトリ内の全ファイルをコピー対象とする
    • コピー元に/が入っていない場合、ディレクトリ自体をコピー対象とする
    • /の有無でコピー対象の考え方が変わるので、-nでテストしつつ慎重に判断すること

データのみ復旧する場合は、システム復旧時とは異なりシングルユーザーモードで実行する必要はありません。
ただ設定ファイルを強制的に書き換えることで、ユーザープロセスがクラッシュすることがあります。
例えば、私の環境では日本語入力を管理するiBusデーモンがクラッシュし、一時的に日本語入力ができなくなりました。
これらのプロセスを復旧するためにも、/home配下のデータ復旧後は最低でもユーザーの再ログインは実施してください。

# データ復旧
sudo rsync -a -ADHSX -vi --force --stats  \
--exclude-from=/home/stopendy/Documents/backup/exclude.list \
/run/media/stopendy/toshiba_ext/pc/home/ \
/home

(3) システムとデータの復旧

基本的には、上述の(1) システムの復旧(2) データの復旧をそれぞれ実行してください。
同時に実行することはおすすめしません。
システム復旧とデータ復旧は、実行したい場面も目的もコマンドラインオプションも色々異なります。
たまたまそれら全てが一致することもあるかもしれませんが、手順がややこしくなるので辞めておいたほうが良いと思います。

以上で3記事に渡るTimeshift と rsync によるバックアップ構成の紹介は終了となります。
ご覧いただき、ありがとうございました。

以下に補足情報が続きます。

(参考) Timeshift と rsync の違いの詳細

Timeshift と rsync を使い分けているのは、それぞれの短所を補い合うためです。
概要は上述の今回実現したいバックアップ構成にて紹介しましたが、本セクションではより詳細に紹介します。

実行間隔

Timeshift は定期的にバックアップを取得します (日次や週次など)。
一方で、rsync不定期 (恐らく3ヶ月〜半年に1度程度) でバックアップを取得する想定です。

不意にシステムが起動しなくなってしまったときは、Timeshift により最近のバックアップデータから復旧させることでデータ損失を最小限にできます

なお、rsyncのバックアップ実行タイミングは以下の想定です。

  • システムに問題が発生するリスクの高い作業の直前
    • OSバージョンアップ
    • パッケージ一括更新 (sudo dnf upgrade)
  • ディスク故障に備えたユーザーデータのバックアップ (半年に一度程度)

世代管理

先に一般論を書きます。
バックアップの定期実行は、ある程度頻度を高くしないと復旧時のデータ損失が大きくなりますが、一方でデータの保管期間をある程度長くしないと復旧が間に合わずに手遅れになるリスクが高まります。

ここでツールの話に戻りますが、Timeshift はバックアップを複数世代持ちます。
rsync はバックアップを1世代のみ保管し、バックアップの度に同一ディレクトリに差分ファイルを追加・削除する動きです。

Timeshift は差分バックアップを複数世代持つことで、バックアップ取得間隔を短く保ちつつも、バックアップの保管期間を長くすることができます。
これは定期的にバックアップする用途として、重要な特徴です。

例えばバックアップを日次・週次で実行し、日次バックアップを3世代、週次バックアップを2世代保管する設定にしていたとします。
その場合は、基本的には当日・前日・前々日のデータに戻すことで最低限の損失でシステムを復旧させることができます。
対応が遅れてしまった場合でも、1、2週間前のバックアップから復旧させれば、多くの場合は不具合が発生する前まで戻せます。

普段遣いのLinux PC は「作業の度に手動バックアップ、作業後に正常性確認 (または常時監視)、異常を検知したらすぐに復旧」とは中々いかないと思います。
こういった定期バックアップ + 世代管理の仕組みがあることで、より快適にLinux を扱えるようになります。

バックアップ対象

一般的には、システムバックアップとデータバックアップは分けるべきです。
例えばTimeshift によってユーザーデータも含めてバックアップを取得してしまうと、システムを復旧したい時にユーザーデータも巻き添えを食って古いデータで上書きされてしまいます。
従って、Timeshift はシステムの (/homeを除いた) バックアップのみ取得する構成としています。
これはTimeshift 公式でも推奨されている使い方です。

一方で、データバックアップの取得はrsyncで行います。
今回の構成では事情により、rsyncでシステムとデータ両方のバックアップを取得しています。
しかしrsyncの場合は、コマンドや除外リストを少し弄ることでシステムのみ復旧、データのみ復旧といった操作も容易にできるので、特に問題はありません。

rsync でシステムとデータの両方のバックアップを取得していることは、rsyncが外付けHDDにバックアップを取得していることや、ハードリンクを保持できることに関係します。
これらの事情については、この後のセクションで紹介します。

バックアップ先 (同一ディスク vs 外部ディスク)

バックアップを実行する際、当然ながらバックアップ先のファイルシステムはマウントされている必要があります。

Timeshift は定期的にバックアップを取得します。
その性質から、Timeshift のマウント先は常時マウントされていることが望ましいです。
私の環境ではPCの内蔵ディスクはメインの1本しかありませんし、NASNFSも持っていません。
常時マウントされているディスクは、コピー元と同じメインのディスクしかない構成です。
Timeshift はシステムのみ (/homeを除外する) のバックアップですので、バックアップ先として/homeを指定します。

バックアップ元とバックアップ先が同一のディスクだと、設定ミスやファイル破損には対処できますが、ディスク故障でデータが失われてしまいます。
そこで、外付けHDDの出番です。
データバックアップは、外付けHDDを接続してrsyncコマンドを実行することで不定期に取得します。
これにより、内蔵ディスク自体が故障しても外付けHDDから復旧できます。

コピー元のハードリンクを保持するか否か

Timeshift はシステムバックアップを取得しますが、実は完璧ではありません。
Timeshift が内部的に実行しているrsyncでは、-AHXが指定されていません。
これにより、Timeshift のバックアップから復旧した場合、以下のような制限を伴います。

  • バックアップ元のACLを保持しない (-Aの指定がないため)
  • バックアップ元のハードリンクを保持せず、複数ファイルのコピーとして扱われる (-Hの指定がないため)
  • バックアップ元の拡張ファイル属性を保持しない (-Xの指定がないため)

特に-Hの指定がないことが影響を大きくしています。
ハードリンクの関係性にあるファイル群は、本来それらのうちどれか1つを更新すれば、他のハードリンクにも変更が反映されます。
しかし、単純なファイルコピーではそうも行かず、全てのファイルをそれぞれ同様に更新する必要が出てきてしまいます。
Timeshiftユーザーでこのことが問題になっている話は聞いたことがありませんが、システムファイルではハードリンクが少なからず使われているので、この影響は小さくないと思います。

一方でrsyncは、当然ながらどのオプションを付与するか自分で選択できます。
rsyncコマンドを実行する際、-AHXオプションを指定することで、上記の問題を回避できます。

Timeshiftよりも低頻度ではありますが、不定期にrsyncでシステムバックアップも取得することで、ハードリンクに関わる問題を回避することがrsyncを使う目的の一つです。

(参考) rsync実行時のフラグの意味

rsync実行時、-i-iiを付与して実行すると変更対象のファイル一覧の左にフラグが付きます。
例えば.f.d などのフラグです。

ここではフラグの読み方を紹介します。
正式な情報は、man rsync--itemize-changes, -iセクション配下を参照してください。

フラグの基本的な書式は、YXcstpoguax です。

Y (1文字目) は、更新方法を表します(update types)。
Yが最も重要です。

フラグ 意味
. 更新なし
> ローカルへのファイルコピー
< リモートへのファイルコピー (SSHなどによって外部ホストにファイルコピーしたときのみこのフラグが付く)
c ファイル・ディレクトリの生成
h ハードリンクとして生成 (-H, --hard-linksオプション利用時のみ)
* *MESSAGE と表記され、その他の変更を表す。主に*deletingでファイルの削除を表す

X (2文字目) は、コピーされるファイルの分類です。

フラグ 意味
f ファイル
d ディレクト
L シンボリックリンク
D バイスファイル
S special file (named socket や fifo など)

cstpoguax (3文字目〜11文字目) は、変更の理由として検知されたファイル属性です。

フラグ 意味
c チェックサム
s サイズ
t,T t: 更新時刻 (要:-t, --times)
T: 更新時刻 (-t, --timesを指定しなかった場合のみ表示される)
p パーミッション (要:-p, --perms)
o オーナー (要:-o, --owner。要:特権付き (sudoなど) でのrsync実行)
g グループ (要:-g, --group。要:特権付き (sudoなど) でのrsync実行)
u,n,b u: アクセス時刻 (use time。要:--atimes)
n: 作成時刻 (要:crtimes)
b: アクセス時刻と作成時刻の両方(both)
a ACL
x 拡張ファイル属性 (extended attribute)

cstpoguax (3文字目〜11文字目) は、以下の文字で表現されることもあります。

フラグ 意味
. 変更なし
+ 全フラグ変更あり (大抵、ファイルやディレクトリ生成になる)
? 不明 (宛先のrsyncのバージョンが古い場合に表示されることがある)
※スペース 全て変更なし (.) の場合は、スペースで省略表記される

最後に具体例を示します。

フラグ 意味
.f.d 何もしない (差分なし)
>f..t...... ファイルコピー (ファイルの更新時間のみ差分)
.d..t...... 何もしない (ディレクトリの更新時間のみ差分)
*deleting ファイル削除 (--deleteオプションなどが指定されており、対応するファイルがコピー先に存在しないため)
>f.st.....x ファイルコピー (更新時刻とファイルサイズと拡張ファイル属性の差分)
cd+++++++++ ディレクトリ生成 (全部差分あり)

(参考) レスキューモードからの起動

RHEL8のドキュメントによると、レスキューモードとはsystemdの機能で提供されるものとLinux起動イメージファイルを利用するものの2種類ありますが、ここでは後者のイメージファイルを利用したレスキューモードのお話をします。

手順はFedoraをベースに紹介します。
Debian系など他のディストリビューションでは手順が異なる可能性もありますが、ご了承ください。

レスキューモードでは、OSインストール用のISOファイルやUSBを利用してOS起動します。
ルートファイルシステム上でプロセスが動作していない状態で各種操作ができます。

rsyncによるシステム復旧の文脈においてどの程度の意味があるかはわかりませんが、ご参考までに手順を記載しておきます。

まず、起動順序を通常のHDDよりもUSB、またはCDドライブなどを優先するようにBIOS/UEFI (VMの場合は仮想マシンのオプション) を設定しておきます。
その上で、起動用USBを物理サーバに挿すか、仮想サーバの場合はCDROMにOSインストール用のISOファイルを紐づけます。

その状態でOSを起動すると、コンソール画面に以下のようなメニューが表示されるので、Troubleshooting -->を選択します。

rescue1

そして Rescue a Fedora systemを選択します。

rescue2

レスキューモードの起動方法の選択肢が出てくるので、基本的には1) Continueを選択します。
ハードディスクのマウントやネットワークの起動など実施してくれるので色々と便利です。

もしこれらの自動起動箇所に不具合があるなどの理由で1. を選択できない場合には、3) Skip to shellを選択します。
このオプションは何もせずISOファイルの中身だけを使ってシェルを起動するので、基本トラブルが発生することはありません。

rescue3

1) Continue を選択した場合

このモードを選んだ場合、ディスク上にインストールされたLinuxを検知し、自動的に/mnt/sysrootにマウントします。
マウントオプションはrwで、読み書き可能です (ちなみに2を選んだ場合はroになります)。
また、NetworkManager.serviceも起動し、ネットワークも使用可能になります。

このモードを選んだら、後は簡単です。
mnt/sysrootchrootして、リストア作業を続行するのみです。
chrootコマンドは以下のコマンドで実行できます。
また、su -コマンドにより、必要に応じてrootユーザのログイン処理を読み込むことも可能です (プロンプト文字列が変わると思います)。
またこのモードではデフォルトでUSキーボードになっているので、お好みに応じてキーボードの言語をchroot前に変えておきます。

localectl set-keymap jp
chroot /mnt/sysroot
su -

外付けHDDからリストアしたい場合は、外付けHDDを手動でマウントする必要があります。
まずはマウントポイントを作成します。
私の環境では、以下のようになります。

sudo mkdir -p /run/media/stopendy/toshiba_ext/

続いて、マウントしたい外付けHDDのデバイスファイルを特定します。
fdisk -lで詳細情報を表示し、ディスクサイズなどをヒントにデバイスファイルを特定します。
今回は/dev/sda2でした。

特定が難しい場合は、usb-devicesコマンドも試してみましょう。
ManufacturerSerial Numberを元にusb-devicesの表示の中から目的のUSBデバイスは容易に特定できます。
そのUSBデバイスには、Product=External USB 3.0と書かれていました。
一方でfdisk -lにもDisk model: External USB 3.0と同様に書かれていましたので、これらを見比べれば確実に紐づけできます。

usb-devices
sudo fdisk -l

最後に、手動でマウントします。

sudo mount /dev/sda2 /run/media/stopendy/toshiba_ext/

以降は、(1) システムの復旧に従ってリストアを続行します。
リストアが完了したら、exitで抜けます。
するとOSが自動が再起動し、うまくいけば復旧しています。

仮想マシンの場合はshutdown -h nowで一度シャットダウンしてから、ISOファイルの取り外しや起動順序の設定戻しなどを対応します。

3) Skip to shell を選択した場合

自動マウントやネットワーク起動などの処理が一切行われず、直接シェルが起動します。
まずは、Linux Root Filesystem を手動で /mnt/sysroot にマウントします。

まずは、LVMを使える状態にします。
LVMをそもそも利用していない場合は、この工程をスキップして問題ありません。
lvdisplayコマンドでLogical Volume を表示します。
以下の情報から、Volume Group の名前がfedora_pcであることと、LV StatusがNOT availableであることがわかります。

$ lvdisplay
# 一部のみ表示
  LV Path                /dev/fedora_pc/root
  LV Name                home
  VG Name                fedora_pc
  LV Status              NOT available

lvchangeコマンドにより、LVを利用可能にします。
--activate yは、対象をAvailableにするという意味です。
その後の引数には、LV PathVG Nameを指定します。
VG Nameを指定すると、対象のVolume Group に紐づくLogical Volume に対して一括変更できて便利なので、今回はこちらを指定します。
再びlvdisplayを実行し、LV Statusavailableとなっていることを確認します。

lvchange --activate y fedora_pc
lvdisplay

次に、システムディレクトリをマウントします。
マウントディレクトリを作成し、マウントします。
mountコマンドに指定するデバイスファイルには、先ほどのlvdisplayで表示されたLV Pathを指定します。
最初にRoot File Systemからマウントしますが、他にもファイルシステムがある場合は同じ手順を繰り返します。

mkdir /mnt/sysroot
mount /dev/fedora_pc/root /mnt/sysroot

そしてchrootします。

chroot /mnt/sysroot
su -

あとは1) Continue を選択した場合と同じ手順です。

Linux PC関連まとめ

endy-tech.hatenablog.jp

まとめ

Timeshiftの弱点を補強するシステム・データバックアップ方法として、rsyncコマンドについてまとめました。
本来であればTimeshift が-AHXオプションに対応するようプルリクエストを送るか、自力でrsync定期実行 & 差分管理スクリプトを組むのが理想かもしれません。
しかし、自身のスキルでは理想を追うのが難しいため、現時点で実現できる仕組みとして今回のやり方を考えました。

皆さまにとっても参考になれば幸いです。

Timeshift の仕組みを調べてわかったこと

timeshift_black_bold
引用元:timeshift_black_bold.png

前の記事

Timeshift の機能や特徴、基本的な使い方を紹介しました。

endy-tech.hatenablog.jp

お伝えしたいこと

Timeshift の内部動作に踏み込んで確認し、わかったことをまとめます。
Timeshift を普通に使う上では不要な知識かもしれませんが、以下の内容が気になった方はぜひ読んでみてください。

  • Timeshift が内部的にどのようにcronと連携しているのか
  • そもそもcrondとanacronは何が違うのか
  • Timeshift はrsyncコマンドをどのオプションをつけて実行しているのか

特にrsyncコマンドはオプションが多いため、既成品が使っているオプションを見るだけでも参考になると思います。

ソースコードにはほぼ触れず、Timeshift の実行ログやLinuxの設定ファイルを確認することで考察します。

バックアップ処理の定期実行の仕組み

Timeshift 定期実行の概要

後述のシステム起動からTimeshift トリガーまでの動きを追ってみるに書いてあるとおり、 Timeshift は crond によって毎時バックアップ処理をトリガーされています。
そして、Timeshiftのソースコードによると、バックアップが最後に実行された時刻を内部的に管理し、設定したとおりの実行周期 (日次や週次) となるようにバックアップ処理をスキップしたり実行したりしています。
Timeshift のこういった動きは、crondよりも寧ろanacronに近いです。

cron と anacron の違い

さて、anacron は cron と同様にインターバルを置きながら処理を繰り返したい場合に使われますが、cronと比較していくつかの違いがあります。
以下の2枚のスライドで、その要点を示します。

赤字部分が重要なポイントです。
また前のセクションで記載の通り、Timeshift は anacron に近い性質を持っているので、anacron の性質には特にご注目ください。

cron_vs_anacron_summary

cron_vs_anacron_example

crond は指定した時刻にPCを起動していないと日次ジョブ実行されません。
一方、anacronであれば1時間ほどPCを起動していれば起動時刻が何時であっても日次ジョブが実行されます。
パソコン用途としては、anacronの方が扱いやすい印象を受けます。

次のセクションでcronとanacronの違いについて、詳細を補足します。

(参考) cron の詳細

cronの設定ファイルである /etc/crontabは、以下のような構造になっています。
何曜、何月、何日、何時、何分に実行するかを明確に記述し、定刻に処理を実行する作りであることが読み取れると思います。
また、設定ファイルに記述できる最小単位がminuteであることから、最小実行間隔は「毎分」です。

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name  command to be executed

man crondによると、crondの設定ファイルは、以下の3箇所のいずれかに配置可能です。

  • /etc/crontab
  • /etc/cron.d/*
  • /var/spool/cron/<username>

各ファイルは、デフォルトでrootユーザのみ編集可能となるようパーミッションが設定されています。
ただし、/var/spool/cron/<username>だけは例外で、crontab -eコマンドで一般ユーザーの権限で編集できます。

anacron の詳細

anacronの設定ファイルである /etc/anacrontabは、以下のような構造になっています。
anacronは1日を最小単位として、一定時間間隔でコマンド実行する作りであることがわかると思います。

#period in days   delay in minutes   job-identifier   command
1  5  cron.daily      nice run-parts /etc/cron.daily
7  25 cron.weekly     nice run-parts /etc/cron.weekly
@monthly 45    cron.monthly        nice run-parts /etc/cron.monthly

システム起動からTimeshift トリガーまでの動きを追ってみる

コマンドの出力を順に貼りつつ紹介します。
出力は一部のみ抜き出しています。

最初に、システム起動の過程でcrond.serviceが起動します。
そこで、systemctl cat crondコマンドによって crond.service の中身を見てみました。
/etc/sysconfig/crondによると、$CRONDARGSの中身は空っぽでした。
つまり、サービス起動時に実行しているコマンドは /usr/sbin/crond -n です。
-n はフォアグラウンド実行という意味で、サービスから起動する際にしばしば指定するオプションのようです。

$ systemctl cat crond
# /usr/lib/systemd/system/crond.service
# (略)
[Service]
EnvironmentFile=/etc/sysconfig/crond
ExecStart=/usr/sbin/crond -n $CRONDARGS
# (略)

man crondによると、crondが起動した時には以下の3ファイルを読み込みます。

  • /etc/crontab
  • /etc/cron.d/*
  • /var/spool/cron/<username>

/etc/cron.d/timeshift-hourlytimeshift --check --scriptedコマンドを毎時0分 (hh:00) に発行し、Timeshiftの定期実行をトリガーしていました。
このコマンドを発行したタイミングが Timeshift に設定した定期バックアップの間隔以降であれば、バックアップを取得します。
それ以外のタイミングでトリガーされた場合は、処理がスキップされます。
ちなみに、このファイルは毎日/毎週/毎月/N日のスケジュール実行を指定している場合のみ生成されます。

$ cat /etc/cron.d/timeshift-hourly
# (略)
0 * * * * root timeshift --check --scripted

また、「OS起動時」のスケジュール実行を指定している場合は、/etc/cron.d/timeshift-bootが生成します。

@rebootによって「OS起動時」を表現しています (man 5 crontab参照)。

$ cat /etc/cron.d/timeshift-boot
# (略)
@reboot root sleep 10m && timeshift --create --scripted --tags B

(参考) 続き

以下は Timeshift とは関係ありませんが、参考情報としてcronの動きの続きを追ってみます。
/etc/cron.d/0hourly/etc/cron.hourly/ディレクトリ配下の実行ファイルを毎時の定刻 (hh:01) に実行していました。
run-partsとは、指定したディレクトリ配下の実行ファイルを全て実行するコマンドです。

$ cat /etc/cron.d/0hourly 
# (略)
01 * * * * root run-parts /etc/cron.hourly

/etc/cron.hourly/0anacron が一時間ごとにanacron コマンドをトリガーしています。

$ cat /etc/cron.hourly/0anacron
# (略)
/usr/sbin/anacron -s

anacronコマンドは、/etc/anacrontabを読み取って実行します。
/etc/anacrontabから、/etc/cron.daily/etc/cron.weekly/etc/cron.monthlyを実行しています。
つまり、cronの日次実行、週次実行、月次実行はanacronによって実行されています。
昔はこれらの実行が/etc/crontabに書かれていたのですが、ある時点でanacronが利用されるようになりました (man run-partsにそう書かれていました)。

$ cat /etc/anacrontab
# (略)
#period in days   delay in minutes   job-identifier   command
1  5  cron.daily      nice run-parts /etc/cron.daily
7  25 cron.weekly     nice run-parts /etc/cron.weekly
@monthly 45    cron.monthly        nice run-parts /etc/cron.monthly

Timeshift が実行しているrsyncコマンド

コマンドの確認方法

Timeshift のログから、Timeshift が実行しているrsyncコマンドを確認できます。
※バックアップ実行中にps auxで確認するという手も恐らくあります

Timeshift を起動し、下図の赤枠部分 (Menu > View TimeShift Logs) を選択することでログを開けます。

timeshift_logs

2種類のログファイルが存在します。
これらのファイルは/var/log/timeshift/配下に存在します。

  • yyyy-mm-dd_hh-mm-ss_backup.log
    • anacron により、1時間に1度timeshift --check --scriptedコマンドが実行される度に生成する
    • 定期バックアップ処理がスキップされても実行されてもこのファイルが生成する
    • rsync のオプションを確認できるのは、定期バックアップ処理が実行された時のログファイルのみ
  • yyyy-mm-dd_hh-mm-ss_gui.log
    • GUI操作のログ
    • バックアップの手動実行の際のログも含まれる

コマンドの中身

Timeshift が実行していたコマンドを貼り付けます。
以下のコマンドでは、コピー元が / で、コピー先が/run/timeshift/backup/timeshift/snapshots/2020-11-07_17-00-02/localhost/ です。

rsync \
-aii \
--recursive \
--verbose \
--delete \
--force \
--stats \
--sparse \
--delete-excluded \
--link-dest='/run/timeshift/backup/timeshift/snapshots/2020-11-02_23-00-01/localhost/' \
--log-file='/run/timeshift/backup/timeshift/snapshots/2020-11-07_17-00-02/rsync-log' \
--exclude-from='/run/timeshift/backup/timeshift/snapshots/2020-11-07_17-00-02/exclude.list' \
--delete-excluded \
'/' \
'/run/timeshift/backup/timeshift/snapshots/2020-11-07_17-00-02/localhost/'

オプションの意味は以下の通りです (man rsync より)。
-a, --delete, --exclude-from, --link-destが特に重要です。

オプション 意味
-a, --archive -rlptgoD と同等。
-A, -H, -X は含まない
-r, --recursive ディレクトリ配下もコピーする
-l, --links シンボリックリンクをそのままコピーする
一方で-L, --copy-linksを指定すると、リンクを追跡してファイルの実体をコピーする
-p, --perms パーミッションを保持する
-t, --times 更新時刻を保持する。
ファイルのタイムスタンプが同じだった場合にコピーをスキップさせたい場合は、必須のオプション
-g, --group 所有グループを保持する
-o, --owner オーナーユーザーを保持する
-D --devices --specials と同義
--devices バイスファイル (/dev/*) も送れるようにする
--specials 特殊ファイル (named sockets, fifo など) も送れるようにする
-i, --itemize-changes 変更点のあったファイルを一覧表示する。
-iiのように繰り返すと、変更されていないファイルも一覧表示する
v, --verbose より詳細に画面/ログ出力する
--delete バックアップ先のディレクトリ内のファイルで、対応するバックアップ元のディレクトリに存在しないものがあった場合、そのファイルを削除する
この挙動はバックアップ元のパスにディレクトリを指定した場合のみ有効となる
--force バックアップ先の空でないディレクトリ名とバックアップ元のファイル名が同じだった場合、ディレクトリを削除してファイルをコピーする
--stats コピー完了後に表示される統計値をより詳細にする
-S, --sparse スパースファイルを認識し、スパースファイルとしてコピーする
--delete-excluded --excluded--exclude-fromなどによってコピー対象から除外されたファイルも、コピー先に存在した場合は削除する
--link-dest=<DIR> 差分バックアップをしたいときに指定する。
--link-destを指定しなかった場合は、コピー先に既にコピー元と同じファイルが存在していると認識された場合はコピーをスキップする。
--link-destを指定した場合は、スキップする代わりに<DIR>からのハードリンクを生成する。
Timeshiftは、<DIR>に前回のスナップショットを指定することで、ハードリンクによる差分バックアップを実現している
--log-file=<FILE> rsyncの実行ログを<FILE>に出力する
--exclude-from=<FILE> コピー対象から除外するファイル・ディレクトリの一覧を<FILE>から読み取る。
コマンドラインに除外条件を直接書く場合は、--exclude=<PATTERN>を使う

--exclude-fromに指定されていたファイルは、TimeshiftのSettngs > Filters > Summary と同じ内容が書かれていました。

(考察) Timeshiftはハードリンクを保持しない

Timeshiftでは -A, -H, -X は指定されていませんでした。
従って、以下の挙動となります。

  • -A, --acls指定なし → ACLを保持しない
  • -X, --xattrs指定なし → 拡張ファイル属性を保持しない
  • -H, --hard-links指定なし → コピー元のハードリンクを認識せず、ハードリンクについても別ファイルとしてそれぞれコピーする

特に気になるのは、-Hが指定されていない点です。
Linux のシステムディレクトリでは、ハードリンクを使用している箇所が多数あります。
ハードリンクの関係にあるファイル群のうち一つを編集すると他のハードリンクにも反映されるという特徴は、ハードリンクファイルを編集する手順に影響する重要な要素だと思います。
実際に Timeshift を利用しているユーザーから特に問題提起がされていないことから、実はそれほど大きな問題ではない可能性もあります。

しかし、どうしても気になる場合は、Timeshift と rsync を使い分けるのがおすすめです。
Timeshift の定期実行・差分管理と、rsyncのハードリンク保持で手軽にバックアップ周期とハードリンク保持のメリットを享受できます。
また、rsync は Timeshift のサポート範囲外であるデータバックアップにも使うことができます。
詳細は、次の記事で紹介します。

/run/timeshift/backup

理由は不明ですが、Timeshiftはスナップショット保存先のディレクトリを/run/timeshift/backup/ にマウントする仕様のようです。
このことはmountコマンドや、Timeshiftの実行ログから確認できます。

私はスナップショットの保存先に/homeを指定しているのですが、/run/timeshift/backup/配下にもバックアップが生成していました。
初めて気づいたときは二重にバックアップを取得していると思って焦ったのですが、そんなことはなく、問題ありませんでした。

$ mount | grep timeshift
/dev/mapper/fedora_pc-home on /run/timeshift/backup type ext4 (rw,relatime,seclabel)

Linux PC関連まとめ

endy-tech.hatenablog.jp

次の記事

Timeshift と rsync を併用し、システムバックアップとデータバックアップに両対応したバックアップ方式を紹介します。

endy-tech.hatenablog.jp

まとめ

Timeshift に関連して気になった点をメモしました。
最初は興味だけで調べていたのですが、cronとanacronの連携、Timeshiftが rsync -AHXを指定していないことがわかり、私としては役に立つ情報が得られました。
結果として、Timeshift と rsync を併用するバックアップ方式を考案できたのは大きかったです。

皆様にとっても参考になる情報があれば幸いです。