えんでぃの技術ブログ

えんでぃの技術ブログ

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

ファイル実行時に呼ばれるfork,clone,execveについて調べてみた

20200927134745

お伝えしたいこと

Linuxでファイルを実行すると、子プロセスが生成します。
そして実行したプロセスが親プロセスになります。

その際、内部的にはforkとexecveというシステムコールが呼ばれていると聞いたことがあります。
私の環境 (Fedora35) で試してみたところ、forkではなくcloneが呼ばれ、その後execveが呼ばれていました。

本記事では、システムコール、fork、clone、execveという用語を整理したいと思います。

Linuxカーネル周りの技術に触れると、このあたりの用語が当たり前のように使われます。
そんなときに (私のように) 困らないよう、よろしければ読んでいってください。

今回の記事は情報ソースが少なめですが、実機でstraceコマンドを使った実験をすることで情報の信頼性を補完しています。

システムコールとは

システムコールとは、LinuxプロセスがLinuxカーネルに対して行う命令のことです1

Linuxでは、Linuxカーネルが全てのハードウェアに直接アクセスするなどの特権を持っています。

一般的なアプリケーションはこのような特権を直接行使することはできません。
代わりに、アプリケーションからカーネルに対してシステムコールを発行してカーネルに依頼を出します。
そしてカーネルが必要な処理を実行し、アプリケーションに処理結果 (戻り値) を返すという仕組みになっています。

ファイル実行時に呼ばれるシステムコール

bashシェル上でpsコマンド (/usr/bin/ps) を実行すると、bashを親プロセスとしてpsプロセスが生成します。
そして、psプロセスはpsコマンドの処理を実行します。

このようなファイル実行処理の裏では、以下の流れで処理が呼ばれています2

  1. 親プロセスがfork (またはclone) され、新たなPIDを持つ子プロセスが生成する
  2. 子プロセス上でexecveが実行され、親プロセスとは異なる動作を開始する

file_execution_flow

fork、clone、execveは、システムコールの一種です。
次のセクションでそれぞれの意味を紹介します。

fork

forkシステムコールは、親プロセスをコピーして子プロセスを生成します。
親プロセスのメモリ上のデータをそのまま子プロセスのメモリアドレス上にコピーします。
子プロセスはPIDなど一部の情報を除き、親プロセスと全く同じものがコピーされます。
詳細はman 2 forkWikipediaのforkに関する記事の情報が参考になります。

ちなみに、forkはC言語のライブラリ関数としても実装されています。
man 3am forkで仕様を確認できます (確認しなくて大丈夫です)。

forkライブラリ関数を呼び出すと、内部的にはforkシステムコールが呼ばれるという作りになっています。

clone

cloneシステムコールはforkと似ていますが、cloneの方が子プロセスに引き継ぐ情報をより細かく制御できるようです。
forkとcloneのユースケースの違いは理解していませんが、cloneもプロセスをコピーする処理なんだな、とここでは理解しておきます。

詳細はman 2 cloneに書いてあります。

execve

execveシステムコールは、呼び出したプロセス自身のメモリ上でプログラムを実行します。
結果として、execveを実行することで新たなプロセスに "変身" します。
man 2 execveのNOTESセクションにも書かれていますが、execveは新しいプロセスを生成しません。
また、execveの前後でPID (Process ID) は変わりません。
execveは別のアプリケーションを読み込み、同じプロセスの動作を置き換える命令です。

#ファイル実行時に呼ばれるシステムコールの繰り返しになりますが、ファイル実行したときはforkしてからexecveします。
forkで親プロセスから子プロセスをPID以外ほぼ丸ごとコピーし、execveで子プロセスならではの処理をインプットするという流れで処理が進みます。

ちなみに、execveにも関連するライブラリ関数があります。
man 3 execにexec系のC言語ライブラリ関数が列挙されていますが、これらの関数は内部的にexecveを利用しています。
(execl, execlp, execle, execv, execvp, execvpe)

実機確認

「ファイル実行はfork (またはclone) → execveの流れで進む」と説明しましたが、この情報に公式ドキュメントによる裏付けはありません。

ということで、2つだけですが実機ベースのログを添付することで、情報の確かさを補完したいと思います。

ファイル実行するとPPIDに実行元のPIDがセットされることの検証

bash上でpsコマンドを実行する例を考えてみます。

ps -fと入力してEnterキーを押しているのは私達人間ですが、実際に私達のキーボード入力を標準入力として受け取って命令を出しているのはbashプロセスです。

bashプロセスが/usr/bin/psファイルを実行してpsプロセスを起動しているので、psプロセスのPPIDにはbashのPIDがセットされるはずです。

このことは、ps -fを実行すればすぐに検証できます。
ps -fのPPID (Parent PID = 親プロセスのPID) が14429と、/bin/bashのPIDと一致していることがわかります。

ps -f
# UID      PID    PPID  C STIME TTY          TIME CMD
# endy   14429    5207  0 16:51 pts/2    00:00:00 /bin/bash
# endy   20072   14429  0 18:29 pts/2    00:00:00 ps -f

ファイル実行すると、fork (またはclone), execveの順に実行されることの検証

この検証では、straceコマンドを使います。

straceは指定したプロセスに接続し、そのプロセスに関わるシステムコールやシグナルを監視し、詳細なデバッグ情報を表示するツールです。
straceコマンドを実行するには、straceパッケージを事前にインストールしておく必要があります。

sudo dnf install strace

試しにpsプロセスをstraceしてみます。

strace ps -f
# execve("/usr/bin/ps", ["ps", "-f"], 0x7ffed16ab5e8 /* 60 vars */) = 0
# (以下略)

いきなりexecveを呼び出しているように見えます。
fork/cloneせずにexecveを呼び出すと、呼び出し元のbashがpsに置き換わってしまい、bashを維持できなくなってしまいます。
もちろん、そんなことはありません。

straceは、下図のようにbashプロセスがforkされて子プロセスが発行された後に、その子プロセスに接続して監視を開始しているようです。
したがって、上記の方法ではstraceでfork/cloneを観測することはできません。

straceでforkを観測するには、psの親プロセスであるbashを監視する必要があります3

strace_monitoring

では、上図の右側のやり方でfork/cloneを観測します。

まず、ターミナルを2つ起動しておきます。

1つ目のターミナルで現在実行しているシェルのPIDを確認します。
今回は8319だったとします。

※ちなみに、bashにおける$$、つまり$という特殊変数の意味は、man bashのSpecial Parametersセクションに書いてあります

echo $$
# 8319

続いて、2つ目のターミナルでPID 8319のプロセスをstraceで監視します。
-p <pid>で監視対象のプロセスを指定し、-fで子プロセスの情報も監視対象に含めています。
監視対象のbashから見てpsは子プロセスにあたるので、-fが必要となります。

strace -f -p 8319

1つ目のターミナルに戻り、psコマンドを実行します。

ps -f
# UID      PID    PPID  C STIME TTY          TIME CMD
# endy    8319    5207  0 Jan04 pts/1    00:00:00 /bin/bash
# endy   44850    8319  0 01:54 pts/1    00:00:00 ps -f

2つ目のターミナルにstrace結果が出力されるので、内容を確認します。
出力を抜粋したものを以下に示します。

# clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLDstrace: Process 44850 attached
# , child_tidptr=0x7f6d2616da10) = 44850

# (中略)

# [pid 44850] execve("/usr/bin/ps", ["ps", "-f"], 0x5573175278a0 /* 60 vars */) = 0

上記より、psを実行するとclone, execveの順にシステムコールが実行されていることがわかりました。
今回のケースでは、forkは呼ばれていませんでした。

まとめ

Linuxにおけるファイル実行時は、fork (またはclone)、execveシステムコールが呼ばれていることについて共有しました。

これからforkという単語が会話に出てきても、この知識でバッチリイメージできると思います。

Linux デスクトップファイルの書き方

desktop-computer
Icons made by Flat Icons from www.flaticon.com

お伝えしたいこと

Linuxデスクトップで使われるDesktop Entry (通称:デスクトップファイル) を自分で書くときに最低限必要な情報をまとめます。

Desktop Entryとは.desktop拡張子を持つテキストファイルです。
デスクトップのMenu上にアプリケーションを表示させるのに使われます。

Desktop Entryの作成は非常に簡単です。
以下の2工程で完結します。

  • 本記事のサンプルをベースに7行ほどのテキストファイルを作成する
  • ~/.local/share/applications/に格納する

「Menuにアプリケーションが表示されない...」と思ったらDesktop Entryを自分で作っちゃいましょう。

作り方を知りたい方は、ぜひ続きをお読みください。

既に慣れている方は、#要点を中心にチェックいただけると効率的です。

要点

Desktop Entryは、以下サンプルを書き換えることで簡単に書けます。

[Desktop Entry]
Name=Virtual Machine Viewer
Exec=virt-viewer
Comment=Display the graphical console
Terminal=false
Icon=virt-viewer
Type=Application

Iconファイルは主に以下のディレクトリから検索されます。
自分で画像を追加する場合は、先頭のホームディレクトリ配下に配置しましょう。

  • ~/.icons/ ★自作アイコンはここに配置
  • /usr/share/icons/
  • /usr/share/pixmaps/

作成したDesktop Entryは以下のディレクトリに配置します。
自分でDesktop Entryファイルを追加する場合は、先頭のホームディレクトリ配下に配置しましょう。

  • ~/.local/share/applications/ ★自作Desktop Entryはここに配置
  • /usr/share/applications/
  • /usr/local/share/applications/

Desktop Entryを作成してもMenuに表示されないときは、以下を疑いましょう。

  • 格納先のディレクトリは正しいか?
  • ファイルパーミッションやオーナーなどの権限情報に問題はないか?
  • Desktop Entryの文法にエラーはないか? → desktop-file-validateコマンドで確認できます

Desktop Entryとは?

Desktop Entryとは、.desktopという拡張子を持つテキストファイルです。
Desktop Fileや、Application Fileとも呼ばれます。

Desktop Entryを特定のディレクトリ配下に格納すると、LinuxデスクトップのMenu (WindowsでいうStart Menu) でアプリケーションとして表示され、クリック操作で起動できるようになります。

GUIでの実行を想定したアプリケーションをインストールすると、多くの場合インストール処理の一環で.desktopファイルも配布されます。
これによって、Menuでアプリケーション名を検索して起動できるようになります。

下記画像において "Google Chrome" という表示名でアイコンを用意し、クリックするとchromeを起動するのがDesktop Entryの役割です。

linux_desktop_menu

(参考) Desktop Entryの仕様を知る必要はあるのか?

デフォルトでDesktop Entryが用意されない場合に、自分で追加したいときに仕様がわかると便利です。

しかし、この知識が必要になる場面は限られているので、デスクトップアプリケーションを日常的に開発する方でなければ暗記する必要はありません。
必要なときに「こんな仕様があった」と思い出し、その場で調べながら書ければ十分です。

上記を踏まえ、本記事もDesktop Entryをちょこっと書きたい方向けに要点が短時間で伝わるように説明します。
より詳細な仕様については、freedesktop.orgを参照するように誘導します。

freedesktop.orgは公的な認証を受けている団体ではありませんが、Linuxデスクトップの標準的な仕様をいくつも定義しています。
そしてGnomeKDEなどのLinuxデスクトップがそれにある程度準拠しているため、実質的にLinuxデスクトップの仕様を知りたいときに重宝する情報源となっています。

Desktop Entryの書き方

まずはサンプルを示します。
私の環境ではvirt-viewerというプログラムにDesktop Entryファイルが付属しなかったので、Desktop Entryを自作しました。

このファイルは、~/.local/share/applications/virt-viewer.desktopに格納しています。
Desktop Entryの格納先については、#Desktop Entryの配置先で触れます。

[Desktop Entry]
Name=Virtual Machine Viewer
Exec=virt-viewer
Comment=Display the graphical console
Terminal=false
Icon=virt-viewer
Type=Application

Desktop EntryはINI形式で書くようです。
ざっくり、意味は以下のようになります。

Iconについては、#Iconの指定方法で別途補足します。

項目 詳細
Name
Menu上の表示名
Virtual Machine Viewer
Exec
クリック時に実行されるコマンド
virt-viewer
Comment
アプリケーションの説明文
Display the graphical console
Terminal
ターミナル上で実行するか?
false
Icon
Menu上のアイコンファイル
virt-viewer.svg
Type
タイプ
アプリケーション。
他にLink, Directoryがあるが、基本Applicationにする

作成したファイルを~/.local/share/applications/virt-viewer.desktopに配置すると、Menu上で検索してヒットするようになります。
以下はアプリケーション名で検索していますが、Exec (virt-viewer) やComment (graphical console) でもヒットします。
このあたりはMenuの実装にも依ると思いますが、とても便利です。

virtual_machine_manager_on_the_menu

Desktop Entryの基本的な書き方については、上表の説明で全てです。
オプションの意味を理解した上で上記サンプルを書き換えれば、Desktop Entryファイルを簡単に作成できると思います。

より詳細な仕様については、freedesktop.orgのDesktop Entry Specificationに記載があります。
また、/usr/share/applications/に既存のDesktop Entryが数多くあるので、見ると参考になると思います。
マニアックですが、デスクトップの言語に併せてNameやCommentを使い分けることもできるようです。

ただよほど凝りたいのでない限りは、詳細な仕様を知る必要はあまりないと思います。

Iconの指定方法

サマリ

Iconの指定方法は2通りあります1

  • 画像ファイルのフルパス指定
  • キーワード指定

キーワードを指定すると、Icon Theme Specificationの仕様に従って画像ファイルを検索し、それを使います。
内部的には難しい仕組みが動いていますが、ざっくり言うと以下の通りです。

  • ファイル名を指定すると指定のディレクトリから画像ファイルを探してきてそれを使う
  • ファイルの拡張子は省略して良い

実用上は、以下のディレクトリ配下の画像ファイルを、拡張子を省いたファイル名で指定する使い方が多いと思います。
上2つのディレクトリには、アプリケーションのインストーラが自動配置する画像ファイルが格納されます。
自前で画像を用意したい場合は、3番目のディレクトリにSVG形式などで画像ファイルを格納します。

  • /usr/share/icons/
  • /usr/share/pixmaps/
  • ~/.icons/

(参考) Iconの詳細仕様

本セクションは参考情報のため、興味のない方はスキップしてください。

Iconをキーワードで指定した際、画像ファイルの検索対象となるディレクトリは以下の3箇所です。
上から順に検索されます。

  • $HOME/.icons/
  • $XDG_DATA_DIRS/icons/
  • /usr/share/pixmaps/

環境変数XDG_DATA_DIRSについては、以下の中身でした。
PATHのように、複数のディレクトリが:で順に並んだ構造です。

echo $XDG_DATA_DIRS
/home/endy/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share

XDG_DATA_DIRSを展開すると、デフォルトでは以下の順で画像ファイルを探索します。
flatpakを使っていない方は、2番目と3番目のディレクトリは無視して問題ありません。

  • ~/.icons/
  • ~/.local/share/flatpak/exports/share/icons/ ※flatpak
  • /var/lib/flatpak/exports/share/icons/ ※flatpak
  • /usr/local/share/icons/
  • /usr/share/icons/
  • /usr/share/pixmaps/

実用上は、特に以下の3箇所をチェックすればほぼ事足ります。

  • /usr/share/icons/, /usr/share/pixmaps/: 既存の画像ファイルの主な格納先
  • ~/.icons/: 自前で用意したアイコンの格納先

今回は目的のファイルがvirt-viewer.svgとして存在することがわかったので、それを使います。

