カーネルを一時的に変更できるgrub2-rebootが便利。結局GRUB2のことをいろいろ調べたよ

# 1. はじめに
Linuxを使っていて「新しいカーネルモジュールをインストールしたら起動しなくなった」「カーネルに渡すブートオプションを変更したら起動しなくなった」という経験をしたことがあるだろうか。

このような状況で便利なのが、次回起動時だけカーネルを変更する **grub2-reboot** コマンドだ。続けて再起動すると元のカーネルに戻るので、コンソールアクセスが制限されているクラウドでとくに便利だ。またテスト目的で複数カーネルを切り替えるときにも役に立つ。

意外に知られていないコマンドなので紹介しようと考えていたが、調べていくうちに結局以下のことを書くことになってしまった。

* GRUB2の設定ファイルと仕組み
* BIOSとUEFIにおける設定ファイルの違い
* RHEL7系とRHEL8/RHEL9系の設定方法の違い
* デフォルトカーネルを変更する方法
* 一時的にカーネルを変更する方法

### 1-1. 対象環境

* RHEL7/RHEL8/RHEL9などGRUB2を使用しているLinuxディストリビューション。CentOS StreamやOracle Linux、AlmaLinux、Rocky Linuxなど互換ディストリビューションを含む

# 2. どんなときに便利？

冒頭でも説明しているが、もう少し詳しく説明する。今まで正常起動していたサーバーが急に起動しなくなるとき、以下のような原因/きっかけが考えられる。

* カーネルをアップデートした（RPM/Yum/DNFでは、新しくインストールしたカーネルがデフォルトになる）
* 手でカーネルをビルドして変更した
* カーネルに渡すブートオプションを変更した
* デバイスドライバなど新しいカーネルモジュールをインストールした
* ハードウェア障害

このようなトラブルが発生したときは、以下の手順のトラブルシュートが必要だ。

**起動しないときのトラブルシュート方法**
1. サーバーを再起動してコンソール画面を表示する
2. 起動メニューから、起動実績のあるカーネルを選択する
3. 正常起動したら、デフォルトカーネルを変更する

サーバー本体が手元にあるときは問題ないが、クラウドやデータセンターなど遠隔地にあるときは、コンソール画面を表示できず、古いカーネルを選択できないことがある。

**パブリック・クラウド：** コンソールログイン機能を提供していないベンダがある
**データセンター：** BMC/IPMIボードやKVM装置（PC切替器）を設定していないサーバーでは、データセンターに行く必要がある

grub2-rebootコマンドは起動カーネルを一時的に変更するだけなので、リブートさえできればリカバリーが可能だ。ただし、Yum/DNFでインストールしたときはデフォルトカーネルも更新されるので注意が必要だ。

# 3. GRUB2

GRUBの設定ファイルは、6系までのGRUBならば`/etc/grub.conf`もしくは`/boot/grub/grub.conf`、7系以降のGRUB2ならば`/etc/grub2.cfg`が基本だ。ところがBIOSモードとUEFIモードでは異なる。とくに物理サーバーの場合、UEFIで使っていることがほとんどだろう。そのあたりを説明する。

### 3-1. GRUB2の設定ファイル

GRUB2には多くのファイルがあり、代表的なのは次のファイルだ。

* **`/etc/grub2.cfg`**  
　/boot/grub2/grub.cfgへのシンボリックリンク
* **`/boot/grub2/grub.cfg`**  
　カーネルエントリの一覧など。8系にも存在するが内容は違う
* **`/boot/loader/entries/*`**  
　8系では、このディレクトリにエントリの一覧がある
* **`/boot/grub2/grubenv`**  
　デフォルトエントリの情報など
* **`/etc/default/grub`**  
　GRUB2全体の設定ファイル
* **`/etc/sysconfig/kernel`**  
　カーネルのアップデート方法など

この中でもデフォルトカーネルの変更などで参照するのは`/etc/grub2.cfg`だ。7系でデフォルトカーネルの一般的な変更手順は次のとおり。8系ではgrubbyを使用する。


1. カーネル一覧を表示。

```shell-session:
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg
```

2.デフォルトカーネルのエントリ番号を設定。

```shell-session
grub2-set-default エントリ番号
```

