えんでぃの技術ブログ

えんでぃの技術ブログ

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

Fedora44の変更点

Fedoraの変更点シリーズ

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

Fedoraの変更点シリーズ

お伝えしたいこと

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

公式情報の見方

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

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

fedora_change_sets

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

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

Release Notes & Changes

boot loaderの段階的な更新 (フェーズ1)

今回の変更は「Fedora 45で導入されるフェーズ2に向けた大きな動きに向けた下準備」という位置づけであり、フェーズ1単体では何の影響もありません。 ただ、今回の変更の背景が興味深く感じられたので、ここで紹介させていただきます。 忙しい方は読み飛ばしてください。

Fedora 43以前のboot loaderのRPM更新では、Linux起動に使われる/boot/boot/efiを直接書き換えていました。 もしインストール処理中で異常が発生すると、/boot/boot/efi配下が壊れて起動できない状態になるリスクがありました。

今回の変更では、boot loaderのインストールを2段階に分けます。

  1. RPMパッケージのファイル配置処理では、/usr/lib/<pkg>/<arch>配下にboot loaderをインストールする
  2. RPMパッケージの事後処理にて、/usr/lib/<pkg>/<arch>から/boot/boot/efiに反映する

今回のフェーズ1の更新単体では、boot loader更新時のリスクは変わっていません。 フェーズ1では/boot/boot/efiへのファイル配置を2段階に分けただけで、リスク回避のためのエンハンスは特に加えられていません。

具体的な改善は、Fedora 45のリリースで導入される「フェーズ 2」で実装される計画になっています。 フェーズ1は、フェーズ2に向けた下準備という位置づけです。

フェーズ2の変更まで反映されれば、以下のメリットが得られる想定のようです。

  • フォールバック機能のサポート。新旧のboot loaderファイルを保持し、更新後のboot loaderによる起動に失敗したときは更新前のboot loaderを参照する (利用者目線のメリット)
  • ブートローダー更新の仕組みを bootupd へ一元化。「新規インストール vs 更新」および「パッケージモード vs イメージモード」の実装を統一する (開発者目線のメリット)

PackageKitでもDNF5を利用

Fedora 41からDNF 4系からDNF 5系に更新されました。 これはCLIの話です。
(参考) Fedora41の変更点 - #DNF5がデフォルトに

デスクトップ環境でパッケージ管理をするPackageKitソフトウェアのバックエンドは、Fedora 43まではDNF 4系のままでした。 これがFedora 44以降はPackageKitについてもDNF 5系に統一されます。
(※) 普段からCLIをお使いの方にはあまり関係ないかもしれませんが...

PackageKitのフロントエンドである以下のアプリケーションに関連する変更です。

  • Cockpit
  • Plasma Discover (KDE Desktop, ...)
  • GNOME Software (GNOME Desktop, Cinnamon Desktop, ...)

Bugs

特段気になるバグはありませんでした。

Fedora43の変更点

Fedoraの変更点シリーズ

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

Fedoraの変更点シリーズ

お伝えしたいこと

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

公式情報の見方

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

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

fedora_change_sets

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

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

Release Notes & Changes

/bootのパーティションサイズの推奨値が1 GBから2 GBに変更

Fedoraインストール画面 (Anaconda) においてパーティションを設定する際、/bootパーティションのデフォルト値 (推奨値) が従来の1 GBから2 GBに引き上げられました。

背景として、ファームウェアやinitramfsのサイズ増加により/bootの容量消費が増加傾向にあることが挙げられます。 経緯を辿ると、/bootの推奨サイズは2016年に500 MBから1 GBに、そして今回2025年に1 GBから2 GBに引き上げられています。

未来のことはわかりませんが、今後もサイズが増えるかもしれませんね。

今回の変更によって、既存のFedoraユーザが直ちに影響を受けることはありません。 「/bootの容量消費がFedora 43で突如増加する」という話ではなく、あくまで「/bootの消費量増加の傾向を鑑みてデフォルト値を見直した」ということであるためです。

慌てて/bootの拡張を検討する前に、まずは現在の消費量を確認してみてください。 GPUを搭載しているのでない限りは、おそらく消費量は500 MiB弱だと思われます。
(※) 私は慌てて拡張した側の人間です...(笑)

df -h /boot
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/nvme0n1p2  7.9G  420M  7.1G   6% /boot

「自分はどうしようか」と迷っている方向けに、Fedoraコミュニティの掲示板に興味深い投稿がありましたのでご紹介します。
Systems with <=1 GB /boot partition may see “need more space” error during system update

以下に要点をまとめます。

  1. /bootの容量消費が増加傾向にある理由
    • Fedoraはデフォルトで/bootに3世代のkernelとinitramfsのペアを保存する
    • さらにrescue mode用のkernelで1ペア、system update (Fedora OS更新) 中には一時的に追加で1ペア増えて合計5ペアになる
    • initramfsには、Linux起動時に周辺機器を認識するためのファームウェアが含まれている
    • 近年、ファームウェアファイル数が急速に増えている
  2. /bootの容量消費が1 GBを超えやすい構成
    • NVIDIAファームウェアのサイズ増加が顕著であり、initramfs 1つにつき100 MiBのサイズになる。5ペアで500 MiBのサイズになる
    • GPUなど追加のハードウェアを搭載したマシンを利用している場合、1 GiBの/bootパーティションサイズでは足りなくなる可能性が高くなる
  3. 回避策として、/bootの容量を一時的に減らす方法
    • 以下のような方法で/bootの容量消費を抑えること
    • システムアップグレード前に古いカーネルを1つアンインストールすること (参考: rpm -q kernel)
      • 以下のコンテンツを持つ/etc/dnf/libdnf5.conf.d/00-kernellimit.confのようなファイルを作成し、kernelの保管世代数を2に減らす
[main]
installonly_limit=2

一方で、恒久対策として/bootを2 GBに拡張する手段としては、以下の2つの方法が挙げられます。
Can’t extend boot partition despite unallocated space

  1. 再インストール
    • データを外付けHDDなどに一時的に退避する
    • Fedora 43を新たなパーティション構成で新規インストールする
    • 退避したデータを書き戻す
    • (えんでぃ補足) バックアップ時の具体的な手順 (書き戻す際はrsyncの引数を逆にして実行するだけ)
  2. GPartedで/bootパーティションを拡張する
    • 操作をミスるか不具合を引き当てるとデータが消えるので、事前にバックアップを取る必要がある
    • (えんでぃ補足) GPartedによる/bootパーティションの拡張で実際に対応したので、参考にしてください

Bugs

system upgrade後、一部のサービスが勝手に無効化される

以下のサービスを元々有効化している方は、アップグレード後に勝手に無効化されていないか確認したほうが良さそうです。 元々有効だったものが無効化されていた場合は上記URLを参照し、対処法を調べてみてください。

  • nftables.service
  • postfix.service
  • sendmail.service
  • exim.service
  • opensmtpd.service

確認コマンドは以下のとおりです。 もしFedora 42時点でenabledが返ってきたら注意しておいてください。

systemctl is-enabled nftables.service postfix.service sendmail.service exim.service opensmtpd.service
# disabled
# not-found
# not-found
# not-found
# not-found

私は上記サービスを使っていないデフォルト構成だったので、特に影響はありませんでした。

GPartedによる/bootパーティションの拡張

序論

背景

Fedora 43が2025年10月にリリースされました。 そこで以下の変更点が告知されました。

Changes in Fedora 43 For System Administrators - #Default /boot partition is now 2G

従来は/bootパーティションの推奨サイズが1 GiBでしたが、今後は2 GiBになります。 私の環境では/bootの使用率はまだ50%程度です。
(※) GPUなど追加のドライバを要求するハードウェアを搭載している方は使用率が高いかもしれません。そういった方は対策したほうが良さそうです

大丈夫です。 まだ慌てるときではありません。

ですが、今後時間が経つにつれて/bootのサイズは徐々に大きくなってきます。 その前にPCが壊れてOS入れ直しになる気はするものの...良い機会なので/bootの拡張にチャレンジすることにしました。

やりたいこと

下図のように、/bootパーティションを拡張します。 Fedora 43の要件を満たすには2 GiBに拡張すれば十分ですが、数年後にまた拡張が必要になるのは嫌なので少し大きめに拡張します。
(※) 流石に8 GiBへの拡張はやりすぎました。GPUを搭載する可能性を考慮しても、さすがに4 GiBで良かったと今は思います

extend_boot_partition

ご参考までに、現在のパーティションサイズとファイルシステム構成を確認するコマンドも貼っておきます。

sudo fdisk /dev/nvme0n1 -l
# (snip)

# Device           Start        End    Sectors   Size Type
# /dev/nvme0n1p1    2048    1230847    1228800   600M EFI System
# /dev/nvme0n1p2 1230848    3327999    2097152     1G Linux filesystem
# /dev/nvme0n1p3 3328000 1953523711 1950195712 929.9G Linux LVM

df --output=source,fstype,size,target -h /boot/efi /boot / /home
# Filesystem                 Type  Size Mounted on
# /dev/nvme0n1p1             vfat  599M /boot/efi
# /dev/nvme0n1p2             ext4  974M /boot
# /dev/mapper/fedora_pc-root ext4   49G /
# /dev/mapper/fedora_pc-home ext4  865G /home

/bootパーティション拡張の難しいところ

/bootの拡張は思ったよりも厄介です。 なぜなら、/bootを拡張するには、後続のパーティション3 (/dev/nvme0n1p3) の先頭アドレスを2 GiB分空けてもらわなければならないためです。 「若番のパーティション拡張」というのは、後ろのパーティションを動かさないことには実現できません。

how_is_extending_boot_challenging

嫌な予感がしてきましたね。

/bootが枯渇するまで放置...?

OS再構築...?

rsyncでファイルを退避して、パーティションを書き換えてからリストアするのはどうか? live USBでFedora Cinnamon Spinを起動して、ローカルディスクから外付けHDDにファイルをフルバックアップ。 ローカルディスクに新たなパーティション構成でFedora 43を新規インストール。 その後、またlive USBで起動し、外付けHDDからファイルを書き戻せば...? コマンドの行数で言えば約20行です。 rsync -a -ADHRSX -vi --delete --force --statsというコマンドラインオプションも過去の経験で知っています。 ...とはいえ、GUI操作と放置時間もあるので中々の苦行です。

前置きが長くなりましたが、そんなことはしなくても大丈夫です。 GPartedというツールが救世主でした。

GParted

GParted (GNOME Partition Editor) は、GUIベースのパーティションエディタです。 GPLv2 (またはそれ以降) のライセンスが適用されたOSSです。
GNOME Partition Editor

パーティションを編集するコマンドとしてfdiskpartedなどがありますが、GPartedはfdiskpartedにはない機能があります。 それは、パーティションの移動という機能です。
(※) 他にもすごい機能がたくさんあるようです

パーティションの移動機能は、下図のとおりまさに今回必要としている機能でした。

move_partition_with_gparted

作業に必要なハードウェア

前置きが長くなりましたが、いよいよ本編に入ります。

まずは、作業に必要なハードウェアを揃えましょう。

  • 容量1 GiB以上のUSB x 2
    • (※) USB内のデータは全て上書きされます
  • データバックアップ先のディスク (外付けHDDなど)

GPartedを動かすだけならUSB 1つだけで十分です。 ルートファイルシステムをアンマウントした状態で作業するため、今回はGPartedをLive USBから起動する構成を取ります。

作業前にバックアップを取る場合は、追加でUSB1つとディスク1つを用意します。
(※) 詳細はこのあと書きますが、今回はClonezillaというバックアップツールをご紹介します。Clonezillaの起動にはUSBが必要です

Clonezillaによるバックアップ

(参考) Clonezillaとは

https://clonezilla.org/

GUI操作でディスク丸ごとバックアップ・リストア・複製する機能を提供するOSSです。 使用済みのディスクブロックを複製するという動作原理です。

ファイルバックアップソリューションのrsyncと比較すると、主に以下の違いがあります。

今回はパーティションを破壊する可能性があるため、Clonezillaでバックアップを取ります。

Clonezilla Live USBの作成

まずはClonezilla Live Imageをダウンロードします。

