VPSサーバー（CentOS8.1）が yum update 後に起動できなくなる現象の回避策


| [../](https://qiita.com/taconana/items/e45f19e83c825a9ba75e) |
---

VPSサーバー（CentOS8.1）の構築中に `yum update` を実行すると再起動できなくなるという洗礼を受けた。yum update中に /boot/grub2/grub.cfg の変更が行われ、その影響のようだ。回避するには、サーバー構築の最初の段階で /boot/grub2/grub.cfg を退避しておき、yum update 後に元に戻してやるといい。

##どういった症状なのか
GMO VPSで利用可能となったサーバー（CentOS8.1）を VPSポータルから通常モードで起動して「ON」状態になったら、TeraTermで接続してrootでログインする。VPSポータルのコンソールからログインしても同様である。そして、yum updateを実行し、サーバーを再起動する。

```bash

login: root
Password: ***
$ yum update -y
$ reboot
```
すると、TeraTermからは接続できなくなり、VPSポータルのコンソールからだとGRUB選択画面が開いて、進めなくなる。

##なぜ起きるのか
GRUBは、ブートローダーであり、どこからOSイメージを読み込んで起動するかを選択するためのもの。古いGRUB（`GRUB Legacy`）と新しい`GRUB2`がある。GRUB2はCentOS7以降からだ。GRUB LegacyとGRUB2のどちらを使うか、どのOSイメージを使うかを選択して、そのOSイメージをロードして、OSが起動される。通常、どのOSイメージを使うかは初期設定されたものや初回にユーザーが選択したものを覚えていて、自動で一連の動作を行ってくれる。それが、yum updateの実行によって、OSイメージの選択が自動で行えなくなってしまうようだ。そのため、TeraTermからは接続時にタイムアウトし、VPSポータルのコンソールからはGRUB LegacyとGRUB2の選択画面で止まってしまう。

VPSポータルのコンソールでは、GRUB選択画面からGRUBのコマンドで対処できるのかもしれない。WindowsPCからはキーバインドも異なり、コピペもできず、GRUBコマンドの知見もないので、私は早々にあきらめた。

CentOS8.1では、GRUB2が使われる。起動時の制御は、`/etc/default/grub`ファイルで指定できる。`GRUB_TIMEOUT`でタイムアウト秒数、`GRUB_DEFAULT`でOSイメージを指定する。タイムアウト秒数を経過したら指定のOSイメージが自動で起動される。正確には、事前に`grub2-mkconfig`コマンドを実行して、grubファイルと`/etc/grub.d/`配下のファイルの記述にもとづいて、`/boot/grub2/grub.cfg`ファイルを生成しておくのである。起動時にはgrub.cfgファイルが実行されるのであって、起動時にgrubファイルを解釈する訳ではない。GRUB2の詳細は、https://documentation.suse.com/ja-jp/sles/12-SP4/html/SLES-all/cha-grub2.html などを参照のこと。

つまり、yum updateによって、grub.cfgファイルが書き換わっているか、grub.cfgファイルの記述どおりに実行できない状況（例えば、必要なファイルがないなど）に陥っているのだ。

また、起動時には `/boot/grub2/grubenv`も参照する。これは前回の起動時の設定を保存し、次回の起動時に参照するものである。GRUB_DEFAULT=saved の指定の時に利用される。起動後に grubenv ファイルに修正を加えることで、次回の起動を制御できる。例えば、`grub2-set-default` コマンドを実行することで、/etc/default/grub を介さずにダイレクトに`saved_entry`を変更することができる。

##回避する手順

初回のログイン後に、grub.cfgファイルを退避しておき、yum update後に戻してやれば、なんとかなりそうだ。以下、手順を説明する。

(1) VPSポータルからサーバーを通常モードで起動し、TeraTermからrootでログインする。そして、初期状態でバージョンを確認する。CentOS8.1である。

```bash

$ cat /etc/redhat-release	
CentOS Linux release 8.1.1911 (Core) 
```
					
(2) yum update後にブートできなくなるので、事前に以下のファイルを退避しておく。実際には、grub.cfgファイルだけでもいいが、他のファイルも合わせて退避しておくとよい。

```bash

$ cp /etc/default/grub /etc/default/grub.original
$ cp /boot/grub2/grub.cfg /boot/grub2/grub.cfg.original
$ cp /boot/grub2/grubenv /boot/grub2/grubenv.original
```
実は、grubファイルは既に`grub.rpmsave`としてバックアップされており、grub.cfgファイルも`grub.cfg.rpmsave`としてバックアップされている。それらのファイルで十分かもしれないが、上記手順では明示的に退避するようにした。ちなみに/etc/default/grubは、以下のようになっている。

```bash

$ cat /etc/default/grub
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="crashkernel=auto biosdevname=0 net.ifnames=0 rhgb quiet"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
GRUB_DISABLE_UUID=true
GRUB_DISABLE_LINUX_UUID=true
```
GRUB_TIMEOUTとGRUB_DEFAULTは、5秒間待った後で、前回使用したOSイメージを起動することを示している。GRUB_DEFAULTは0,1,2,...のような番号で指定したり、OSイメージの名前で指定したり、`"saved"`で指定する。"saved"の場合には、/boot/grub2/grubenvファイルのsaved_entryで前回使用したOSイメージを識別する。

```bash

$ cat /boot/grub2/grubenv
saved_entry=20db05bac820110f5800ebe2e7262b58-4.18.0-193.28.1.el8_2.x86_64
kernelopts=root=/dev/vda1 ro crashkernel=auto biosdevname=0 net.ifnames=0 rhgb quiet
boot_success=1
```

(3) yum updateを実行する。問題のyum updateの実行である。200個くらいのモジュールが更新される。

```bash

$ yum update -y
```

(4) yum update後にバージョンを確認する。CentOS8.2に上がっている。

```bash

$ cat /etc/redhat-release
CentOS Linux release 8.2.2004 (Core)
```
(5) 今後はyum updateでカーネルが更新されないように設定しておく。

```bash

$ vi /etc/yum.conf
exclude=kernel* grub2*
```
※`kernel*`だけでなく、`kernel* grub2*`にしておいた方が無難である。
※ただし、これらを除外しておくと、今後に行うinstallやupdateによって「依存性の欠如」の問題が生じる場合がある。そのときは、excludeの行をコメントアウトして、yum updateを行い、grub2の問題に手動で対処する必要がある。

(6) 退避しておいた grub.cfg を元に戻す。/etc/default/grub は変更されないので、回復は不要。grubenvは、起動時に更新されるが、通常、内容は前回と同じである。`grub2-set-default`コマンドを使うとgrubenvが書き換わるので、その場合には回復すること。

```bash

$ cp /boot/grub2/grub.cfg.original /boot/grub2/grub.cfg
$ cp /boot/grub2/grubenv.original /boot/grub2/grubenv

$ awk -F\' '$1=="menuentry " {print $2}' /boot/grub2/grub.cfg
CentOS Linux (4.18.0-147.5.1.el8_1.x86_64) 8 (Core)
```
(7) この時点で再起動して、TeraTermからログインできることを確認する。

(8) 再起動後にログインできたら、改めてgrubファイルを編集し、grub2-mkconfigコマンドで反映させる。grubファイルでは、`GRUB_CMDLINE_LINUX`のみ変更している。そして、grubenvファイルのsaved_entryで識別されたものが`"Found linux"`のメッセージ内に見当たるかを確認する。awkの結果でも確認できる。先頭（0番目）が該当することが分かる。

```bash

# grubファイルを編集する
$ vi /etc/default/grub
GRUB_TIMEOUT=3
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="rd.lvm.lv=centos/root rd.lvm.lv=centos/swap 
 vconsole.font=latarcyrheb-sun16 vconsole.keymap=jp106 
 biosdevname=0 net.ifnames=0 rhgb quiet "
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
GRUB_DISABLE_UUID=true
GRUB_DISABLE_LINUX_UUID=true

# grub.cfgファイルに反映させる
$ grub2-mkconfig -o /boot/grub2/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-4.18.0-193.28.1.el8_2.x86_64
Found initrd image: /boot/initramfs-4.18.0-193.28.1.el8_2.x86_64.img
Found linux image: /boot/vmlinuz-4.18.0-147.5.1.el8_1.x86_64
Found initrd image: /boot/initramfs-4.18.0-147.5.1.el8_1.x86_64.img
Found linux image: /boot/vmlinuz-0-rescue-20db05bac820110f5800ebe2e7262b58
Found initrd image: /boot/initramfs-0-rescue-20db05bac820110f5800ebe2e7262b58.img
done

$ awk -F\' '$1=="menuentry " {print $2}' /boot/grub2/grub.cfg
CentOS Linux (4.18.0-193.28.1.el8_2.x86_64) 8 (Core)
CentOS Linux (4.18.0-147.5.1.el8_1.x86_64) 8 (Core)
CentOS Linux (0-rescue-20db05bac820110f5800ebe2e7262b58) 8 (Core)

# grubenvファイルを確認する
$ cat /boot/grub2/grubenv
saved_entry=20db05bac820110f5800ebe2e7262b58-4.18.0-193.28.1.el8_2.x86_64
kernelopts=root=/dev/vda1 ro crashkernel=auto biosdevname=0 net.ifnames=0 rhgb quiet
boot_success=1
```
(9) 再度、再起動して、TeraTermからログインできれば問題ない。

##補足1：grub2-mkconfigコマンド
上記手順のyum update直後にgrub2-mkconfigコマンドを実行してgrub.cfgを生成するとどうか試してみた。結果はNGだった。TeraTermからログインできなくなり、VPSポータルのコンソールからはGRUB選択画面で停止することになった。grub.cfg内の`menuentry`の記述が欠落した状態になった。原因は分からない。
上記(8)(9)の手順のように、再起動した後にgrub2-mkconfigを実行するとうまくいくようだ。

##補足2：grub2-set-defaultコマンド
/boot/grub2/grubenvを見ると、saved_entryには番号(0,1,2,...)ではなく、20db05bac820110f5800ebe2e7262b58-4.18.0-193.28.1.el8_2.x86_64の名前が入っている。後半の「4.18.0-193.28.1.el8_2.x86_64」の部分でマッチングされるようだ。

```bash

$ cat /boot/grub2/grubenv
saved_entry=20db05bac820110f5800ebe2e7262b58-4.18.0-193.28.1.el8_2.x86_64
kernelopts=root=/dev/vda1 ro crashkernel=auto biosdevname=0 net.ifnames=0 rhgb quiet
boot_success=1
```
ちなみに、grub2-set-defaultを実行することで、/etc/default/grubを介さずにダイレクトにsaved_entryを変更することができる。

```bash

$ grub2-set-default 0
$ cat /boot/grub2/grubenv
saved_entry=0
kernelopts=root=/dev/vda1 ro crashkernel=auto biosdevname=0 net.ifnames=0 rhgb quiet
boot_success=1
```
##補足3：リカバリモードからの修復
起動できなくなったら、VPSポータルからリカバリモードでサーバー起動して、TeraTermからrootでログインして、以下の手順で修復できる。パスワードは`"recovery"`ではなく、初期パスワードである。VPSポータルのメッセージは間違っているようだ。本体ディスクを識別してマウントしてから操作する手順になる。

```bash

$ fdisk -l | more
  /dev/vda1
$ mount /dev/vda1 /mnt
$ cp /mnt/boot/grub2/grub.cfg.original /mnt/boot/grub2/grub.cfg
```
試していないが、grub.cfg.rpmsaveが既存だったので、そのファイルで回復できたかもしれない。以下のようにコピーしてサーバーを再起動すれば、上記(8)(9)の手順で回復できたかもしれない。

```bash

$ cp /mnt/boot/grub2/grub.cfg.rpmsave /mnt/boot/grub2/grub.cfg
```
## GMOサービスセンターからのアドバイス

GMOサービスセンターにアドバイスをお願いしていたのですが、その回答をいただきました。ありがとうございます。



(1) CentOS 8においては、Kernelのアップグレード前には、yum.confに下記を追加してくださいとのことだった。grub2*も除外した方がいいそうだ。

```bash

$ vi /etc/yum.conf
exclude=kernel* grub2*
```

(2) また、grub2をアップデートした場合には、/etc/default/grubを編集して、GRUB_ENABLE_BLSCFGの行をコメントアウトして grub2-mkconfigを実行するとよいということだった。ただ、私もまだGRUB_ENABLE_BLSCFG=falseの挙動は未確認だ。

```bash
$ yum update grub2*
$ vi /etc/default/grub
GRUB_ENABLE_BLSCFG=true
↓
# GRUB_ENABLE_BLSCFG=true   # コメントアウトする。falseをセットしてもいい。
$ grub2-mkconfig -o /boot/grub2/grub.cfg
```
以上

| [../](https://qiita.com/taconana/items/e45f19e83c825a9ba75e) |
---