find ~/.icons /usr/share/icons /usr/share/pixmaps -iname 'virt-viewer*'
# /usr/share/icons/Paper/24x24@2x/apps/virt-viewer.png
# /usr/share/icons/Paper/16x16/apps/virt-viewer.png
# /usr/share/icons/Paper/32x32@2x/apps/virt-viewer.png
# /usr/share/icons/Paper/48x48@2x/apps/virt-viewer.png
# /usr/share/icons/Paper/48x48/apps/virt-viewer.png
# /usr/share/icons/Paper/512x512@2x/apps/virt-viewer.png
# /usr/share/icons/Paper/32x32/apps/virt-viewer.png
# /usr/share/icons/Paper/512x512/apps/virt-viewer.png
# /usr/share/icons/Paper/24x24/apps/virt-viewer.png
# /usr/share/icons/Paper/16x16@2x/apps/virt-viewer.png
# /usr/share/icons/hicolor/22x22/apps/virt-viewer.png
# /usr/share/icons/hicolor/16x16/apps/virt-viewer.png
# /usr/share/icons/hicolor/256x256/apps/virt-viewer.png
# /usr/share/icons/hicolor/48x48/apps/virt-viewer.png
# /usr/share/icons/hicolor/32x32/apps/virt-viewer.png
# /usr/share/icons/hicolor/scalable/apps/virt-viewer.svg
# /usr/share/icons/hicolor/24x24/apps/virt-viewer.png

画像の見た目はGUIでフォルダ階層を下ってファイルを見ても良いですが、せっかくフルパスがわかっているのでCLIから開いちゃいます。

xdg-open /usr/share/icons/hicolor/scalable/apps/virt-viewer.svg

余談ですが、xdg-openは既定のアプリケーションで目的のファイルやURLを開くコマンドです。
xdg-open .でカレントディレクトリを開いたり、xdg-open https://xxxというコマンドを紐付けたDesktop Launcherを作成してショートカット代わりに使うと便利です。

Desktop Entryの配置先

サマリ

Desktop Entryは主に以下に格納されています。

  • ~/.local/share/applications/
  • /usr/share/applications/
  • /usr/local/share/applications/

自前で作成したDesktop Entryは、基本的に1番目のパスに格納します。
システム全体にインストールされたデスクトップファイルは、2番目か3番目のパスに格納されています。

今回サンプルで作成したvirt-viewer.desktopは、1番目のリンクに格納します。

(参考) Desktop Entry配置先の詳細

本セクションは参考情報のため、興味のない方はスキップしてください。

作成したDesktop Entry (*.desktop) は、$XDG_DATA_DIRS/applications/配下に設置するようです。

$XDG_DATA_DIRSの中身を再掲します。

echo $XDG_DATA_DIRS
/home/endy/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share

したがって、Desktop Entryファイルの格納先は以下のとおりです。

  • ~/.local/share/flatpak/exports/share/applications/
  • /var/lib/flatpak/exports/share/applications/
  • /usr/local/share/applications/
  • /usr/share/applications/

ただ、実際には以下のディレクトリにもDesktop Entryを格納できます。
公式の情報ソースはありませんが、Arch Wikiには記載がありました。

  • ~/.local/share/applications/

補足ですが、経験上ディレクトリには以下のようなルールがあります。

  • /usr//usr/local/~/.local/配下のファイル構成は類似している
  • /usr/にはディストリビューションのパッケージマネージャが配布したファイルが格納される
  • /usr/local/にはその他の方法でシステムにインストールされたファイルが格納される (pipやmake installなど)
  • ~/.local/はホームディレクトリ配下なので、ユーザー固有のリソースが配置される

従って、/usr/share/applications/が使えるということは、~/.local/share/applications/も恐らく使えるだろうと連想できます。

(参考) desktop-file-validate

滅多に使うことはありませんが、Desktop Entryのエラーを検出するプログラムがあります。
もし作成したデスクトップファイルに正しいPermissionを付与し、正しいディレクトリに配置したにも関わらずMenuに表示されない場合は、以下のコマンドでDesktop Entryの定義が正しいかを確認してみると良いかもしれません。

desktop-file-validateコマンドは、以下のように.desktopファイルを指定して使います。
特に何の警告もでなければ、文法上は問題ありません。

desktop-file-validate virt-viewer.desktop
# (出力なし)

試しに、virt-viewer.desktopのNameオプションを以下のようにコメントアウトしてみます。

[Desktop Entry]
#Name=Virtual Machine Viewer
Exec=virt-viewer
Comment=Display the graphical console
Terminal=false
Icon=virt-viewer
Type=Application

そして、再びdesktop-file-validateを実行してみます。
必須パラメータのNameが指定されていないことでエラーになりました。

desktop-file-validate virt-viewer.desktop
# virt-viewer.desktop: error: required key "Name" in group "Desktop Entry" is not present

まとめ

Desktop Entry (デスクトップファイル) の書き方、配置先について書きました。 既にDesktop Entryの概要を知っている方は、#要点を中心にチェックいただけると効率的です。

Linux PC関連まとめ

endy-tech.hatenablog.jp

Ansible学習ロードマップ

ansible_logo_black

お伝えしたいこと

2021年 Ansible Advent Calendar 8日目の記事です。

元のカレンダーはこちらにあります。

Ansibleを始めるにあたり、どのあたりから考えていけば良いかの道標を書きます。
全て私の主観ですので、ユーザーの一意見としてカジュアルに読んでいただければと思います。

ちなみに、私のAnsible経験値は以下ぐらいのレベルです。

  • Ansible Documentationを一通り読んだことはある
  • PlaybookとRoleを作成して、簡易な自動化をしたことはある
  • 業務用途での本格利用の経験は無し
    • Towerは少し触ったことがある程度
    • moleculeやコンテナ、CIなどのテスト自動化・効率化の経験値はゼロ (これから勉強したいところ)
    • 自動化の闇や技術的負債といった部分については、身をもって経験したことはありません

従って、主に初心者〜中級に差し掛かるあたりの知識がカバー範囲となります。

各セクションに参考資料を貼っておきました。
むしろこちらが本編...というぐらいに充実した先人の知恵が詰まっているので、参考資料もぜひチェックしてくださいね。

Ansible学習ロードマップ

大体以下のような順序で進めていくと良いかなと思います。

  • 自動化検討の下準備
  • Ansibleの超概要の理解
  • Ansibleを触ってみて基本機能を理解する
  • 基礎を固めつつプロトタイプの実装
  • 業務をイメージして、必要なことを考える

具体例を示しつつ、一つずつ追っていきます。

自動化検討の下準備

自動化は長い道のりです。
初期学習、簡易検証・プロトタイプ作成 (Proof of Concept)、設計、実装、試験、運用引き継ぎに短くとも1年強、普通に考えると2-3年はかかると思います。
未経験であれば簡易検証の実施期間は予想することは不可能ですし (どのぐらい難しいかわかりませんよね)、設計以降のフェーズが延びることもざらだと思います。

更に、運用引き継ぎ後もトラブル対応、機能追加、運用に合わせた仕様変更、OSやAnsible周りのバージョンアップも継続的に実施が必要となります。

こうした息の長い案件を進めていくためには、以下のあたりを言語化しておくことが大事だと思います。
メモ帳でも良いので、どこかに書いておくと後から見返せるので便利です。
最初は曖昧、または局所的な目的・ゴールで良いと思います。

初期検討項目 詳細
目的 自動化をしたい理由。
人的ミスを減らしたい、繰り返し発生する作業をスクリプト化したいなど
ゴール いつまでに何を達成すれば一旦完了とするか?
ゴールを達成したら一旦案件としてはクローズして、次案件でまた別の目的・ゴールを設定すると管理しやすい
やることリスト ゴール達成までにできなきゃいけないことを書き出す。
中項目 (マイルストーン) と詳細で階層を分けると良いかも。
計画が荒いうちは、期限の設定を後回しにしても良い

5W1Hで言うと、When/Whoといった詳細情報よりも先に、Why/What/Howで先に骨子を決めるという考え方です。

業務であれば、誰を巻き込むか(体制/Who)、早期撤退のタイミングをどこでどうやって判断するか、費用対効果 (How much) をいつ検討するかなどなど考えることはあるかもしれません。
しかし、個人学習であれば上表ぐらいで十分かなと思います。

目的・ゴール・やることリストは流動的なものです。
最初はかなりいい加減なものでも良いので、書くことが大事だと思います。
調査を進めていくにつれて、Ansibleでできること、できないことが見えてきます。
理解が深まったタイミングで、改めて書き直せば良いと思います。
短いサイクルで見直し続ければ、どんどん良い計画になっていくと思います。
こういった理由から、「最初は曖昧、または局所的」でも良いと書きました。

学習・検証を進めていくと、どうしても目的を忘れるレベルで深く調査する機会が出てきます。
そんな時に目的・ゴールを見返せると、その調査が無限に続くものではなく、目的・ゴールを満たせれば十分だと気づくことができます。
自分にとって難しすぎる調査 (ソースコードにしか書いていないような仕様の調査など) をどこまで進めるか、ハードルを下げる材料にもなりますので、目的・ゴールは自分の精神衛生のためにも何かしら書いておき、継続的にメンテすることをおすすめします。

もちろん、やることリストも重要です。
「次何やるか」を常に考え続けることはストレスフルなものです。
一旦リストに書き出し、機械的にタスクをこなしていくほうが、思考の負荷が下がります。
機械的にこなせない場合は、無理のないようにタスクのレベルを落とすか、細かい粒度にするか、順序を変えるか、誰かを頼るといった工夫が必要になると思います。

具体例

今即興で目的とゴールを考えてみます。
やることリストについては、後続の#Ansibleの超概要の理解#Ansibleを触ってみて基本機能を理解するを参考にしてください。

まず材料となる「やりたいこと (≒ゴール)」ですが、「NW機器の初期構築と、よくやる運用作業を自動化したい」とします。
ちょっと範囲が広いので、要素を分解してハードルを下げます。

  • NW機器の初期構築
  • よくやる運用作業の自動化

「よくやる運用作業」も局所化することで更にハードルを下げられます。
「ハードルを下げれば下げるほど効果も下がる」と思われるかもしれませんが、簡単なことを早期にクリアしてレベルを上げ、それから難しい課題に挑戦したほうが効率が良いです。
以下の2つのどちらかに絞ろうと思います。

  • showコマンドを実行して結果を表示する
  • 設定変更を自動化する

※ゆくゆくは、設定変更の自動化を積み上げて「業務フロー全体を自動化」したり、「showコマンドの結果次第で条件分岐」をして安全性を高めたりといったレベルの高いゴールを目指していくことになります。しかし、自動化が初めてであればこういったことは「ロードマップ」として退避しておいて、一旦はハードルの低いゴールを目指したほうが精神的に楽ですし効率が上がります

Ansibleにとってどちらのほうが簡単かはこの時点ではわからないかもしれません。
その場合は「どちらかやる」として、一旦先に進んじゃいましょう。
※どちらも簡単にできます

次に目的ですが、これがなかなか難しいと思います。
インターネット上で情報を集めつつ、一旦「自分が必要だと思った」自動化のメリットを抽出しましょう。
あまり多くを求めず、1つか2つ程度に絞るのが良いと思います。
自動化でありそうなメリットとしては、以下があると思います。

  • 時間節約 (1コマンドで一連の処理が走るため)
  • ミスの削減 (1コマンドの実行にミスが入り込む余地は少ない。もちろん自動化の仕組み自体にミスがないことが前提)
  • 作業が楽になる (1000行のコマンドコピペよりも、自動化コマンドの1回実行の方が楽)
  • 作業前のレビューが楽になる (スクリプト自体のレビューは開発時のみ。運用時はスクリプトに渡すパラメータのみのレビューになる。手順書のファイル全体レビューよりは楽)

時間節約によって結果として「工数削減」、工数削減によって「コスト削減」などもある「かも」しれません。
ただ、これらは上記要素の「結果」ですので、本当にそうなるかの予想は立てづらいです。
高度な目的は、影響を与える要素が多様になります。
例えば、自動化実装して運用引き継ぎした後は自動化サーバや自動化製品、スクリプトの「運用」があります。
こういったことも考慮して序盤から効果予測することは難しいので、最初は「単純な目的」からスタートするのがコツだと思います。
例えば検討フェーズが進み、設計がある程度見えてきたあたりで最終確定させるとか...。

全てフィーリングなので「これが正しい」というものではありませんが、参考になれば幸いです。

参考資料

ここまで、「目的やゴールは簡単で良い」、「継続的に修正していくことで精度が上がる」とお話してきました。
ですが、「とはいえ、業務まで見据えてもう少し精度高く検討したい」という方もいるかもしれません。

業務形態は人それぞれですので、実際の設計・構築・運用の業務フローや各担当者のスキルレベル、部署の理解などを踏まえて戦略を立てていく必要があります。
「部署の理解」が難しい場合は「1人でも良いから仲間を作る」ところから始まるかもしれませんし、「じゃあ自動化やります」で案件化できることもあるかもしれません。

こういったことを全て自分で考えることは難しいので、周囲の知見を持つ方にも相談してみることをおすすめしますが、以下に良い先人の知恵がありますのでURLを貼ります。

業務として自動化を検討する際にどんなことを考えるか、とても勉強になります。
本記事に書いてある内容よりも高度な内容ですが、楽しくわかりやすくまとめてあります。
興味のある方はぜひご覧ください。

  1. 組織で自動化に着手する前に行うべきだと感じた3つの下拵え (鎌田さん)
  2. ネットワーク運用自動化ジャーニーの歩み方と、これから目指す先 (APC 横地さん)
  3. ⾃動化の下ごしらえ (APC 横地さん)

Ansibleの超概要の理解

次に、Ansibleについて表面的なことを理解するとその後の展開がスムーズになると思います。

Ansibleは自動化を実現する手段の一つです。

Ansibleとは何か? (何のための手段か?)
他にどんな手段があるか?
他の手段と比較して、Ansibleにどのような特徴があるか?
Ansibleの動作要件は?
Ansibleは何を自動化できるのか?

このあたりの質問に答えられるぐらいに理解してみるところから始めてみると良いかもしれません。
こういった情報は、日本語 (時には英語) のブログや動画から情報を集めると良いかもしれません。
最終的には英語の公式ドキュメントから最新かつ正確で、サポートを得られるような構成の情報を集めるのが良いですが、最初はわかりやすさを優先するのもアリだと思います。

英語が得意な方はYouTubeで"Ansible" や "Ansible Tutorial" などで検索したり、検索エンジンで"Ansible Getting Started" を探すのも良いでしょう。
日本語で知りたい方は、"Ansible 概要" など日本語を混ぜてググってみるとか。
慣れてきたら、"Ansible Documentation" から公式ドキュメントを探し、読み進めていきましょう。

具体例

ご参考までに、Ansibleの超概要を簡単に書いておきます。

質問 回答
Ansibleとは何か? 自動化、可視化ツール。
べき等性を持たせる実装にすれば構成管理にも使える
他にどんな手段があるか? ChefやPuppet、Saltとよく比較される。
クラウド分野だとTerraformなど。
そしてDevOpsツール全般は、PythonRubyの自力開発や、Teratermマクロなどとも比較される印象です
特徴は? プログラミング不要 (YAML+Jinja2)。
エラーハンドリングなどツール任せにできるのでプログラミングよりハードルが低い。
エージェント不要。
SSHHTTPSで自動化命令を送る。
ソースコードPythonで書かれている。
変数、条件分岐、ループなど色々できる。
凝った条件分岐には向かない。
Chef、Puppet、Saltと比較してメジャーな印象。
クラウドの自動化はTerraformを使う人も多い印象
Ansibleの動作要件は? AnsibleをインストールするサーバはLinux限定。
Pythonバージョン一定以上。
最低限のCPU/RAM性能。
SSH/HTTPSが使えることなど
自動化対象 SSHHTTPS (RestAPI) に対応する製品全て。
特にモジュールが充実している製品が自動化に向いている (参考)。
Linuxが得意だが、NW機器、Windowsクラウド、一部ミドルウェアにも対応

参考資料

下に行くにしたがって内容が分厚くなってきます。
いずれも非常にわかりやすく書かれているので、最初の読み物としておすすめです。

Ansibleを触ってみて基本機能を理解する

実際にハンズオンしつつ、わからないところは検索することで理解が一気に進みます。
大抵の製品は「VMにツールをインストールしてハンズオン」ですが、Ansibleの場合は他にも手段があります。
おすすめ順に書いていきます。