Clonezilla Live Downloadにアクセスし、alternative stable (Ubuntu-based)をダウンロードします。

stable (Debian-based)との違いは、より多くのドライバを含んでいることです。 ハードウェア互換性の問題が発生する可能性を少しでも減らすため、ここはUbuntu版を使っておきましょう。
参考: Why stable and testing Clonezilla live are based on Debian, but alternative one is based on Ubuntu ?

続いて、ダウンロードしたISOファイルを用意したUSBに書き込みます。 Fedoraをお使いでしたらFedora Media Writer (GUIツール)が有名です。 以下のコマンドでインストールできます。

sudo dnf install mediawriter

Fedora Media Writerを起動したら、最初にSelect .iso fileを選択し、あとは画面の流れに従ってISOファイル内のデータをUSBに書き込みます。

fedora-media-writer

Clonezilla Live USBから起動する

作成したUSBをPC本体に挿して電源を入れます。 そして起動順序の指定画面を開き、Clonezillaのイメージを書き込んだUSBから起動します。

"起動順序の指定画面" の開き方は、お使いのPCによって異なります。 私が使っているNUC10でしたら "F7キー連打" ですが、PCによってはF12キーや異なるキーである可能性があります。 詳しくはPCやマザーボードの説明書をご参照ください。

Clonezillaのバックアップ操作

その後はTUI画面で操作を続けます。 基本的には画面に従ってデフォルトの選択肢を選んでいくだけでバックアップ操作が完了します。

具体的な操作画面は、以下の動画が参考になります。 投稿主は英語話者ですが、YouTubeの字幕機能で日本語字幕を出せますのでご安心ください。 "5:45" あたりからClonezillaによるバックアップ操作の説明が始まります。
YouTube - Veronica Explains - Clonezilla: disk cloning, but it's easier than dd

ここでは具体的な操作方法を簡単に示すのみにとどめます。

# 画面のタイトル / プロンプト 実施すべき操作
1 GNU GRUB
(オレンジと白の画面)
Clonezilla live (VGA 800x600)
(デフォルト)
2 Choose language en_US.UTF-8 (English)
(デフォルト)
3 Keyboard configuration Keep
(デフォルト)
4 Start Clonezilla Start_Clonezilla
(デフォルト)
5 Clonezilla - Opensource Clone System (OCS) device-image
(デフォルト)
(※) イメージファイルを生成するバックアップ方式
6 Mount Clonezilla image directory local_dev
(デフォルト)
(※) バックアップ先の領域を選択。外付けHDDならlocal_dev。環境に応じて変更すること
7 Press "Enter" to continue......
(※) 下半分の黒画面の最後の行
バックアップ先のデバイスを接続した上でEnterキーを押す
8 Press Ctrl-C to exit this window.
(※) 黒画面の最後の行
バックアップ先のデバイスが表示されていることを確認し、Ctrl+Cを押す
9 Clonezilla - Opensource Clone System (OCS) | Mode: バックアップ先のデバイスを選択する
10 Clonezilla - Opensource Clone System (OCS): REPOSITORY no-fsck
(デフォルト)
(※) ファイルシステム未作成だとThis directory is not a mount pointとエラーになる
11 Directory Browser for Clonezilla image repository Done
(※) ディレクトリパスを指定。Clonezillaはこのパス配下にディレクトリをもう1つ作成し、イメージを書き込む
12 Press "Enter" to continue......
(※) 下半分の黒画面の最後の行
Enterキーを押す
(デフォルト)
13 Do you want to synchronyze the time...
[Y/n]
y
(デフォルト)
14 No Internet connection was found...
Press "Enter" to continue......
Enterキーを押す
(※) インターネット接続がなくNTP同期に失敗したため、BIOSクロックを参照する
15 Please choose your time zone...
...you want to continue? (Y/n)
y
(デフォルト)
16 Configuring tzdata Asia
(※) aキーを複数回押すとカーソル移動が早い
17 Configuring tzdata Tokyo
(※) tキーを複数回押すとカーソル移動が早い
18 Press "Enter" to continue...... Enterキーを押す
(デフォルト)
19 Clonezilla - Opensource Clone System (OCS) Beginner
(デフォルト)
20 Clonezilla - Opensource Clone System (OCS): Select mode savedisk
(デフォルト)
21 Clonezilla - Opensource Clone System (OCS) | Mode: savedisk yyyy-mm-dd-img
(※) バックアップイメージを格納するディレクトリ名を指定。変えても変えなくても良い
22 Clonezilla - Opensource Clone System (OCS) | Mode: savedisk バックアップ対象のディスクを指定
23 Clonezilla - Opensource Clone System (OCS) | Mode: savedisk -z9p
(デフォルト)
24 Clonezilla - Opensource Clone System (OCS) | Mode: savedisk -sfsck
(デフォルト)
25 Clonezilla - Opensource Clone System (OCS) | Mode: savedisk Yes, check the saved image
(デフォルト)
26 Clonezilla - Opensource Clone System (OCS) | Mode: savedisk -senc
(デフォルト)
27 Clonezilla - Opensource Clone System (OCS) | Mode: savedisk -plu
(デフォルト)
28 Clonezilla - Opensource Clone System (OCS) | Mode: savedisk -p poweroff
(※) バックアップ先の容量不足などで失敗した場合は電源オフされずエラー表示してくれる
29 Press "Enter" to continue... Enterキーを押す
(デフォルト)
(※) TUI操作の選択肢と同等のコマンドを表示してくれる
30 Are you sure you want to continue? (y/n) y
(※) バックアップ元・バックアップ先情報の確認画面

(参考) Clonezillaのリストア操作

リストア操作は、バックアップとほぼ同じです。 以下の部分のみ操作が変わります。

  • 20にて、restorediskを選択
  • 21にて、イメージバックアップ先ディレクトリ (yyyy-mm-dd-img) が見える状態で<Done>を選択する
  • 続いてリストアに使用するイメージ名を選択する (yyyy-mm-dd-img)
  • パーティションテーブル選択画面では-k0を選択 (デフォルト)

GPartedによるパーティション移動

ディスクのバックアップを取得できたので、いよいよ/bootの拡張に移ります。

GParted Live USBの作成

#Clonezilla Live USBの作成と同様の手順で、GParted Live USBを作成します。

GUIを付属したLive USBであれば何を使っても大丈夫です。 今回はFedora Cinnamon Spinをダウンロードします。
(※) 他のイメージを使う方は必要に応じて読み替えてください

ダウンロードしたISOファイルを、Clonezillaのときと同様の手順でUSBに書き込みます。

GParted Live USBから起動する

#Clonezilla Live USBから起動するのときと同様に、GParted Live USBからマシンを起動します。

起動メニューを適切に選択し、Linuxを起動します。

cinnamon_live_grub2

ターミナルを起動し、以下のコマンドでGPartedをインストールします。

sudo dnf install -y gparted

/bootの隣接パーティションを7 GiB縮小する

準備は整いました。 /boot拡張作業の内容を改めて確認しましょう。

move_partition_with_gparted

まずは、/bootに7 GiBを追加で確保するために、隣接するパーティションを7 GiB縮小します。 今回は仮想マシンで作業内容を再現しつつブログを書いているので、LVMのVGは/のみです。 /homeは分かれていない構成となりますが、ご了承ください。

パーティションを書き換えるデバイスを確認します。 パーティション数、ファイルシステム情報 (ext4)、LVMフラグなどの情報から/dev/vdaが作業対象と理解します。

/dev/vda3ext4ファイルシステムであるため、ファイルシステムの縮小に対応することもわかります。
RHEL10 - Managing file systems - 1.5. Comparison of XFS and ext4

sudo -i
lsblk --fs
# NAME         FSTYPE      FSVER            LABEL                   UUID                                   FSAVAIL FSUSE% MOUNTPOINTS
# (snip)
# vda                                                                                                                     
# ├─vda1                                                                                                                  
# ├─vda2       ext4        1.0                                      4150e5a1-0b75-42a4-b844-30cdaf5e871e                  
# └─vda3       LVM2_member LVM2 001                                 A9Qc6e-sVC0-FF1L-uSv2-kiAy-81Uv-eT9W5c                
#   └─fedora-root
#              ext4        1.0                                      e881b911-fab2-41a4-96e1-10625564b47c  

/dev/vdaパーティションテーブルから詳細を確認します。 以下の用途であると確認できます。

fdisk -l /dev/vda
# Disk /dev/vda: 27 GiB, 28991029248 bytes, 56623104 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: BE0547C7-0175-42B2-AA31-EA72BA85D98C

# Device       Start      End  Sectors Size Type
# /dev/vda1     2048     4095     2048   1M BIOS boot
# /dev/vda2     4096  2101247  2097152   1G Linux filesystem
# /dev/vda3  2101248 56623070 54521823  26G Linux LVM

LVMを構成するLV (Logical Volume) のサイズを確認します。 26 GiB程度のサイズであるとわかります。

lvdisplay
  # --- Logical volume ---
  # LV Path                /dev/fedora/root
  # LV Name                root
  # VG Name                fedora
  # LV UUID                5Ec62m-HIrH-xkp1-IkAq-xJxI-h32O-MDBZeT
  # LV Write Access        read/write
  # LV Creation host, time localhost.localdomain, 2026-01-04 09:17:05 -0500
  # LV Status              available
  # # open                 0
  # LV Size                <26.00 GiB
  # Current LE             6655
  # Segments               1
  # Allocation             inherit
  # Read ahead sectors     auto
  # - currently set to     16384
  # Block device           252:0

ファイルシステムの使用量を確認するため、マウントしてdfコマンドを実行します。
(※) GPartedの操作を行うために、後でアンマウントする必要があります

容量は26 GiBあり、21 GiBは空き容量であることがわかります。 7 GiBの縮小には十分耐えられます。

mkdir /mnt/sysroot
mount /dev/fedora/root /mnt/sysroot -o ro

df -h /mnt/sysroot
# Filesystem               Size  Used Avail Use% Mounted on
# /dev/mapper/fedora-root   26G  4.1G   21G  17% /mnt/sysroot

では、いよいよファイルシステムとLVを同時に7 GiB縮小します。 lvredudceはLVの領域を縮小するコマンドですが、-rをつけることでlvreduceコマンドが対応するファイルシステムの縮小を直前に実施してくれます。
man lvreduce

lvreduceコマンドのオプションの説明を以下に貼っておきます。

オプション 意味
-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が上限値となる
-L size,
--size size
縮小後のLVのサイズ。
単位としてbBsSkKmMgGtTpPeEが使える。
bBはByte, sSはセクタ, kKは1024Bといった意味。
+か-をつけると、今のLV Sizeに対する相対値になる。
-L, --sizeの代わりに-l,--extentsを使ってもよい
-r,
--resizefs
ファイルシステムのサイズ変更も同時に行う。
内部的にはファイルシステム固有のリサイズコマンドを実行する。
詳細はman fsadmを参照
-t,
--test
テストモード実行になる。
LVMのメタデータを書き込まずに成功扱いで終了する
-v,
--verbose
より詳細な情報を画面出力する。
-vを複数指定するとより詳細に出力する (最大4回)
-d,
--debug
syslogにも結果を出力する。
-dを複数指定するとより詳細に出力する (最大6回)

まずは-tをつけてテスト実行します。 テスト実行により以下のことがわかります。

lvreduce -r -L -7G /dev/fedora/root -t
#   TEST MODE: Metadata will NOT be updated and volumes will not be (de)activated.
#   File system ext4 found on fedora/root mounted at /mnt/sysroot.
#   File system size (<26.00 GiB) is larger than the requested size (<19.00 GiB).
#   File system reduce is required using resize2fs.
#   File system unmount is needed for reduce.
#   File system fsck will be run before reduce.
# Continue with ext4 file system reduce steps: unmount, fsck, resize2fs? [y/n]:y
#   Skip unmount in test mode.
#   Skip fsck in test mode.
#   Skip fs reduce in test mode.
#   Size of logical volume fedora/root changed from <26.00 GiB (6655 extents) to <19.00 GiB (4863 extents).
#   Logical volume fedora/root successfully resized.

変更内容が想定通りかつ、エラーにもならなかったので本実行します。