3.`/boot/grub2.cfg`はシンボリックリンクなので、信頼性確保のため実体の`/boot/grub2/grub.cfg`を指定して再構築。

```shell-session
grub2-mkconfig -o /boot/grub2/grub.cfg
```

**<font color="blue">ヒント</font>**
<font color="blue">8系ではgrubbyでの操作が必須だが、7系でもgrubbyを使用できる。ただし古いバージョンは使用できないかもしれない。</font>

**<font color="red">注意</font>**
<font color="red">6系までのGRUBでは、/boot/grub/grub.confをエディタで修正してデフォルトカーネルを変更した。しかし、GRUB2では/boot/grub2/grub.cfgを手で変更してはいけない。必ずgrub2-mkconfigやgrubbyなどのコマンドを使って変更する。

### 3-2. UEFIモードに気をつけろ

BIOSモードのときには、これまでの説明でよいのだが、UEFIモードでは異なる。一般には次のように説明されているが、実体に加えてシンボリックリンクも異なる。詳しく説明する。

**BIOSブート**：/boot/grub2/grub.cfg
**UEFIブート**：/boot/efi/EFI/*/grub.cfg

#### 3-2-1. UEFIモードを判別する

現在使用しているサーバーや仮想マシンがUEFIモードかどうかは次のコマンドで判別できる。クラウドを調べたところ、AWSやAzureはBIOSモード、Oracle CloudはUEFIモードだった。すべてのインスタンスタイプを調べていないので参考程度に。

```shell-session:UEFIモードのとき
$ ls -l /sys/firmware/efi
total 0
-r--r--r--.  1 root root 4096 Aug 13 10:00 config_table
drwxr-xr-x.  2 root root    0 Aug 13 09:06 efivars
-r--r--r--.  1 root root 4096 Aug 13 10:00 fw_platform_size
-r--r--r--.  1 root root 4096 Aug 13 10:00 fw_vendor
-r--r--r--.  1 root root 4096 Aug 13 10:00 runtime
drwxr-xr-x.  9 root root    0 Aug 13 10:00 runtime-map
-r--------.  1 root root 4096 Aug 13 10:00 systab
drwxr-xr-x. 29 root root    0 Aug 13 10:00 vars
```

```shell-session:BIOSモードのとき
$ ls -l /sys/firmware/efi
ls: cannot access /sys/firmware/efi: No such file or directory
```

#### 3-2-2. UEFIモードの実体ファイル

UEFIモードにおける設定ファイルは`/boot/efi/EFI/*/grub.cfg`と紹介した。アスタリスクの部分はディストリビューションによって異なる。

| ディストリビューション | ファイル名 |
|:--|:--|
| RHEL  | /boot/efi/EFI/redhat/grub.cfg  |
| CentOS | /boot/efi/EFI/centos/grub.cfg |
| Oracle Linux  | /boot/efi/EFI/redhat/grub.cfg |
| Amazon Linux  | /boot/efi/EFI/amzn/grub.cfg |
| Ubuntu  | /boot/grub/grub.cfg(UEFI/BIOS共通)  |

#### 3-2-3. シンボリックリンクと実体の関係

おそらく戸惑うのはシンボリックリンクだろう。UEFIモードでは`/etc/grub2-efi.cfg`が追加されている。

```shell-session:BIOSモード
$ ls -l /etc/grub2*
lrwxrwxrwx. 1 root root 22 Sep  3 13:31 /etc/grub2.cfg -> ../boot/grub2/grub.cfg
```

```shell-session:UEFIモード
$ ls -l /etc/grub2*
lrwxrwxrwx. 1 root root 22 Sep  3 13:31 /etc/grub2.cfg -> ../boot/grub2/grub.cfg
lrwxrwxrwx. 1 root root 31 Sep  3 13:31 /etc/grub2-efi.cfg -> ../boot/efi/EFI/
centos/grub.cfg
```

シンボリックリンクを使っていれば、よしなにやってくれそうだ。しかし`/etc/grub2.cfg`は`/boot/grub2/grub.cfg`を参照しているが実体は存在しない。

```shell-session
$ ls -l /boot/efi/EFI/centos/grub.cfg
-rwx------. 1 root root 10223 Sep  3 13:35 /boot/efi/EFI/centos/grub.cfg
$ ls -l /boot/grub2/grub.cfg ★存在しない
ls: cannot access /boot/grub2/grub.cfg: No such file or directory
```

そのためUEFIモードで次のコマンドは失敗する。

```shell-session:
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg
```

UEFIであることを認識し、`/etc/grub2-efi.cfg`もしくは`/boot/efi/EFI/*/grub.cfg`を指定する必要がある。

```shell-session:
awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2-efi.cfg
```

### 3-3. その他のファイル
これまで説明していない、次のファイルを説明する。今回はCentOS 8の内容を表示しているが、7系でもほとんど同じだ。

* /etc/default/grub
* /boot/grub2/grubenv
* /etc/sysconfig/kernel

#### 3-3-1. /etc/default/grub
このファイルにはGRUB2全体の設定が含まれている。デフォルトカーネルに関係するのは**GRUB_DEFAULT**ディレクティブだ。キーが「**saved**」になっている。

```
GRUB_TIMEOUT=5 ★起動時のカーネル選択メニューの表示時間
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved　★デフォルトカーネルが使用するディレクティブの名前
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
```

#### 3-3-2. /boot/grub2/grubenv
このファイルにはデフォルトカーネルの設定が含まれる。先ほど「**saved**」だったため、**saved_entry**ディレクティブで指定したカーネルがデフォルトカーネルになる。

RHEL7系では「メニュー番号」もしくは「メニュー名」を設定されているが、RHEL8系では「ID+カーネルバージョン」が設定されている。

```:7系
# GRUB Environment Block
saved_entry=CentOS Linux (3.10.0-1062.el7.x86_64) 7 (Core)
```

```:8/9系
# GRUB Environment Block
saved_entry=0aa24a7da6284581821b2215d0757580-4.18.0-147.el8.x86_64
kernelopts=root=/dev/mapper/cl-root ro resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet
boot_success=0
boot_indeterminate=0
```

RHEL8/9系では、一致する設定ファイルがこちらに存在する。これは[BootLoaderSpecByDefault（BLS）](https://fedoraproject.org/wiki/Changes/BootLoaderSpecByDefault)という仕様である。

```shell-session:RHEL8/9系のみ
# ls /boot/loader/entries/
0aa24a7da6284581821b2215d0757580-0-rescue.conf
0aa24a7da6284581821b2215d0757580-4.18.0-147.el8.x86_64.conf
0aa24a7da6284581821b2215d0757580-4.18.0-193.14.2.el8_2.x86_64.conf
```

#### 3-3-3. /etc/sysconfig/kernel
このファイルにはカーネルのアップデート方法が含まれる。

```shell-session
# UPDATEDEFAULT specifies if new-kernel-pkg should make
# new kernels the default
UPDATEDEFAULT=yes

# DEFAULTKERNEL specifies the default kernel package type
DEFAULTKERNEL=kernel-core
```

**UPDATEDEFAULT**は、カーネルをアップデートしたときデフォルトカーネルにするか否かを指定する。yesのときはデフォルトカーネルになるが、noにするとデフォルトカーネルにはならない。

たとえば`yum update`でカーネルを追加インストールしても、新しいカーネルを使いたくないときにnoを指定する。

そもそもカーネルのインストール自体を抑制したいときは、次のようにYum/DNFの設定ファイルで除外設定したほうがいいだろう。

```shell-session:/etc/yum.conf（/etc/dnf/dnf.conf）
exclude=kernel
```

**DEFAULTKERNEL**には、デフォルトカーネルの種類を設定する。通常のカーネルはkernelやkernel-coreだが、デバックカーネルではkernel-debugになる。またOracle LinuxのUEKではkernel-uekになっている。

# 4. 起動するカーネルを変更する

grub2-rebootの使い方を説明するつもりが、だいぶ遠回りをしてしまった。デフォルトカーネルを変更する方法もほとんど同じなので一緒に説明する。おもな手順は次のとおり。

1. デフォルトカーネルを調べる
2. カーネルのリストを表示する
3. 次回起動するカーネルを設定する（永続or一時的）
4. 再起動する

カーネルを変更するコマンドは、バージョンによって異なるので、それぞれについて説明する。7系でもgrubbyを使うときは、8/9系の説明を見てほしい。

**7系：** grub2-mkconfig/grub2-set-defaultもしくはgrubby
**8/9系：** grubby

**<font color="red">注意</font>**
<font color="red">システム起動時のカーネル選択メニューを表示できないクラウド環境では、デフォルトカーネルを変更する前にバックアップすることを推奨。筆者はUbuntuでやからしました（笑）。RHEL系と似ているようで似ていないので別途執筆予定。</font>

### 4-1. RHEL7系（非grubby）

#### 4-1-1. デフォルトカーネルを調べる
現在のデフォルトは`grub2-editenv`コマンドで表示できる。saved_entryディレクティブの値がデフォルトカーネルだ。

```shell-session
# grub2-editenv list
saved_entry=CentOS Linux (3.10.0-1127.19.1.el7.x86_64) 7 (Core)
```

```shell-session
# uname -a
Linux hostname 3.10.0-1127.19.1.el7.x86_64 #1 SMP Tue Mar 17 23:49:17 UTC 2020
 x86_64 x86_64 x86_64 GNU/Linux
```

#### 4-1-2. カーネルのリストを表示する
インストールされているカーネルの一覧を表示する。先ほどの結果と合わせると、インデックス番号0がデフォルトカーネルだ。

**BIOSモード**

```shell-session
# awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg
```

**UEFIモード**

```shell-session
# awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2-efi.cfg
```

```shell-session:出力例
0 : CentOS Linux (3.10.0-1127.19.1.el7.x86_64) 7 (Core)
1 : CentOS Linux (3.10.0-1062.18.1.el7.x86_64) 7 (Core)
2 : CentOS Linux (3.10.0-1062.el7.x86_64) 7 (Core)
3 : CentOS Linux (0-rescue-b5a029229fd6464ca40d2e93351d5c5f) 7 (Core)
```

#### 4-1-3. デフォルトカーネルを変更する
次の例では、一つバージョンが古いエントリ番号1のカーネルを指定している。

1. デフォルトカーネルをエントリ番号1に変更する。

```shell-session
# grub2-set-default 1
```

2.デフォルトカーネルが変わっていることがわかる。

```shell-session
# grub2-editenv list
saved_entry=1
```


#### 4-1-4. 再起動する
変更したカーネルを有効にするために再起動する。これで終了だ。

```shell-session
# systemctl reboot
```

### 4-2. RHEL7/RHEL8/RHEL9系（grubby）

#### 4-2-1. デフォルトカーネルを調べる
grubbyではカーネルのファイル名やインデックスを表示できる。

```shell-session
# grubby --default-kernel
/boot/vmlinuz-4.18.0-193.14.2.el8_2.x86_64
```

```shell-session
# grubby --default-index
0
```

#### 4-2-2. カーネルのリストを表示する
インストールされているカーネルの一覧を表示する。この例では`-A 2`オプションを付けて、grepに一致する次の行も表示している。

```shell-session
# grubby --info=ALL | grep -A 2 index
index=0
kernel="/boot/vmlinuz-4.18.0-193.14.2.el8_2.x86_64"
args="ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet $tuned_params"
--
index=1
kernel="/boot/vmlinuz-4.18.0-147.el8.x86_64"
args="ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet $tuned_params"
--
index=2
kernel="/boot/vmlinuz-0-rescue-0aa24a7da6284581821b2215d0757580"
args="ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet"
```

#### 4-2-3. デフォルトカーネルを変更する

デフォルトカーネルを永続的に変更するには、次のようにgrubbyコマンドを使用する。今回の例では`--set-default`オプションと値の間にイコール（=）を指定しているが、空白スペースでもよい。

```shell-session
# grubby --set-default=/boot/vmlinuz-4.18.0-147.el8.x86_64
```

次のようにインデックス番号も指定できる。

```shell-session
# grubby --set-default-index=1
```

変更されたことを確認する。

```shell-session
# grubby --default-kernel
/boot/vmlinuz-4.18.0-147.el8.x86_64
```

`--info`を使うと、指定したカーネルのエントリ全体を表示できる。この例ではカーネルファイル名を指定しているが、インデックスも指定できる。

```shell-session
# grubby --info=/boot/vmlinuz-4.18.0-147.el8.x86_64
index=1
kernel="/boot/vmlinuz-4.18.0-147.el8.x86_64"
args="ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv
=cl/swap rhgb quiet $tuned_params"
root="/dev/mapper/cl-root"
initrd="/boot/initramfs-4.18.0-147.el8.x86_64.img $tuned_initrd"
title="CentOS Linux (4.18.0-147.el8.x86_64) 8 (Core)"
id="0aa24a7da6284581821b2215d0757580-4.18.0-147.el8.x86_64"
```

#### 4-2-4. 再起動する
変更したカーネルを有効にするために再起動する。これで終了だ。

```shell-session
# systemctl reboot
```

### 4-3. 一時的にカーネルを変更する
先ほどはカーネルを永続的に変更したが、次回起動時だけカーネルを変更することもできる。もう一度、再起動すると元のカーネルに戻るので、次ような目的のときに便利だ。

* テスト目的で別のカーネルに変更したい
* デバックカーネルを使いたい
* カーネルに渡すブートオプションをテストしたい

変更には**grub2-reboot**を使う。次のようにメニューエントリ番号（インデックス番号）を指定する。

```shell-session
grub2-reboot 1
```

動作を確認するために、一連の流れで説明する。

1.現在のカーネルバージョンは「4.18.0-147」。

```shell-session
# uname -r
4.18.0-147.el8.x86_64
```

2.デフォルトカーネルも当然同じ。

```shell-session
# grubby --default-kernel
/boot/vmlinuz-4.18.0-147.el8.x86_64

# grubby --default-index
1
```

3.grub2-rebootを使い、次回再起動時だけインデックス"0"に変更する。

```shell-session
# grub2-reboot 0
```

4.デフォルトカーネルは変更していないのでインデックスは変わっていない。

```shell-session
# grubby --default-index
1
```

5.再起動する。

```shell-session
# systemctl reboot
```

6.再起動後にログインすると、インデックス"0"のカーネルに変わっている。

```shell-session
# uname -r
4.18.0-193.14.2.el8_2.x86_64

# grubby --info=/boot/vmlinuz-4.18.0-193.14.2.el8_2.x86_64
index=0
kernel="/boot/vmlinuz-4.18.0-193.14.2.el8_2.x86_64"
args="ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet $tuned_params"
root="/dev/mapper/cl-root"
initrd="/boot/initramfs-4.18.0-193.14.2.el8_2.x86_64.img $tuned_initrd"
title="CentOS Linux (4.18.0-193.14.2.el8_2.x86_64) 8 (Core)"
id="0aa24a7da6284581821b2215d0757580-4.18.0-193.14.2.el8_2.x86_64"
```

7.さらに再起動する。

```shell-session
# systemctl reboot
```

8.デフォルトカーネルに戻っている。grub2-rebootの効力は、次回再起動時だけということが確認できた。

```shell-session
# uname -r
4.18.0-147.el8.x86_64
```

# 5. まとめ
* **RHEL7以降のGRUB2ではコマンドを使ってデフォルトカーネルを変更する。手でファイルを変更してはいけない。**
* **BIOSモードとUEFIモードではGRUBの設定ファイル名が異なる。**
* **デフォルトカーネルを変更するには、RHEL7系ではgrub2-mkconfig/grub2-set-defaultもしくはgrubbyを使用する。RHEL8/9系ではgrubbyを使用する。**
* **grub2-rebootコマンドを使うと、次回起動時だけカーネルを一時的に変更できる。このような特性はコンソールアクセスが難しいクラウドやテストのときに便利。**

# 6. 参考

* [GRUB 2 設定ファイルのカスタマイズ](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/system_administrators_guide/sec-customizing_the_grub_2_configuration_file)
* [Oracle Linux - How to Change Default Kernel](https://community.oracle.com/docs/DOC-1026887#jive_content_id_Change_Default_Kernel_in_Oracle_Linux_7)
* [@IT grub2-mkconfig／grub-mkconfig コマンド](https://www.atmarkit.co.jp/ait/articles/1902/01/news045.html)
* [@IT grub2-set-default／grub-set-defaultコマンド](https://www.atmarkit.co.jp/ait/articles/1901/31/news048.html)
* [@IT grub2-reboot／grub-rebootコマンド](https://www.atmarkit.co.jp/ait/articles/1901/25/news044.html)
* [Grubby Bootloader for Linux](https://www.linuxsecrets.com/2741-grubby-bootloader-for-linux)