ハンズオン方法 詳細
Ansibleもくもく会 数ヶ月に一度、無料ハンズオンがあります。
Connpass上で登録することでイベント開催の通知を受け取れます。
Ansibleインストール済みの無料AWS環境、相談に載ってくれるメンター、教材と全て揃っています。
Ansibleの経験値ゼロでも、まずは参加することをおすすめします。
最初はServer編にご参加ください
Katacoda ブラウザ上でAnsibleハンズオンができるコンテンツです。
メンターは不在ですが、説明不要なほど親切な解説があります。
もくもく会のタイミングが合わない時、予習・復習したい時などにおすすめです
自力構築 KVMVirtualBoxVMを立てて、Ansibleをインストールして検証します。
インストール方法を学べる点、時間制限なく学習できる点ではこちらもオススメです。
インストール方法はディストリビューションのパッケージマネージャーか、pipがあります。
ユーザー権限でインストールできるpip install --user ansible、またはvenvの中にインストールする方法をおすすめします

ハンズオンの進め方案

ハンズオンを始める前に、既に自動化で実装してみたい処理は1、2あると思います。
そして予習を進めている方は、変数設計やRole分けの重要性を理解されているかもしれません。

ただ、最初はゴールに直結しないネタでも良いので可能な限りハードルを下げることをおすすめします。
例えば、以下のような流れです。

  • debugモジュールで画面出力してみる
  • 変数なし、単一Playbookで実装してみる (まずはLinuxから)
  • 変数を使ってみる
  • 条件分岐を入れてみる
  • ループ処理を試してみる
  • ansible.cfgをホームディレクトリに作っていじってみる
  • フィルタを使ってみる
  • import_tasks/include_tasks, import_vars, include_vars などを使い、Playbookファイルが肥大化しないよう整理してみる
  • Role化してみる
  • (このあたりでそろそろやりたかった自動化にチャレンジ)

その後も続きます。

  • PlaybookとRoleを別リポジトリに分けてGitに保存してみる
  • Gitで保存したPlaybookやRoleに簡単なREADME.mdをつけてみる
  • Roleをrequirements.ymlでGitからインストールできるようにしてみる
  • Moleculeでテスト自動化に挑戦
  • CIに挑戦
  • ...

参考資料

基本は、公式ドキュメントに従って手を動かしていくのが良いと思います。
...と言いたいところですが、「Ansible の使い方」という日本語で公式に負けないレベルのすごいドキュメントがあるので、そちらを先に読んでみると良いと思います。

『Ansible実践ガイド』という書籍もAnsible界隈で人気があるので、本屋に行く機会があれば中身をチェックしてみるのも良いと思います。

初期の学習にあたり、公式情報と合わせて知っておくと便利な内容を#(参考) Ansibleのテクニック面の話に書きましたので、そちらも合わせてご覧ください。

基礎を固めつつプロトタイプの実装

#Ansibleを触ってみて基本機能を理解すると内容が被るので、ここは割愛します。

プロトタイプの実装が終わるまでは納期の予想が困難であるため、ゆるめにスケジュールを立てると良いかもしれません。

業務をイメージして、必要なことを考える

私も未経験なので、このあたりは全て想像ですがご参考までに...。

Ansibleを使っている状況をリアルに想像すると、色々な心配事が出てくると思います。
多分これがシステム設計や運用設計につながるんだろうなと思います。
ぱっと思いつくだけでこれだけあります。

  • Playbookの実行は運用者にとって本当に「楽」なのか?
  • Ansibleサーバは誰が管理するのか?
  • Playbookは同僚がメンテしてくれるのか?できるのか?
  • 新規参入者がAnsibleとPlaybookの構成を理解できるか?
  • Playbookが乱立しないか?
  • Ansibleを毎年バージョンアップするたびに全てのPlaybookを動作確認するのか?
    • テストに追われて業務が回らなくなるのでは?
  • AnsibleサーバのOSがEOLになったらどうするのか?
  • バックアップはどう実装するか?
    • Ansibleサーバのバックアップ
    • Playbook/Roleのバックアップ
  • Ansible実行で大事故が起こるリスクは?
  • 運用フローにはまるのか?
  • Ansibleサーバの監視はどうやるのか?
  • Ansibleサーバに侵入された時のリスクは?どう回避する?どう被害を極小化する?
  • Playbookが知らぬ間に書き換わっていたらどうなる?
  • Ansibleサーバに誰でもログインできてよいのか?
  • Playbook実行の監査ログは取るのか?
  • Ansibleという製品が突然...なくなったら...
  • レアケースでPlaybookが暴走したら?
  • Playbookに想定外のパラメータが渡されたら?
  • Ansibleサーバにログイン情報をテキストで保存するのってどうなのか?
  • 知識のない人がPlaybookを書き換える心配はないか?
  • 誰かメガンテする心配はないか?
    • (例:ansible -a 'shutdown -h now' all)
  • Ansible Playbookに何でもやらせすぎて後で困らないか?
    • 用途と利用部署がわからず、闇鍋状態にならないか?
    • 廃止できない、メンテナンスの調整がつかないサーバにならないか?

最初から考えると先に進めなくなるので、初期検討の間に課題を思いついた場合はどこかのメモ帳に書いて忘れることをおすすめします...w

(参考) Ansibleのテクニック面の話

Ansibleの公式ドキュメントは非常に良くできています。
これを読めば大体わかると言っても過言ではありません。
しかし、「これを知っていればハマらずに済んだ」ということもあるので、簡単に触れておきたいと思います。

予備知識がないと「何のことやら」となってしまいますが、その点はご了承ください。

インストール方法

インストール方法はいくつかあります。

  • OSのパッケージマネージャーを使う
  • pipでインストールする
  • コンテナを使う

とりあえず検証で使うだけであれば、コンテナを使うかユーザー権限でインストールすることをおすすめします。
VM上でユーザー権限で使う方法は、2案あります。

  • venv内でpip install ansible
  • venvを作らずpip install --user ansible

後述の#エディタ機能を活用するを意識するなら、venvを作らずpip install --user ansibleがおすすめです。
venv無しの方が、拡張機能との相性で問題が発生しにくいためです。

venv無しでpipを使うと環境が汚れないか...と心配の方は、以下の記事も併せてご覧ください。
Pythonパッケージを一括削除するだけなら、pip freeze --user | xargs pip uninstall -yでできます。

endy-tech.hatenablog.jp

Ansibleの処理の順序

経験則なので100%正確とは言えませんが、ざっくり以下の流れで進んでいると思います。

  • Playbookの評価
    • YAML観点の評価
    • Jinja2観点の評価・展開
  • PlaybookをPythonコードに変換
  • Pythonの実行
    • Linuxなど、connection: sshの場合
      • PlaybookをSCP/SFTPで自動化対象のLinuxに送る
      • 自動化対象のLinuxPythonを実行
    • NW機器など、connection: localやnetwork_cliの場合
      • 自動化サーバでPythonを実行する
      • Pythonコードに従って対象機器にSSH/HTTP(S)接続して処理を実行

従って、YAML観点の文法エラーがあるとその先に進みません。
エラーメッセージから、どの段階で躓いているか判断できるようになると切り分けが早くなります。

YAMLとJinja2

YAML観点とJinja2観点の文法にも詳しくなると必ず役に立ちます。
変数やフィルタ、条件分岐、ループなどを使わないうちは、Jinja2は登場しません。
なので、最初はこれらの機能を省いてYAMLに慣れることをおすすめします。

YAMLについては、以下の記事が参考になります。
yaml.orgにも仕様書はあるのですが、難解なので最初はあまりおすすめしません。

その後で、Jinja2の文法を覚えていきましょう。
Jinja2については、以下のドキュメントが参考になります。
このドキュメントに記載されている知識はAnsibleにも使えますが、一部YAMLの構文と競合することで実質的に使えない部分もあるのでご注意ください。
template Moduleで使う.j2ファイルであれば、大体使えます。
Jinja2 - Template Designer Documentation

Jinja2について更にTipsですが、Python文法の基本の型と四則演算、メソッドを覚えておくとオトクです。
配列の足し算など、以外と使えることがあります。

エディタ機能を活用する

viでPlaybookを書いて、ansible-playbookコマンドで実行。
それでも十分ではあります。

しかし、最近はVS CodeのAnsible拡張機能があるので、高機能なエディタ機能を使うと捗ると思います。
キーワードの色分け、エラーの自動検出、入力補完など色々できます。

概要資料を貼っておきますので、よろしければチェックください。
どちらのリンクも同じ資料です。
資料を書いたのは私なので、質問も受け付けられます。

『AnsibleユーザーのためのVS Code拡張機能の紹介』

メインマシンがWindowsで、AnsibleサーバがLinux VMのという構成でもRemote - SSHなどの拡張機能VM上のPlaybookを直接編集できます。

VS Codeは...いいものですよ。

まとめ

Ansibleを始めるにあたって考えること、各工程の参考URLを示しました。

私の意見については、2割ぐらい信じつつ参考にしていただければと思います。
参考URLは本当にオススメなので、ぜひ目を通してください。

Fedora35の変更点

fedora_logo

前の記事

Fedora34のアップデート情報はこちらをどうぞ。

endy-tech.hatenablog.jp

お伝えしたいこと

Fedora35のリリースノートを読んで、個人的に気になった項目をまとめます。
末尾のsssdの問題は、バージョンアップした方全員に該当すると思いますのでぜひチェックしてください。

docs.fedoraproject.org

公式情報の見方

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

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

fedora_change_sets

Fedora35の既知の問題は、以下にまとめられています。

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

Release Notes & Changes

firewalldがv1.0.0に

firewalldを操作した経験がないので、あっさりめに触れます。
以下が主な変更点とのことです。

  • ゾーン内通信がデフォルト許可に
  • NAT rulesがinet family配下に移動した
  • Default target は reject と類似した挙動になる
  • ICMPは受信時のみフィルタする。ルーティングする際はフィルタしない
  • iptables backendが非推奨に
  • Direct interfaceが非推奨に

既存でfirewalldをカスタマイズしている方にとって、一部通信制御の挙動が変わるのは影響が大きい印象です。

音声サービスのセッションマネージャーにWirePlumberを採用

Fedora34では、既存の音声サーバサービスが全てPipeWireに集約されました (過去記事)。

PipeWireのSessionManagerは簡素な作りで、カスタマイズできる範囲が少なかったとのことです。
SessionManagerの機能をPipeWireに移行することで、カスタマイズ性が上がるとあります。
また、WirePlumberはPipeWireのSession Managerと完全に同等の機能を持ちます。

詳しいところは調べていません。
Fedora33から34、35とバージョンアップした後も音が出たので、私としてはヨシとします。
しかし、残念ながら音が出なくなるケースもあるようです。
音が出ないという方は、#WirePlumberが自動起動せず、音が出ないことがあるを参照してください。

デフォルトのPythonが3.10に

Fedora34まではデフォルトのPythonがバージョン3.9でしたが、Fedora35以降では3.10になります。
Python 3.10の特徴は、python.org - PEP 619にまとめられています。

詳細は上記のPEP 619を参照なのですが、Python 3.10以降はOpenSSL 1.1.1以上を必須とするとのことです。
これにより、Python 3.10は基本的にRHEL8.0以上でしかPython3.10が動かなくなるようです。
他のディストリビューションも含めた詳細は、python.org - PEP 644を参照してください。

sssdがデフォルトで無効に

sssdとは、リモートアクセスやログイン認証を取り扱うデーモンです。
NSS (Name System Switch -> /etc/nsswitch.conf) やPAM (Pluggable Authentication Modules)と連携して動作します。
sssdはキャッシュ機構を持つため、filesと比較して認証処理が効率的であることが利点です。

Fedora34以前では、sssdというデーモンがデフォルトで起動していましたが、Fedora35以降ではデフォルトで停止されます。

それに伴い、/etc/nsswitch.confの記載内容が以下のように変わっています。
パスワード、及びユーザーグループの参照にsssではなくfile (/etc/passwd, /etc/group) を使うようになりました。

passwd:     files sss systemd
group:      files sss systemd

# Fedora34以前では以下の内容だった
# passwd:     sss files systemd
# group:      sss files systemd

変更の理由は、「最小構成のFedoraにおいて自動起動するデーモンを減らしたかったから」とのことです。
今回の変更に伴い、Fedoraコンテナからsssd-clientパッケージが除外され、ファイルサイズが軽量化されました。

実施することはないと思いますが、Fedora Wikiのリンクにsssdを再び利用するための手順もありました。
sudoを使うようアレンジを加えたコマンドは、以下のとおりです。
これらのコマンドは、手元のFedora35環境で動作確認済みです。

sudo authselect select sssd with-files-domain
sudo bash -c "echo -e '[sssd]\nenable_files_domain = True'  > /etc/sssd/conf.d/files_domain.conf"
sudo chmod 600 /etc/sssd/conf.d/files_domain.conf
sudo systemctl start sssd

更に、再びFedora35のデフォルトに戻すコマンドは、以下の通りです。

sudo systemctl stop sssd
sudo rm /etc/sssd/conf.d/files_domain.conf
sudo authselect select sssd with-fingerprint with-silent-lastlog