lvreduce -r -L -7G /dev/fedora/root
#   File system ext4 found on fedora/root mounted at /mnt/sysroot.
#   File system size (<26.00 GiB) is larger than the requested size (<19.00 GiB).
#   File system reduce is required using resize2fs.
#   File system unmount is needed for reduce.
#   File system fsck will be run before reduce.
# Continue with ext4 file system reduce steps: unmount, fsck, resize2fs? [y/n]:y
#   Reducing file system ext4 to <19.00 GiB (20396900352 bytes) on fedora/root...
# unmount /mnt/sysroot
# unmount done
# e2fsck /dev/fedora/root
# /dev/fedora/root: 57890/1703936 files (0.1% non-contiguous), 1202895/6814720 blocks
# e2fsck done
# resize2fs /dev/fedora/root 19918848k
# resize2fs 1.47.3 (8-Jul-2025)
# Resizing the filesystem on /dev/fedora/root to 4979712 (4k) blocks.
# The filesystem on /dev/fedora/root is now 4979712 (4k) blocks long.

# resize2fs done
# remount /dev/fedora/root /mnt/sysroot
# remount done
#   Reduced file system ext4 on fedora/root.
#   Size of logical volume fedora/root changed from <26.00 GiB (6655 extents) to <19.00 GiB (4863 extents).
#   Logical volume fedora/root successfully resized.

ファイルシステムとLVが26Gから19Gに縮小されたことを確認します。

df -h /mnt/sysroot/
# Filesystem               Size  Used Avail Use% Mounted on
# /dev/mapper/fedora-root   19G  4.1G   14G  23% /mnt/sysroot

lvdisplay
#   --- Logical volume ---
#   LV Path                /dev/fedora/root
#   LV Name                root
#   VG Name                fedora
#   LV UUID                5Ec62m-HIrH-xkp1-IkAq-xJxI-h32O-MDBZeT
#   LV Write Access        read/write
#   LV Creation host, time localhost.localdomain, 2026-01-04 09:17:05 -0500
#   LV Status              available
#   # open                 1
#   LV Size                <19.00 GiB
#   Current LE             4863
#   Segments               1
#   Allocation             inherit
#   Read ahead sectors     auto
#   - currently set to     16384
#   Block device           252:0

GPartedの操作を行うために、操作対象のパーティションに属するファイルシステムをアンマウントします。

umount /mnt/sysroot

↓続いて、GUIでGPartedを起動します。 右上のドロップダウンリストで操作対象のデバイスが選択されていることを確認します。 また、LVMの行に鍵マークがついていることを確認します。 LVMが有効状態 (activate) 状態だとGPartedによる操作を行うことができません。 次工程で無効化し、鍵マークを外したあとで操作を続行します。

gparted_01_first_glance

↓LVMの行を右クリックし、Deactivateを選択してLVMパーティションを編集可能な状態にします。 この操作は即時反映されます。

gparted_02_deactivate_lvm

↓LVMの行を右クリックし、Resize/Moveからパーティション/LVM/ファイルシステムの編集画面に移動します。

gparted_03_move_partition

↓今回はディスクアドレスの前半部分を7 GiB分空けたいので、Free space preceding (MiB)7168 (※) と入力します。 すると、New size (MiB)が自動的に更新されて7168だけ減算されます。 最後にResize/Moveボタンをクリックして編集画面を閉じます。
(※) 7 GiB = 7 * 1024 MiB = 7168 MiB

gparted_04_move_partition2

↓定型文の警告が表示されます。 構わずOKを押します。 バックアップを取っていれば大丈夫です。

gparted_05_warning

/bootの後ろに7 GiBの空きができたことを確認します。 この時点ではまだ「変更後の想定状態」を画面表示しているだけで、実際のパーティションは書き換わっていません。 チェックマークをクリックして変更を反映します。
(※) この後の/bootの拡張まで実施してから反映しても良いのですが、この時点でも想定外のエラーが出る可能性があります。エラーの発生箇所を特定しやすくするため、細かく刻んで反映しておきます。

gparted_06_apply_all_operations

↓処理結果が表示されます。 全行にチェックマークがついていることから、一連の反映処理が成功したことがわかります。 Closeで結果画面を閉じます。

gparted_07_results

↓忘れないうちにLVMを再度有効化しておきます。 LVMの行を右クリックし、Activateを選択します。

gparted_08_activate_lvm

/bootを7 GiB拡張する

引き続きGPartedで操作を続行します。

/bootを拡張します。 /bootの行を右クリックし、Resize/Moveからパーティション/LVM/ファイルシステムの編集画面に移動します。

gparted_09_resize_move_boot1

↓今回は7 GiB分拡張して合計8 GiBに変更したいので、New size (MiB)8192 (※) と入力します。 すると、Free space preceding (MiB)が自動的に更新されて7168だけ減算されます。 最後にResize/Moveボタンをクリックして編集画面を閉じます。
(※) 8 GiB = 8 * 1024 MiB = 8192 MiB

gparted_10_resize_move_boot2

↓最後にチェックマークをクリックして変更を反映します。

gparted_11_apply_all_operations

↓定型文の警告が表示されるのでApplyを選択します。

gparted_12_warning

↓処理の成功を確認して画面を閉じます。

gparted_13_results

事後確認

上記スクリーンショットを見る限り、GParted上は目的の状態になったことを確認できました。 最後に、コマンドベースで目的の状態になったことを細かく確認していきます。

dfコマンドで容量を確認するために、確認したいディスクをマウントします。
(※) 最後のLVM領域のマウントに失敗した場合は、GParted上のLVM領域のactivate忘れを疑ってください。activateを忘れた場合は、lvdisplayのコマンド上もLV StatusNOT availableと表示されます

sudo -i

lsblk /dev/vda --fs
# NAME   FSTYPE      FSVER    LABEL UUID                                   FSAVAIL FSUSE% MOUNTPOINTS
# vda                                                                                     
# ├─vda1                                                                                  
# ├─vda2 ext4        1.0            4150e5a1-0b75-42a4-b844-30cdaf5e871e                  
# └─vda3 LVM2_member LVM2 001       A9Qc6e-sVC0-FF1L-uSv2-kiAy-81Uv-eT9W5c    

lvdisplay
#   --- Logical volume ---
#   LV Path                /dev/fedora/root
#   LV Name                root
#   VG Name                fedora
#   LV UUID                5Ec62m-HIrH-xkp1-IkAq-xJxI-h32O-MDBZeT
#   LV Write Access        read/write
#   LV Creation host, time localhost.localdomain, 2026-01-04 09:17:05 -0500
#   LV Status              available
#   LV Size                <19.00 GiB
#   Current LE             4863
#   Segments               1
#   Allocation             inherit
#   Read ahead sectors     auto

mkdir -p /mnt/sysroot/boot
mount /dev/fedora/root /mnt/sysroot/ -o ro
mount /dev/vda2 /mnt/sysroot/boot -o ro

df -h /mnt/sysroot/boot /mnt/sysroot
# Filesystem               Size  Used Avail Use% Mounted on
# /dev/vda2                7.9G  311M  7.2G   5% /mnt/sysroot/boot
# /dev/mapper/fedora-root   19G  4.1G   14G  23% /mnt/sysroot

参考情報

(参考) Clonezillaのイメージファイルからファイルを取り出す

Clonezillaにより取得したイメージファイルからファイルを取り出す手順を解説します。 本手順は、以下の公式情報を参考にしました。
How can I restore those *-ptcl-img.* images into a file manually ?

まずは必要なプログラムをインストールします。

sudo dnf install partclone

続いてバックアップディスクを特定します。 今回は/dev/vdb1だったとします。

# バックアップ先のデバイスファイルを特定する
# 今回は/dev/vdb1だったとする
lsblk
# NAME            MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
# (snip)
# vda             253:0    0   20G  0 disk 
# ├─vda1          253:1    0    1M  0 part 
# ├─vda2          253:2    0    1G  0 part /boot
# └─vda3          253:3    0   19G  0 part 
#   └─fedora-root 252:0    0   19G  0 lvm  /
# vdb             253:16   0   20G  0 disk 
# └─vdb1          253:17   0 18.6G  0 part 

適当なパスにバックアップディスクをマウントします。 今回は/mntのサブディレクトリをマウントポイントとします。
man hier - #/mnt

さらにバックアップ情報を含むイメージファイル名を特定します。 今回はrootファイルシステムに対応するfedora-root.ext4-ptcl-img.zstがPartcloneイメージファイル名だったとします。

# 適当なパスにマウントする
sudo mkdir /mnt/backup
sudo mount /dev/vdb1 /mnt/backup -o ro

