背景と目的:カーネルアップグレード時におけるツリー外ドライバの維持
45台のエッジサーバークラスタの定期メンテナンス中、マイナーなカーネルセキュリティアップデートによって常に頭を悩ませる障害が発生していました。それはツリー外(out-of-tree)ハードウェアドライバの破損です。システムが再起動すると、2.5GbE Realtek NICや専用のFPGAアクセラレータが認識されなくなっていました。その結果、オンコールのエンジニアが深夜に新しいカーネルヘッダーに対してソースツリーを手動で再コンパイルしなければならない事態に陥っていました。
この問題は、Linuxカーネルの内部インターフェースの扱いに直接起因しています。WindowsやmacOSとは異なり、Linuxは意図的に安定した内部ABI(Application Binary Interface)を維持していません。
カーネル開発者がパッチリリース間で内部データ構造や関数シグネチャを変更すると、カーネル5.15.0-80用にビルドされたロード可能なカーネルモジュール(.koファイル)は5.15.0-84でロードに失敗します。プロプライエタリなRAIDコントローラ、カスタムネットワークインターフェース、ZFS、VirtualBoxドライバなどを運用している場合、そのコードはアップストリームのカーネルツリー外に存在するため、カーネルアップデートごとに毎回再コンパイルが必要になります。
DKMS(Dynamic Kernel Module Support)は、このプロセス全体を自動化します。ディストリビューションのパッケージマネージャのインストール後トリガーに直接フックします。apt、dnf、またはpacmanが新しいカーネルとそのヘッダーを取得するたびに、DKMSが処理を引き継ぎます。/usr/src/<module>-<version>/からモジュールのソースを取得し、新しく導入されたカーネルヘッダーに対して約15〜30秒でコンパイルを行い、生成された新しい.koバイナリをモジュールツリーに配置します。
DKMSを、依存関係の解決とドライバのロードを行う標準ユーティリティであるmodprobeと組み合わせることで、手動でのドライバ再構築を完全に排除できます。
インストール:ビルド環境の準備
DKMSを使用するには、動作するCコンパイラツールチェーンと、稼働中または対象のカーネルに一致するカーネル開発用ヘッダーが必要です。
1. DebianおよびUbuntuシステム
ローカルのパッケージリストを更新し、ビルドツール、DKMS、および現在のカーネルヘッダーをインストールします:
sudo apt update
sudo apt install -y build-essential dkms linux-headers-$(uname -r)
2. RHEL、Rocky Linux、AlmaLinux
RPMベースのエンタープライズディストリビューションでは、まずEPELリポジトリを有効化してから、必要な開発パッケージをインストールします:
sudo dnf install -y epel-release
sudo dnf install -y gcc make dkms kernel-devel-$(uname -r) kernel-headers
ビルド用シンボリックリンクがインストールされたヘッダーディレクトリを指しているか確認します:
ls -ld /lib/modules/$(uname -r)/build
これが/usr/src/kernels/または/usr/src/linux-headers-*/配下の有効なディレクトリに解決されれば、ビルドツールチェーンの準備は完了です。
設定:モジュールの登録とmodprobeの調整
モジュールの自動化を実行するには、dkms.confマニフェストの作成と、modprobeによるランタイムロードルールの定義という2つのステップが必要です。
ステップ1:DKMSソースとdkms.confのセットアップ
DKMSは/usr/src/<module_name>-<module_version>/内のソースツリーを参照します。そのディレクトリ内のdkms.confという設定ファイルで、ビルドコマンドとバイナリのターゲットパスを定義します。
例えば、バージョン1.0.0のcustom_nicというカスタムネットワークドライバをパッケージ化する場合、対象のソースパスを作成します:
sudo mkdir -p /usr/src/custom_nic-1.0.0
そのディレクトリにCソースファイルとMakefileを配置し、/usr/src/custom_nic-1.0.0/dkms.confを作成します:
PACKAGE_NAME="custom_nic"
PACKAGE_VERSION="1.0.0"
CLEAN="make clean"
MAKE[0]="make -C /lib/modules/${kernelver}/build M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build modules"
BUILT_MODULE_NAME[0]="custom_nic"
DEST_MODULE_LOCATION[0]="/updates/dkms"
AUTOINSTALL="yes"
主なディレクティブの説明:
AUTOINSTALL="yes": パッケージマネージャが新しいカーネルをインストールした際に、DKMSがこのモジュールを自動的にリビルドするよう指示します。${kernelver}: 対象のカーネルバージョン文字列(例:5.15.0-88-generic)を保持する、DKMSから渡される環境変数です。DEST_MODULE_LOCATION[0]: コンパイルされた.koバイナリが/lib/modules/<kernel_version>/配下のどこにインストールされるかを指定します。
これで、以下の3つの簡単なコマンドでモジュールの登録、コンパイル、インストールを実行できます:
sudo dkms add -m custom_nic -v 1.0.0
sudo dkms build -m custom_nic -v 1.0.0
sudo dkms install -m custom_nic -v 1.0.0
ステップ2:modprobeによる永続的なロードとオプションの設定
DKMSによってバイナリが/lib/modules/$(uname -r)/updates/dkms/に配置されたら、モジュールの依存関係マップを最新化します:
sudo depmod -a
システム起動時にモジュールを自動的にロードするには、/etc/modules-load.d/に登録します:
echo "custom_nic" | sudo tee /etc/modules-load.d/custom_nic.conf
ドライバに特定のハードウェアパラメータが必要な場合や、競合するモジュールをオーバーライドする必要がある場合は、/etc/modprobe.d/で設定します:
sudo tee /etc/modprobe.d/custom_nic.conf << 'EOF'
# custom_nicのランタイムパラメータ
options custom_nic debug=1 rx_buffer_size=4096
# 競合するインツリードライバのロードを防止
blacklist generic_legacy_nic
EOF
Ubuntu 22.04 LTSサーバー環境では、この自動化によりカーネル更新後のメンテナンスに伴うダウンタイムをゼロに短縮できました。バックグラウンドのセキュリティパッチ適用時、再起動が行われる前のパッケージアップグレード段階でドライバのリビルドと登録が完了します。
検証と監視:ドライバのライフサイクル管理
設定が完了したら、インストールされている各カーネルにおけるモジュールのステータスを確認し、/var/lib/dkms/配下のログを監査します。
1. DKMS登録ステータスの確認
dkms statusを実行して、すべてのローカルカーネルにおける登録済みモジュールとそのビルド状態を一覧表示します:
dkms status
正常にインストールされているモジュールは、以下のように出力されます:
custom_nic/1.0.0, 5.15.0-84-generic, x86_64: installed
custom_nic/1.0.0, 5.15.0-88-generic, x86_64: installed
ステータスがinstalledではなくaddedやbuiltと表示されている場合、ドライバのバイナリがモジュールディレクトリにコピーされていないため、modprobeで検出できません。
2. modprobeによるモジュールの検証とロード
詳細出力(verbose)を有効にして、稼働中のカーネルにモジュールをロードします:
sudo modprobe -v custom_nic
コンパイルされたモジュールの詳細、作成者メタデータ、ライセンス文字列を確認します:
modinfo custom_nic
カーネルがモジュールをロードしたことを確認し、アクティブなメモリ使用状況をチェックします:
lsmod | grep custom_nic
カーネルリングバッファを確認し、初期化パラメータが正常に受け入れられたかを検証します:
sudo dmesg | grep -i custom_nic
3. アンロードとクリーンアップ手順のテスト
ドライバのバージョン1.0.1をリリースする際は、メモリからアクティブなモジュールをアンロードし、古いDKMSビルド成果物を削除します:
# 実行中のモジュールをメモリから安全に削除
sudo modprobe -r custom_nic
# インストールされているすべてのカーネルからバージョン1.0.0の登録を解除
sudo dkms remove -m custom_nic -v 1.0.0 --all
カーネルアップグレード中にビルドが失敗した場合は、/var/lib/dkms/custom_nic/1.0.0/build/make.logを確認してください。このログにより、コンパイラエラーの原因となった具体的なCヘッダーファイル、構造体メンバー、または非推奨となった関数呼び出しを正確に特定できます。