手順について、いくつか補足します。

  • /etc/nsswitch.confは手動での変更ではなく、authselectコマンドによって変更するのが推奨
  • authselect currentで現在のモードを表示
  • authselect show sssdsssdモードの詳細を表示
  • Fedora35から、sssd.serviceの起動条件に「/etc/sssd/sssd.conf/etc/sssd/conf.d/*.confのいずれかが存在すること」が追加された
  • glibc 2.33以降 (Fedora34以降)、/etc/nsswitchのタイムスタンプが変わっていると再読込が走る。ファイル更新後のOS再起動は不要となった

Bugs

Bugsの中から気になる情報を拾います。

WirePlumberが自動起動せず、音が出ないことがある

Fedora35にバージョンアップした後、WirePlumberが、時折自動起動しない設定になってしまうことがあると報告されています。
WirePlumberは音声サーバーのSession Managerとして動作しており、このサービスが起動しないと音声が流れません。

WirePlumberの起動有無は、以下のコマンドで確認可能です。

systemctl --user status wireplumber

WirePlumberが一般ユーザー権限で起動していなかった場合は、一般ユーザーで以下のコマンドを実行することで復旧するとのことです。
rootユーザーによる実行や、sudoを付けての実行は不要です。

systemctl --user enable --now wireplumber

その他

Release NotesやBugsには載っていませんが、私が遭遇した事象と対処法をメモします。

Fedora35にアップグレード後、ユーザー追加・削除時にエラーメッセージが出る

タイトルの通りです。
Fedora34以前からFedora35にアップグレードした後、useradduserdelコマンドを実行するたびにエラーメッセージが出ました。
passwdgroupaddなどは試していませんが、同様にエラーが出る可能性はあると思います。

実際にユーザー追加・削除はできているので、エラーメッセージに実害はないと思います。

実際に発生するエラーメッセージは、以下のような内容です。
sssdのバージョンが変わったが、キャッシュファイルが古いバージョンのsssdのままであるためにエラーが出力されています。
Fedora35へのバージョンアップによってsssdが新しいバージョンとなり、なおかつsssdが自動起動しなくなったために古いキャッシュが残ったことが原因です。

sudo useradd endy2
# [sss_cache] [sysdb_domain_cache_connect] (0x0010): DB version too old [0.22], expected [0.23] for domain implicit_files!
# Higher version of database is expected!
# In order to upgrade the database, you must run SSSD.
# Removing cache files in /var/lib/sss/db should fix the issue, but note that removing cache files will also remove all of your cached credentials.
# Could not open available domains
# [sss_cache] [sysdb_domain_cache_connect] (0x0010): DB version too old [0.22], expected [0.23] for domain implicit_files!
# Higher version of database is expected!
# In order to upgrade the database, you must run SSSD.
# Removing cache files in /var/lib/sss/db should fix the issue, but note that removing cache files will also remove all of your cached credentials.
# Could not open available domains

id endy2
# uid=1001(endy2) gid=1002(endy2) groups=1002(endy2)

sudo userdel -r endy2
# [sss_cache] [sysdb_domain_cache_connect] (0x0010): DB version too old [0.22], expected [0.23] for domain implicit_files!
# Higher version of database is expected!
# In order to upgrade the database, you must run SSSD.
# Removing cache files in /var/lib/sss/db should fix the issue, but note that removing cache files will also remove all of your cached credentials.
# Could not open available domains
# [sss_cache] [sysdb_domain_cache_connect] (0x0010): DB version too old [0.22], expected [0.23] for domain implicit_files!
# Higher version of database is expected!
# In order to upgrade the database, you must run SSSD.
# Removing cache files in /var/lib/sss/db should fix the issue, but note that removing cache files will also remove all of your cached credentials.
# Could not open available domains

対処法は、上記エラーメッセージが示すとおり/var/lib/sss/db内のファイルを削除することです。
フォルダごと削除しないよう気をつけてください。
フォルダを削除すると、sssd.serviceが起動しなくなります。
間違えて消した場合は、sudo mkdir /var/lib/sss/dbで再作成すれば大丈夫です。

以下のコマンドでエラーは出なくなります。

sudo bash -c 'rm /var/lib/sss/db/*'

/var/lib/sss/db/は、sssdのキャッシュが保存されるディレクトリです。
内部のファイルは、#sssdがデフォルトで無効にで紹介した手順などでsssd.serviceを起動することで再生成するので、消してしまっても問題ありません。

ちなみに、キャッシュを削除する代わりにsssdを起動・停止することでもこの問題は一時的に対処できますが、将来sssdをバージョンアップしたときに再びエラーが出る懸念があります。

一連の手順は、手元のFedora35で動作確認済みです。

まとめ

興味を持ったトピック限定ですが、Fedora35の更新点をまとめました。

sssdのエラーにヒットした場合、sudo bash -c 'rm /var/lib/sss/db/*'ワークアラウンドをぜひお試しくださいね。

Fedora34の変更点

fedora_logo

前の記事

Fedora33のアップデート情報はこちらをどうぞ。

endy-tech.hatenablog.jp

お伝えしたいこと

Fedora34のリリースノートを読んで、個人的に気になった項目をまとめます。

docs.fedoraproject.org

公式情報の見方

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

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

fedora_change_sets

Fedora34の既知の問題は、以下にまとめられています。

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

Release Notes & Changes

System Administrators

zramに全メモリ容量を割り当てるように

Fedora33からzramをswap領域に割り当てるようになりました。
/usr/lib/systemd/zram-generator.confにzram設定のデフォルト値が記述されていましたが、Fedora34でこのデフォルト値が微調整されました。

調整の内容は以下のとおりです。

Fedora33 Fedora34
[zram0]
zram-fraction = 0.5
max-zram-size = 4096
[zram0]
zram-fraction = 1.0
max-zram-size = 8192

Fedora33では、zramのサイズが全メモリ容量の半分 (最大4GiBまで) でした。
Fedora34では、zramのサイズが全メモリ容量の100% (最大8GiBまで) となりました。

これにより、Fedora34においてメモリ容量が8GiBの場合、メモリの空き容量が足りなくなってくると最大8GiB分のデータがswappingされます。
実際にはデータ圧縮されるので、最大の8GiB分のデータがswappingされたとしてもzramによるメモリ消費量は4GiB前後になると思います。
残りの4GiBはswapされていないメモリ消費のままです。

中々衝撃的な内容ではありますが、実は性能上ほぼ問題ありません。
それどころか、zramをフル活用することでメモリ使用率が約半分になり、実質倍以上のデータをメモリ上に保存できるようになります。

もちろん、圧縮率はメモリ上に保存されるデータ次第ではあります。
圧縮処理のためにCPU使用率は上がります。
しかし、メモリ容量が少ない環境においては夢のような実装ではないでしょうか。
RHEL9にも同様に実装されるのか、気になるところです。

zramによるswappingについては、別記事にて詳しく説明しています。
興味のある方は、こちらの記事もご覧ください。

endy-tech.hatenablog.jp

/etc/selinux/configからのSELinux無効化ができなくなった

元々、SELinuxを無効化する方法は2つありました。

  1. Bootloader (GRUB2) のkernel command lineに、selinux=0を追記してから起動する
  2. /etc/selinux/configSELINUX=disabledを追記してから起動する

これらのうち、2の方法でSELinuxを無効化できなくなります。
今後SELinuxを恒久的に無効化する際は、以下のようなコマンドでBootloaderの設定を書き換えた上でOS再起動することになります1

sudo grubby --update-kernel ALL --args selinux=0

ちなみに変更の背景ですが、Linux Kernelのセキュリティ向上のためのようです。
詳細は2つ目のChangesのリンクをご覧ください。

Desktop Users

Pipewireがデフォルトの音声サーバに

音声周りの理解は浅いですが、ざっくりご説明します。
細かい文言に違和感を感じる部分もあるかもしれませんが、ご容赦ください。

実装の変化を図解します。

sound_server_architecture

Fedora33ではALSA、PulseAudio、JACKの3つが製品が独立して動作していました。
基本的には、デスクトップの音量調整アプリなどのクライアントソフトはPulseAudioにアクセスし、PulseAudioが裏でALSAと連携しながら音声サービスを提供しているという構成でした。

Fedora34では、上記の3製品がPipewireという単一のソフトウェアに集約されます。
ただし、プロセスとしては別々に動作します。
PipewireはALSA、PulseAudio、JACKのAPIに対応するので、クライアントは今までどおりPulseAudioとの互換性を保ったまま動作できます。

何が嬉しいかというと、Fedora Wikiには以下のように書かれていました。
ほとんどの方には関係ないと思いますが、開発が活発な製品を使っておいて損はないという印象です。

  • JACKと他製品の連携をしやすくなる
  • PulseAudioと比較して、設定の柔軟性が向上する
  • 性能・セキュリティ・メンテナンス性向上

Fedora33とFedora34のパッケージ構成を比較したところ、下表のように変わっていました。
pipewire-alsa, pipewire-pulseaudio, pipewire-jack-audio-connection-kit は、pipewireがALSA、PulseAudio、JACKの3製品を置き換えるための互換プログラムを含むパッケージです。

減ったもの 増えたもの
alsa-plugins-pulseaudio pipewire-alsa
pulseaudio
pulseaudio-module-bluetooth
pulseaudio-module-x11
pipewire-pulseaudio
pipewire-utils
pipewire-gstreamer
jack-audio-connection-kit.x86_64 pipewire-jack-audio-connection-kit

Pipewireの詳しい仕様については調べていませんが、私の環境ではバージョンアップ後も問題なく音は出たのでヨシとします。

最後に補足ですが、Fedora35にてSession ManagerがPipeWireからWirePlumberに変更されます (参考)。

ibus-anthyがデフォルトの日本語IME

Fedora19〜Fedora33は、ibus-kkcがデフォルトの日本語IMEでした。
Fedora34以降は、ibus-anthyがデフォルトの日本語IMEになります。

変更の理由は、kkcが暫くの間メンテされていないためとのことです。

Bugs

Bugsの中から気になる情報を拾います。

新規インストール時、btrfsのサブボリュームが圧縮されない

この事象はFedora34だけでなく、Fedora35にも該当します (Bugzilla #1952764より)。

該当する条件は、以下のとおりです。

  • Fedora34の新規インストール画面において、"custom partitioning"を選択する
  • btrfsのsubvolume名を変更し、インストールを続行する

影響は以下のとおりです。

  • /にマウントされたsubvolume名を変更した場合は、全データが影響を受ける
  • /以外にマウントされたsubvolume名を変更した場合は、/etc/fstabの表記でcompress=zstd:1が漏れてしまう

回避方法として、上記リンクにWorkaround A, Workaround Bが紹介されています。
Workaround B (インストール後にsubvolume名を変更する) が明らかに楽だと思います。
詳細はリンク先をご確認ください。

まとめ

Fedora34の更新点をまとめました。

zramのチューニングは、ほぼ全員にとって関心のある内容だと思います。

デスクトップをお使いの方は、Pipewireの採用も影響があるかもしれません。
スピーカーとマイクがちゃんと動作するかご確認ください。

次の記事

Fedora35の更新内容から気になる部分のみ抽出しました。

endy-tech.hatenablog.jp

zram swappingによるRAM節約、高速I/O【Fedora33以降】

swap

お伝えしたいこと

Fedora33以降では、ディスク上に割り当てたswap領域 (swapパーティション、swapファイル) をほぼ使わなくなりました。
代わりにzramと呼ばれるメモリ上の領域を使います。

もちろん、仕組み上ディスクを利用したswapよりもzramの方が従来よりもメモリ消費量は少し上がります。
しかし、zramに入るデータは圧縮される上、メモリなのでI/O性能が良くなることが期待されています。
メモリ容量の少ない環境においては、この圧縮機構によって寧ろメモリ容量を節約できてしまいます。

そんなzramを使ったswapの画期的な実装について、今回は簡単に触れたいと思います (※)。

(※) ちなみにWikipedia - zramによれば、zram自体は2014/3/30にLinux kernel 3.14でzramは安定していると判断され、メインラインにリリースされています。実は結構歴史がある技術なんですね

Fedora32以前の挙動

概要

恐らく、今動作している大半のLinuxのデフォルトに該当する挙動です。
swap領域はディスク上に作成します。
作成方法は、以下の流れです。

  1. swap専用のパーティション、またはファイルを用意する
  2. swap用にフォーマットする
  3. OS起動時にswap領域を自動認識させる
  4. swap領域を有効化する

RHEL8の公式手順1を参考にしつつ、コマンド例を簡単に示します。
既に知っているという方は、#Fedora33以降の挙動までスキップしてください。

swap用のパーティション、またはファイルを用意する

swapファイルを使う場合は、ddコマンドでファイルを作成しておきます。
以下のコマンドでは1KiBのブロックを1048576 (1024*1024) 個生成し、1GiBのswapfileを生成しています。

sudo dd if=/dev/zero of=/swapfile bs=1024 count=1048576
sudo chmod 600 /swapfile

swapパーティションを作る場合は、fdiskなどでパーティションを作っておきます。
パーティションの作成方法については、ここでは割愛します。
必要に応じて、過去の記事を参考にしてください。
ここでは、/dev/vda6パーティションを作成し、swap用に割り当てたとします。

swap用にフォーマットする

mkswapコマンドにファイルパス、またはデバイスファイルを指定してフォーマットします。

sudo mkswap /swapfile
# または、sudo mkswap /dev/vda6 (swapパーティションの場合)

OS起動時にswap領域を自動認識させる

/etc/fstabに以下の行を追加します。

/swapfile swap swap defaults 0 0
# または、/dev/vda6 swap swap defaults 0 0 (swapパーティションの場合)

/etc/fstabを編集したら、systemdが変更を認識できるように以下のコマンドを実行し、各unitに設定ファイルを再読込させます。

sudo systemctl daemon-reload

swap領域を有効化する

swaponコマンドでswap領域をその場で有効化します。
OS再起動は不要です。

sudo swapon /swapfile
# または、sudo swapon /dev/vda6 (swapパーティションの場合)

確認

swapon --showでswap領域が認識されたことを確認できます。
freeでも、swap領域が増えたことを確認できます。

swapon --show
# NAME      TYPE      SIZE USED PRIO
# /swapfile partition   1G   0B   -2

free -h
#       total  used   free  shared  buff/cache  available
# Mem:   62Gi  17Gi   14Gi   1.3Gi        30Gi       43Gi
# Swap: 1.0Gi    0B  1.0Gi

Fedora33以降の挙動

概要

Fedora33では、デフォルト設定でzramと呼ばれる領域がメモリ上に作成され、swap領域として使用されます。
実はswap領域にはpriorityというパラメータがあり、複数のswap領域が存在する場合にはpriorityが高い領域から優先的に使用されます。
デフォルトでは、以下のpriority値になっていました。

swapの作成場所 priority値
zram 100
ディスク
(swap file, swap partition)
-2

つまり、Fedora33以降では、ディスク上のswap領域は基本的に使われません。
zram上のswap領域が枯渇した場合のみ、ディスク上のswap領域が使われ始めます。
滅多にないと思いますが、priorityが同じデバイスが複数あった場合にはラウンドロビンになります。
※この挙動は、man 2 swaponに書いてあります

swapのpriorityは、zramについてはzram-generator.confで (#後述)、ディスクについては/etc/fstabswaponのオプションで指定可能です。
ディスクのpriorityのデフォルト値は、man mkswapによると本来-1のはずです。
ただ、実機を見ると-2になっていたので、実質的にzram以外のswapはデフォルトで最小のpriorityになります。
-2に制御しているところがどこにあるかまではわかりませんでした。

zramによるswap作成を無効化してFedora32以前の動作に戻すことも可能ですが、メリットもあるので不具合等なければそのまま使うのが良いでしょう。

以下のセクションで、仕様の詳細に触れていきたいと思います。

段々深くなっていきますが、#zramとは#zramの特徴はオススメです。
Fedoraを初めとするRed Hat系のディストリビューションをお使いの方は是非目を通してください。

なお、本記事ではFedoraのmanやリリースノートを主な情報源としています。
Linux Kernelのレイヤーでの深い解説については、後述の#参考記事にわかりやすい記事がありますので、そちらを参考にしてください。

zramとは

zramとはRAM上に動的に生成されたブロックデバイスです。
zram上に格納されるデータは全て圧縮され、対応する無圧縮のメモリ使用量と比較して、zramにswap outされた時のデータサイズは小さくなります。

「RAM上にブロックデバイスを動的に生成する」といえばRAM Diskというものもありますが、zramの場合はデータを圧縮するため、容量効率が良くなります。

バイスファイルは、/dev/zramN (Nは0以上の整数)で、Fedoraでは/dev/zram0がデフォルトで作成されています。

Linux Kernelには、zswapという似たような技術もありますが、zramとは別物です。2, 3

zramの特徴

zram視点でメリット/デメリットを列挙します。

- 詳細
Disk I/Oが少なくなる。
swap outを伴うI/O処理が高速化される。
Flashバイス (SSDなど) を使用している場合も、繰り返しの書き込みに伴う故障/性能劣化 (wearing out) を軽減する効果がある
RAM容量を効率的に活用できる。
zramも結局のところRAM上にswapをするので、RAM容量を消費する。
しかし、データ圧縮によってswap outされた領域が約1/2程度のサイズになるので、メモリが少ない環境において十分に役に立つ (参考)
データ退避の効率が50%程度 (目安)。
1つ上の行の言い換えになる。
データ圧縮効率が50%ということは、サイズ100のデータをswap outすることで、サイズ50のメモリ容量を消費するということ。
ディスク退避する場合は、サイズ100のデータをswap outした場合のメモリ消費量は0。
この点も考慮して、zramのswap sizeはディスクの場合と比べて2倍程度で検討するのが良いという考え方もありそう (えんでぃ主観)
圧縮処理に多少のリソースを使う。
メモリのオーバーヘッドは0.1%程度 (1GiBのswapあたり、メタデータなどにより1MiBを追加で消費) です。
CPUにも負荷がかかります。
従って、CPU/メモリの負荷を考慮すると、zramのサイズを極端に大きくすべきではありません (参考)
× 圧縮効率の悪いデータとは相性が悪い。
効率の悪いデータを頑張って圧縮しても、CPU時間を浪費するだけになってしまう...可能性がある (参考)。
しかし、Fedora33で試したところ実際は圧縮効率が悪いケースは観測されなかったという報告もあり、Fedora34以降はよりzramを積極活用するようチューニングされた (参考)

まとめると、全体的にメリットが大きいですが以下の点には注意が必要です。

  • ディスクに退避する場合と比較してRAM消費量が大きくなる
  • CPU使用率が上がる

逆に、主観ですがCPU、メモリ共に余裕のある設計であれば、あまり気にしなくても良いと思います。

zramの設定ファイル

zramのデフォルトの挙動は、/usr/lib/systemd/zram-generator.confに記載があります。
このファイルを直接編集することは推奨されていません。
編集方法については、次の#zram-generator.confの設定変更にて触れます。

Fedora34以降のファイルの中身は、以下のようになっています。

[zram0]
zram-fraction = 1.0
max-zram-size = 8192

ちなみに、Fedora33時点では以下の設定内容でした。
/dev/zram0を作成し、後はデフォルト値を使用していました。

[zram0]

[zramN] (Nは0以上の整数)の配下にパラメータを設定することが可能です。
man zram-generator.confによると、デフォルト値は以下のようになっています。
※Fedora33では、man 5 zram-generatorで確認できます
※手元のFedora35で使っているカーネルバージョンは、5.14.16-301.fc35.x86_64です
※swapと関係ないパラメータはいくつか省略しています

パラメータ名 詳細
host-memory-limit zramに割り当て可能な仮想メモリ容量 (物理メモリ容量 + swapメモリ容量) の上限を指定する。
/proc/meminfo相当の情報で、詳細はman procを参照。
単位はMB。
この上限を超えると、zramデバイスはそもそも生成しなくなる。
デフォルト値はnoneのため、制限なし
zram-fraction zramデバイスのサイズを決定する係数 (比例定数) を指定する。
0以上の小数が入る。
デフォルト値は0.5。
例えばホストのRAMサイズが8GiBで、zram-fraction=0.5の場合、zramサイズは4GiBとなる。
zram-fraction=1.0の場合、ホストのRAMサイズと同等のzramサイズになる。
実際のzramサイズは、この上でmax-zram-sizeの影響も受けて最終的に決定される
max-zram-size zram-fractionで得られたzramサイズの上限値を指定する。
単位はMB。
noneを指定すると上限無しになる。
デフォルト値は4096
compression-algorithm 圧縮処理のアルゴリズムを指定する。
デフォルト値はカーネルによって決まる。
※Fedora35ではlzo-rleでした
他に指定可能なアルゴリズムは、/sys/block/zram0/comp_algorithmから読み取れる。
※Fedora35では、lzo lzo-rle lz4 lz4hc 842 zstdでした
swap-priority swapのpriority値を指定する。
-1〜32667の間で指定可能。
デフォルト値は100。
/etc/fstabでswapのpriorityを指定していない場合は、ディスクベースのswapは負のpriority値を持ちます。従って、デフォルトの挙動ではディスクよりもzramのswapが優先的に使用されます

なお、本セクションで述べた「zramデバイスのサイズ」とは、圧縮前のサイズです (#後述のzramctlのDISKSIZE相当)。
実際に消費されるRAMサイズ (zramctlのCOMPRやTOTAL相当) は、zramデバイスのサイズよりも小さくなります。
Fedora34以降で採用されたzram-fraction = 1.0という設定は確かにアグレッシブではありますが、実際にzramctlで圧縮効率を計測して1/2や1/3であったことから、ある程度勝算を持ってのパラメータ設計となっています4

まとめると、zram-generator.confのデフォルト設定 (=Fedora33のデフォルト) では以下のような動作になります。
※zramのサイズはRAMサイズの0.5倍。ただし、4GiBが上限

zram device size [MB]
    ^
    │
 4G>│               ooooooooooooo
    │             o
    │           o
    │         o
 2G>│       o
    │     o
    │   o
512M>│ o
    0───────────────────────> total usable RAM [MB]
      ^     ^       ^
      1G    4G      8G

Fedora35のデフォルト設定はzram-fraction=1.0, max-zram-size=8192のため、以下のようなグラフになります。

zram device size [MB]
    ^
    │
 8G>│               ooooooooooooo
    │             o
    │           o
    │         o
 4G>│       o
    │     o
    │   o
 1G>│ o
    0───────────────────────> total usable RAM [MB]
      ^     ^       ^
      1G    4G      8G

zram-generator.confの設定変更

本セクションの情報は、man zram-generator.confに基づいています。

設定ファイルの配置場所は、これだけあるようです。

  • /usr/lib/systemd/zram-generator.conf
  • /usr/local/lib/systemd/zram-generator.conf
  • /etc/systemd/zram-generator.conf
  • /run/systemd/zram-generator.conf

  • /usr/lib/systemd/zram-generator.conf.d/*.conf

  • /usr/local/lib/systemd/zram-generator.conf.d/*.conf
  • /etc/systemd/zram-generator.conf.d/*.conf
  • /run/systemd/zram-generator.conf.d/*.conf

デフォルト設定は、/usr/lib/systemd/zram-generator.confにあります。
その後、パッケージインストールなどによって設定ファイルが追加配置される場合は、/usr/local/lib/systemd/zram-generator.conf.d/*.confにファイルが追加されていきます。

*.conf *.confのファイルは文字列ソート順に読み込まれます。
後から読み込まれた設定値が先に読み込んだ設定値を上書きします。

手動で設定変更する場合は、/etc/systemd/zram-generator.conf/etc/systemd/zram-generator.conf.d/*.confに記載します。
/etc配下にファイルを配置すると、/usr/lib配下のファイルが読み込まれなくなります。
従って、既存設定が必要な場合はそちらも忘れずに書く必要があります。

裏を返せば、sudo touch /etc/systemd/zram-generator.confで空のファイルを生成すれば、デフォルトの/dev/zram0を作成するという挙動がなくなります。
つまり、zramによるswap動作を無効化できます5
実際にファイルを生成し、OS再起動したら反映できました。

zram、swapの状態確認

zramctlswapon --showでswapの状態を確認できます。

  • zramctl: swapに関わらず全てのサイズが0以上のzramデバイスを表示
  • swapon --show: zramに関わらず全てのswap領域を表示

手元のFedora35で実行したログを掲載します。

zramctl
# NAME       ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
# /dev/zram0 lzo-rle         8G   4K   74B   12K       8 [SWAP]

zramctl --output-all
# NAME       DISKSIZE DATA COMPR ALGORITHM STREAMS ZERO-PAGES TOTAL MEM-LIMIT MEM-USED MIGRATED MOUNTPOINT
# /dev/zram0       8G   4K   74B lzo-rle         8          0   12K        0B      12K       0B [SWAP]

swapon --show
# NAME       TYPE      SIZE USED PRIO
# /swapfile  file        2G   0B   -2
# /dev/zram0 partition   8G   0B  100

zramctlの出力について補足します。
zramctl --helpから確認できます6

項目が多いですが、実用上はNAME, DISKSIZE, DATA, TOTALあたりを見れば十分だと思います。
zramctl --output-allを使うことは基本ありません。

列名 今回の値 意味
NAME /dev/zram0 zramデバイス
DISKSIZE 8G zramに格納可能なデータサイズの上限 (未圧縮換算)
DATA 4K zramに実際に格納されているデータサイズ (未圧縮換算)
COMPR 74B zramに実際に格納されているデータサイズ (圧縮済換算)
ALGORITHM lzo-rle 圧縮に使用しているアルゴリズム
STREAMS 8 圧縮処理の同時並列実行数
ZERO-PAGES 0 page frameと紐付いていないswap page数 (※)
TOTAL 12K COMPR (実データ) に、メモリフラグメンテーションメタデータなどのオーバーヘッドを加えた正味のデータサイズ (圧縮済換算)
MEM-LIMIT 0B データ格納に使えるメモリ容量の上限 (圧縮済換算)
zram-generator.confhost-memory-limitなど変えてみましたが、この値とは関係ありませんでした
MEM-USED 12L zramが圧縮データを格納するために使用しているメモリサイズ
※TOTALとの違いがいまいちわかりません
MIGRATED 0B memory compactionによって移動されたメモリのサイズ1
MOUNTPOINT [SWAP] zramデバイスがマウントされている場所 (※2)

(※1)
swap pageとは、swap領域上のvirtual pageのことだと理解しています。
page frameとは、物理メモリ上の最小アドレス単位です。
virtual pageとは、仮想メモリ上の最小アドレス単位で、OSが認識するメモリアドレス単位でもあります。7,
仮想メモリとは、OSが認識している抽象化されたメモリで、物理的に複数枚に分かれたメモリやswapなどを単一のアドレスで管理するものです8
memory pageについてより深く知りたい方は、Wikipedia - Memory pagingもチェックしてみてください

(※2)
zramそのものはブロックデバイスを生成する仕組みで、swapだけに使われるとは限りません

今回はzramctlコマンドを参照目的で使いましたが、このコマンドによりzramの動作を動的に変更することも可能です。

参考記事

この記事よりも先に、@shimauma_Zzzzzさんがzramについて深く解説されていました。
Linux Kernelのドキュメントまで踏み込んで、低レイヤーから仕様を解説してくださっています。
/proc周りの値の意味や、echoで書き換えた時の挙動まで解説されていて、zram-generatorの内部処理をより具体的にイメージできました。
この記事を書くに先立ち、私も大いに勉強させていただきました。
ありがとうございます。

qiita.com

(参考) swap領域の一般論

参考情報として、swapに関する雑多な情報をこちらに書きます。
参考元URLなどは特にありません。
カジュアルなまとめとして見てください。

swap領域とは、基本的にメモリ容量が足りなくなった時にメモリ上のデータが退避される領域です。
ファイルシステムによってはキャッシュに使われることもあるようで、メモリが枯渇していなくても少量使われることがあると聞いたことがあります。

多くの構成では、swap領域はディスク上に作成されるため、メモリと比較してI/Oが遅いです。
特にハードディスクを使っている環境において、メモリ不足に起因するswap退避が大量に発生すると性能劣化が顕著になると言われています。

性能観点でのswap領域に関する議論は、様々なブログや掲示板で展開されています。
例えば...

  • 今どきのメモリ容量は十分に大きいので、swap領域を作る必要はない (過去に口頭で言われたことがあります)
  • RHELについては、RAMサイズの半分のswap領域を作成することが推奨。メモリ8GiB以上の場合は、4GiBのswap領域が一つの目安9
  • swappinessを変更することで、swap発生頻度を調整する主張もある (全ての主張は、ブログや掲示板など非公式のものです)
    • swappinessをデフォルトの60よりも小さく、1〜3にすべきという意見がある (根拠は怪しい)
    • 変えるべきではないとの意見もある
    • zramを使う場合は最大の100でも良いという意見もある
    • swappinessの挙動の理解は非常に難しい。親切なドキュメントはない。ソースコードを追えたとしても難しい
    • swappinessの挙動は、過去に大幅に変わったことがある。今ある解説記事も最新とは限らない

このような形で、意見を総合して明確な結論を出すことは難しいです。
明確な根拠はありませんが、私は以下の方針としました。

  • swappinessはいじらない
  • swap領域のサイズは、RHELの推奨に従う (検証サーバの場合は1GiBなどにすることも多い)

Fedora33以降の場合はzramに任せておけば自動的に上記の構成になるので、楽で良いですね。
時代が進むに連れて仕組みが整備されているので、理由がなければ標準に乗るのが一番です。

まとめ

Fedora34、Fedora35で大幅に挙動が変わったzram, swap周りについて紹介しました。
RHEL9も同様の設計になった場合、swapfileやswap partitionを作るのはやめようかな...と思い始めています。

今後の動きが楽しみですね。

Linuxのディスク拡張手順 (KVM, LVM, EXT4)

thumbnail

お伝えしたいこと

KVM仮想マシンのディスク拡張手順をご紹介します。

ディスク拡張方法は、利用しているストレージ、LVM2の利用有無、ファイルシステムによって変わります。
今回ご紹介する手順は、以下の環境を前提としています。

ディスク拡張を行う際は、必ずバックアップを取った上で作業してください。
本手順においても、#パーティション拡張の工程で「間違えるとデータが消える」部分があります。
fdiskの操作でyes/noを間違えてそのまま気づかずwriteしてしまい、更に対処を間違えると詰みます。

なお、記事の後半でパーティションテーブルやアラインメントなどの背景知識に触れていますので、興味のある方はぜひご覧ください。

作業の流れ

ディスク拡張ですが、基本的にファイルシステムを作成する時の流れに沿って作業します。

冒頭で述べたストレージ環境を構築する手順は、ざっくり以下の流れです。

  1. qcow2ファイルの作成 (ホストマシン上で実施)
  2. パーティション作成
  3. LVMの設定
    1. PV作成 (PVとパーティションを紐づけて登録)
    2. VG作成 (VGにPVを所属させる)
    3. LV作成 (VGの領域の一部を切り出してLV作成)
  4. LV上にファイルシステム作成

従って、ディスク拡張作業は以下の流れで進めることになります。

  1. qcow2ファイルの拡張 (ホストマシン上で実施)
  2. パーティション拡張
  3. LVMの設定
    1. PV拡張 & VG拡張 (PVを拡張すると、VGも拡張された扱いになります)
    2. LV拡張
  4. ファイルシステム拡張

LVMを使っていない場合、「LVMの設定」は丸ごとスキップします。
また、ファイルシステムを拡張するコマンドが若干変わります。
今回の環境ではLVM2を使っているので、この設定についても触れていきます。

図解

#作業の流れと重なりますが、ディスク拡張前後の状態を図解しました。

まずは、ディスク拡張前の状態です。
今回の作業では、(1)〜(6)を順に拡張していきます。

first_state

ディスク拡張後は、以下の状態になります。
qcow2のサイズを5GiB増やし、その分ルートファイルシステムを拡張しています。
拡張によって、ルートファイルシステムのアドレスがSWAP用の領域を挟んで不連続になっているように表現していますが、LVM2の仮想化機能によりこれでも問題なく動作します。

last_state

次のセクションから、具体的なコマンドを紹介します。

qcow2ファイルの拡張

ここの説明は、KVMで作成したVMのディスク拡張をしたい方にのみ関係があります。
また、KVMのセットアップが完了していることが前提です。
KVMの設定については、過去の記事を参照してください。

endy-tech.hatenablog.jp

さて、本題に入ります。

KVM仮想マシンのボリュームのリサイズにはvirshコマンドを使用します。
コマンドの書式は以下の通りです。
以下のコマンドをKVMのホストマシン上で実行します。

virsh vol-resize vol-name-or-path capacity [--pool pool-or-uuid] [--allocate] [--delta] [--shrink]

それぞれのキーワードの意味は、以下のとおりです。
詳細はman virshvol-resizeの項や、Red Hat Virtualization Deployment and Administration Guide を参照してください。

キーワード 意味
vol-name-or-path ボリューム名、またはファイルフルパス (*.qcow2)
capacity ボリュームのサイズ。
1GB (1000MB), 1GiB (1024MiB) など
--pool pool-or-uuid プール名、またはUUID。
vol-name-or-path にフルパスを指定した場合は省略可
--allocate rawファイルにのみ対応。
qcow2ファイルに対して指定するとエラーになる。
基本使わない
--delta --deltaを指定しない場合、capacity には変更後のサイズを記載する。
--deltaを指定すると、代わりにサイズ拡張/縮小の差分の値を記載する。
--shrink ボリュームを縮小する際に指定する。
拡張の際に指定するとエラーになる

さて、ここでvolume名とpool名が必要であることがわかります。
それらを先に調べておきます。

以下のコマンドより、volume名はvm_name.qcow2、pool名はdefaultと判明しました。

virsh pool-list
#  Name                     State    Autostart
# ----------------------------------------------
#  default                  active   yes

virsh vol-list --pool default
#  Name          Path
# -----------------------------------------------------
#  vm_name.qcow2 /home/shared/kvm_volumes/vm_name.qcow2

ついでに、現在のボリュームサイズも調べておきます。

virsh vol-info --pool default my_vm.qcow2
# Name:           my_vm.qcow2
# Type:           file
# Capacity:       20.00 GiB
# Allocation:     16.46 GiB

さて、情報が揃いました。
以下のコマンドにより、pool defaultに存在するsize 20GiB のvolume my_vm.qcow2を、25GiBに拡張します。

2種類のコマンドを提示していますが、どちらも同じ結果になります。
現在の容量を意識せずに済む分--deltaを使う方が検証用途ではお手軽ですが、間違えて2回同じコマンドを実行しても安全なのは--deltaを使わないコマンドですね。

# virsh vol-resize my_vm.qcow2 --pool default --delta 5GiB
virsh vol-resize my_vm.qcow2 --pool default 25GiB

今回のケースとは関係ないですが、ご参考までに5GiB縮小する際のコマンドも記載しておきます。
ディスクサイズの縮小はデータを破壊するリスクがあるので、試す際は事前にデータバックアップを取っておきましょう。

# virsh vol-resize my_vm.qcow2 --pool default --delta -5GiB --shrink
virsh vol-resize my_vm.qcow2 --pool default 15GiB --shrink

パーティション拡張

以降のコマンドはVMゲスト上で実行します。
今回はfdiskコマンドでパーティションを拡張します。

fdiskコマンドを実行するために、デバイスファイル名を確認します。

3行目の出力より、今回は/dev/vdaを見ればよいとわかります。
他にもdf -Thsudo blkidなどで確認できます。

なお、冒頭の2行に警告が出ていますが、こちらはあまり気にする必要はありません。
パーティションテーブルを書き換えないままディスクサイズ (qcow2のサイズ) が変わったことで、fdiskが不整合を検出したというメッセージです。
メッセージに書いてあるとおり、fdiskでwriteすることでPMBRを含むパーティションテーブルの情報が上書きされてエラーは解消されます (本当に何もせず、wでwriteするだけでエラーは解消されます)。
エラーメッセージの意味については、#(参考) GPTとPMBRの概要にて考察するので、気になる方はこちらも読んでみてください。

sudo fdisk -l
# GPT PMBR size mismatch (41943039 != 52428799) will be corrected by write.
# The backup GPT table is not on the end of the device. This problem will be corrected by write.
# Disk /dev/vda: 25 GiB, 26843545600 bytes, 52428800 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes
# Disklabel type: gpt
# Disk identifier: 3AED67ED-0074-4379-987F-320ED57ED3CB

# Device       Start      End  Sectors  Size Type
# /dev/vda1     2048  1230847  1228800  600M EFI System
# /dev/vda2  1230848  3327999  2097152    1G Linux filesystem
# /dev/vda3  3328000 41940991 38612992 18.4G Linux LVM


# Disk /dev/mapper/my_vm-root: 16.26 GiB, 17448304640 bytes, 34078720 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes


# Disk /dev/mapper/my_vm-swap: 2 GiB, 2147483648 bytes, 4194304 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes

ついでに、LVM周りの情報とファイルシステムの情報を予め取得しておいてください。
fdisk -lのログも含めて、取得した情報はホストPC上のテキストファイルなどゲストOS以外の場所に保存してください。
いざというときの復旧に役に立ちます。

# LVM Information
pvdisplay
vgdisplay
lvdisplay
cat /etc/lvm/backup/*

# File System Information
df -Th

では、fdiskによってパーティションサイズを20GiBから25GiBに拡張します。
手順は以下の流れです。

  • d, 3: /dev/vda3の削除
  • n, 3, (Enter)x2, n: 新しいサイズで/dev/vda3の再作成
  • w: 変更の確定。パーティションテーブルに情報を書き込む

2番目の工程でパーティションを作成する際のパラメータ指定について補足です。
man fdiskに書いてあるように安全な方法でパラメータを指定します (Enter x2)。
First sectorはデフォルト値で指定することで、Alignmentの問題を踏まないようにしています。
Last sectorは単位 (MiB/GiB) を付けてサイズで指定するのが一般的ですが、今回は最終セクタを指定したいのでここもデフォルト値を使っています。

また、「Do you want to remove the signature? [Y]es/[N]o:」と聞かれる箇所がありますが、ここでは絶対にNoを選択してください。
間違えてYesを選択した場合は、writeをせず、落ち着いてqから抜けてください。
もしYesを選択した上でwriteしてしまうと、/dev/vda3パーティションタイプがLinux LVMであるという情報が消えてしまいます。
結果として、次のPV拡張の工程でpvdisplayなどを実行しても何も表示されず、pvresizeコマンドも失敗してしまいます。
※既存のデータを全て削除して問題ない場合はYesでも良いのですが...

LVMを間違えて削除してしまった時も復旧できますが、それでも対処を間違えると詰みます。
もしLVMを削除した場合は、ひとまずOSシャットダウンや再起動は絶対にしないでください。
その上で#(参考) LVMを削除した時の復旧方法を参考にしつつ、対処法をご検討ください。

ただ、上記の方法で復旧するぐらいなら作業前のバックアップからやり直したほうが楽でしょう。
fdiskの操作は、くれぐれもお気をつけください。

前置きが長くなりましたが、具体的なfdiskのコマンドを以下に記載します。

Command (m for help): p
# Disk /dev/vda: 25 GiB, 26843545600 bytes, 52428800 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes
# Disklabel type: gpt
# Disk identifier: 3AED67ED-0074-4379-987F-320ED57ED3CB

# Device       Start      End  Sectors  Size Type
# /dev/vda1     2048  1230847  1228800  600M EFI System
# /dev/vda2  1230848  3327999  2097152    1G Linux filesystem
# /dev/vda3  3328000 41940991 38612992 18.4G Linux LVM

Command (m for help): d
# Partition number (1-3, default 3): 3

# Partition 3 has been deleted.

Command (m for help): p

# Disk /dev/vda: 25 GiB, 26843545600 bytes, 52428800 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes
# Disklabel type: gpt
# Disk identifier: 3AED67ED-0074-4379-987F-320ED57ED3CB

# Device       Start     End Sectors  Size Type
# /dev/vda1     2048 1230847 1228800  600M EFI System
# /dev/vda2  1230848 3327999 2097152    1G Linux filesystem

Command (m for help): n
Partition number (3-128, default 3): 
First sector (3328000-52428766, default 3328000): 
Last sector, +/-sectors or +/-size{K,M,G,T,P} (3328000-52428766, default 52428766): 

# Created a new partition 3 of type 'Linux filesystem' and of size 23.4 GiB.
# Partition #3 contains a LVM2_member signature.

Do you want to remove the signature? [Y]es/[N]o: n

Command (m for help): p

# Disk /dev/vda: 25 GiB, 26843545600 bytes, 52428800 sectors
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes
# Disklabel type: gpt
# Disk identifier: 3AED67ED-0074-4379-987F-320ED57ED3CB

# Device       Start      End  Sectors  Size Type
# /dev/vda1     2048  1230847  1228800  600M EFI System
# /dev/vda2  1230848  3327999  2097152    1G Linux filesystem
# /dev/vda3  3328000 52428766 49100767 23.4G Linux filesystem

Command (m for help): w
# The partition table has been altered.
# Syncing disks.

最後に、パーティションサイズが変更されたことを確認します。

sudo fdisk -l

# (中略)

# Device       Start      End  Sectors  Size Type
# /dev/vda1     2048  1230847  1228800  600M EFI System
# /dev/vda2  1230848  3327999  2097152    1G Linux filesystem
# /dev/vda3  3328000 52428766 49100767 23.4G Linux filesystem

# (中略)

最後の/dev/vda3のSectorsが奇数になっていますが、Alignmentの観点では気にする必要はありません。
Alignmentが問題となるのはあくまで開始セクタの位置のみです。

もちろん、例えば/dev/vda1の終了セクタは、次の/dev/vda2の開始セクタのデフォルト値に影響するので、配慮した方が良いとは思います。
ここで言っているのは、最後のパーティションの最終セクタ位置は気にしなくても良いということです1

(参考) LVMを削除した時の復旧方法

ここでは、上記のfdisk操作で誤ってLinux LVM Partition Type を誤って削除し、そのままwriteしてしまった時の対処法を紹介します。

最初に補足しておきます。
今回の障害の契機は、fdiskによるパーティションタイプの削除です。
fdiskには、tパーティションタイプを作成することができ、30 (Linux LVM) を指定することで同様のパーティションタイプを作成することができます。
しかし、今回のケースはパーティションタイプの再作成では復旧できません。
fdiskパーティションテーブルを削除する際、どうやらLVMのPV、LV、VGなどのメタデータも全て削除してしまっているようです。
その証拠に、実際にパーティションタイプを再作成しても、pvdisplayなどのコマンドでPV、VG、LVは表示されませんでした。

さて、本題に入ります。

基本的には、Red Hat社のマニュアルにあるPVの復旧方法2 を参考に進めます。
具体的には、以下のような流れで進めます。

  1. LVMメタデータのバックアップ
  2. レスキューモードの起動
  3. (任意) キーボードの日本語化
  4. (任意) SSHサーバの有効化、バックアップファイル送付
  5. (任意) 非LVM領域のマウント
  6. PVの復旧
  7. VG,LVの復旧

具体的な内容をstep by stepで紹介します。

LVMメタデータのバックアップ

まずは、以下のパスにあるLVMメタデータを外部ストレージに退避してください。

  • /etc/lvm/backup
    • lvcreateなどの変更コマンドを実行する度にバックアップを自動保存するフォルダ
    • 最新の情報のみ保存されている
  • /etc/lvm/archive
    • 過去のバックアップを世代管理しているフォルダ
    • archive内の最新のファイルは、/etc/lvm/backupと同じ中身

私の場合は、VMを動かしているホストPCにSCPでファイルコピーしておきました。
外部ストレージをどうしても用意できない場合は、/bootなどLVM外のパーティションにファイルコピーしておくのでも最悪OKです。

これらのバックアップファイルは、復旧時にレスキューモードで起動したVM上に送る必要があります。
ファイルの送り方はSCPと非LVM領域のマウントの2パターンがあります (他にもあるかもしれません)。

SCPの場合は、パスワード無しでrootログインされてしまうというセキュリティリスクがあります。
詳細は、#(任意) SSHサーバの有効化、バックアップファイル送付を参照してください。

非LVM領域のマウントは、レスキューモードでマウントできる構成か、事前に確認が必要です。
/bootであれば簡単にマウントできます。
詳細は、#(任意) 非LVM領域のマウントを参照してください。

レスキューモードでの起動

この手順が必要か否かは状況によって変わります。
後続の手順でpvcreateコマンドを実行する際、対象のパーティションはアンマウントされている必要があります。

そして、今回のケースではルートファイルシステムも復旧対象に含んでいます。
ルートファイルシステムをアンマウントするために、レスキューモードの起動が必要となります。

レスキューモードに入る前に、必ず上述の#LVMメタデータのバックアップが完了していることをご確認ください。
ここでOSシャットダウンすると、ルートファイルシステムが読み込めないため、次回以降通常のOS起動ができなくなってしまいます。

レスキューモードへの入り方は過去記事に書いてあるので、そちらを参照してください。
レスキューモードからの起動

ディスクのマウントができないので、最後の起動メニューは3) Skip to shellを選択し、ディスクをマウントせずにシェルを起動してください。

KVMを使っている方向けに補足です。
KVMを使っている場合は、OS起動順序をCD Drive優先にして、CDにLinuxインストール用のISOファイルをセットすることでISOファイルから起動することができます。
KVMの操作方法については、以下のリンクを参考にしてください。

以降の作業は、レスキューモードに入った後のウィンドウで実行します。

(任意) キーボードの日本語化

こちらの作業は任意で実行してください。

レスキューモードでは、以下のようにキーボードの言語が英字配列 (us) になっています。
日本語キーボードに慣れている方は、まずはキーボードの言語を変更しましょう。

蛇足ですが、普段私はコンソールとデスクトップ環境のキーボード設定を両方変更可能なlocalectl set-x11-keymapばかりを使っています。

localectl
  #  System Locale: LANG=C.UTF-8
  #      VC Keymap: us
  #     X11 Layout: n/a

localectl set-keymap jp

localectl
  #  System Locale: LANG=C.UTF-8
  #      VC Keymap: jp
  #     X11 Layout: n/a

(任意) SSHサーバの有効化、バックアップファイル送付

SSHサーバの有効化、SCPによるファイル送付は、任意で実行してください。

SSHサーバの有効化により、SCPによるファイル転送やSSHログインが可能となります。
パスワード無しでrootユーザにログインできてしまうので、商用環境では辞めておいた方が良いかもしれません...。
最低限、ファイアウォールやNATでガチガチに守られたサブネットで作業するなどの検討をした方が良いと思います。
私の環境は手元の検証用VMなので、気にせず有効化します。

単純にサービス起動しようとすると、sshd_configが無いとのエラーで起動失敗します。

systemctl start sshd
# Job for sshd.service failed because the control process exited with error code.
# See "systemctl status sshd.service" and "journalctl -xe" for details.

journalctl -e
# Nov 12 08:11:34 my-vm /etc/ssh/sshd_config: No such file or directory

これに対処するため、sshd_configを用意します。

cp /etc/ssh/sshd_config.anaconda /etc/ssh/sshd_config

cat /etc/ssh/sshd_config

sshdサービスを起動します。
今度はうまくいきます。

systemctl start sshd

ホストPCにLVMのバックアップを取得していた方は、SCPでファイルを送ってしまいましょう。
バックアップファイルの選択ですが、最新のバックアップファイルの場合はbackup/配下のファイルを使用します。
2世代以上前のファイルを使いたい場合は、archive/配下のファイルから適切なものを選択してください。

バックアップファイル名がmy_vmだったとしたときのコマンドは、以下のようになります。
レスキューモードの場合はパスワード無しでSCPできます。

scp my_vm root@x.x.x.x:/

更に、そのままSSHログインします。

ssh root@x.x.x.x
cd /

以降の操作は、SSHログインして行います。
SSHを長時間有効化することに抵抗がある場合は、systemctl stop sshdsshdを停止しても結構です。

(任意) 非LVM領域のマウント

非LVM領域のマウントは、任意で実施してください。

前の手順でSSHを有効化しなかった場合、別の方法でファイルを送付する必要があります。
例えば、LVMを利用していない/bootにファイルを配置していた場合は、以下のようなコマンドで/bootをマウントします。

環境によっては/bootではなく、NFSストレージや、LVMを使っていない別パーティションの領域かもしれません。
そのあたりは、環境によってアレンジしてください。

/mnt/sysroot/は、レスキューモードで1) Continueを選択した時などにマウント先として自動で選択される領域です。
深い意味はありませんが、何となく合わせました。

mkdir -p /mnt/sysroot/boot
mount /dev/vda2 /mnt/sysroot/boot

最後に、マウントしたディスク上に、予め配置しておいたバックアップファイルがあることを確認してください。

もし/bootを一時的なファイル置き場に指定する場合は、一連の復旧作業が終わった後にゴミファイルを削除するのを忘れずに...。

PVの復旧

次に、バックアップファイルを利用してPVを再作成します。

バックアップファイルを利用しなくてもPVの再作成は可能ですが、以下の観点でバックアップファイルを使ったほうが良いと思います。

  • UUIDの保持 (ただし、--uuid, --norestorefileオプションの組み合わせでも可能)
  • --restorefileを使った場合、安全に復旧するための追加ロジックが発動する (man pvcreate --restorefileの説明より)

コマンド実行の前に、まずはバックアップファイルを開いてPVのUUIDを確認します。
以下の例では、PVのUUIDはBPTO1p-dz0e-gM36-37ls-nGY0-cUeD-Zcmt4bであるとわかります。
また、PVの作成先が/dev/vda3であると再確認できます。
※PVの作成先は、事前ログのdf -Thからも確認できます

cat my_vm
# (中略)
  # physical_volumes {
  # pv0 {
  #   id = "BPTO1p-dz0e-gM36-37ls-nGY0-cUeD-Zcmt4b"
  #   device = "/dev/vda3"    # Hint only
  #   status = ["ALLOCATABLE"]
  #   flags = []
  #   dev_size = 38612992     # 18.4121 Gigabytes
  #   pe_start = 2048
  #   pe_count = 4713 # 18.4102 Gigabytes
  # }
  # }
# (中略)

では、pvcreateコマンドによってPVを復旧させます。
復旧の前に、ステータス確認とテストモード実行をしてみます。

pvdisplay
# (出力なし)

pvcreate --uuid "BPTO1p-dz0e-gM36-37ls-nGY0-cUeD-Zcmt4b" --restorefile my_vm /dev/vda3 -t
  # TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.
  # WARNING: Couldn't find device with uuid BPTO1p-dz0e-gM36-37ls-nGY0-cUeD-Zcmt4b.
  # Physical volume "/dev/vda3" successfully created.

問題なく通ったので、今度は-tオプションを外して実際に実行します。

pvcreate --uuid "BPTO1p-dz0e-gM36-37ls-nGY0-cUeD-Zcmt4b" --restorefile my_vm /dev/vda3
  # WARNING: Couldn't find device with uuid BPTO1p-dz0e-gM36-37ls-nGY0-cUeD-Zcmt4b.
  # Physical volume "/dev/vda3" successfully created.

成功しました。
PVの状態を確認します。
PE Sizeなどが0になっていますが、これはVG作成前であるためなので気にしないで大丈夫です。

  # "/dev/vda3" is a new physical volume of "18.41 GiB"
  # --- NEW Physical volume ---
  # PV Name               /dev/vda3
  # VG Name               
  # PV Size               18.41 GiB
  # Allocatable           NO
  # PE Size               0   
  # Total PE              0
  # Free PE               0
  # Allocated PE          0
  # PV UUID               BPTO1p-dz0e-gM36-37ls-nGY0-cUeD-Zcmt4b

最後に、pvcreateコマンドの構文を紹介します。

pvcreate pv-name [options...]

今回使ったものを中心に、構文の詳細を下表にまとめます。
バックアップファイルから復旧する際は、--uuid--restorefileを使います。

キーワード 説明
pv-name PVの名前。
/dev/vda3など。
pvdisplayで確認可能
-t,
--test
テストモード実行。
実際にはメタデータを書き込まずに終了する
-u uuid,
--uuid uuid
PVのUUIDを明示的に指定したい場合に記載する。
--uuidを指定した場合、--restorefile string, または--norestorefileのどちらかが必須になる
--restorefile string --uuid uuidとセットで指定する。
stringにはvgcfgbackupで取得したバックアップファイルパスを指定する。
--restorefileを指定することで、PEを書き込むディスクアドレスなどが復旧前と同一になるようにPVを作成し、新たに書き込んだメタデータが通常データを上書きしないよう制御する。
※バックアップファイルにpe_startなどのパラメータが記録されている
vgcfgbackupは、pvcreatevgcreateなどのLVM変更コマンドを実行する度に内部的に実行されている
--norestorefile --uuid uuidとセットで指定する。
UUIDを指定しつつも、バックアップファイルを使わずにPVを作成したい時に使う

VG,LVの復旧

VG、LVは、バックアップファイルを用いて1コマンドで復旧できます。

復旧コマンドを実行する前に状態確認とテストモード実行をしてみます。

vgdisplay
# (出力なし)

lvdisplay
# (出力なし)

vgcfgrestore -f my_vm my_vm -t
  # TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.
  # Restored volume group my_vm.

テストモード実行は問題なく通りました。
-tオプションを外して実行し、実際に復旧させます。

vgcfgrestore -f my_vm fedora_cinnamon
  # Restored volume group fedora_cinnamon.

確認コマンドを実行します。
PV, VG, LV共に無事作成されました。
これにて復旧完了です。

復旧を確認できたら、再度fdiskによるパーティション拡張に挑戦してみてください。
よろしければ、再挑戦の前にVMクローンによるバックアップもご検討ください。

pvdisplay
  # --- Physical volume ---
  # PV Name               /dev/vda3
  # VG Name               fedora_cinnamon
  # PV Size               18.41 GiB / not usable 2.00 MiB
  # Allocatable           yes 
  # PE Size               4.00 MiB
  # Total PE              4713
  # Free PE               41
  # Allocated PE          4672
  # PV UUID               BPTO1p-dz0e-gM36-37ls-nGY0-cUeD-Zcmt4b

vgdisplay
  # --- Volume group ---
  # VG Name               fedora_cinnamon
  # System ID             
  # Format                lvm2
  # Metadata Areas        1
  # Metadata Sequence No  4
  # VG Access             read/write
  # VG Status             resizable
  # MAX LV                0
  # Cur LV                2
  # Open LV               0
  # Max PV                0
  # Cur PV                1
  # Act PV                1
  # VG Size               18.41 GiB
  # PE Size               4.00 MiB
  # Total PE              4713
  # Alloc PE / Size       4672 / 18.25 GiB
  # Free  PE / Size       41 / 164.00 MiB
  # VG UUID               RTiZsE-pO7c-2rMe-O5So-h2FA-PcFk-gz3wSi

lvdisplay
  # --- Logical volume ---
  # LV Path                /dev/fedora_cinnamon/root
  # LV Name                root
  # VG Name                fedora_cinnamon
  # LV UUID                geKCRT-Q1TN-rJBV-zP4F-jdjI-Sucm-eichFQ
  # LV Write Access        read/write
  # LV Creation host, time fedora-cinnamon, 2020-09-11 02:31:26 +0000
  # LV Status              available
  # # open                 0
  # LV Size                16.25 GiB
  # Current LE             4160
  # Segments               1
  # Allocation             inherit
  # Read ahead sectors     auto
  # - currently set to     256
  # Block device           253:2
   
  # --- Logical volume ---
  # LV Path                /dev/fedora_cinnamon/swap
  # LV Name                swap
  # VG Name                fedora_cinnamon
  # LV UUID                ZyTqNg-8MTo-JuVE-JWTi-1kfy-ZxJU-7fe812
  # LV Write Access        read/write
  # LV Creation host, time fedora-cinnamon, 2020-09-11 02:31:26 +0000
  # LV Status              available
  # # open                 0
  # LV Size                2.00 GiB
  # Current LE             512
  # Segments               1
  # Allocation             inherit
  # Read ahead sectors     auto
  # - currently set to     256
  # Block device           253:3

PV拡張 & VG拡張

PVを拡張すると、対応するVGも拡張された扱いになります。
PVの拡張には、pvresizeコマンドを使用します3

pvresizeコマンドを実行するためにPV (Physical Volume) の名前を事前に調べておきます。
PVを表示するコマンドはpvdisplaypvscanpvsの3種類がありますが、一番見やすいpvdisplayで表示します4
PV名は/dev/vda3で、現時点のサイズは18.41 GiBと認識されていることがわかります。

sudo pvdisplay
#   --- Physical volume ---
#   PV Name               /dev/vda3
#   VG Name               fedora_my_vm
#   PV Size               18.41 GiB / not usable 2.00 MiB
#   Allocatable           yes 
#   PE Size               4.00 MiB
#   Total PE              4713
#   Free PE               41
#   Allocated PE          4672
#   PV UUID               BPTO1p-dz0e-gM36-37ls-nGY0-cUeD-Zcmt4b

では、pvresizeでPVサイズを拡張します。

pvresizeのSyntaxは以下の構造です。

pvresize <pv-name> [options...]

オプションは多数ありますが、代表的なものは以下の通りです。

オプション 意味
-t, --test 実際に変更を行わず、想定結果のみ返して終了する
--setphysicalvolumesize Size PVのサイズを指定する。
指定しない場合は自動認識する。
基本使わない

まずは、テストモードで実行してみます。
"1 physical volume(s) resized or updated" と出ているので、テスト実行としては成功しています。

sudo pvresize -t /dev/vda3
#   TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.
#   Physical volume "/dev/vda3" changed
#   1 physical volume(s) resized or updated / 0 physical volume(s) not resized

次は実際に変更します。

sudo pvresize /dev/vda3
#   TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.
#   Physical volume "/dev/vda3" changed
#   1 physical volume(s) resized or updated / 0 physical volume(s) not resized

pvdisplayで確認します。
変更前のサイズは18.41 GiBでしたが、正しく23.41 GiBに拡張されました。

sudo pvdisplay
#   --- Physical volume ---
#   PV Name               /dev/vda3
#   VG Name               my_vm
#   PV Size               23.41 GiB / not usable 1.98 MiB
#   Allocatable           yes 
#   PE Size               4.00 MiB
#   Total PE              5993
#   Free PE               1321
#   Allocated PE          4672
#   PV UUID               BPTO1p-dz0e-gM36-37ls-nGY0-cUeD-Zcmt4b

VGのサイズも確認します。
PV拡張後、VG Sizeも23.41 GiBになっていることがわかります。

sudo vgdisplay
#   --- Volume group ---
#   VG Name               my_vm
#   System ID             
#   Format                lvm2
#   Metadata Areas        1
#   Metadata Sequence No  4
#   VG Access             read/write
#   VG Status             resizable
#   MAX LV                0
#   Cur LV                2
#   Open LV               2
#   Max PV                0
#   Cur PV                1
#   Act PV                1
#   VG Size               23.41 GiB
#   PE Size               4.00 MiB
#   Total PE              5993
#   Alloc PE / Size       4672 / 18.25 GiB
#   Free  PE / Size       1321 / 5.16 GiB
#   VG UUID               RTiZsE-pO7c-2rMe-O5So-h2FA-PcFk-gz3wSi

LV拡張 & File System拡張

LV (Logical Volume) の拡張には、lvextendコマンドを使います5
lvextend--resizefsオプションを付与することで、ファイルシステムの拡張も同時に行えます。
ファイルシステムの同時拡張は、ext2, ext3, ext4, ReiserFS, XFSに対応しているようです (※)。

(※)
man lvextendによると、--resizefsは裏でfsadm resizeコマンドを実行することでファイルシステムのサイズ変更を行っています。
そしてman fsadmによると、fsadmは上述のファイルシステムに対応しているようでした。

なお、lvresizeというlvextend(拡張)とlvreduce(縮小) を両方行えるコマンドもあります。
今回は「サイズ指定を間違えて誤って縮小し、データが破損した...」といったミスを防ぐために、lvextendを使います。

さて、LVを拡張するために、まずはlvdisplayによってLVの名前を調べておきます。
他にもlvslvscanといった同様のコマンドがありますが、ほぼ見た目しか変わりません。

コマンド出力より、今回拡張したいLVの名前は、/dev/my_vm/rootであるとわかります。
また、拡張前のLV Sizeは16.25 GiBでした。

sudo lvdisplay
#   --- Logical volume ---
#   LV Path                /dev/my_vm/root
#   LV Name                root
#   VG Name                my_vm
#   LV UUID                geKCRT-Q1TN-rJBV-zP4F-jdjI-Sucm-eichFQ
#   LV Write Access        read/write
#   LV Creation host, time my-vm, 2020-09-11 11:31:26 +0900
#   LV Status              available
#   # open                 1
#   LV Size                16.25 GiB
#   Current LE             4160
#   Segments               1
#   Allocation             inherit
#   Read ahead sectors     auto
#   - currently set to     256
#   Block device           253:0
   
#   --- Logical volume ---
#   LV Path                /dev/my_vm/swap
#   LV Name                swap
#   VG Name                my_vm
#   LV UUID                ZyTqNg-8MTo-JuVE-JWTi-1kfy-ZxJU-7fe812
#   LV Write Access        read/write
#   LV Creation host, time my-vm, 2020-09-11 11:31:26 +0900
#   LV Status              available
#   # open                 2
#   LV Size                2.00 GiB
#   Current LE             512
#   Segments               1
#   Allocation             inherit
#   Read ahead sectors     auto
#   - currently set to     256
#   Block device           253:1

次に、LVを拡張します。

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

lvextend [options...] LV-name [PV-name]

主なオプションの意味は、下表のとおりです。
LVのサイズは--extents--sizeの2通りの指定方法がありますが、今回の場合は--extents +100%FREEを使うことで、LVの空き容量を全てroot LVに追加で割り当てます。
VGの空き領域などは、vgdisplayで確認できます。
もちろん、vgdisplayで確認したFREE PE / Sizeの値を参考に、--sizeで指定しても問題ありません。

PV-nameは、--extentsでPE (Physical Extent) を利用したサイズ指定を行う場合や、%PVSを使う際にPE Sizeを特定する際に必要となります。

オプション 意味
-l size,
--extents size
拡張後のLVのサイズ。
数値のみ記載した場合、LE (Logical Extent) 数での指定となる。
他に%を後置した動的な指定も可能。
%VG: VGの総容量
%FREE: VGの空き容量
%PVS: 指定したPVの空き容量
%ORIGIN: スナップショット元 (Original) のVGの合計サイズ
 (LVM Snapshot領域のLVに対して使う)
+-を先頭に付けると、今のLV Sizeに対する相対値になる。
-L, --sizeと併用すると、sizeが上限値となる
例1: -l 100%FREEでVGの空き領域を全て割り当て
例2: -l +100%FREEでVGの空き領域を現状のLV Sizeに上乗せして割り当て
-L size,
--size size
拡張後のLVのサイズ。
単位としてbBsSkKmMgGtTpPeEが使える。
bBはByte, sSはセクタ, kKは1024Bといった意味。
+か-をつけると、今のLV Sizeに対する相対値になる。
-L, --sizeの代わりに-l,--extentsを使ってもよい
-r,
--resizefs
ファイルシステムのサイズ変更も同時に行う。
ext2, ext3, ext4, ReiserFS, XFSに対応。
詳細はman fsadmを参照
-t,
--test
テストモード実行になる。
LVMのメタデータを書き込まずに成功扱いで終了する。
-rを指定すると、fsadm --dry-runも内部的に実行してくれる。
lvreduceの場合はfsadm --dry-runの実行は行わないので注意(参考)
-v,
--verbose
より詳細な情報を画面出力する。
-vを複数指定するとより詳細に出力する (最大4回)
-d,
--debug
syslogにも結果を出力する。
-dを複数指定するとより詳細に出力する (最大6回)

まずはテストモードで実行してみます。
無事に21.41 GiBまで拡張できそうです。

sudo lvextend -t -r -l +100%FREE /dev/my_vm/root
  TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.
  Size of logical volume my_vm/root changed from 16.25 GiB (4160 extents) to 21.41 GiB (5481 extents).
  Logical volume my_vm/root successfully resized.

-vオプション付きでテストモード実行してみます。
後半でfsadm --dry-runが実行され、さらにfsadmが内部的にresize2fsコマンドを呼んでいるところまで読み取れます。
理解が深まりますね。

sudo lvextend -t -r -l +100%FREE /dev/my_vm/root -v
  TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.
  Converted 100%FREE into at most 1321 physical extents.
  Executing: /usr/sbin/fsadm --dry-run --verbose check /dev/my_vm/root
fsadm: "ext4" filesystem found on "/dev/mapper/my_vm-root".
fsadm: Skipping filesystem check for device "/dev/mapper/my_vm-root" as the filesystem is mounted on /
  /usr/sbin/fsadm failed: 3
  Test mode: Skipping archiving of volume group.
  Extending logical volume my_vm/root to up to 21.41 GiB
  Size of logical volume my_vm/root changed from 16.25 GiB (4160 extents) to 21.41 GiB (5481 extents).
  Test mode: Skipping backup of volume group.
  Logical volume my_vm/root successfully resized.
  Executing: /usr/sbin/fsadm --dry-run --verbose resize /dev/my_vm/root 22450176K
fsadm: "ext4" filesystem found on "/dev/mapper/my_vm-root".
fsadm: Device "/dev/mapper/my_vm-root" size is 17448304640 bytes
fsadm: Parsing tune2fs -l "/dev/mapper/my_vm-root"
fsadm: Resizing filesystem on device "/dev/mapper/my_vm-root" to 22988980224 bytes (4259840 -> 5612544 blocks of 4096 bytes)
fsadm: Dry execution resize2fs /dev/mapper/my_vm-root 5612544

さて、テスト実行に成功したので、実際にLVを拡張します。
テストモード実行時のコマンドから-tオプションを外して実行します。
ログ出力を見るに、LV、ファイルシステム共に正しく拡張されたようです。

sudo lvextend -r -l +100%FREE /dev/my_vm/root
#   Size of logical volume my_vm/root changed from 16.25 GiB (4160 extents) to 21.41 GiB (5481 extents).
#   Logical volume my_vm/root successfully resized.
# resize2fs 1.45.5 (07-Jan-2020)
# Filesystem at /dev/mapper/my_vm-root is mounted on /; on-line resizing required
# old_desc_blocks = 3, new_desc_blocks = 3
# The filesystem on /dev/mapper/my_vm-root is now 5612544 (4k) blocks long.

拡張後のLVとファイルシステムの状態をそれぞれ確認します。
いずれも21GiBに拡張されていることが読み取れました。

sudo lvdisplay /dev/my_vm/root
#   --- Logical volume ---
#   LV Path                /dev/my_vm/root
#   LV Name                root
#   VG Name                my_vm
#   LV UUID                geKCRT-Q1TN-rJBV-zP4F-jdjI-Sucm-eichFQ
#   LV Write Access        read/write
#   LV Creation host, time my-vm, 2020-09-11 11:31:26 +0900
#   LV Status              available
#   # open                 1
#   LV Size                21.41 GiB
#   Current LE             5481
#   Segments               2
#   Allocation             inherit
#   Read ahead sectors     auto
#   - currently set to     256
#   Block device           253:0

df -Th
# Filesystem                       Type      Size  Used Avail Use% Mounted on
# (中略)
# /dev/mapper/my_vm-root ext4       21G   14G  6.7G  67% /
# (中略)

以上でディスク拡張は無事完了です。

最後に補足ですが、lvextend-r, --resizefsを指定しなかった場合、LV拡張後に手動でファイルシステムの拡張コマンドを実行する必要があります。

EXT4の場合、手動実行のコマンドは、以下のいずれかになります。

# sudo fsadm resize /dev/my_vm/root
# sudo resize2fs /dev/my_vm/root

基本的には、lvextend -rを使うのが簡単でおすすめです。
-rを付けるのを忘れてしまった場合は、fsadmファイルシステムの違いを抽象化してくれる上に-nでテスト実行もできるので良いと思います。
resize2fsは、Fedoraにおいて使うことはあまりないかなと思います。

参考情報

ディスク拡張手順の説明では省略した、背景知識について補足します。
記事を書き始めた時点では私も知らず、もやもやした部分が対象です。

参考情報については、本題とは関係ないので読み飛ばしても問題ありません。
もし興味のあるトピックがあれば、拾い読みいただければと思います。

(参考) fdisk vs parted

今回はパーティション拡張にfdiskを使いましたが、partedを使っても良いと思います。
他にもファイルをInputとして動作する自動化向きのsfdiskや、TUI (Table User Interface) のcfdisk、partedのGUI版であるGPartedなどのバリエーションもあり、基本的にどれを使っても問題ないとは思います。

RHEL 8 Managing storage devicesによると、2021年11月現在においてはpartedよりもfdiskの方がサポートするパーティションタイプの種類が多いようです
ただ、こういった情報は今後ソフトウェアがアップデートされるごとに変わっていきます。
その時々で気になった際にmanなどの情報を確認し、要件にあっているか確認すると良いでしょう。

機能面で特に差がなければ、自分が慣れている方を使えば良いと思います。
fdiskもpartedのどちらも対話式のインターフェースを持ちつつも、スクリプトで自動化することも可能です6
私はpartedとfdiskのどちらにも慣れていませんが、writeしない限り何も起こらないfdiskの方が気軽に色々試せて好みです。
使い捨てのVM上で作業しているとは言え、何度もクローンし直すのは手間ですから...。

(参考) GPTとPMBRの概要

Wikipedia - GUID Partition Tableの記事を参考に、GPTやPMBRといった用語について補足します。

GPT (GUID Partition Table) は、従来のMBR (Master Boot Record) を置き換えるものです。
GUID (Globally Unique Identifier) は、UUIDと同じ意味で、「絶対に被らない128bitの16進数」で、要するに"ID"です。
GPTは、BIOSの後継であるUEFIの仕様の一部として定義されています。

GPTとは、その名の通りPartition Tableであり、ディスク上でLVM2やファイルシステムなどを含むパーティションの前後に書き込まれたメタデータを表します。
GPTのフォーマットは、Wikipediaの図が参考になります。

GUID_Partition_Table_Scheme
引用元:Wikipedia - GUID Partition Table

図に出てくるLBA (Logical Block Address) とは、物理的なディスク上のアドレス (CHS: cylinder-head-sectorなど) を抽象化する論理アドレスです。
LBA0が先頭アドレス、LBA1が次のアドレス...といったように表記します。
(参考:Wikipedia - Logical Block Addressing)

ディスク先頭のLBA0 (ディスク製品によって512 Byteであったり、4096 Byteであったりします) にPMBR (Protective Master Boot Record) の領域が存在します。
PMBRは、従来のMBRを認識する古いプログラムが、GPTの先頭ディスク領域を上書きしてPartition Tableを破壊することを防ぐための仕組みです。
原理について補足すると、PMBRには、MBRと同様のフォーマットでデータが書き込まれています。
そして、PMBRのPartition IDには "EFI" を表すEEh というデータを書き込まれています。
するとMBRしか認識できない古いプログラムは、後続のGPTの領域 (LBA1以降) を「不明なフォーマットの単一のパーティションが存在する」と認識し、データを書き込む前に大抵何らかの警告を出すようになります。
こうして、PMBRは古いプログラムの誤動作によるパーティションの破壊を防いでいます。

さて、ここでディスクサイズ拡張直後にfdisk -lを実行した時のエラーメッセージについて振り返ります。

# GPT PMBR size mismatch (41943039 != 52428799) will be corrected by write.
# The backup GPT table is not on the end of the device. This problem will be corrected by write.

このエラーメッセージが出る前に、ゲストOS側では何の操作もすることなく仮想ディスクファイル (qcow2) を5 GiB拡張しています。
この時、qcow2の中身は以下のようになっています。

after_qcow2_extension

qcow2の拡張操作は、GPTを含むディスク上のデータを一切書き換えることなく、単純にディスク容量が増えています。
これによって、以下の点で整合性が取れなくなっています。

  • PMBRから読み取ったパーティションの最終セクタ番号 (=ディスクの最終セクタ) と実際のディスクの最終セクタ番号が異なっている7, 8
  • Secondary GPT Headerはディスクの最後のアドレス (LBA-1) に書き込まれるのが正しいが、実際にはSecondary GPT Headerの後に空き領域がある (上述のWikipediaの図も参照)

fdiskでwriteすると、GPT (= パーティションテーブル = PMBR + Partition Header + Partition Entries) が再書き込みされます。
#パーティション拡張では、この操作によりエラーが解消されたというわけです。

(参考) 論理セクタと物理セクタ

ディスクのデータ読み書きの単位であるセクタは、物理と論理の2種類があります。
それぞれの意味は、以下のとおりです9

  • 物理セクタ: ディスクが一度のI/Oでデータを読み書きする最小単位 (atomic write)
  • 論理セクタ: LBA 1ブロック分のサイズ。ディスクが受け付け可能なI/Oの最小単位

なお、LBA 1ブロックあたりのデータ容量は、論理セクタのサイズと同じです。

物理セクタと論理セクタのサイズは、ディスクが工場から出荷された段階で予めセットされています。
ディスク製品の多くは、出荷時の段階でセクタ境界にbit列を書き込み、物理セクタサイズを設定しています (LLF: Low-Level Format)。
論理セクタサイズがどのように決まるかは情報が見つかりませんでしたが、恐らく同様に製品出荷時に決まっています10

ちなみに、製品によってはre-initialization といってユーザー側で再度フォーマットすることでセクタサイズを変更できるものもあるそうです11

論理セクタサイズと物理セクタサイズは、fdiskコマンドによって確認できます。
ホストマシンで実行した以下のログによると、論理セクタサイズ, 物理セクタサイズ共に512 Byte であることがわかります。
ただkernelのバージョンにも依りますが、この情報が必ずしも正しいとは限らないという話もあるようです12
正確な情報が必要な場合は、製造メーカーのデータシートなどを参照することになりそうです。
ただセクタサイズを意識する場面はAlignmentを意識するときぐらいです (#後述)。
現時点で一番大きい4KiBやそれよりも大きい1MiB単位でAlignmentしていれば、実用上セクタサイズに対してそれほど神経質になる必要はないと思います。

sudo fdisk -l
# Disk /dev/nvme0n1: 931.51 GiB, 1000204886016 bytes, 1953525168 sectors
# Disk model: WDC WDS100T2B0C-00PXH0                  
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes

今どきのディスク製品のフォーマット (FMT) 形式は、論理セクタサイズと物理セクタサイズによって以下のように分類できます13, 14

フォーマット名 論理 物理
512n (512 Native) 512 Bytes 512 Bytes
512e (AF, 512-byte Emulation) 512 Bytes 4096 Bytes
4Kn (AF, 4KB Native) 4096 Bytes 4096 Bytes

AFとはAdvanced Format の略で、物理セクタサイズを従来型の512 Bytesから4096 Bytesに増やし、ディスクの容量効率を向上させる技術のことです15

AFに対応した製品は2010年から出荷され始めた関係で、4096 Bytesのセクタサイズに対応していないOSやプログラムが「昔は」あったようです。
2015年より前のWindows Server 2008、RHEL6、ESXi5.5などの時代の話です。
恐らく、今の段階で4K非対応を気にする必要はあまりないのではないかと思います16

(参考) 4K Disk Alignment

概要

結論から言えば、パーティション開始位置をfdiskのデフォルトに任せつつ、パーティションサイズを1MiBの倍数で指定していれば基本問題ありません。

1MiBの倍数で指定する場合、1M Alignmentと呼ばれることもあります17
ここでの説明だけを参照すると4KiBでも十分そうに見えるのですが、RAID5, RAID6を組んでいる場合は16KiB, 256KiBのデータストライプに合わせる必要があります。18

LVMにもAlignmentは関係します。
LVM2はデフォルトで1MiBでAlignmentを調整します。
詳しい内容は、man pvcreateに書かれています。
LVMを使う場合は、1MiB単位でAlignmentを揃えるのが良さそうです。

さらに別の話題としてSSDのErase Block Sizeに合わせると16KiB〜8MiB程度の粒度になってしまうのですが、本当にErase Block SizeとPartition開始点を合わせる必要があるのかははっきりとしていません。
業務用ストレージでSSDを使う場合は、恐らくメーカーのドキュメントなどを参照して推奨される設計を確認することになりそうですが、個人利用の範囲ではあまり気にしないようにします。
SSDについてはsuperuser - Is partition alignment to SSD erase block size pointless?にて興味深い議論がされていますので、興味のある方は読んでみてください。

上記を総合すると、「パソコン用途では基本1MiBでAlignmentを調整し、エンタープライズ用途のSSDストレージやRAID構成であればメーカーの仕様に沿って検討するのが良さそう」というのが私なりの結論です。

さて、本題の「Alignmentの理解」に入ります。

パーティションを作成する際、セクター開始位置には注意を払う必要があります。
セクター開始位置のアドレス (LBA: Logical Block Address) が4KiBの倍数でない場合、I/Oのパフォーマンスが低下する可能性があります。
2021年現在においては、ディスクのフォーマット形式が512e (物理セクタサイズ=4KiB、論理セクタサイズ=512B) の場合のみ、この問題が発生する可能性があると理解しています。
512eについては、#(参考) 論理セクタと物理セクタに記載があります。

Misalignmentによってなぜパフォーマンスが低下するのか、以下の図で説明します。
Alignmentが正しいケースと誤っているケースを比較してみましょう。

correct_alignment

incorrect_alignment

上記スライドは、File Systemのブロックサイズ (※) が4KiBの場合を想定しています。
つまり、File Systemが発行するI/O命令は必ず4KiBの倍数になります。
(※) ディスクのセクターサイズとはまた別物。多くのファイルシステムでは、デフォルト値は4096Bか512B。例えばEXT4の場合、詳細はman mke2fsを参照。

ここで4KiBのI/Oが発生したとします。
Alignmentが適切であれば、物理ディスク上で実際に発生するI/Oは物理セクタ1つ分 (4KiB) のI/Oになります。
一方でAlignmentが適切ではない場合、物理ディスク上で物理セクタ2つにまたがってI/Oが発生してしまうため、非効率的です

File Systemのブロックサイズ、パーティションの開始位置、物理セクタサイズに合わせてシステムを構成することで、上述のMisalignmentによるI/Oパフォーマンスの低下を防ぐことができます。

では、実際に構築する際、どうすればAlignmentを正しくできるのでしょうか?
それについては、次のセクションに記載します。

Misaligmnetを防ぐ方法

今どきのfdisk (util-linux 2.18以降。RHEL6以降に同梱) であれば、以下のやり方でパーティションを作成すればMisalignmentは起こらないようになっています19, 20

なぜなら、fdiskは以下のように動作するためです。
上記ルールと合わせれば、各パーティションの開始セクターは必ず2048セクター (1MiB) の倍数、つまり8セクターの倍数となります。
従って、Misalignmentは起こりえません。

なお、PartedやGPartedについても同様のAlignmentへの配慮がされています21

Misalignmentが発生しているか否かの確認方法

sudo fdisk -lにおいて論理セクタサイズが512 Bytes 表記の場合、各パーティションの開始セクタを8で割り切れない場合は、misalignmentが発生している可能性があります。
ディスクのフォーマット形式が512eかどうかを調べる作業は労力がかかりますので、そういったことを考えずに済むように、1MiBの倍数でセクタ開始点を定めるように意識していきたいところです。

以下にfdisk -lの出力の一部を貼ります。
Startの部分が2048, 1230848, 3328000と並んでいます。
これらはいずれも8で割り切れます。
つまり、パーティションの開始点は4KiB (512 Bytes * 8) の倍数なので、正しく4K Alignmentされています。
更に言うと2048でも割り切れるので、1M Alignmentされています (1MiB = 512 Bytes * 2048)。
1M Alignmentは、RAIDなどを考慮に入れても対応可能で、4K Alignmentよりも更に手堅いAlignment方法です22

sudo fdisk -l
# (中略)
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes

# (中略)

# Device       Start      End  Sectors  Size Type
# /dev/vda1     2048  1230847  1228800  600M EFI System
# /dev/vda2  1230848  3327999  2097152    1G Linux filesystem
# /dev/vda3  3328000 52428766 49100767 23.4G Linux LVM

まとめ

KVM仮想マシンのディスク拡張の手順を紹介しました。
LVMならではのトラブルの対処方法や、GPTやAlignmentの予備知識を参考情報として盛り込んだことで、内容が大変盛りだくさんとなりました。

目次を活用して、必要な情報を拾って読んでいただければと思います。


  1. Unix & Linux - How to calculate partition Start End Sector?

  2. Red Hat - RHEL8 - Configuring and managing logical volumes - 19.5. Restoring metadata on an LVM physical volume

  3. Red Hat - RHEL8 - Configuring and managing logical volumes - 7.3. Resizing an LVM physical volume

  4. Red Hat - RHEL8 - Configuring and managing logical volumes - 3.1. Creating LVM physical volume

  5. Red Hat - RHEL8 - Configuring and managing logical volumes - 5.1. Growing a logical volume and file system

  6. Red Hat Customer Portal - Tool Battle Royale - FDISK vs. PARTED ※fdiskをシェルスクリプトで自動化している方もいました

  7. Wikipedia - GUID Partition Table #Protective_MBR_(LBA_0))

  8. GitHub - util-linux/util-linux - gpt.c

  9. Dell Engineering - 512e and 4Kn Disk Formats p9

  10. Wikipedia - Disk formatting

  11. WD Community - SN550 - Why it uses 512B sector instead of 4096? ※WD製やIntelSSDの例

  12. IBM - Linux on 4 KB sector disks: Practical advice - #Determining physical sector size

  13. Dell Engineering - 512e and 4Kn Disk Formats - p4

  14. Wikipedia - Advanced Format

  15. Wikipedia - Advanced Format

  16. Dell Engineering - 512e and 4Kn Disk Formats - p9〜p10

  17. Wikipedia - Partition alignment

  18. IBM - Linux on 4 KB sector disks: Practical advice #Determining physical sector size

  19. libblkid, or why you don’t need to worry about 4K disk format

  20. IBM - Linux on 4 KB sector disks: Practical advice #The fdisk family

  21. IBM - Linux on 4 KB sector disks: Practical advice #The libparted library

  22. Wikipedia - Partition alignment