# イメージファイルを確認する
# 今回はfedora-root.ext4-ptcl-img.zstのファイルを取り出す (rootファイルシステム)
ls -1 /mnt/backup/2026-01-12-17-img/*.zst
# /mnt/2026-01-12-17-img/fedora-root.ext4-ptcl-img.zst
# /mnt/2026-01-12-17-img/vda1.dd-ptcl-img.zst
# /mnt/2026-01-12-17-img/vda2.ext4-ptcl-img.zst

圧縮されたPartcloneイメージファイルから、loopデバイスとしてマウント可能な形式のファイルを生成します。 生成するイメージファイルは、バックアップされたパーティションと同じサイズを持ちます。 展開先のファイルシステムに十分な空き領域があることをご確認ください。
(※) loopデバイスとは、ブロックデバイスのようにマウント可能な疑似デバイスファイルのことです (Wikipedia - Loop device)

cat /mnt/backup/2026-01-12-17-img/fedora-root.ext4-ptcl-img.zst | zstd -d -c | partclone.ext4 -r --restore_raw_file -s - -o fedora-root.ext4.img

loopデバイスをマウントします。 あとはマウント先の/mnt/oldrootから好きなようにファイルを取り出せます。

sudo mkdir /mnt/oldroot
sudo mount fedora-root.ext4.img /mnt/oldroot -o ro

ls /mnt/oldroot/
# afs  boot  etc   lib    lost+found  mnt  proc  run   srv  tmp  var
# bin  dev   home  lib64  media       opt  root  sbin  sys  usr

(参考) GParted上でLVM領域のMoveに失敗する

LVM領域の縮小は以下の流れで実行します。

  • lvreduce -r ...コマンドによりファイルシステムとLVを縮小する
  • GParted上でLVMをdeactivateする (コマンドでも可)
  • GParted上でLVMをmoveする (ディスクブロックの前半アドレスを削ることでパーティションを縮小する)
    • (内部処理) pvresize --setphysicalvolumesize ...コマンドによりPVを縮小する
    • (内部処理) ファイルシステムのデータ領域を前半アドレスから後半アドレスに移動する (GParted独自実装)
    • (内部処理) パーティション縮小

この中で曲者なのがpvresizeコマンドによるPVの縮小です。 うまく行かないときは、以下のエラーが発生します。

cannot resize to XXX extents as later ones are allocated

このエラーに対する解決策は、以下のブログに書いてあります。

私は上記ブログ情報とman pvresize - #NOTESman pvsman pvmoveの組み合わせで解決しましたが、情報源を補完する意味で以下のリンクも掲載しておきます。

以降は、私なりに「何が起こったか」、「どう解決したか」をご説明します。

事象発生時、私の環境は以下の状況でした。

pvdisplay
  # --- Physical volume ---
  # PV Name               /dev/nvme0n1p3
  # VG Name               fedora_pc
  # PV Size               <929.93 GiB / not usable 4.00 MiB
  # Allocatable           yes 
  # PE Size               4.00 MiB
  # Total PE              238060
  # Free PE               127724
  # Allocated PE          110336
  # PV UUID               8YLtdy-5itA-1PwJ-8BYJ-vjcI-Nf6C-iihOsr
   
pvs --segments -v
  # PV             VG        Fmt  Attr PSize   PFree   Start  SSize  LV   Start Type   PE Ranges                   
  # /dev/nvme0n1p3 fedora_pc lvm2 a--  929.92g 498.92g      0  97536 home     0 linear /dev/nvme0n1p3:0-97535      
  # /dev/nvme0n1p3 fedora_pc lvm2 a--  929.92g 498.92g  97536 127488          0 free                               
  # /dev/nvme0n1p3 fedora_pc lvm2 a--  929.92g 498.92g 225024  12800 root     0 linear /dev/nvme0n1p3:225024-237823
  # /dev/nvme0n1p3 fedora_pc lvm2 a--  929.92g 498.92g 237824    236          0 free                               

前半のpvdisplayの出力で、PE (Physical Extent) 1つあたり4.00 MiBのサイズであることがわかります。

後半のpvsの出力からは、PEのアドレスのフラグメント (データの断片化) っぷりがわかります。 pvsから読み取れる情報を下表にまとめました。

# 開始アドレス 終了アドレス セグメントサイズ LV
1 0 97535 97536 home
2 97536 225023 127488 free (未使用)
3 225024 237823 12800 root
4 237824 238060 236 free (未使用)

表の各列の法則性は以下のようになっています。

  • 開始アドレスStart列から読み取れる
  • 終了アドレスは次の行の開始アドレスから1を引いた値になる
  • セグメントサイズSSize列から読み取れる
  • 開始アドレス + セグメントサイズ - 1 = 終了アドレス
  • PE Rangesの表記はデバイス名:開始アドレス-終了アドレスとなっている

上表から読み取れるように、homerootのPE番号は連続していません。 LV (Logical Volume) の作成・拡張時には基本的には連続するようにPEがアサインされます。 このことはType列がlinearであることと、man lvextendの--typeの説明、そして/etc/lvm/lvm.confuse_linear_target = 1という設定値などから読み取れます。

ではなぜhomerootの間にfreeが入り込んでいるかというと、過去に私がlvextendによるLV拡張やlvreduceによるLV縮小を何度か実施した結果として "隙間" が生まれたことが原因です。

この隙間が (間接的に) 悪さをして、今回のエラーに繋がっています。

以下がエラーになったときのコマンドです。

pvresize -v --yes --setphysicalvolumesize 967757824K '/dev/nvme0n1p3'

サイズ変更後のPVは967757824Kとあります。 967757824 / 1024 / 1024 = 922.92578125です。

GPartedを操作する前のPVサイズは、pvdisplayより929.93 GiB (以下) と読み取れます。 pvresizeコマンドにより、約7 GiBを縮小しようとしていることがわかります。 この"7 GiB"という値は、"私がGPartedによってLVMパーティションの前半アドレスから削ろうとしたサイズ"と一致します。

つまり、GPartedによって前半7 GiBを削るためには、先にPVの後半アドレスを7 GiB削る必要があります。 その後に"ファイルシステムを後ろのアドレスに移動"することで"前半7 GiBを削った"ことになります。
(※) ここで間違えないでいただきたいのは、末尾に7 GiBの空きを作ることが要件です。冒頭に7 GiBの空きを作ってもGPartedの移動処理は失敗します。 (実機検証済)

上の表を改めてみてみましょう。 一番最後のfreeとなっている領域のサイズはわずか236 PEsです。 1 PEあたり4 MiBなので、236 PEs = 944 MiBと、1 GiBにも満たないサイズです。 7 GiB縮小しようとしても当然失敗します。

当時のPVサイズの内訳は以下のようになっています。

用途 容量
/home 381 GiB
/ 50 GiB
空き領域 498.9 GiB

うまいことPEのアドレスをスライドしてあげれば、末尾の7 GiBは簡単に工面できます。 ここで使うのがpvmoveコマンドになります。

pvmove --alloc anywhere 移動元 移動先というコマンドにより、PEの場所を移動することができます。 移動先はfreeと書いてある部分のみが指定可能です。

移動元や移動先の表現の仕方はいくつかあります。 詳細はman pvmoveの"VARIABLES -> PV"や"EXAMPLES"のあたりに書いてあります。 具体例を示すと以下の通りです。

  • /dev/nvme1p3:225024-237823 → アドレス225024から237823の範囲
    • 移動元の指定に便利な表現方法
    • 移動元のPE Rangesからコピペするのが実践的
  • /dev/nvme0n1p3:97536+12800 → アドレス97536から110335の範囲 (97536を開始点に、12800の幅を持つ範囲。110336 = 97536 + 12800 - 1)
    • 移動先の指定に便利な表現方法
    • 開始点の97536free行のStart列の値をコピペする
    • 幅を表現する+12800は、移動元のSSize列からコピペする

図に表すと以下のとおりです。
(※) 元ブログのほうが見やすい...ですが、移動元の表現方法が若干異なります

pvs

以上を踏まえてpvmoveを実行した結果がこちらになります。 rootが移動してフラグメントがなくなったことがわかると思います。

pvs --segments -v
  # PV             VG        Fmt  Attr PSize   PFree   Start  SSize  LV   Start Type   PE Ranges                   
  # /dev/nvme0n1p3 fedora_pc lvm2 a--  929.92g 498.92g      0  97536 home     0 linear /dev/nvme0n1p3:0-97535      
  # /dev/nvme0n1p3 fedora_pc lvm2 a--  929.92g 498.92g  97536 127488          0 free                               
  # /dev/nvme0n1p3 fedora_pc lvm2 a--  929.92g 498.92g 225024  12800 root     0 linear /dev/nvme0n1p3:225024-237823
  # /dev/nvme0n1p3 fedora_pc lvm2 a--  929.92g 498.92g 237824    236          0 free                               

pvmove --alloc anywhere /dev/nvme0n1p3:225024-237823 /dev/nvme0n1p3:97536+12800
  # /dev/nvme0n1p3: Moved: 0.10%
  # (snip)
  # /dev/nvme0n1p3: Moved: 100.00%

pvs --segments -v
  # PV             VG        Fmt  Attr PSize   PFree   Start  SSize  LV   Start Type   PE Ranges                  
  # /dev/nvme0n1p3 fedora_pc lvm2 a--  929.92g 498.92g      0  97536 home     0 linear /dev/nvme0n1p3:0-97535     
  # /dev/nvme0n1p3 fedora_pc lvm2 a--  929.92g 498.92g  97536  12800 root     0 linear /dev/nvme0n1p3:97536-110335
  # /dev/nvme0n1p3 fedora_pc lvm2 a--  929.92g 498.92g 110336 127724          0 free      

...ちなみに、このあとhomerootの順番を入れ替えました。 実際の用途ではhomeを容量いっぱいに使い切りたいのでhomelvextend -r ...により拡張することになります。 ここで拡張すると、home > root > homeというデータの並びになってしまいます。 LVMの柔軟性によりこの並びでも全く問題なく動作しますが、なんとなくもやもやしたので私の環境では並べ替えちゃいました。

まとめ

Fedoraバージョンアップに伴う/bootサイズの推奨値変更に伴い、Clonezilla, GParted, そしてLVMのデフラグという盛りだくさんの学びを得られました。

Changes/2GbootPartitionによると、2016年に/bootサイズの推奨値が500 MBから1 GBに増加、そして今回は2025年に1 GBから2 GBに増加、という経緯を辿ってきたそうです。 5年後は3 GBや4 GBになっているかもしれません。 そう考えると、今回大変な手間がかかりましたが/bootを大きめサイズに拡張できて安心を買えたかなと思います。

この記事が似たような悩みを抱えるLinuxユーザの皆様の安心材料に繋がれば幸いです。

FallibleでRegister Projectionsを試してみた

本記事はAnsible Advent Calendar 2025 7日目の記事です。

お伝えしたいこと

Ansibleに今後リリースされるかもしれない、されないかもしれないRegister Projections機能をご紹介します。

(背景) 記事を書こうと思ったきっかけ

Ansible Forumのメーリングリストで面白そうな記事の情報が届きました。

Fallible 2025.12.5 released with Register Projections and new Action API previews

Fallibleってなんでしょうか? 何やらAnsibleと関係ありそうな名前です。

調べてみると、PyPIにFallibleの説明が簡潔に書いてありました。

fallible, an experimental ansible

PyPI - fallible 2025.12.5

Ansibleの実験的な機能リリースが提供されるパッケージのようです。 冒頭の記事は、そのFallibleで「Register Projections」と「new Action API」がプレビュー版として提供されたというお知らせでした。

今回の記事は、その「Register ProjectionをFallibleで試してみた」という主旨になります。

(本題) 試してみる

さっそく試してみましょう。 まずはFallibleをインストールします。

python3 -m venv venv
source venv/bin/activate
pip install fallible

続いてPlaybookを書きます。 Register Projections機能を使うには、「タスクの実行結果を格納する」という意味を持つregisterキーワードを特別な方法で指定します。

- name: Test
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Execute `uptime`
      ansible.builtin.command:
        cmd: uptime
      register:
        _uptime: _task.result.stdout
      changed_when: true

    - name: Debug
      ansible.builtin.debug:
        msg: '{{ _uptime }}'

あれ、なんだか変ですね。 普段registerキーワードで変数に実行結果を保存するときは、register: 変数名のように書きます。 上記のPlaybookはvarsキーワードのように変数を代入しているように見えます。 Playbookを実行して、何が起こるのか確認してみましょう。

実行コマンドはfallible-playbookです。 ansible-playbookじゃないのでお気をつけください。

fallible-playbook playbook.yml

# (snip)

# TASK [Execute `uptime`] ******
# changed: [localhost]

# TASK [Debug uptime] ******
# ok: [localhost] => 
#     msg: ' 22:26:18 up  3:25,  2 users,  load average: 1.56, 1.99, 1.90'

なんと、_uptime変数にコマンド実行結果が格納されました。 Register Projectionsとは、以下のような機能になります。

  • registerキーワードにて変数を (複数) 作れる
  • registerキーワード内で変数代入する際、_task.resultというマジック変数から今のタスク実行結果にアクセスできる

今回の場合、1つ目のタスクのcommandモジュールの実行結果が_task.resultという変数に格納されています。 registerキーワード内ではこの_task.result変数を利用して_task.result.stdoutと指定することで、「uptimeコマンド実行後の標準出力」を取り出しています。 このように、Register Propergationsを利用した書き方により、タスクの実行結果をその場で整形できます。 私は、この書き方を採用することでレジスタ変数周りの可読性を大幅に向上できると期待しています。

Register Projectionsを使わない従来の書き方だと、整形の記述を次タスク以降で行う必要がありました。 具体的には以下のようになります。
(敢えていくつかの方法で書いてみます)

- name: Test2
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Execute `uptime`
      ansible.builtin.command:
        cmd: uptime
      register: _uptime_result
      changed_when: true

    # パターン1. シンプル
    - name: Debug1
      ansible.builtin.debug:
        msg: '{{ _uptime_result.stdout }}'

    # パターン2. 可読性を上げるためにタスク変数で名前をつけるケースもあるでしょう
    - name: Debug2
      ansible.builtin.debug:
        msg: '{{ _uptime }}'
      vars:
        _uptime: '{{ _uptime_result.stdout }}'

    # パターン3. _uptimeをこの後何度も使う場合は、一度set_factに格納するケースもあるでしょう
    - name: Set fact _uptime
      ansible.builtin.set_fact:
        _uptime: '{{ _uptime_result.stdout }}'

    - name: Debug3
      ansible.builtin.debug:
        msg: '{{ _uptime }}'

Register Projectionsはパターン3の挙動に最も近いです。 整形されたタスク実行結果がAnsible実行終了までアクセス可能 (グローバル変数) であるためです。 パターン3ではset_factモジュールを使う関係で2タスクに分かれるところが、Register Projectionsを使えば1タスクで済みます。

とても便利なので、ぜひ正式実装されてほしいなと思います。

(蛇足) _taskの中身を表示することはできない

Register Projectionsにおいては、_task.resultがいわゆる「従来のレジスタ変数の中身」でした。 _taskにはどんなデータが入っているのでしょうか?

...残念ながら、Ansible実行により_task変数の中身を表示することはできませんでした。 以下のPlaybookを実行すると、debugモジュールの実行時に「_uptime_task変数が未定義」だと怒られます。 1つ目のタスクで_uptime_task: _task_uptime_task: _task.resultにすれば動くようになります。

- name: Test
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Execute `uptime`
      ansible.builtin.command:
        cmd: uptime
      register:
        _uptime_task: _task
      changed_when: true

    - name: Debug _uptime_task
      ansible.builtin.debug:
        msg: '{{ _uptime_task }}'

設計がしっかりしてますね。

参考情報

リンク 説明
Fallible 2025.12.5 released with Register Projections and new Action API previews Ansible Forumの元記事
Register projections and action plugin dynamic host/group/var API #86241 機能追加のPull Request。使い方を詳しく書いてある

Fedora42の変更点

Fedoraの変更点シリーズ

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

Fedoraの変更点シリーズ

お伝えしたいこと

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

公式情報の見方

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

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

fedora_change_sets

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

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

Release Notes & Changes

binとsbinの統合

Fedora 41以前の構成では、RPMでインストールされた実行ファイルを格納するディレクトリ周りの構成が以下のようになっていました。

Fedora 42以降は、/usr/sbin/usr/local/sbinシンボリックリンクになります。

つまり、Fedora 42以降は/usr/sbin, /usr/bin, /bin, /sbinという4つのファイルパスが全て同じ場所を指すようになりました。 実質的に、RPMでインストールされた実行ファイルの格納先は/usr/binに一本化されたことになります。

同じ変更が/usr/localについても行われました。 Fedora41までは以下の構成でした。

Fedora42以降は以下のように変更され、実質的に/usr/local/bin/usr/local/sbinが一本化されました。 rpm以外のシステムインストール (make install等) についてもbinsbinを迷う必要はありません。

細かい話ですが環境変数PATHも微妙に更新されていて、一般ユーザのPATHの末尾から/usr/sbinが削除されました。 ただ、Fedora42時点ではまだ随所にsbinが見える状態です。 今後も段階的に移行されていく...のかもしれませんね。

# Fedora 41以前

## Fedora 41 一般ユーザ
/home/endy/.local/bin:/home/endy/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin

# Fedora 42 一般ユーザ
/home/endy/.local/bin:/home/endy/bin:/usr/local/bin:/usr/bin:/usr/local/sbin

# Fedora 41/42 (su -)
/root/.local/bin:/root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin

# Fedora 41/42 root (sudo -i) -> cf. `grep secure_path /etc/sudoers`
/root/.local/bin:/root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/var/lib/snapd/snap/bin

今回の変更により以下のメリットが生まれました。

  • パッケージマネージャーは、特定の実行ファイルを/usr/binに格納するか/usr/sbinに格納するかを迷わなくてよい
  • ディストリビューションごとに同じ実行ファイルが/binにあったり/sbinにあったりすることで互換性が損なわれる問題を解決した

Bugs

f42タグ付きのCommon Issuesを確認しましたが、特に気になるものはありませんでした。

HAProxyによるL4負荷分散実装

お伝えしたいこと

Linuxにインストール可能なオープンソースソフトウェアのロードバランサーであるHAProxyについて、以下のトピックをご紹介します。

  • 概要
  • インストール手順
  • L4ロードバランスの実装手順

HAProxyとは

HAProxyにはざっくりOSS版と商用版があります。 本記事では、単にHAProxyと記載した場合はOSS版を指すものとします。

HAProxyはTCP, HTTP, HTTPSをサポートするロードバランサーです。 つまりHAProxyは対象の通信を一度受信し、配下のサーバ群に対して負荷が分散するよう通信を転送するネットワーク機器です。

HAProxyがサポートする機能を一部のみ抜粋します。 BIG-IP用語でいうとtcp/http/client-ssl/server-ssl profileとsnatに近い機能を持ちます。
(HAProxy 3.1 Starter Guide - #What HAProxy is and isn't)

  • TCP (L4) ロードバランシング (UDPのロードバランシング機能はない)
  • HTTP/HTTPS (L7) ロードバランシング
  • SSL/TLS オフロード/イニシエイト (HTTPS通信を受信・復号してHTTPで転送したり、再暗号化してHTTPSで転送したりできる)
  • ヘッダ情報に応じた通信制御 (例: URLによる負荷分散先の切り替え)
  • 透過型フォワードプロキシ
  • 負荷分散時の送信元NAT (デフォルト有効。無効化することも可能)

一方で、サポートしない機能は以下の通りです。

  • UDPロードバランシング (代替選択肢: Keepalivedなど、Linux Virtual Serverベースのロードバランサー)
  • 明示型フォワードプロキシ (代替選択肢: squid)
  • DSR (Direct Server Return)

ここに記載した情報は全量ではないので、詳細が気になる方は上記リンク先の公式情報を参照してください。

(参考) HAProxyの有償版について

HAProxy Technologies, LLCは、OSS版のHAProxyをベースとした製品をいくつか販売しています。

製品の1つであるHAProxy EnterpriseはHAProxyの有償版とも言うべきソフトウェアロードバランサーで、OSS版よりも多くの機能をサポートしています。 追加機能にはUDPロードバランシングも含まれます。

詳細はFeature comparison tableの比較表をご覧ください。

(参考) えんでぃ目線のHAProxyの使い方

あくまで私の場合ですが、「KubernetesでControl-plane Nodesを冗長化したい」など、非クラウドのローカル検証環境にて簡易的なロードバランサーが欲しくなったときにHAProxyを使いました。 このユースケースであれば、本記事で紹介するHAProxyのL4負荷分散機能で十分にカバーできます。

HAProxyの構成

簡易構成図と用語説明

今回はHTTPクライアント 1台、HAProxyロードバランサー (LB) 1台、HTTPサーバ 2台の構成でTCPロードバランシングを実装してみます。

haproxy_topology

構成上のポイントは2つあります。 2のこだわりポイントを実現するために、今回はHAProxyに加えてKeepalivedという別ソフトも併せてインストールします。

  1. 全てのホストを同一ネットワークに構築した (設計・実装の難易度を下げるため)
  2. LB VIP (front_endの待ち受けIP) をfloating IPで構成した。つまりLinuxレイヤーでインターフェースに設定していないIPをLB VIPとして使える (こだわりポイント)

Keepalivedとの組み合わせ

多くのLinuxにおいて、デフォルトではOSレイヤーに設定済みのIPアドレスしかVIPとして使えません。

例えば、192.168.0.10/24というIPアドレスを持つLinuxにHAProxyをインストールしたとします。 このLinuxに192.168.0.11:80というTCPソケットをbind (利用登録) し、LB VIPとして使うことはデフォルトではできないことが多いです。 一方で192.168.0.10:80のように、OSレイヤーで持っているIPアドレスであればLB VIPとして使うことが可能です。

LBの設計によっては、NICに設定していないIPアドレスもLB VIPとして設定し、floating VIPを構成したいケースもあると思います。 そんなときに役立つのが以下2つの設定です。
(※) 私はまだ検証していませんが、VRRPでLBを冗長化することも可能です

  1. sysctlによりnet.ipv4.ip_nonlocal_bind=1というkernel parameterを設定することで、NICに設定していないIPを含むソケットのbindを許可する
  2. KeepalivedによりVRRPを構成することで、VRRP VIPを生成する。1の設定と合わせることで、このVRRP VIPをHAProxy側でLB VIPとして設定できる

なお、上記2つの設定なしでもNIC上に複数のIPアドレスを設定することでもHAProxyに複数IPのLB VIPを設定することが可能です。 LB冗長化に対応しない簡易的な設定方法ですが、既存のネットワークの制約でVRRPを使いたくないときなどに役立つかもしれません。

(参考) Keepalivedの概要

Keepalived User Guide

Keepalivedは大きく分けて2つの機能を持ちます。

  1. VRRPによるゲートウェイ冗長化
  2. L4ロードバランシング

1と2の機能はそれぞれ独立して実装できます。 例えば1に関連する情報だけを設定ファイルに記述した場合はVRRPのみ動作し、L4ロードバランシングは動作しません。

今回のケースでは、KeepalivedのVRRP機能のみが必要です。 したがって、設定ファイルにもVRRPに関連する設定のみ記述します。

なお2のL4ロードバランシングについては、全体的にHAProxyと比較すると簡易的な機能です。 ヘルスチェックと負荷分散は可能なので、簡易的な負荷分散のみを実装するのであればKeepalivedだけで完結できます。 特に (無償版の) HAProxyはUDPの負荷分散に対応していないので、UDPの負荷分散がどうしても必要な場合はKeepalivedの利用を検討すると良さそうです。

HAProxyのセットアップ手順サンプル

では、実際のセットアップ手順のサンプルを示したいと思います。 今回実装する構成を再掲します。

haproxy_topology

全てのホストがCentOS Stream 9である前提で手順を紹介します。

Linuxの基本設定

本手順はLBとWEBサーバに対して実行します (lb1, web1, web2)。以下の流れで作業します。

  1. ホスト名の設定
  2. IPアドレスの設定
  3. firewalldのTCP 80 (http) 通信許可設定

私の環境は以下の構成になっています。 環境の前提が異なる方は必要に応じて読み替えてください。

  • ens3インターフェースを使う
  • 192.168.0.1がデフォルトゲートウェイかつDNSサーバである
  • デフォルトゲートウェイからインターネットに出られる
  • シリアルコンソール接続して操作している (nmcli deleteでネットワーク疎通が切れる手順になっています。困る方はアレンジしてください)

以下はLBの手順です。 2台のWEBサーバについても、ホスト名とIPアドレスを読み替えつつ同様に設定します。

sudo hostnamectl set-hostname lb1.test

sudo nmcli connection delete ens3
sudo nmcli connection add autoconnect yes type ethernet ifname ens3 con-name ens3 ipv4.method manual ipv4.addresses 192.168.0.10/24 ipv4.gateway 192.168.0.1 ipv4.dns 192.168.0.1
sudo nmcli connection up ens3

sudo firewall-cmd --add-port 80/tcp --permanent
sudo firewall-cmd --reload

以降の手順は各LinuxサーバにSSHログインして実行できます。

Keepalivedのセットアップ

本手順はLBに対してのみ実行します (lb)。 以下の流れで作業します。

  1. Keepalivedのインストール
  2. Keepalived設定ファイルの更新 (VRRP VIP 192.168.0.100の作成)
  3. Keepalivedサービスの起動
# 1
# sudo firewall-cmd --add-protocol vrrp --permanent
# sudo firewall-cmd --reload

# 2
sudo dnf -y install keepalived

# 3-1
sudo mv -i /etc/keepalived/keepalived.conf{,.orig}
sudo mkdir /etc/keepalived/conf.d

cat << EOF | sudo tee /etc/keepalived/keepalived.conf > /dev/null
vrrp_track_process haproxy {
    process haproxy
    quorum 1
    weight 0
}

include /etc/keepalived/conf.d/*.conf
EOF

# 3-2
cat << EOF | sudo tee /etc/keepalived/conf.d/10_vrrp_web.conf > /dev/null
vrrp_instance web {
    virtual_router_id 2
    state MASTER
    interface ens3
    virtual_ipaddress {
        192.168.0.100
    }
    priority 255
    track_process {
        haproxy
    }
}
EOF

# keepalived -t ; echo $?

# 4
sudo systemctl enable keepalived.service --now

# 5
journalctl -eu keepalived.service -g state

コメント行に書かれた番号は、本セクションの冒頭に記載した「手順の流れ」の項番と対応しています。

以下、参考情報としていくつかの操作内容について補足します。

# 1

  • firewalldでVRRPの通信許可
  • 2台以上のHAProxyでVRRP冗長構成を組むときに必要
  • 今回の1台構成では不要のためコメントアウトした

# 3-1

  • デフォルトの/etc/keepalived/keepalived.confには不要な設定が多数あるので、一旦退避した
  • VRRP VIPが増えた場合も設定ファイルの見通しを良くするため、用途に応じてファイルを分割する構成とした (-> include)
  • Keepalived全体に影響を与えるようなグローバル設定のみ本ファイルに直接記述した

グローバル設定としては、以下の内容を実装した。

  • 「Keepalivedは同じLinux上で動作するhaproxyプロセスを監視し、haproxyが起動していなかった場合にVRRPステータスをFAULTに遷移する」設定を追加した
  • この設定がなくてもVRRPとしては最低限動作する

vrrp_track_processブロック配下の設定の意味は、それぞれ以下のとおりです。

オプション 意味
process <str> 指定した名前のプロセスが起動しているか否かを監視する
quorum <int> 指定した数のプロセス数が起動していたら監視成功
そうでなければ失敗
weight <int> 整数値を指定する。
正の数を指定すると、監視成功時にpriorityが加算される。
負の数を指定すると監視失敗時にpriorityを減算する。
0を指定すると監視失敗時にVRRPインスタンスが失敗する。0 reverseを指定すると監視成功時にVRRPインスタンスが失敗する。
デフォルトは1

# 3-2

  • VRRPインスタンス設定を投入する
  • VRRP ID、VRRP VIP、Priorityなど、一般的なVRRP設定を一通り指定する
  • # 2-1で設定したtrack設定を紐付けることで、trackの成否をVRRP状態と連動させる

vrrp_instanceブロック配下の設定の意味は、それぞれ以下のとおりです。

オプション 意味
virtual_router_id <int> VRRP ID (1〜255) の指定。
VRRP冗長化する2インスタンス間では値を揃える。
詳細はVRRPの標準仕様を調べること
state <str> VRRPのステータス初期値。
MASTER: priority255の場合に即時MASTERに昇格する。
BACKUP: BACKUPで起動し、3秒間のネゴシエーションの末MASTERに昇格。
今回はシングル構成なのでVIP起動を早めるためstate MASTERかつpriority 255とした。
冗長化する場合は起動時のIP重複を避けるためpriority 150など即昇格を避けるべきかも
interface <str> VRRPを動作させるインターフェース名を指定する
virtual_ipaddress {...} VRRP VIP (Virtual IP) を指定する。
CIDR形式で記述できるが、Prefix長を省略してホストIPとするのが一般的 (参考)
priority <int> VRRP Priority。
値が大きいほど優先的にMASTERに昇格しやすくなる。
nopreempt設定と密に関係する
track_process {...} # 2-2で設定したvrrp_track_process設定名を指定。
指定したtrack設定を有効化する。
有効化により、trackの成否がprioritystateが連動するようになる
nopreempt preemptを無効化する設定。
デフォルトはpreempt有効。
preemptが有効の場合、既存のMASTERよりも自身のpriorityが大きいときにMASTERを交代する。
preemptが無効の場合は既存のMASTERを維持する。
preemptを言い換えると自動フェイルバック設定

設定値の詳細はman keepalived.confを参照してください。

最後にコメントアウトされた以下のコマンドは、確認用です。 keepalived -tはconfig testを意味しており、keepalivedを起動せず設定ファイルの確認のみ行います。 Exit statusが0であれば正常で、それ以外の値であれば設定ファイルにエラーが存在します。 echo $?は、直前のkeepalived -tのExit statusを表示するコマンドです。

keepalived -t ; echo $?

HAProxyのセットアップ

本手順はLBに対してのみ実行します (lb1)。 以下の流れで作業します。

  1. HAProxyのインストール
  2. net.ipv4.ip_nonlocal_bind = 1のsysctl設定を追加し、VRRP VIPをLB VIPとして使えるようにする
  3. (必要に応じて) SELinuxの許可設定を有効化する
  4. HAProxyの設定ファイル更新
  5. HAProxyのサービス起動

LB VIPが待ち受けているTCP 80ポートは、すでに#Linuxの基本設定で通信許可されています。

# 1
sudo dnf -y install haproxy

# 2
cat << EOF | sudo tee /etc/sysctl.d/50_haproxy.conf
net.ipv4.ip_nonlocal_bind = 1
EOF

sudo sysctl --system

# sysctl net.ipv4.ip_nonlocal_bind

# 3
# sudo dnf -y install setools-console
# sesearch -A -ds -s haproxy_t -p name_bind
# seinfo --portcon | grep -P ':(commplex_main_port_t|http_cache_port_t|http_port_t):' | awk '{print $2, $3}'

# getsebool haproxy_connect_any
# seinfo -x -a port_type
# for PORT_T in $(seinfo -x -a port_type | grep -Po '\S+_t$') ; do seinfo --portcon | grep -P ":${PORT_T}:" | awk '{print $2, $3}'; done

sudo setsebool -P haproxy_connect_any=True

# getsebool haproxy_connect_any

# 4
sudo cp -pi /etc/haproxy/haproxy.cfg{,.orig}

cat << EOF | sudo tee /etc/haproxy/haproxy.cfg > /dev/null
global
    log         127.0.0.1 local2

    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon

    # turn on stats unix socket
    stats socket /var/lib/haproxy/stats
    stats timeout 2m

    # utilize system-wide crypto-policies
    ssl-default-bind-ciphers PROFILE=SYSTEM
    ssl-default-server-ciphers PROFILE=SYSTEM

defaults tcp
    mode    tcp
    log     global
    maxconn                 3000
    option  dontlognull
    retries 3
    timeout check           10s
    timeout client          1m
    timeout connect         10s
    timeout queue           1m
    timeout server          1m

defaults http from tcp
    mode    http
    option  httplog
    option  http-server-close
    option  forwardfor except 127.0.0.0/8
    option  redispatch
    timeout http-request    10s
    timeout http-keep-alive 10s
EOF

cat << EOF | sudo tee /etc/haproxy/conf.d/web.cfg > /dev/null
frontend web_http from tcp
  description Web Servers (HTTP)
  bind 192.168.0.100:80
  default_backend web_http

backend web_http from tcp
  description Web Servers (HTTP)
  server web1 192.168.0.11:80 check
  server web2 192.168.0.12:80 check
EOF

# haproxy -c -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/conf.d

# 5
systemctl enable haproxy.service --now

以下、詳細を補足します。

# 3

この手順では、SELinuxの許可設定を追加しています。 今回の構成では不要なのですが、LB VIPに割り当てるポート番号によっては必要となります。

また、そもそもSELinuxをpermissiveモードに変更するなどしてSELinuxの防御を無効化している場合には、この設定は不要です。
(参考) SELinuxのステータス

前半のコメントアウト部分は、全て現状のSELinuxルールを確認するための確認コマンドです。

最初は以下の操作を実施しています。 最後のseinfoコマンドで出力されたポート番号は、HAProxyがデフォルトでLB VIPとして設定可能なポート番号です。 今回はtcp 80のLB VIPを作れれば良いので、デフォルト構成のままでも動作します。

  • SELinuxの確認コマンドを実行するため、必要なrpmパッケージをインストールする (setools-console)
  • haproxyプロセスがbind可能なポート番号を調査する (sesearch, seinfo)
sudo dnf -y install setools-console

sesearch -A -ds -s haproxy_t -p name_bind
# allow haproxy_t commplex_main_port_t:tcp_socket { name_bind name_connect };
# allow haproxy_t http_cache_port_t:tcp_socket { name_bind name_connect };
# allow haproxy_t http_port_t:tcp_socket { name_bind name_connect };
# allow haproxy_t port_type:tcp_socket name_bind; [ haproxy_connect_any ]:True

getsebool haproxy_connect_any
# haproxy_connect_any --> off

seinfo --portcon | grep -P ':(commplex_main_port_t|http_cache_port_t|http_port_t):' | awk '{print $2, $3}'
# tcp 10001-10010
# tcp 443
# tcp 488
# tcp 5000
# tcp 80
# tcp 8008
# tcp 8009
# tcp 8080
# tcp 81
# tcp 8118
# tcp 8123
# tcp 8443
# tcp 9000
# udp 3130
# udp 5000

上記以外のポート番号をbindする要件がある場合は、以下のコマンドを実行してください。 追加で許可できるポート番号がfor文で表示されます。

seinfo -x -a port_type
# 
# Type Attributes: 1
#    attribute port_type;
#   afs3_callback_port_t
#   afs_bos_port_t
# 以下略

for PORT_T in $(seinfo -x -a port_type | grep -Po '\S+_t$') ; do seinfo --portcon | grep -P ":${PORT_T}:" | awk '{print $2, $3}'; done
# tcp 7001
# udp 7001
# udp 7007
# 以下略

sudo setsebool -P haproxy_connect_any=True

getsebool haproxy_connect_any
# haproxy_connect_any --> on

一連のSELinux操作についてもっと詳しく理解したい方は、以下の記事をご覧ください。
SELinuxの実践

今回扱った操作に関連する部分だけ拾い読みするなら、Boolean有無の確認(参考) SELinuxの確認コマンドをご確認ください。

# 4

本手順では、まずオリジナルの/etc/haproxy/haproxy.cfgをベースにして以下のように書き換えています。

まずはglobalセクションです。 globalセクションにはHAProxy全体の基本設定が入っています。 globalセクションの設定はほぼ書き換えていません。

ただ唯一、stats timeout 2mという設定のみ書き換えました。 HAProxyの対話プロンプトを開いてステータス確認などを行う際のセッションタイムアウトをデフォルトの5秒から2分に延長するための便利設定です。 詳しくは#(参考) HAProxyのステータス確認で扱います。

globalセクション配下の各設定について、詳細はHAProxy 3.1 Configuration Manual - Global parametersを参照してください。

続いてdefaultsセクションです。 defaultsセクションには、後続のfrontendセクションやbackendセクションのデフォルト値を指定します。 frontend xxx from yyybackend xxx from yyyのようにfrom yyyの形式でdefaultsセクション名を指定することで、yyyセクションの設定をデフォルト値として参照するようになります。

HAProxyの負荷分散設定はざっくりL4ロードバランシング (mode tcp) とL7ロードバランシング (mode http) の2種類が存在することを想定し、今回もtcphttpという2種類のdefaultsセクションを定義しました。 インストール時に初期設定されていたdefaults設定はmode httpを想定した設定でしたが、今回はその設定をtcphttpの2つに分割しました。 そしてdefaults tcpセクションにはTCPの設定のみを指定し、defaults http from tcpセクションにはHTTPの設定のみを指定しました。 defaults http from tcpセクションはdefaults tcpセクションの設定を踏襲するので、結果としてオリジナルのdefaultsセクションと全く同じ設定値になります。 (BIG-IP用語でいうと、デフォルトのtcp profileとhttp profileの作成に相当します)

本件のハンズオンではdefaults tcpセクションしか使わないものの、将来的にはdefaults http from tcpセクションが役立つときが来るかもしれません。 TLSオフロード/TLSイニシエイトを実装したくなった場合は、新たにdefaults https from httpセクションを作成して設定を拡張できる想定です。

続いて/etc/haproxy/conf.d/web.cfgという設定ファイルですが、ここには今回のハンズオンで使用する負荷分散設定を実装しました。

frontendセクションにはLB VIPの設定を指定します。 (BIG-IP用語ではVirtual Serverに相当します)

frontendセクションで使ったオプションは以下の通りです。 HAProxy 3.1 Configuration Manual - Proxy keywords matrix

オプション 意味
description 説明文。
任意の文字列を指定可能
bind LB VIPの設定 (待ち受けIPアドレスとポート番号)
default_backend frontendと紐付けるbackend名を指定。
結果として負荷分散先のサーバが決まる

backendセクションには負荷分散先のサーバへの転送設定を指定します。 (BIG-IP用語ではpoolに相当します)

backendセクションで使ったオプションは以下の通りです。
HAProxy 3.1 Configuration Manual - Proxy keywords matrix
HAProxy 3.1 Configuration Manual - Server and default-server options

オプション 意味
description 説明文。
任意の文字列を指定可能
server 負荷分散先サーバの転送先IPアドレスとポート番号。
checkを指定するとヘルスチェックが有効になる (デフォルト無効)。
デフォルトではtcptlsによるヘルスチェックとなる
source backendに転送する際の送信元IP指定。
デフォルトではLBのIPアドレスに送信元NATされる。
デフォルトの送信元ポート番号はEphemeral Portとなる。
ヘルスチェックもデフォルトで同様の挙動。
送信元IP/ポート番号変換を無効化することも可能

Webサーバのセットアップ

本手順はWEBサーバ (web1, web2) に対してのみ実行します。 以下の流れで作業します。

  1. Apache httpdのインストール
  2. httpdサービスの起動
  3. HTMLファイルの配置

http通信のためのTCP 80ポートは、すでに#Linuxの基本設定で通信許可されています。

# 1
sudo dnf -y install httpd

# 2
sudo systemctl enable httpd.service --now

# 3
echo "${HOSTNAME}" | sudo tee /var/www/html/index.html > /dev/null

# curl localhost

最後のcurl localhostを実行すると、index.htmlに記述されたホスト名が返ってくる想定です。

疎通確認

では、最後にクライアントPCから疎通確認してみましょう。 今回はcurlを使っていますが、環境によってはブラウザアクセスでご確認いただいても結構です。

複数回アクセスすると、負荷分散によりweb1web2に対して交互に転送されていることがわかります。

curl 192.168.0.100
# web1.test

curl 192.168.0.100
# web2.test

(参考) Keepalivedのステータス確認

以下のようにstateキーワードでログを検索することで、VRRPのステータス遷移を確認できます。

journalctl -eu keepalived.service -g state

# Apr 03 00:24:16 lb1.test Keepalived_vrrp[1546]: (web) Entering FAULT STATE
# Apr 03 00:24:16 lb1.test Keepalived_vrrp[1546]: (web) Entering BACKUP STATE
# Apr 03 00:24:19 lb1.test Keepalived_vrrp[1546]: (web) Entering MASTER STATE
# Apr 03 00:24:36 lb1.test Keepalived_vrrp[1567]: (web) Entering MASTER STATE

ちなみに4行のログは、それぞれ以下のコマンドにより発生しました。

  • sudo systemctl restart haproxy.serviceを実行したことで、Keepalivedがhaproxyプロセスの障害を検知してFAULT > BACKUP > MASTERと遷移 (FAULTに遷移した場合は、state MASTERかつpriority 255に設定していても一度BACKUPになるようです)
  • sudo systemctl restart keepalived.serviceを実行したことで、MASTER > (停止) > MASTERと遷移。サービス起動直後にMASTERに遷移するのはstate MASTERかつpriority 255に設定したため

sedコマンドでより簡潔な形に整形することもできます。

journalctl -eu keepalived.service -g state | sed -Ee 's/(.{15}).* \((\S+)\) entering (\S+) state(.*)/\1 | \2 -> \3\4/i'

# Apr 03 00:24:16 | web -> FAULT
# Apr 03 00:24:16 | web -> BACKUP
# Apr 03 00:24:19 | web -> MASTER
# Apr 03 00:24:36 | web -> MASTER

また、ipコマンドでもVRRPのステータスをうかがい知ることができます。 VRRP MASTERのときしかVRRP VIPが表示されないことから、VRRPの状態を察知できます。

以下のように、haproxyサービスを停止することで意図的にVRRPインスタンスをFAULT状態に遷移させると、VRRP VIP (192.168.0.100/24) が見えなくなることがわかります。

sudo systemctl stop haproxy.service

ip -br address show
# lo               UNKNOWN        127.0.0.1/8 ::1/128 
# ens3             UP             192.168.0.10/24 fe80::b4e3:96cd:51f2:6fb7/64 
# ens4             DOWN           
# ens5             DOWN 

sudo systemctl start haproxy.service

ip -br address show
# lo               UNKNOWN        127.0.0.1/8 ::1/128 
# ens3             UP             192.168.0.10/24 192.168.0.100/24 fe80::b4e3:96cd:51f2:6fb7/64 
# ens4             DOWN           
# ens5             DOWN        

(参考) HAProxyのステータス確認

CLIによるステータス確認

HAProxy 3.1 Configuration Manual - stats socket

/etc/haproxy/haproxy.cfgstats socket /var/lib/haproxy/statsという設定行があります。

この設定により、HAProxyはUNIXソケットファイルを生成します。 UNIXソケットファイルに接続することで、ネットワーク機器に対して専用コマンドを実行するかのように設定変更やステータス確認コマンドを実行できます。

HAProxy 3.1 Management Guide - Unix Socket commands

UNIXソケットファイルに接続してステータスを確認してみましょう。 手順は以下のとおりです。

sudo dnf -y install socat

sudo socat /var/lib/haproxy/stats readline
prompt

show backend
# web_http

show servers state web_http
# 3 web_http 1 web1 192.168.0.11 2 0 1 1 277 6 3 4 6 0 0 0 - 80 - 0 0 - - 0
# 3 web_http 2 web2 192.168.0.12 2 0 1 1 277 6 3 4 6 0 0 0 - 80 - 0 0 - - 0

使ったコマンドは以下のとおりです。

コマンド 意味
prompt 対話モード・非対話モードを切り替える。
とりあえず最初に実行する
show backend backend名を一覧表示する
show servers state backendのステータス表示。
6列目がステータスで、2が起動状態を表す。
0は停止状態、1は起動中、3は停止中

このままだと少々見づらいので、Linuxコマンドで整形しましょう。

まず基礎知識として、以下のようにsocatstdioと接続することで、非対話形式でコマンドを実行できます。

echo 'show servers state web_http' | socat /var/lib/haproxy/stats stdio
# 3 web_http 1 web1 192.168.0.11 2 0 1 1 769 6 3 4 6 0 0 0 - 80 - 0 0 - - 0
# 3 web_http 2 web2 192.168.0.12 2 0 1 1 769 6 3 4 6 0 0 0 - 80 - 0 0 - - 0

この形式であれば整形できますね。cutコマンドで1〜6列目のみを取り出しつつ、column -tで列の間隔を揃えて表示します。

echo 'show servers state web_http' | socat /var/lib/haproxy/stats stdio | sed 's/^# /#/' | cut -d' ' -f 1-6 | column -t
# #be_id  be_name   srv_id  srv_name  srv_addr      srv_op_state
# 4       web_http  1       web1      192.168.0.11  2
# 4       web_http  2       web2      192.168.0.12  2

だいぶ見やすくなりましたね。 srv_op_state2なので、負荷分散先のサーバへのヘルスチェックは成功しており、通信可能であることがわかります。

GUIによるステータス確認

(参考元: Exploring the HAProxy Stats Page (What You Should Know))

先ほどのCLI手順でも登場したHAProxyのStatsを、今度はWEB画面で確認する手順を紹介します。 ポート番号は80にしていますが、別の番号にしたい場合は80808008などでも良いかもしれません。 これらのポート番号であればデフォルトのSELinuxポリシーで許可されますが、80以外を使う場合はfirewalldの穴あけが必要です。

今回の設定ではstats admin if TRUEによってGUIアクセス時にHAProxyの操作権限が付与されます。 つまりGUI上で負荷分散先Serverの無効化 (BIG-IP風に言えばPool member無効化) などの操作が行なえます。 stats admin if TRUEstats admin if LOCALHOSTに置き換えることで、リモートログイン時には特権なしにできますので、お好みでアレンジしてください。

cat << EOF | sudo tee /etc/haproxy/conf.d/stat.cfg > /dev/null
frontend stats
    mode http
    bind *:80
    stats enable
    stats uri /
    stats admin if TRUE
    # stats refresh 10s
EOF

# sudo firewall-cmd --add-port 80/tcp --permanent
# sudo firewall-cmd --reload

systemctl restart haproxy.service

上記設定を入れた上で、HAProxyのIPアドレス (LB VIPではありません。今回の場合はhttp://192.168.0.10) にクライアントPCのブラウザからHTTPアクセスすると、GUIダッシュボードを確認できます。

ピンク色の枠で囲んだように、緑色のセルから直感的にサーバがUpしていることがわかりますし、Status列からも同様な情報を確認できます。 検証においては便利だと思います。

haproxy_gui_dashboard

以上でHAProxy + Keepalived構成のデモは終わりです。 お疲れ様でした。

参考URL

Ansibleによるネットワークステータス確認101

お伝えしたいこと

この記事はAnsible Advent Calendar 2024の11日目の記事です。

AnsibleでLinuxクラウド、ネットワーク機器の設定変更を自動化できるのは周知のとおりですが、情報取得の機能も充実しています。
充実...していますが、機能が多すぎて「やりたいこと1つに対して実現手段が2つも3つも存在する」状況になっています。

一体私たちはどの機能を使いたいのでしょうか?
私自身あまり理解できていなかったので、この場でまとめてみたいと思います。

サマリ表

下表にNW機器のステータス情報を取得する方法をまとめます。
取得できる情報は大きく分けて4種類あります。
もっと大きく分けると、(1)の文字列データと(2-1)〜(2-3)の構造化データの2つに大別できます。

network_configuration_gathering_pattern

(※) 本記事では例示にArista EOSを使います

自分が使いたい機能を見つける

以降のセクションでは、いくつかの具体的なユースケースに基づいて上表の機能を選択し、実行結果を示していきます。
少しでもイメージが湧きやすくなれば幸いです。

未加工のshowコマンドログを取得する

showコマンドの出力をそのまま見たりファイルに保存したいときは、#サマリ表「(1) showコマンドそのまま」 の機能を使用します。

ユースケース (1) showコマンドを未加工で保存したいのはどんな場面?

ネットワーク機器のshowコマンド出力結果を加工せずそのままファイルに保存したいケースとして、私の中では以下の2点が思い当たります。

  1. 作業やメンテナンス前後で対象機器にshowコマンドをAnsibleで一括実行し、実行結果をファイル保存する。ファイルを目視で確認して作業の成否を判断する (コマンド実行のみ自動化、確認はマニュアル)
  2. Ansibleでコンフィグを定期的に一括取得・ファイル保存する。作業者は必要なときにコンフィグを参照し、設計業務などに活用する

これらのユースケースを満たすAnsibleの具体的な実装方法を次のセクションで見ていきましょう。

実行例 (1) arista.eos.eos_command module

arista.eos.eos_command moduleでshowコマンドを実行し、結果を記録する例を示します。
今回はshow running-configを実行し、ネットワーク機器のコンフィグを表示します。

例示ではansible.builtin.debugモジュールでコマンド出力を画面表示していますが、実際に業務で使う際はansible.builtin.copyモジュールなどに置き換えてファイル保存する想定です。

- name: Retrieve Arista EOS config
  hosts: eos
  gather_facts: false
  tasks:
    - name: Execute show running-config
      arista.eos.eos_command:
        commands:
          - show running-config
      register: _eos_show_running_config_result

    - name: Debug _eos_show_running_config_result
      ansible.builtin.debug:
        msg: '{{ _eos_show_running_config_result.stdout }}'

以下に実行結果を示します。

TASK [Debug _eos_show_running_config_result] ***
ok: [veos1] => 
  msg:
  - |-
    ! Command: show running-config
    ! device: veos1 (vEOS-lab, EOS-4.26.5M)
    !
    ! boot system flash:/vEOS-lab.swi
    !
    enable password sha512 $6$xxxxxx # マスクしました
    no aaa root
# ...

showコマンドログを構造化データに変換し、活用する

showコマンドの実行結果を構造化データに変換したいときは、#サマリ表の(2-1)〜(2-3)の機能を使用します。

  • (2-1) パーサ利用
  • (2-2) network resource module
  • (2-3) gather_subset

(参考) 構造化データへの変換のイメージ

「構造化データへの変換」のイメージを補足として説明します。
既に知っている方は読み飛ばしてください。

「構造化データへの変換」がどのようなものかを理解するには、実際に動いているところを見るのが一番です。

以下のネットワーク構成を例にします。
Arista vEOSスイッチが3台接続されています。

nw_diagram1

veos1show interface descriptionを実行し、インターフェースのステータスを確認します。

Interface  Status  Protocol  Description
Et1        up      up        veos2 Et1
Et2        up      up        veos3 Et1

これを構造化データに変換すると、以下のようなデータ構造になります。
利用するパーサによってデータ構造は変わりますが、基本的にはlistdictのネスト構造になります。
例えばntc_templatesTextFSMを利用した場合は以下のようなデータ構造になります。
(※) パーサとは、文字列を構造化データとして解釈するプログラムのことです。本記事の文脈では文字列を構造化するプログラムと思って差し支えありません。ntc_templatesTextFSM正規表現で実装されています

interfaces:
  - port: Et1
    status: up
    protocol: up
    description: veos2 Eth1

  - port: Et2
    status: up
    protocol: up
    description: veos3 Eth1

構造化することで「特定の値を取り出す」ことが非常に容易になります。
例えばEt1status{{ interfaces.0.status }}{{ (interfaces.selectattr("port", "eq", "Et1"))["status"] }}で取り出せます。

後者の参照方法は一見すると難しく思えるかもしれません。
しかし「同じパーサを使う限りどのshowコマンドでも基本的に同じ方法でデータを取り出せる」ので、分析対象のshowコマンドが今後増えた場合も同じフィルタを使い回せます。
構造化されていないデータを都度grepansible.builtin.regex_search filterで都度整形するよりも遥かに少ない手間でデータを構造化できます。

...話が少し逸れましたが、データを構造化できれば夢がかなり広がります。
構造化されたデータを変数に格納して追加のロジックを組めるのです。

例えば、以下のような処理が考えられます。

  1. Et1がupなら何もせず、downならエラーメッセージを出力する (→ showコマンドの実行だけでなく確認まで自動化できる)
  2. ansible.builtin.templateモジュールで表やレポートを自動生成 (→ ステータス可視化、レポート自動生成)

2の具体例を挙げます。
上述のlist of dict形式に構造化されたデータをinputに、templateモジュールを使えば以下のmarkdownを容易に生成できます。
(※) 余談ですが、list of dictは本質的には表と同じ構造のデータなのです。なので表形式に変換することは本当に簡単です

| port | status | protocol | description |
| ---- | ------ | -------- | ----------- |
| Et1 | up | up | veos2 Eth1 |
| Et2 | up | up | veos3 Eth1 |

適切なツールでmarkdownをhtml化すると、以下のような見た目の表になります。

interface_list

showコマンドログを構造化し、整形することで見やすい表を生成できました。

ユースケース (2) showコマンドを構造化したいのはどんな場面?

#構造化データへの変換のイメージで既に触れたとおり、私が思いつくユースケースは以下の2点です。

  1. コマンド実行だけでなく、実行結果の確認まで自動化する
  2. コマンド実行結果を整形してレポートや表形式で出力する

実行例 (2-1) パーサを使う場合

さて、Ansibleでネットワーク機器のステータス情報をパースする手段は3つあります。
順番に説明していきますが、まずは私が個人的に一番好んでいる「(2) パーサ利用」について説明します。

パーサを使う場合、基本的にはansible.utils.cli_parseモジュールを使います。

以下にサンプルを示します。
show interfaces descriptionをパースするplaybookです。
今回はntc_templatesパーサを利用しているので、動作にはntc_templates Pythonパッケージが必要です。

- name: Parse EOS show commands
  hosts: eos
  gather_facts: false
  tasks:
    - name: Parse show interfaces description
      ansible.utils.cli_parse:
        command: show interfaces description
        parser:
          name: ansible.netcommon.ntc_templates
        set_fact: _show_interfaces_description_parsed

    - name: Debug _show_interfaces_description_parsed
      ansible.builtin.debug:
        msg: '{{ _show_interfaces_description_parsed }}'

実行結果は以下のようなイメージになります。
TextFSMは必ずlist[dict] (list of dicts) というデータ型に構造化します。
したがって、showコマンドが変わってもそこそこ画一的な方法でデータを加工できることが利点だと私は考えています。

TASK [Debug _show_interfaces_description_parsed] ***
ok: [veos1] => 
  msg:
  - description: veos2 Et1
    port: Et1
    protocol: up
    status: up
  - description: ''
    port: Et2
    protocol: up
    status: up
  # ...

ご参考までに、show interfaces descriptionの実行ログも再掲します。

Interface  Status  Protocol  Description
Et1        up      up        veos2 Et1
Et2        down    down      veos3 Et1

ansible.utils.cli_parse module以外にも、似た機能を持つフィルタとしてansible.netcommon.parse_cli filteransible.netcommon.cli_parse_textfsm filterが存在します。
これらのフィルタはcli_parseモジュールよりも先にリリースされた経緯があり使われたこともありますが、対応しているパーサも限定的ですし現在ではあまり使う理由がないと思います。
フィルタについては実行例を割愛します。

実行例 (2-2) network resource moduleを使う場合

network resource moduleが存在するOSの場合、この方法が使えます。
#実行例 (2-1) パーサを使う場合と比較するとパーサ導入が不要であるため、お手軽に使い始めることができます。
ntc_templatesやTextFSMは「CLIで得られる情報であれば全て構造化できる」汎用性と、「必ずlist[dict]形式でデータを返す」統一感があるので、私個人としてはパーサ利用に統一するのが好きです。
このあたりは宗教戦争です。

同じパース結果を得られる書き方が私の知る限り3通り存在するので、順番に挙げていきます。

1つ目の書き方はarista.eos.eos_facts moduleを呼び出す方法です。
Factsを収集すると、ansible_facts変数に自動的に格納されます。

- name: Test gather facts on EOS
  hosts: eos
  gather_facts: false
  tasks:
    - name: Gather facts
      arista.eos.eos_facts:
        gather_network_resources:
          - interfaces
          - l2_interfaces
          - l3_interfaces

    - name: Debug _eos_facts_result
      ansible.builtin.debug:
        msg: '{{ ansible_facts }}'

実行結果は以下の通りです。
network_resourcesの配下がnetwork resource moduleの機能で収集した情報です。 Factsを使うと、network_resource_module以外のFacts情報も勝手に収集されます。
gather_subsetパラメータに!allを指定してもminに相当する情報は最低限収集されます。

TASK [Debug _eos_facts_result] ***
ok: [veos1] => 
  msg:
    net_api: cliconf
    net_fqdn: veos1
# ...
    net_system: eos
    net_version: 4.26.5M
    network_resources:
      interfaces:
      - description: veos2 Et1
        enabled: true
        name: Ethernet1
# ...
      l2_interfaces:
      - mode: trunk
        name: Ethernet1
        trunk:
          trunk_allowed_vlans:
          - '2'
# ...
      l3_interfaces:
# ...
      - ipv4:
        - address: 192.168.0.11/24
        name: Management1

2つ目の書き方は、network resource moduleのstate: gatheredを指定する方法です。
変数格納するにはregisterキーワードが必要です。

1つのFactsだけを取得するときはこの書き方でも良いと思います。
複数のFactsを収集する場合は、arista.eos.eos_factsの方がコンパクトに書けると思います。

- name: Gather EOS Facts
  hosts: eos
  gather_facts: false
  tasks:
    - name: Gather interfaces Facts
      arista.eos.eos_interfaces:
        state: gathered
      register: _eos_interfaces_result

    - name: Debug _eos_facts_result
      ansible.builtin.debug:
        msg: '{{ _eos_interfaces_result.gathered }}'

3つ目の書き方は、gather_factsを指定する方法です。
書き方がやや特殊で、個人的に覚えにくいです。
またFactsの収集タイミングがPlayの冒頭に固定されます。
私の環境では実機検証できていませんが、module_defaultsの配下にeos以外のOSについてもデフォルト値を指定することで複数OS分のデータ取得を1つのPlayでスッキリ表現できることがこの書き方のメリットなのだと推測します。
Gathering facts from network devices

- name: Test gather facts on EOS
  hosts: eos
  gather_facts: true
  module_defaults:
    arista.eos.eos_facts:
      gather_network_resources:
        - interfaces
        - l2_interfaces
        - l3_interfaces
  tasks:
    - name: Debug _eos_facts_result
      ansible.builtin.debug:
        msg: '{{ ansible_facts }}'

(参考) cli_parseモジュールの概要説明

cli_parseモジュールは、文字列をパースして構造化データを変数格納する機能を持ちます。
cli_parseモジュールを実行するには以下のような情報が必要となります。

cli_module_data_structure

構造化に使用するパーサ名をパラメータに指定する必要があり、選択可能なパーサとして以下の選択肢があります。

  1. ntc_templates
  2. TextFSM
  3. TTP (dmulyalin/ttp_templatesにテンプレートサンプルあり)
  4. PyATS
  5. Ansible Native Parsing Engine

parsers

パーサが複数あるとどれを使うか迷うと思います。
一概には言えませんが、「使い慣れたパーサを使いたい」、「日本語情報が多いものを使いたい」、「自分の現場にはCisco機器が多い」など様々な事情を総合して選定するものと想像します。

ご参考までに、私個人としてはansible.netcommon.ntc_templatesansible.utils.textfsmを好んで使います。

ntc_templatesについては主要なネットワークOSのパーサ定義は既にある程度揃っていて、GitHub上に公開されています (networktocode/ntc-templates - /ntc_templates/templates)。
自分の欲しいパーサテンプレートがGitHub上に公開されていれば、まずはそれを使ってみます。

もしntc-templatesリポジトリに既存のパーサ定義が存在しなかったり、既存のパーサ定義とは異なるデータ構造が欲しかった場合にはTextFSMテンプレートを自分で記述しつつansible.utils.textfsmを使います。
テンプレートファイルの文法はntc-templatesと同様なので、他のテンプレートファイルの書き方を参考にできます。
テンプレートファイルの文法を調べる際は、TextFSMのWiki情報るつぼっとの記事がおすすめです。

その他、使い方の詳細はansible.utils.cli_parseのパラメータ一覧や実行例が参考になります。

実行例 (2-3) gather_subsetを使う場合

network resource moduleがAnsibleに登場する前から、eos_factsモジュールなどネットワーク機器からFactsを収集する機能は存在していました。
ややこしいですが、この機能はgather_subsetというキーワードとしてまだ残っています。
network resource moduleの方が機能としては後発ですし、恐らくgather_subset周りの機能が今後拡充されることはないと思います。
かといって廃止の予告は今のところないので、network resource moduleで取得できない情報の中でgather_subsetで取れる情報があれば、選択肢に入れてみても良いかもしれません。

くどいようですが私個人としては#実行例 (2-1) パーサを使う場合で全て賄ってしまうのが今のところは好きです。
とはいえパーサの使い方を学習するコストもありますし、一概にそれが正義だとは言えません (宗教戦争です)。

今回もFacts機能のため、書き方としてはeos_factsgather_facts2通りがあります。
1つずつ紹介していきます。

まずは1つ目のarista.eos.eos_factsを利用したplaybookのサンプルです。

- name: Test gather facts on EOS
  hosts: eos
  gather_facts: false
  tasks:
    - name: Gather facts
      arista.eos.eos_facts:
        gather_subset:
          - interfaces

    - name: Debug _eos_facts_result
      ansible.builtin.debug:
        msg: '{{ ansible_facts }}'

実行結果は以下の通りです。
なんだかshow interfacesをパースした感がありますね。

TASK [Debug _eos_facts_result] ***
ok: [veos1] => 
  msg:
# ...
    net_interfaces:
      Ethernet1:
        bandwidth: 1000000000
        description: veos2 Et1
        duplex: duplexFull
        ipv4: {}
        lineprotocol: up
        macaddress: 0c:65:34:fe:00:01
        mtu: 9214
        operstatus: connected
        type: bridged
# ...
    network_resources: {}

続いて2つ目のgather_factsを利用した書き方です。
Factsの取得タイミングがPlayの冒頭に固定されるものの、ネットワーク機器のOSの差分を吸収できる書き方にはなってそうです。
「AnsibleをOSごとの操作手順の違いを吸収する抽象化レイヤーとして使用する」考え方、昔は流行ってましたね。
抽象化の利点について聞くことは最近減ってきましたが、gather_factsを使用する唯一のモチベーションはそこなんじゃないかと思っています。

- name: Test gather facts on EOS
  hosts: eos
  gather_facts: true
  gather_subset:
    - interfaces
  tasks:

    - name: Debug _eos_facts_result
      ansible.builtin.debug:
        msg: '{{ ansible_facts }}'

おわりに

Ansibleのネットワーク機器の情報を集める方法は本当にたくさんあります。

showコマンドログを保存するだけなら迷いませんが、パースの手段は本当にたくさんあります。
ですが、迷ったらパーサかnetwork resource module系の機能を使いましょう。
言い換えると、#サマリ表の(2-3)は理由がなければ使わなくて良いと思います。

(Aristaのnetwork resource moduleはlldp_interfacesの情報をなぜか収集できないので、show lldp neighbors相当の情報をパースしたいときはcli_parsegather_subsetのどちらかを使う必要があるという話はありますがそれはそのうち修正される小さなissueです)

結局のところは宗教戦争ですが、この記事がパース手段の選択の一助となれば幸いです。